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

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

23
scene/resources/2d/SCsub Normal file
View File

@@ -0,0 +1,23 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")
env.add_source_files(env.scene_sources, "tile_set.cpp")
if not env["disable_physics_2d"]:
env.add_source_files(env.scene_sources, "capsule_shape_2d.cpp")
env.add_source_files(env.scene_sources, "circle_shape_2d.cpp")
env.add_source_files(env.scene_sources, "concave_polygon_shape_2d.cpp")
env.add_source_files(env.scene_sources, "convex_polygon_shape_2d.cpp")
env.add_source_files(env.scene_sources, "rectangle_shape_2d.cpp")
env.add_source_files(env.scene_sources, "segment_shape_2d.cpp")
env.add_source_files(env.scene_sources, "separation_ray_shape_2d.cpp")
env.add_source_files(env.scene_sources, "shape_2d.cpp")
env.add_source_files(env.scene_sources, "world_boundary_shape_2d.cpp")
if not env["disable_navigation_2d"]:
env.add_source_files(env.scene_sources, "navigation_mesh_source_geometry_data_2d.cpp")
env.add_source_files(env.scene_sources, "navigation_polygon.cpp")
env.add_source_files(env.scene_sources, "polygon_path_finder.cpp")
SConscript("skeleton/SCsub")

View File

@@ -0,0 +1,144 @@
/**************************************************************************/
/* 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.5f + radius : height * 0.5f - 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.0f, "CapsuleShape2D radius cannot be negative.");
if (radius == p_radius) {
return;
}
radius = p_radius;
if (height < radius * 2.0f) {
height = radius * 2.0f;
}
_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.0f, "CapsuleShape2D height cannot be negative.");
if (height == p_height) {
return;
}
height = p_height;
if (radius > height * 0.5f) {
radius = height * 0.5f;
}
_update_shape();
}
real_t CapsuleShape2D::get_height() const {
return height;
}
void CapsuleShape2D::set_mid_height(real_t p_mid_height) {
ERR_FAIL_COND_MSG(p_mid_height < 0.0f, "CapsuleShape2D mid-height cannot be negative.");
height = p_mid_height + radius * 2.0f;
_update_shape();
}
real_t CapsuleShape2D::get_mid_height() const {
return height - radius * 2.0f;
}
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.0f) };
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.5f);
return Rect2(-half_size, half_size * 2.0f);
}
real_t CapsuleShape2D::get_enclosing_radius() const {
return height * 0.5f;
}
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);
ClassDB::bind_method(D_METHOD("set_mid_height", "mid_height"), &CapsuleShape2D::set_mid_height);
ClassDB::bind_method(D_METHOD("get_mid_height"), &CapsuleShape2D::get_mid_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_PROPERTY(PropertyInfo(Variant::FLOAT, "mid_height", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater,suffix:px", PROPERTY_USAGE_NONE), "set_mid_height", "get_mid_height");
ADD_LINKED_PROPERTY("radius", "height");
ADD_LINKED_PROPERTY("height", "radius");
}
CapsuleShape2D::CapsuleShape2D() :
Shape2D(PhysicsServer2D::get_singleton()->capsule_shape_create()) {
_update_shape();
}

View File

@@ -0,0 +1,64 @@
/**************************************************************************/
/* capsule_shape_2d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#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;
void set_mid_height(real_t p_mid_height);
real_t get_mid_height() 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();
};

View File

@@ -0,0 +1,98 @@
/**************************************************************************/
/* 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.");
if (radius == p_radius) {
return;
}
radius = p_radius;
_update_shape();
}
real_t CircleShape2D::get_radius() const {
return radius;
}
void CircleShape2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_radius", "radius"), &CircleShape2D::set_radius);
ClassDB::bind_method(D_METHOD("get_radius"), &CircleShape2D::get_radius);
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater,suffix:px"), "set_radius", "get_radius");
}
Rect2 CircleShape2D::get_rect() const {
Rect2 rect;
rect.position = -Point2(get_radius(), get_radius());
rect.size = Point2(get_radius(), get_radius()) * 2.0;
return rect;
}
real_t CircleShape2D::get_enclosing_radius() const {
return radius;
}
void CircleShape2D::draw(const RID &p_to_rid, const Color &p_color) {
Vector<Vector2> points;
points.resize(24);
const real_t turn_step = Math::TAU / 24.0;
for (int i = 0; i < 24; i++) {
points.write[i] = Vector2(Math::cos(i * turn_step), Math::sin(i * turn_step)) * get_radius();
}
Vector<Color> col = { p_color };
RenderingServer::get_singleton()->canvas_item_add_polygon(p_to_rid, points, col);
if (is_collision_outline_enabled()) {
points.push_back(points[0]);
col = { Color(p_color, 1.0) };
RenderingServer::get_singleton()->canvas_item_add_polyline(p_to_rid, points, col);
}
}
CircleShape2D::CircleShape2D() :
Shape2D(PhysicsServer2D::get_singleton()->circle_shape_create()) {
_update_shape();
}

View File

@@ -0,0 +1,55 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#pragma once
#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();
};

View File

@@ -0,0 +1,119 @@
/**************************************************************************/
/* concave_polygon_shape_2d.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "concave_polygon_shape_2d.h"
#include "core/math/geometry_2d.h"
#include "servers/physics_server_2d.h"
#include "servers/rendering_server.h"
bool ConcavePolygonShape2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const {
Vector<Vector2> s = get_segments();
int len = s.size();
if (len == 0 || (len % 2) == 1) {
return false;
}
const Vector2 *r = s.ptr();
for (int i = 0; i < len; i += 2) {
Vector2 closest = Geometry2D::get_closest_point_to_segment(p_point, r[i], r[i + 1]);
if (p_point.distance_to(closest) < p_tolerance) {
return true;
}
}
return false;
}
void ConcavePolygonShape2D::set_segments(const Vector<Vector2> &p_segments) {
PhysicsServer2D::get_singleton()->shape_set_data(get_rid(), p_segments);
emit_changed();
}
Vector<Vector2> ConcavePolygonShape2D::get_segments() const {
return PhysicsServer2D::get_singleton()->shape_get_data(get_rid());
}
void ConcavePolygonShape2D::draw(const RID &p_to_rid, const Color &p_color) {
Vector<Vector2> s = get_segments();
int len = s.size();
if (len == 0 || (len % 2) == 1) {
return;
}
const Vector2 *r = s.ptr();
for (int i = 0; i < len; i += 2) {
RenderingServer::get_singleton()->canvas_item_add_line(p_to_rid, r[i], r[i + 1], p_color, 2);
}
}
Rect2 ConcavePolygonShape2D::get_rect() const {
Vector<Vector2> s = get_segments();
int len = s.size();
if (len == 0) {
return Rect2();
}
Rect2 rect;
const Vector2 *r = s.ptr();
for (int i = 0; i < len; i++) {
if (i == 0) {
rect.position = r[i];
} else {
rect.expand_to(r[i]);
}
}
return rect;
}
real_t ConcavePolygonShape2D::get_enclosing_radius() const {
Vector<Vector2> data = get_segments();
const Vector2 *read = data.ptr();
real_t r = 0.0;
for (int i(0); i < data.size(); i++) {
r = MAX(read[i].length_squared(), r);
}
return Math::sqrt(r);
}
void ConcavePolygonShape2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_segments", "segments"), &ConcavePolygonShape2D::set_segments);
ClassDB::bind_method(D_METHOD("get_segments"), &ConcavePolygonShape2D::get_segments);
ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "segments"), "set_segments", "get_segments");
}
ConcavePolygonShape2D::ConcavePolygonShape2D() :
Shape2D(PhysicsServer2D::get_singleton()->concave_polygon_shape_create()) {
Vector<Vector2> empty;
set_segments(empty);
}

View File

@@ -0,0 +1,52 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#pragma once
#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();
};

View File

@@ -0,0 +1,143 @@
/**************************************************************************/
/* convex_polygon_shape_2d.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "convex_polygon_shape_2d.h"
#include "core/math/geometry_2d.h"
#include "servers/physics_server_2d.h"
#include "servers/rendering_server.h"
bool ConvexPolygonShape2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const {
return Geometry2D::is_point_in_polygon(p_point, points);
}
#ifdef DEBUG_ENABLED
// Check if point p3 is to the left of [p1, p2] segment or on it.
bool left_test(const Vector2 &p1, const Vector2 &p2, const Vector2 &p3) {
Vector2 p12 = p2 - p1;
Vector2 p13 = p3 - p1;
// If the value of the cross product is positive or zero; p3 is to the left or on the segment, respectively.
return p12.cross(p13) >= 0;
}
bool is_convex(const Vector<Vector2> &p_points) {
// Pre-condition: Polygon is in counter-clockwise order.
int polygon_size = p_points.size();
for (int i = 0; i < polygon_size && polygon_size > 3; i++) {
int j = (i + 1) % polygon_size;
int k = (j + 1) % polygon_size;
// If any consecutive three points fail left-test, then there is a concavity.
if (!left_test(p_points[i], p_points[j], p_points[k])) {
return false;
}
}
return true;
}
#endif
void ConvexPolygonShape2D::_update_shape() {
Vector<Vector2> final_points = points;
if (Geometry2D::is_polygon_clockwise(final_points)) { //needs to be counter clockwise
final_points.reverse();
}
#ifdef DEBUG_ENABLED
if (!is_convex(final_points)) {
WARN_PRINT("Concave polygon is assigned to ConvexPolygonShape2D.");
}
#endif
PhysicsServer2D::get_singleton()->shape_set_data(get_rid(), final_points);
emit_changed();
}
void ConvexPolygonShape2D::set_point_cloud(const Vector<Vector2> &p_points) {
Vector<Point2> hull = Geometry2D::convex_hull(p_points);
ERR_FAIL_COND(hull.size() < 3);
set_points(hull);
}
void ConvexPolygonShape2D::set_points(const Vector<Vector2> &p_points) {
points = p_points;
_update_shape();
}
Vector<Vector2> ConvexPolygonShape2D::get_points() const {
return points;
}
void ConvexPolygonShape2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_point_cloud", "point_cloud"), &ConvexPolygonShape2D::set_point_cloud);
ClassDB::bind_method(D_METHOD("set_points", "points"), &ConvexPolygonShape2D::set_points);
ClassDB::bind_method(D_METHOD("get_points"), &ConvexPolygonShape2D::get_points);
ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "points"), "set_points", "get_points");
}
void ConvexPolygonShape2D::draw(const RID &p_to_rid, const Color &p_color) {
if (points.size() < 3) {
return;
}
Vector<Color> col = { p_color };
RenderingServer::get_singleton()->canvas_item_add_polygon(p_to_rid, points, col);
if (is_collision_outline_enabled()) {
col = { Color(p_color, 1.0) };
RenderingServer::get_singleton()->canvas_item_add_polyline(p_to_rid, points, col);
// Draw the last segment.
RenderingServer::get_singleton()->canvas_item_add_line(p_to_rid, points[points.size() - 1], points[0], Color(p_color, 1.0));
}
}
Rect2 ConvexPolygonShape2D::get_rect() const {
Rect2 rect;
for (int i = 0; i < points.size(); i++) {
if (i == 0) {
rect.position = points[i];
} else {
rect.expand_to(points[i]);
}
}
return rect;
}
real_t ConvexPolygonShape2D::get_enclosing_radius() const {
real_t r = 0.0;
for (int i(0); i < get_points().size(); i++) {
r = MAX(get_points()[i].length_squared(), r);
}
return Math::sqrt(r);
}
ConvexPolygonShape2D::ConvexPolygonShape2D() :
Shape2D(PhysicsServer2D::get_singleton()->convex_polygon_shape_create()) {
}

View File

@@ -0,0 +1,56 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#pragma once
#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();
};

View File

@@ -0,0 +1,374 @@
/**************************************************************************/
/* 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 "core/variant/typed_array.h"
void NavigationMeshSourceGeometryData2D::clear() {
RWLockWrite write_lock(geometry_rwlock);
traversable_outlines.clear();
obstruction_outlines.clear();
_projected_obstructions.clear();
bounds_dirty = true;
}
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();
bounds_dirty = true;
}
void NavigationMeshSourceGeometryData2D::_set_traversable_outlines(const Vector<Vector<Vector2>> &p_traversable_outlines) {
RWLockWrite write_lock(geometry_rwlock);
traversable_outlines = p_traversable_outlines;
bounds_dirty = true;
}
void NavigationMeshSourceGeometryData2D::_set_obstruction_outlines(const Vector<Vector<Vector2>> &p_obstruction_outlines) {
RWLockWrite write_lock(geometry_rwlock);
obstruction_outlines = p_obstruction_outlines;
bounds_dirty = true;
}
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);
bounds_dirty = true;
}
}
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);
bounds_dirty = true;
}
}
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];
}
bounds_dirty = true;
}
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];
}
bounds_dirty = true;
}
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];
}
bounds_dirty = true;
}
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];
}
bounds_dirty = true;
}
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);
bounds_dirty = true;
}
}
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);
bounds_dirty = true;
}
}
void NavigationMeshSourceGeometryData2D::merge(const Ref<NavigationMeshSourceGeometryData2D> &p_other_geometry) {
ERR_FAIL_COND(p_other_geometry.is_null());
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);
bounds_dirty = true;
}
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);
bounds_dirty = true;
}
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);
bounds_dirty = true;
}
}
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;
bounds_dirty = true;
}
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;
}
Rect2 NavigationMeshSourceGeometryData2D::get_bounds() {
geometry_rwlock.read_lock();
if (bounds_dirty) {
geometry_rwlock.read_unlock();
RWLockWrite write_lock(geometry_rwlock);
bounds_dirty = false;
bounds = Rect2();
bool first_vertex = true;
for (const Vector<Vector2> &traversable_outline : traversable_outlines) {
for (const Vector2 &traversable_point : traversable_outline) {
if (first_vertex) {
first_vertex = false;
bounds.position = traversable_point;
} else {
bounds.expand_to(traversable_point);
}
}
}
for (const Vector<Vector2> &obstruction_outline : obstruction_outlines) {
for (const Vector2 &obstruction_point : obstruction_outline) {
if (first_vertex) {
first_vertex = false;
bounds.position = obstruction_point;
} else {
bounds.expand_to(obstruction_point);
}
}
}
for (const ProjectedObstruction &projected_obstruction : _projected_obstructions) {
for (int i = 0; i < projected_obstruction.vertices.size() / 2; i++) {
const Vector2 vertex = Vector2(projected_obstruction.vertices[i * 2], projected_obstruction.vertices[i * 2 + 1]);
if (first_vertex) {
first_vertex = false;
bounds.position = vertex;
} else {
bounds.expand_to(vertex);
}
}
}
} else {
geometry_rwlock.read_unlock();
}
RWLockRead read_lock(geometry_rwlock);
return bounds;
}
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);
ClassDB::bind_method(D_METHOD("get_bounds"), &NavigationMeshSourceGeometryData2D::get_bounds);
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "traversable_outlines", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_traversable_outlines", "get_traversable_outlines");
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "obstruction_outlines", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_obstruction_outlines", "get_obstruction_outlines");
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "projected_obstructions", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_projected_obstructions", "get_projected_obstructions");
}

View File

@@ -0,0 +1,112 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#pragma once
#include "core/io/resource.h"
#include "core/os/rw_lock.h"
class NavigationMeshSourceGeometryData2D : public Resource {
friend class NavMeshGenerator2D;
GDCLASS(NavigationMeshSourceGeometryData2D, Resource);
RWLock geometry_rwlock;
Vector<Vector<Vector2>> traversable_outlines;
Vector<Vector<Vector2>> obstruction_outlines;
Rect2 bounds;
bool bounds_dirty = true;
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);
Rect2 get_bounds();
~NavigationMeshSourceGeometryData2D() { clear(); }
};

View File

@@ -0,0 +1,638 @@
/**************************************************************************/
/* 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 "thirdparty/misc/polypartition.h"
#ifdef DEBUG_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 // DEBUG_ENABLED
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] = 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];
}
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);
polygons.push_back(p_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];
}
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 = p_polygons;
{
MutexLock lock(navigation_mesh_generation);
navigation_mesh.unref();
}
}
void NavigationPolygon::set_data(const Vector<Vector2> &p_vertices, const Vector<Vector<int>> &p_polygons, const Vector<Vector<Vector2>> &p_outlines) {
RWLockWrite write_lock(rwlock);
vertices = p_vertices;
polygons = p_polygons;
outlines = p_outlines;
rect_cache_dirty = true;
{
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 = polygons;
}
void NavigationPolygon::get_data(Vector<Vector2> &r_vertices, Vector<Vector<int>> &r_polygons, Vector<Vector<Vector2>> &r_outlines) {
RWLockRead read_lock(rwlock);
r_vertices = vertices;
r_polygons = polygons;
r_outlines = outlines;
}
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::set_outlines(const Vector<Vector<Vector2>> &p_outlines) {
RWLockWrite write_lock(rwlock);
outlines = p_outlines;
rect_cache_dirty = true;
}
Vector<Vector<Vector2>> NavigationPolygon::get_outlines() const {
RWLockRead read_lock(rwlock);
return outlines;
}
void NavigationPolygon::set_polygons(const Vector<Vector<int>> &p_polygons) {
RWLockWrite write_lock(rwlock);
polygons = p_polygons;
{
MutexLock lock(navigation_mesh_generation);
navigation_mesh.unref();
}
}
Vector<Vector<int>> NavigationPolygon::get_polygons() const {
RWLockRead read_lock(rwlock);
return polygons;
}
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 NavigationPolygon."
"\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 (const TPPLPoly &tp : out_poly) {
Vector<int> 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.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_sample_partition_type(SamplePartitionType p_value) {
ERR_FAIL_INDEX(p_value, SAMPLE_PARTITION_MAX);
partition_type = p_value;
}
NavigationPolygon::SamplePartitionType NavigationPolygon::get_sample_partition_type() const {
return partition_type;
}
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_sample_partition_type", "sample_partition_type"), &NavigationPolygon::set_sample_partition_type);
ClassDB::bind_method(D_METHOD("get_sample_partition_type"), &NavigationPolygon::get_sample_partition_type);
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("Sampling", "sample_");
ADD_PROPERTY(PropertyInfo(Variant::INT, "sample_partition_type", PROPERTY_HINT_ENUM, "Convex Partition,Triangulate"), "set_sample_partition_type", "get_sample_partition_type");
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(SAMPLE_PARTITION_CONVEX_PARTITION);
BIND_ENUM_CONSTANT(SAMPLE_PARTITION_TRIANGULATE);
BIND_ENUM_CONSTANT(SAMPLE_PARTITION_MAX);
BIND_ENUM_CONSTANT(PARSED_GEOMETRY_MESH_INSTANCES);
BIND_ENUM_CONSTANT(PARSED_GEOMETRY_STATIC_COLLIDERS);
BIND_ENUM_CONSTANT(PARSED_GEOMETRY_BOTH);
BIND_ENUM_CONSTANT(PARSED_GEOMETRY_MAX);
BIND_ENUM_CONSTANT(SOURCE_GEOMETRY_ROOT_NODE_CHILDREN);
BIND_ENUM_CONSTANT(SOURCE_GEOMETRY_GROUPS_WITH_CHILDREN);
BIND_ENUM_CONSTANT(SOURCE_GEOMETRY_GROUPS_EXPLICIT);
BIND_ENUM_CONSTANT(SOURCE_GEOMETRY_MAX);
}
void NavigationPolygon::_validate_property(PropertyInfo &p_property) const {
if (p_property.name == "parsed_collision_mask") {
if (parsed_geometry_type == PARSED_GEOMETRY_MESH_INSTANCES) {
p_property.usage = PROPERTY_USAGE_NONE;
return;
}
}
if (p_property.name == "parsed_source_group_name") {
if (source_geometry_mode == SOURCE_GEOMETRY_ROOT_NODE_CHILDREN) {
p_property.usage = PROPERTY_USAGE_NONE;
return;
}
}
}

View File

@@ -0,0 +1,174 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#pragma once
#include "scene/2d/node_2d.h"
#include "scene/resources/navigation_mesh.h"
#include "servers/navigation/navigation_globals.h"
class NavigationPolygon : public Resource {
GDCLASS(NavigationPolygon, Resource);
RWLock rwlock;
Vector<Vector2> vertices;
Vector<Vector<int>> 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 = NavigationDefaults2D::NAV_MESH_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 DEBUG_ENABLED
Rect2 _edit_get_rect() const;
bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const;
#endif // DEBUG_ENABLED
enum SamplePartitionType {
SAMPLE_PARTITION_CONVEX_PARTITION = 0,
SAMPLE_PARTITION_TRIANGULATE,
SAMPLE_PARTITION_MAX
};
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;
SamplePartitionType partition_type = SAMPLE_PARTITION_CONVEX_PARTITION;
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 set_outlines(const Vector<Vector<Vector2>> &p_outlines);
Vector<Vector<Vector2>> get_outlines() const;
void clear_outlines();
#ifndef DISABLE_DEPRECATED
void make_polygons_from_outlines();
#endif // DISABLE_DEPRECATED
void set_polygons(const Vector<Vector<int>> &p_polygons);
Vector<Vector<int>> get_polygons() const;
Vector<int> get_polygon(int p_idx);
void clear_polygons();
void set_sample_partition_type(SamplePartitionType p_value);
SamplePartitionType get_sample_partition_type() const;
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 set_data(const Vector<Vector2> &p_vertices, const Vector<Vector<int>> &p_polygons, const Vector<Vector<Vector2>> &p_outlines);
void get_data(Vector<Vector2> &r_vertices, Vector<Vector<int>> &r_polygons);
void get_data(Vector<Vector2> &r_vertices, Vector<Vector<int>> &r_polygons, Vector<Vector<Vector2>> &r_outlines);
};
VARIANT_ENUM_CAST(NavigationPolygon::SamplePartitionType);
VARIANT_ENUM_CAST(NavigationPolygon::ParsedGeometryType);
VARIANT_ENUM_CAST(NavigationPolygon::SourceGeometryMode);

View File

@@ -0,0 +1,553 @@
/**************************************************************************/
/* 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;
const Vector2 segment_a = points[e.points[0]].pos;
const Vector2 segment_b = points[e.points[1]].pos;
Vector2 closest = Geometry2D::get_closest_point_to_segment(from, segment_a, segment_b);
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;
const Vector2 segment_a = points[e.points[0]].pos;
const Vector2 segment_b = points[e.points[1]].pos;
Vector2 closest = Geometry2D::get_closest_point_to_segment(to, segment_a, segment_b);
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.is_empty()) {
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;
const Vector2 segment_a = points[e.points[0]].pos;
const Vector2 segment_b = points[e.points[1]].pos;
Vector2 closest = Geometry2D::get_closest_point_to_segment(p_point, segment_a, segment_b);
float d = p_point.distance_squared_to(closest);
if (d < closest_dist) {
closest_dist = d;
closest_point = closest;
}
}
ERR_FAIL_COND_V(Math::is_equal_approx(closest_dist, 1e20f), Vector2());
return closest_point;
}
Vector<Vector2> PolygonPathFinder::get_intersections(const Vector2 &p_from, const Vector2 &p_to) const {
Vector<Vector2> inters;
for (const Edge &E : edges) {
Vector2 a = points[E.points[0]].pos;
Vector2 b = points[E.points[1]].pos;
Vector2 res;
if (Geometry2D::segment_intersects_segment(a, b, p_from, p_to, &res)) {
inters.push_back(res);
}
}
return inters;
}
Rect2 PolygonPathFinder::get_bounds() const {
return bounds;
}
void PolygonPathFinder::set_point_penalty(int p_point, float p_penalty) {
ERR_FAIL_INDEX(p_point, points.size() - 2);
points.write[p_point].penalty = p_penalty;
}
float PolygonPathFinder::get_point_penalty(int p_point) const {
ERR_FAIL_INDEX_V(p_point, points.size() - 2, 0);
return points[p_point].penalty;
}
void PolygonPathFinder::_bind_methods() {
ClassDB::bind_method(D_METHOD("setup", "points", "connections"), &PolygonPathFinder::setup);
ClassDB::bind_method(D_METHOD("find_path", "from", "to"), &PolygonPathFinder::find_path);
ClassDB::bind_method(D_METHOD("get_intersections", "from", "to"), &PolygonPathFinder::get_intersections);
ClassDB::bind_method(D_METHOD("get_closest_point", "point"), &PolygonPathFinder::get_closest_point);
ClassDB::bind_method(D_METHOD("is_point_inside", "point"), &PolygonPathFinder::is_point_inside);
ClassDB::bind_method(D_METHOD("set_point_penalty", "idx", "penalty"), &PolygonPathFinder::set_point_penalty);
ClassDB::bind_method(D_METHOD("get_point_penalty", "idx"), &PolygonPathFinder::get_point_penalty);
ClassDB::bind_method(D_METHOD("get_bounds"), &PolygonPathFinder::get_bounds);
ClassDB::bind_method(D_METHOD("_set_data", "data"), &PolygonPathFinder::_set_data);
ClassDB::bind_method(D_METHOD("_get_data"), &PolygonPathFinder::_get_data);
ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_data", "_get_data");
}
PolygonPathFinder::PolygonPathFinder() {
}

View File

@@ -0,0 +1,95 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#pragma once
#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();
};

View File

@@ -0,0 +1,110 @@
/**************************************************************************/
/* 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.");
if (size == p_size) {
return;
}
size = p_size;
_update_shape();
}
Size2 RectangleShape2D::get_size() const {
return size;
}
void RectangleShape2D::draw(const RID &p_to_rid, const Color &p_color) {
RenderingServer::get_singleton()->canvas_item_add_rect(p_to_rid, Rect2(-size * 0.5, size), p_color);
if (is_collision_outline_enabled()) {
// Draw an outlined rectangle to make individual shapes easier to distinguish.
Vector<Vector2> stroke_points;
stroke_points.resize(5);
stroke_points.write[0] = -size * 0.5;
stroke_points.write[1] = Vector2(size.x, -size.y) * 0.5;
stroke_points.write[2] = size * 0.5;
stroke_points.write[3] = Vector2(-size.x, size.y) * 0.5;
stroke_points.write[4] = -size * 0.5;
Vector<Color> stroke_colors = { Color(p_color, 1.0) };
RenderingServer::get_singleton()->canvas_item_add_polyline(p_to_rid, stroke_points, stroke_colors);
}
}
Rect2 RectangleShape2D::get_rect() const {
return Rect2(-size * 0.5, size);
}
real_t RectangleShape2D::get_enclosing_radius() const {
return size.length() / 2;
}
void RectangleShape2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_size", "size"), &RectangleShape2D::set_size);
ClassDB::bind_method(D_METHOD("get_size"), &RectangleShape2D::get_size);
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size", PROPERTY_HINT_NONE, "suffix:px"), "set_size", "get_size");
}
RectangleShape2D::RectangleShape2D() :
Shape2D(PhysicsServer2D::get_singleton()->rectangle_shape_create()) {
size = Size2(20, 20);
_update_shape();
}

View File

@@ -0,0 +1,57 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#pragma once
#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();
};

View File

@@ -0,0 +1,105 @@
/**************************************************************************/
/* 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 closest = Geometry2D::get_closest_point_to_segment(p_point, a, b);
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) {
if (a == p_a) {
return;
}
a = p_a;
_update_shape();
}
Vector2 SegmentShape2D::get_a() const {
return a;
}
void SegmentShape2D::set_b(const Vector2 &p_b) {
if (b == p_b) {
return;
}
b = p_b;
_update_shape();
}
Vector2 SegmentShape2D::get_b() const {
return b;
}
void SegmentShape2D::draw(const RID &p_to_rid, const Color &p_color) {
RenderingServer::get_singleton()->canvas_item_add_line(p_to_rid, a, b, p_color, 3);
}
Rect2 SegmentShape2D::get_rect() const {
Rect2 rect;
rect.position = a;
rect.expand_to(b);
return rect;
}
real_t SegmentShape2D::get_enclosing_radius() const {
return (a + b).length();
}
void SegmentShape2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_a", "a"), &SegmentShape2D::set_a);
ClassDB::bind_method(D_METHOD("get_a"), &SegmentShape2D::get_a);
ClassDB::bind_method(D_METHOD("set_b", "b"), &SegmentShape2D::set_b);
ClassDB::bind_method(D_METHOD("get_b"), &SegmentShape2D::get_b);
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "a", PROPERTY_HINT_NONE, "suffix:px"), "set_a", "get_a");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "b", PROPERTY_HINT_NONE, "suffix:px"), "set_b", "get_b");
}
SegmentShape2D::SegmentShape2D() :
Shape2D(PhysicsServer2D::get_singleton()->segment_shape_create()) {
a = Vector2();
b = Vector2(0, 10);
_update_shape();
}

View File

@@ -0,0 +1,60 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#pragma once
#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();
};

View File

@@ -0,0 +1,123 @@
/**************************************************************************/
/* 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) {
if (length == p_length) {
return;
}
length = p_length;
_update_shape();
}
real_t SeparationRayShape2D::get_length() const {
return length;
}
void SeparationRayShape2D::set_slide_on_slope(bool p_active) {
if (slide_on_slope == p_active) {
return;
}
slide_on_slope = p_active;
_update_shape();
}
bool SeparationRayShape2D::get_slide_on_slope() const {
return slide_on_slope;
}
SeparationRayShape2D::SeparationRayShape2D() :
Shape2D(PhysicsServer2D::get_singleton()->separation_ray_shape_create()) {
_update_shape();
}

View File

@@ -0,0 +1,58 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#pragma once
#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();
};

View File

@@ -0,0 +1,123 @@
/**************************************************************************/
/* 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/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_CACHED(bool, "debug/shapes/collision/draw_2d_outlines");
}
Shape2D::Shape2D(const RID &p_rid) {
shape = p_rid;
}
Shape2D::~Shape2D() {
ERR_FAIL_NULL(PhysicsServer2D::get_singleton());
PhysicsServer2D::get_singleton()->free(shape);
}

View File

@@ -0,0 +1,67 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#pragma once
#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();
};

View File

@@ -0,0 +1,16 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")
env.add_source_files(env.scene_sources, "skeleton_modification_2d.cpp")
env.add_source_files(env.scene_sources, "skeleton_modification_2d_ccdik.cpp")
env.add_source_files(env.scene_sources, "skeleton_modification_2d_fabrik.cpp")
env.add_source_files(env.scene_sources, "skeleton_modification_2d_lookat.cpp")
env.add_source_files(env.scene_sources, "skeleton_modification_2d_stackholder.cpp")
env.add_source_files(env.scene_sources, "skeleton_modification_2d_twoboneik.cpp")
env.add_source_files(env.scene_sources, "skeleton_modification_stack_2d.cpp")
if not env["disable_physics_2d"]:
env.add_source_files(env.scene_sources, "skeleton_modification_2d_jiggle.cpp")
env.add_source_files(env.scene_sources, "skeleton_modification_2d_physicalbones.cpp")

View File

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

View File

@@ -0,0 +1,86 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#pragma once
#include "scene/resources/2d/skeleton/skeleton_modification_stack_2d.h"
///////////////////////////////////////
// SkeletonModification2D
///////////////////////////////////////
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();
};

View File

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

View File

@@ -0,0 +1,113 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#pragma once
#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();
};

View File

@@ -0,0 +1,457 @@
/**************************************************************************/
/* 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"
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 = ObjectDB::get_instance<Node2D>(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 = ObjectDB::get_instance<Bone2D>(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 = ObjectDB::get_instance<Bone2D>(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 = ObjectDB::get_instance<Bone2D>(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 = ObjectDB::get_instance<Bone2D>(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 = ObjectDB::get_instance<Bone2D>(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 = ObjectDB::get_instance<Bone2D>(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 = ObjectDB::get_instance<Bone2D>(fabrik_data_chain[i].bone2d_node_cache);
Transform2D current_pose = fabrik_transform_chain[i];
Transform2D next_pose = fabrik_transform_chain[i + 1];
float current_bone2d_node_length = current_bone2d_node->get_length() * MIN(current_bone2d_node->get_global_scale().x, current_bone2d_node->get_global_scale().y);
float length = current_bone2d_node_length / (next_pose.get_origin().distance_to(current_pose.get_origin()));
Vector2 finish_position = current_pose.get_origin().lerp(next_pose.get_origin(), length);
current_pose.set_origin(finish_position);
// Apply to the bone
fabrik_transform_chain.write[i + 1] = current_pose;
}
}
void SkeletonModification2DFABRIK::_setup_modification(SkeletonModificationStack2D *p_stack) {
stack = p_stack;
if (stack != nullptr) {
is_setup = true;
if (stack->skeleton) {
for (int i = 0; i < fabrik_data_chain.size(); i++) {
fabrik_joint_update_bone2d_cache(i);
}
}
update_target_cache();
}
}
void SkeletonModification2DFABRIK::update_target_cache() {
if (!is_setup || !stack) {
if (is_setup) {
ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!");
}
return;
}
target_node_cache = ObjectID();
if (stack->skeleton) {
if (stack->skeleton->is_inside_tree()) {
if (stack->skeleton->has_node(target_node)) {
Node *node = stack->skeleton->get_node(target_node);
ERR_FAIL_COND_MSG(!node || stack->skeleton == node,
"Cannot update target cache: node is this modification's skeleton or cannot be found!");
ERR_FAIL_COND_MSG(!node->is_inside_tree(),
"Cannot update target cache: node is not in scene tree!");
target_node_cache = node->get_instance_id();
}
}
}
}
void SkeletonModification2DFABRIK::fabrik_joint_update_bone2d_cache(int p_joint_idx) {
ERR_FAIL_INDEX_MSG(p_joint_idx, fabrik_data_chain.size(), "Cannot update bone2d cache: joint index out of range!");
if (!is_setup || !stack) {
if (is_setup) {
ERR_PRINT_ONCE("Cannot update FABRIK Bone2D cache: modification is not properly setup!");
}
return;
}
fabrik_data_chain.write[p_joint_idx].bone2d_node_cache = ObjectID();
if (stack->skeleton) {
if (stack->skeleton->is_inside_tree()) {
if (stack->skeleton->has_node(fabrik_data_chain[p_joint_idx].bone2d_node)) {
Node *node = stack->skeleton->get_node(fabrik_data_chain[p_joint_idx].bone2d_node);
ERR_FAIL_COND_MSG(!node || stack->skeleton == node,
"Cannot update FABRIK joint " + itos(p_joint_idx) + " Bone2D cache: node is this modification's skeleton or cannot be found!");
ERR_FAIL_COND_MSG(!node->is_inside_tree(),
"Cannot update FABRIK joint " + itos(p_joint_idx) + " Bone2D cache: node is not in scene tree!");
fabrik_data_chain.write[p_joint_idx].bone2d_node_cache = node->get_instance_id();
Bone2D *bone = Object::cast_to<Bone2D>(node);
if (bone) {
fabrik_data_chain.write[p_joint_idx].bone_idx = bone->get_index_in_skeleton();
} else {
ERR_FAIL_MSG("FABRIK joint " + itos(p_joint_idx) + " Bone2D cache: Nodepath to Bone2D is not a Bone2D node!");
}
}
}
}
}
void SkeletonModification2DFABRIK::set_target_node(const NodePath &p_target_node) {
target_node = p_target_node;
update_target_cache();
}
NodePath SkeletonModification2DFABRIK::get_target_node() const {
return target_node;
}
void SkeletonModification2DFABRIK::set_fabrik_data_chain_length(int p_length) {
fabrik_data_chain.resize(p_length);
notify_property_list_changed();
}
int SkeletonModification2DFABRIK::get_fabrik_data_chain_length() {
return fabrik_data_chain.size();
}
void SkeletonModification2DFABRIK::set_fabrik_joint_bone2d_node(int p_joint_idx, const NodePath &p_target_node) {
ERR_FAIL_INDEX_MSG(p_joint_idx, fabrik_data_chain.size(), "FABRIK joint out of range!");
fabrik_data_chain.write[p_joint_idx].bone2d_node = p_target_node;
fabrik_joint_update_bone2d_cache(p_joint_idx);
notify_property_list_changed();
}
NodePath SkeletonModification2DFABRIK::get_fabrik_joint_bone2d_node(int p_joint_idx) const {
ERR_FAIL_INDEX_V_MSG(p_joint_idx, fabrik_data_chain.size(), NodePath(), "FABRIK joint out of range!");
return fabrik_data_chain[p_joint_idx].bone2d_node;
}
void SkeletonModification2DFABRIK::set_fabrik_joint_bone_index(int p_joint_idx, int p_bone_idx) {
ERR_FAIL_INDEX_MSG(p_joint_idx, fabrik_data_chain.size(), "FABRIK joint out of range!");
ERR_FAIL_COND_MSG(p_bone_idx < 0, "Bone index is out of range: The index is too low!");
if (is_setup) {
if (stack->skeleton) {
ERR_FAIL_INDEX_MSG(p_bone_idx, stack->skeleton->get_bone_count(), "Passed-in Bone index is out of range!");
fabrik_data_chain.write[p_joint_idx].bone_idx = p_bone_idx;
fabrik_data_chain.write[p_joint_idx].bone2d_node_cache = stack->skeleton->get_bone(p_bone_idx)->get_instance_id();
fabrik_data_chain.write[p_joint_idx].bone2d_node = stack->skeleton->get_path_to(stack->skeleton->get_bone(p_bone_idx));
} else {
WARN_PRINT("Cannot verify the FABRIK joint " + itos(p_joint_idx) + " bone index for this modification...");
fabrik_data_chain.write[p_joint_idx].bone_idx = p_bone_idx;
}
} else {
fabrik_data_chain.write[p_joint_idx].bone_idx = p_bone_idx;
}
notify_property_list_changed();
}
int SkeletonModification2DFABRIK::get_fabrik_joint_bone_index(int p_joint_idx) const {
ERR_FAIL_INDEX_V_MSG(p_joint_idx, fabrik_data_chain.size(), -1, "FABRIK joint out of range!");
return fabrik_data_chain[p_joint_idx].bone_idx;
}
void SkeletonModification2DFABRIK::set_fabrik_joint_magnet_position(int p_joint_idx, Vector2 p_magnet_position) {
ERR_FAIL_INDEX_MSG(p_joint_idx, fabrik_data_chain.size(), "FABRIK joint out of range!");
fabrik_data_chain.write[p_joint_idx].magnet_position = p_magnet_position;
}
Vector2 SkeletonModification2DFABRIK::get_fabrik_joint_magnet_position(int p_joint_idx) const {
ERR_FAIL_INDEX_V_MSG(p_joint_idx, fabrik_data_chain.size(), Vector2(), "FABRIK joint out of range!");
return fabrik_data_chain[p_joint_idx].magnet_position;
}
void SkeletonModification2DFABRIK::set_fabrik_joint_use_target_rotation(int p_joint_idx, bool p_use_target_rotation) {
ERR_FAIL_INDEX_MSG(p_joint_idx, fabrik_data_chain.size(), "FABRIK joint out of range!");
fabrik_data_chain.write[p_joint_idx].use_target_rotation = p_use_target_rotation;
}
bool SkeletonModification2DFABRIK::get_fabrik_joint_use_target_rotation(int p_joint_idx) const {
ERR_FAIL_INDEX_V_MSG(p_joint_idx, fabrik_data_chain.size(), false, "FABRIK joint out of range!");
return fabrik_data_chain[p_joint_idx].use_target_rotation;
}
void SkeletonModification2DFABRIK::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_target_node", "target_nodepath"), &SkeletonModification2DFABRIK::set_target_node);
ClassDB::bind_method(D_METHOD("get_target_node"), &SkeletonModification2DFABRIK::get_target_node);
ClassDB::bind_method(D_METHOD("set_fabrik_data_chain_length", "length"), &SkeletonModification2DFABRIK::set_fabrik_data_chain_length);
ClassDB::bind_method(D_METHOD("get_fabrik_data_chain_length"), &SkeletonModification2DFABRIK::get_fabrik_data_chain_length);
ClassDB::bind_method(D_METHOD("set_fabrik_joint_bone2d_node", "joint_idx", "bone2d_nodepath"), &SkeletonModification2DFABRIK::set_fabrik_joint_bone2d_node);
ClassDB::bind_method(D_METHOD("get_fabrik_joint_bone2d_node", "joint_idx"), &SkeletonModification2DFABRIK::get_fabrik_joint_bone2d_node);
ClassDB::bind_method(D_METHOD("set_fabrik_joint_bone_index", "joint_idx", "bone_idx"), &SkeletonModification2DFABRIK::set_fabrik_joint_bone_index);
ClassDB::bind_method(D_METHOD("get_fabrik_joint_bone_index", "joint_idx"), &SkeletonModification2DFABRIK::get_fabrik_joint_bone_index);
ClassDB::bind_method(D_METHOD("set_fabrik_joint_magnet_position", "joint_idx", "magnet_position"), &SkeletonModification2DFABRIK::set_fabrik_joint_magnet_position);
ClassDB::bind_method(D_METHOD("get_fabrik_joint_magnet_position", "joint_idx"), &SkeletonModification2DFABRIK::get_fabrik_joint_magnet_position);
ClassDB::bind_method(D_METHOD("set_fabrik_joint_use_target_rotation", "joint_idx", "use_target_rotation"), &SkeletonModification2DFABRIK::set_fabrik_joint_use_target_rotation);
ClassDB::bind_method(D_METHOD("get_fabrik_joint_use_target_rotation", "joint_idx"), &SkeletonModification2DFABRIK::get_fabrik_joint_use_target_rotation);
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node2D"), "set_target_node", "get_target_node");
ADD_PROPERTY(PropertyInfo(Variant::INT, "fabrik_data_chain_length", PROPERTY_HINT_RANGE, "0, 100, 1"), "set_fabrik_data_chain_length", "get_fabrik_data_chain_length");
}
SkeletonModification2DFABRIK::SkeletonModification2DFABRIK() {
stack = nullptr;
is_setup = false;
enabled = true;
editor_draw_gizmo = false;
}
SkeletonModification2DFABRIK::~SkeletonModification2DFABRIK() {
}

View File

@@ -0,0 +1,105 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#pragma once
#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();
};

View File

@@ -0,0 +1,577 @@
/**************************************************************************/
/* skeleton_modification_2d_jiggle.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "skeleton_modification_2d_jiggle.h"
#include "scene/2d/skeleton_2d.h"
#include "scene/resources/world_2d.h"
bool SkeletonModification2DJiggle::_set(const StringName &p_path, const Variant &p_value) {
String path = p_path;
if (path.begins_with("joint_data/")) {
int which = path.get_slicec('/', 1).to_int();
String what = path.get_slicec('/', 2);
ERR_FAIL_INDEX_V(which, jiggle_data_chain.size(), false);
if (what == "bone2d_node") {
set_jiggle_joint_bone2d_node(which, p_value);
} else if (what == "bone_index") {
set_jiggle_joint_bone_index(which, p_value);
} else if (what == "override_defaults") {
set_jiggle_joint_override(which, p_value);
} else if (what == "stiffness") {
set_jiggle_joint_stiffness(which, p_value);
} else if (what == "mass") {
set_jiggle_joint_mass(which, p_value);
} else if (what == "damping") {
set_jiggle_joint_damping(which, p_value);
} else if (what == "use_gravity") {
set_jiggle_joint_use_gravity(which, p_value);
} else if (what == "gravity") {
set_jiggle_joint_gravity(which, p_value);
} else {
return false;
}
} else if (path == "use_colliders") {
set_use_colliders(p_value);
} else if (path == "collision_mask") {
set_collision_mask(p_value);
} else {
return false;
}
return true;
}
bool SkeletonModification2DJiggle::_get(const StringName &p_path, Variant &r_ret) const {
String path = p_path;
if (path.begins_with("joint_data/")) {
int which = path.get_slicec('/', 1).to_int();
String what = path.get_slicec('/', 2);
ERR_FAIL_INDEX_V(which, jiggle_data_chain.size(), false);
if (what == "bone2d_node") {
r_ret = get_jiggle_joint_bone2d_node(which);
} else if (what == "bone_index") {
r_ret = get_jiggle_joint_bone_index(which);
} else if (what == "override_defaults") {
r_ret = get_jiggle_joint_override(which);
} else if (what == "stiffness") {
r_ret = get_jiggle_joint_stiffness(which);
} else if (what == "mass") {
r_ret = get_jiggle_joint_mass(which);
} else if (what == "damping") {
r_ret = get_jiggle_joint_damping(which);
} else if (what == "use_gravity") {
r_ret = get_jiggle_joint_use_gravity(which);
} else if (what == "gravity") {
r_ret = get_jiggle_joint_gravity(which);
} else {
return false;
}
} else if (path == "use_colliders") {
r_ret = get_use_colliders();
} else if (path == "collision_mask") {
r_ret = get_collision_mask();
} else {
return false;
}
return true;
}
void SkeletonModification2DJiggle::_get_property_list(List<PropertyInfo> *p_list) const {
p_list->push_back(PropertyInfo(Variant::BOOL, "use_colliders", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT));
if (use_colliders) {
p_list->push_back(PropertyInfo(Variant::INT, "collision_mask", PROPERTY_HINT_LAYERS_2D_PHYSICS, "", PROPERTY_USAGE_DEFAULT));
}
for (int i = 0; i < jiggle_data_chain.size(); i++) {
String base_string = "joint_data/" + itos(i) + "/";
p_list->push_back(PropertyInfo(Variant::INT, base_string + "bone_index", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT));
p_list->push_back(PropertyInfo(Variant::NODE_PATH, base_string + "bone2d_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Bone2D", PROPERTY_USAGE_DEFAULT));
p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "override_defaults", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT));
if (jiggle_data_chain[i].override_defaults) {
p_list->push_back(PropertyInfo(Variant::FLOAT, base_string + "stiffness", PROPERTY_HINT_RANGE, "0, 1000, 0.01", PROPERTY_USAGE_DEFAULT));
p_list->push_back(PropertyInfo(Variant::FLOAT, base_string + "mass", PROPERTY_HINT_RANGE, "0, 1000, 0.01", PROPERTY_USAGE_DEFAULT));
p_list->push_back(PropertyInfo(Variant::FLOAT, base_string + "damping", PROPERTY_HINT_RANGE, "0, 1, 0.01", PROPERTY_USAGE_DEFAULT));
p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "use_gravity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT));
if (jiggle_data_chain[i].use_gravity) {
p_list->push_back(PropertyInfo(Variant::VECTOR2, base_string + "gravity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT));
}
}
}
}
void SkeletonModification2DJiggle::_execute(float p_delta) {
ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr,
"Modification is not setup and therefore cannot execute!");
if (!enabled) {
return;
}
if (target_node_cache.is_null()) {
WARN_PRINT_ONCE("Target cache is out of date. Attempting to update...");
update_target_cache();
return;
}
Node2D *target = ObjectDB::get_instance<Node2D>(target_node_cache);
if (!target || !target->is_inside_tree()) {
ERR_PRINT_ONCE("Target node is not in the scene tree. Cannot execute modification!");
return;
}
for (int i = 0; i < jiggle_data_chain.size(); i++) {
_execute_jiggle_joint(i, target, p_delta);
}
}
void SkeletonModification2DJiggle::_execute_jiggle_joint(int p_joint_idx, Node2D *p_target, float p_delta) {
// Adopted from: https://wiki.unity3d.com/index.php/JiggleBone
// With modifications by TwistedTwigleg.
if (jiggle_data_chain[p_joint_idx].bone_idx <= -1 || jiggle_data_chain[p_joint_idx].bone_idx > stack->skeleton->get_bone_count()) {
ERR_PRINT_ONCE("Jiggle joint " + itos(p_joint_idx) + " bone index is invalid. Cannot execute modification on joint...");
return;
}
if (jiggle_data_chain[p_joint_idx].bone2d_node_cache.is_null() && !jiggle_data_chain[p_joint_idx].bone2d_node.is_empty()) {
WARN_PRINT_ONCE("Bone2D cache for joint " + itos(p_joint_idx) + " is out of date. Updating...");
jiggle_joint_update_bone2d_cache(p_joint_idx);
}
Bone2D *operation_bone = stack->skeleton->get_bone(jiggle_data_chain[p_joint_idx].bone_idx);
if (!operation_bone) {
ERR_PRINT_ONCE("Jiggle joint " + itos(p_joint_idx) + " does not have a Bone2D node or it cannot be found!");
return;
}
Transform2D operation_bone_trans = operation_bone->get_global_transform();
Vector2 target_position = p_target->get_global_position();
jiggle_data_chain.write[p_joint_idx].force = (target_position - jiggle_data_chain[p_joint_idx].dynamic_position) * jiggle_data_chain[p_joint_idx].stiffness * p_delta;
if (jiggle_data_chain[p_joint_idx].use_gravity) {
jiggle_data_chain.write[p_joint_idx].force += jiggle_data_chain[p_joint_idx].gravity * p_delta;
}
jiggle_data_chain.write[p_joint_idx].acceleration = jiggle_data_chain[p_joint_idx].force / jiggle_data_chain[p_joint_idx].mass;
jiggle_data_chain.write[p_joint_idx].velocity += jiggle_data_chain[p_joint_idx].acceleration * (1 - jiggle_data_chain[p_joint_idx].damping);
jiggle_data_chain.write[p_joint_idx].dynamic_position += jiggle_data_chain[p_joint_idx].velocity + jiggle_data_chain[p_joint_idx].force;
jiggle_data_chain.write[p_joint_idx].dynamic_position += operation_bone_trans.get_origin() - jiggle_data_chain[p_joint_idx].last_position;
jiggle_data_chain.write[p_joint_idx].last_position = operation_bone_trans.get_origin();
// Collision detection/response
if (use_colliders) {
if (execution_mode == SkeletonModificationStack2D::EXECUTION_MODE::execution_mode_physics_process) {
Ref<World2D> world_2d = stack->skeleton->get_world_2d();
ERR_FAIL_COND(world_2d.is_null());
PhysicsDirectSpaceState2D *space_state = PhysicsServer2D::get_singleton()->space_get_direct_state(world_2d->get_space());
PhysicsDirectSpaceState2D::RayResult ray_result;
PhysicsDirectSpaceState2D::RayParameters ray_params;
ray_params.from = operation_bone_trans.get_origin();
ray_params.to = jiggle_data_chain[p_joint_idx].dynamic_position;
ray_params.collision_mask = collision_mask;
// Add exception support?
bool ray_hit = space_state->intersect_ray(ray_params, ray_result);
if (ray_hit) {
jiggle_data_chain.write[p_joint_idx].dynamic_position = jiggle_data_chain[p_joint_idx].last_noncollision_position;
jiggle_data_chain.write[p_joint_idx].acceleration = Vector2(0, 0);
jiggle_data_chain.write[p_joint_idx].velocity = Vector2(0, 0);
} else {
jiggle_data_chain.write[p_joint_idx].last_noncollision_position = jiggle_data_chain[p_joint_idx].dynamic_position;
}
} else {
WARN_PRINT_ONCE("Jiggle 2D modifier: You cannot detect colliders without the stack mode being set to _physics_process!");
}
}
// Rotate the bone using the dynamic position!
operation_bone_trans = operation_bone_trans.looking_at(jiggle_data_chain[p_joint_idx].dynamic_position);
operation_bone_trans.set_rotation(operation_bone_trans.get_rotation() - operation_bone->get_bone_angle());
// Reset scale
operation_bone_trans.set_scale(operation_bone->get_global_scale());
operation_bone->set_global_transform(operation_bone_trans);
stack->skeleton->set_bone_local_pose_override(jiggle_data_chain[p_joint_idx].bone_idx, operation_bone->get_transform(), stack->strength, true);
}
void SkeletonModification2DJiggle::_update_jiggle_joint_data() {
for (int i = 0; i < jiggle_data_chain.size(); i++) {
if (!jiggle_data_chain[i].override_defaults) {
set_jiggle_joint_stiffness(i, stiffness);
set_jiggle_joint_mass(i, mass);
set_jiggle_joint_damping(i, damping);
set_jiggle_joint_use_gravity(i, use_gravity);
set_jiggle_joint_gravity(i, gravity);
}
}
}
void SkeletonModification2DJiggle::_setup_modification(SkeletonModificationStack2D *p_stack) {
stack = p_stack;
if (stack) {
is_setup = true;
if (stack->skeleton) {
for (int i = 0; i < jiggle_data_chain.size(); i++) {
int bone_idx = jiggle_data_chain[i].bone_idx;
if (bone_idx > 0 && bone_idx < stack->skeleton->get_bone_count()) {
Bone2D *bone2d_node = stack->skeleton->get_bone(bone_idx);
jiggle_data_chain.write[i].dynamic_position = bone2d_node->get_global_position();
}
jiggle_joint_update_bone2d_cache(i);
}
}
update_target_cache();
}
}
void SkeletonModification2DJiggle::update_target_cache() {
if (!is_setup || !stack) {
if (is_setup) {
ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!");
}
return;
}
target_node_cache = ObjectID();
if (stack->skeleton) {
if (stack->skeleton->is_inside_tree()) {
if (stack->skeleton->has_node(target_node)) {
Node *node = stack->skeleton->get_node(target_node);
ERR_FAIL_COND_MSG(!node || stack->skeleton == node,
"Cannot update target cache: node is this modification's skeleton or cannot be found!");
ERR_FAIL_COND_MSG(!node->is_inside_tree(),
"Cannot update target cache: node is not in scene tree!");
target_node_cache = node->get_instance_id();
}
}
}
}
void SkeletonModification2DJiggle::jiggle_joint_update_bone2d_cache(int p_joint_idx) {
ERR_FAIL_INDEX_MSG(p_joint_idx, jiggle_data_chain.size(), "Cannot update bone2d cache: joint index out of range!");
if (!is_setup || !stack) {
if (is_setup) {
ERR_PRINT_ONCE("Cannot update Jiggle " + itos(p_joint_idx) + " Bone2D cache: modification is not properly setup!");
}
return;
}
jiggle_data_chain.write[p_joint_idx].bone2d_node_cache = ObjectID();
if (stack->skeleton) {
if (stack->skeleton->is_inside_tree()) {
if (stack->skeleton->has_node(jiggle_data_chain[p_joint_idx].bone2d_node)) {
Node *node = stack->skeleton->get_node(jiggle_data_chain[p_joint_idx].bone2d_node);
ERR_FAIL_COND_MSG(!node || stack->skeleton == node,
"Cannot update Jiggle joint " + itos(p_joint_idx) + " Bone2D cache: node is this modification's skeleton or cannot be found!");
ERR_FAIL_COND_MSG(!node->is_inside_tree(),
"Cannot update Jiggle joint " + itos(p_joint_idx) + " Bone2D cache: node is not in scene tree!");
jiggle_data_chain.write[p_joint_idx].bone2d_node_cache = node->get_instance_id();
Bone2D *bone = Object::cast_to<Bone2D>(node);
if (bone) {
jiggle_data_chain.write[p_joint_idx].bone_idx = bone->get_index_in_skeleton();
} else {
ERR_FAIL_MSG("Jiggle joint " + itos(p_joint_idx) + " Bone2D cache: Nodepath to Bone2D is not a Bone2D node!");
}
}
}
}
}
void SkeletonModification2DJiggle::set_target_node(const NodePath &p_target_node) {
target_node = p_target_node;
update_target_cache();
}
NodePath SkeletonModification2DJiggle::get_target_node() const {
return target_node;
}
void SkeletonModification2DJiggle::set_stiffness(float p_stiffness) {
ERR_FAIL_COND_MSG(p_stiffness < 0, "Stiffness cannot be set to a negative value!");
stiffness = p_stiffness;
_update_jiggle_joint_data();
}
float SkeletonModification2DJiggle::get_stiffness() const {
return stiffness;
}
void SkeletonModification2DJiggle::set_mass(float p_mass) {
ERR_FAIL_COND_MSG(p_mass < 0, "Mass cannot be set to a negative value!");
mass = p_mass;
_update_jiggle_joint_data();
}
float SkeletonModification2DJiggle::get_mass() const {
return mass;
}
void SkeletonModification2DJiggle::set_damping(float p_damping) {
ERR_FAIL_COND_MSG(p_damping < 0, "Damping cannot be set to a negative value!");
ERR_FAIL_COND_MSG(p_damping > 1, "Damping cannot be more than one!");
damping = p_damping;
_update_jiggle_joint_data();
}
float SkeletonModification2DJiggle::get_damping() const {
return damping;
}
void SkeletonModification2DJiggle::set_use_gravity(bool p_use_gravity) {
use_gravity = p_use_gravity;
_update_jiggle_joint_data();
}
bool SkeletonModification2DJiggle::get_use_gravity() const {
return use_gravity;
}
void SkeletonModification2DJiggle::set_gravity(Vector2 p_gravity) {
gravity = p_gravity;
_update_jiggle_joint_data();
}
Vector2 SkeletonModification2DJiggle::get_gravity() const {
return gravity;
}
void SkeletonModification2DJiggle::set_use_colliders(bool p_use_colliders) {
use_colliders = p_use_colliders;
notify_property_list_changed();
}
bool SkeletonModification2DJiggle::get_use_colliders() const {
return use_colliders;
}
void SkeletonModification2DJiggle::set_collision_mask(int p_mask) {
collision_mask = p_mask;
}
int SkeletonModification2DJiggle::get_collision_mask() const {
return collision_mask;
}
// Jiggle joint data functions
int SkeletonModification2DJiggle::get_jiggle_data_chain_length() {
return jiggle_data_chain.size();
}
void SkeletonModification2DJiggle::set_jiggle_data_chain_length(int p_length) {
ERR_FAIL_COND(p_length < 0);
jiggle_data_chain.resize(p_length);
notify_property_list_changed();
}
void SkeletonModification2DJiggle::set_jiggle_joint_bone2d_node(int p_joint_idx, const NodePath &p_target_node) {
ERR_FAIL_INDEX_MSG(p_joint_idx, jiggle_data_chain.size(), "Jiggle joint out of range!");
jiggle_data_chain.write[p_joint_idx].bone2d_node = p_target_node;
jiggle_joint_update_bone2d_cache(p_joint_idx);
notify_property_list_changed();
}
NodePath SkeletonModification2DJiggle::get_jiggle_joint_bone2d_node(int p_joint_idx) const {
ERR_FAIL_INDEX_V_MSG(p_joint_idx, jiggle_data_chain.size(), NodePath(), "Jiggle joint out of range!");
return jiggle_data_chain[p_joint_idx].bone2d_node;
}
void SkeletonModification2DJiggle::set_jiggle_joint_bone_index(int p_joint_idx, int p_bone_idx) {
ERR_FAIL_INDEX_MSG(p_joint_idx, jiggle_data_chain.size(), "Jiggle joint out of range!");
ERR_FAIL_COND_MSG(p_bone_idx < 0, "Bone index is out of range: The index is too low!");
if (is_setup) {
if (stack->skeleton) {
ERR_FAIL_INDEX_MSG(p_bone_idx, stack->skeleton->get_bone_count(), "Passed-in Bone index is out of range!");
jiggle_data_chain.write[p_joint_idx].bone_idx = p_bone_idx;
jiggle_data_chain.write[p_joint_idx].bone2d_node_cache = stack->skeleton->get_bone(p_bone_idx)->get_instance_id();
jiggle_data_chain.write[p_joint_idx].bone2d_node = stack->skeleton->get_path_to(stack->skeleton->get_bone(p_bone_idx));
} else {
WARN_PRINT("Cannot verify the Jiggle joint " + itos(p_joint_idx) + " bone index for this modification...");
jiggle_data_chain.write[p_joint_idx].bone_idx = p_bone_idx;
}
} else {
jiggle_data_chain.write[p_joint_idx].bone_idx = p_bone_idx;
}
notify_property_list_changed();
}
int SkeletonModification2DJiggle::get_jiggle_joint_bone_index(int p_joint_idx) const {
ERR_FAIL_INDEX_V_MSG(p_joint_idx, jiggle_data_chain.size(), -1, "Jiggle joint out of range!");
return jiggle_data_chain[p_joint_idx].bone_idx;
}
void SkeletonModification2DJiggle::set_jiggle_joint_override(int p_joint_idx, bool p_override) {
ERR_FAIL_INDEX(p_joint_idx, jiggle_data_chain.size());
jiggle_data_chain.write[p_joint_idx].override_defaults = p_override;
_update_jiggle_joint_data();
notify_property_list_changed();
}
bool SkeletonModification2DJiggle::get_jiggle_joint_override(int p_joint_idx) const {
ERR_FAIL_INDEX_V(p_joint_idx, jiggle_data_chain.size(), false);
return jiggle_data_chain[p_joint_idx].override_defaults;
}
void SkeletonModification2DJiggle::set_jiggle_joint_stiffness(int p_joint_idx, float p_stiffness) {
ERR_FAIL_COND_MSG(p_stiffness < 0, "Stiffness cannot be set to a negative value!");
ERR_FAIL_INDEX(p_joint_idx, jiggle_data_chain.size());
jiggle_data_chain.write[p_joint_idx].stiffness = p_stiffness;
}
float SkeletonModification2DJiggle::get_jiggle_joint_stiffness(int p_joint_idx) const {
ERR_FAIL_INDEX_V(p_joint_idx, jiggle_data_chain.size(), -1);
return jiggle_data_chain[p_joint_idx].stiffness;
}
void SkeletonModification2DJiggle::set_jiggle_joint_mass(int p_joint_idx, float p_mass) {
ERR_FAIL_COND_MSG(p_mass < 0, "Mass cannot be set to a negative value!");
ERR_FAIL_INDEX(p_joint_idx, jiggle_data_chain.size());
jiggle_data_chain.write[p_joint_idx].mass = p_mass;
}
float SkeletonModification2DJiggle::get_jiggle_joint_mass(int p_joint_idx) const {
ERR_FAIL_INDEX_V(p_joint_idx, jiggle_data_chain.size(), -1);
return jiggle_data_chain[p_joint_idx].mass;
}
void SkeletonModification2DJiggle::set_jiggle_joint_damping(int p_joint_idx, float p_damping) {
ERR_FAIL_COND_MSG(p_damping < 0, "Damping cannot be set to a negative value!");
ERR_FAIL_INDEX(p_joint_idx, jiggle_data_chain.size());
jiggle_data_chain.write[p_joint_idx].damping = p_damping;
}
float SkeletonModification2DJiggle::get_jiggle_joint_damping(int p_joint_idx) const {
ERR_FAIL_INDEX_V(p_joint_idx, jiggle_data_chain.size(), -1);
return jiggle_data_chain[p_joint_idx].damping;
}
void SkeletonModification2DJiggle::set_jiggle_joint_use_gravity(int p_joint_idx, bool p_use_gravity) {
ERR_FAIL_INDEX(p_joint_idx, jiggle_data_chain.size());
jiggle_data_chain.write[p_joint_idx].use_gravity = p_use_gravity;
notify_property_list_changed();
}
bool SkeletonModification2DJiggle::get_jiggle_joint_use_gravity(int p_joint_idx) const {
ERR_FAIL_INDEX_V(p_joint_idx, jiggle_data_chain.size(), false);
return jiggle_data_chain[p_joint_idx].use_gravity;
}
void SkeletonModification2DJiggle::set_jiggle_joint_gravity(int p_joint_idx, Vector2 p_gravity) {
ERR_FAIL_INDEX(p_joint_idx, jiggle_data_chain.size());
jiggle_data_chain.write[p_joint_idx].gravity = p_gravity;
}
Vector2 SkeletonModification2DJiggle::get_jiggle_joint_gravity(int p_joint_idx) const {
ERR_FAIL_INDEX_V(p_joint_idx, jiggle_data_chain.size(), Vector2(0, 0));
return jiggle_data_chain[p_joint_idx].gravity;
}
void SkeletonModification2DJiggle::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_target_node", "target_nodepath"), &SkeletonModification2DJiggle::set_target_node);
ClassDB::bind_method(D_METHOD("get_target_node"), &SkeletonModification2DJiggle::get_target_node);
ClassDB::bind_method(D_METHOD("set_jiggle_data_chain_length", "length"), &SkeletonModification2DJiggle::set_jiggle_data_chain_length);
ClassDB::bind_method(D_METHOD("get_jiggle_data_chain_length"), &SkeletonModification2DJiggle::get_jiggle_data_chain_length);
ClassDB::bind_method(D_METHOD("set_stiffness", "stiffness"), &SkeletonModification2DJiggle::set_stiffness);
ClassDB::bind_method(D_METHOD("get_stiffness"), &SkeletonModification2DJiggle::get_stiffness);
ClassDB::bind_method(D_METHOD("set_mass", "mass"), &SkeletonModification2DJiggle::set_mass);
ClassDB::bind_method(D_METHOD("get_mass"), &SkeletonModification2DJiggle::get_mass);
ClassDB::bind_method(D_METHOD("set_damping", "damping"), &SkeletonModification2DJiggle::set_damping);
ClassDB::bind_method(D_METHOD("get_damping"), &SkeletonModification2DJiggle::get_damping);
ClassDB::bind_method(D_METHOD("set_use_gravity", "use_gravity"), &SkeletonModification2DJiggle::set_use_gravity);
ClassDB::bind_method(D_METHOD("get_use_gravity"), &SkeletonModification2DJiggle::get_use_gravity);
ClassDB::bind_method(D_METHOD("set_gravity", "gravity"), &SkeletonModification2DJiggle::set_gravity);
ClassDB::bind_method(D_METHOD("get_gravity"), &SkeletonModification2DJiggle::get_gravity);
ClassDB::bind_method(D_METHOD("set_use_colliders", "use_colliders"), &SkeletonModification2DJiggle::set_use_colliders);
ClassDB::bind_method(D_METHOD("get_use_colliders"), &SkeletonModification2DJiggle::get_use_colliders);
ClassDB::bind_method(D_METHOD("set_collision_mask", "collision_mask"), &SkeletonModification2DJiggle::set_collision_mask);
ClassDB::bind_method(D_METHOD("get_collision_mask"), &SkeletonModification2DJiggle::get_collision_mask);
// Jiggle joint data functions
ClassDB::bind_method(D_METHOD("set_jiggle_joint_bone2d_node", "joint_idx", "bone2d_node"), &SkeletonModification2DJiggle::set_jiggle_joint_bone2d_node);
ClassDB::bind_method(D_METHOD("get_jiggle_joint_bone2d_node", "joint_idx"), &SkeletonModification2DJiggle::get_jiggle_joint_bone2d_node);
ClassDB::bind_method(D_METHOD("set_jiggle_joint_bone_index", "joint_idx", "bone_idx"), &SkeletonModification2DJiggle::set_jiggle_joint_bone_index);
ClassDB::bind_method(D_METHOD("get_jiggle_joint_bone_index", "joint_idx"), &SkeletonModification2DJiggle::get_jiggle_joint_bone_index);
ClassDB::bind_method(D_METHOD("set_jiggle_joint_override", "joint_idx", "override"), &SkeletonModification2DJiggle::set_jiggle_joint_override);
ClassDB::bind_method(D_METHOD("get_jiggle_joint_override", "joint_idx"), &SkeletonModification2DJiggle::get_jiggle_joint_override);
ClassDB::bind_method(D_METHOD("set_jiggle_joint_stiffness", "joint_idx", "stiffness"), &SkeletonModification2DJiggle::set_jiggle_joint_stiffness);
ClassDB::bind_method(D_METHOD("get_jiggle_joint_stiffness", "joint_idx"), &SkeletonModification2DJiggle::get_jiggle_joint_stiffness);
ClassDB::bind_method(D_METHOD("set_jiggle_joint_mass", "joint_idx", "mass"), &SkeletonModification2DJiggle::set_jiggle_joint_mass);
ClassDB::bind_method(D_METHOD("get_jiggle_joint_mass", "joint_idx"), &SkeletonModification2DJiggle::get_jiggle_joint_mass);
ClassDB::bind_method(D_METHOD("set_jiggle_joint_damping", "joint_idx", "damping"), &SkeletonModification2DJiggle::set_jiggle_joint_damping);
ClassDB::bind_method(D_METHOD("get_jiggle_joint_damping", "joint_idx"), &SkeletonModification2DJiggle::get_jiggle_joint_damping);
ClassDB::bind_method(D_METHOD("set_jiggle_joint_use_gravity", "joint_idx", "use_gravity"), &SkeletonModification2DJiggle::set_jiggle_joint_use_gravity);
ClassDB::bind_method(D_METHOD("get_jiggle_joint_use_gravity", "joint_idx"), &SkeletonModification2DJiggle::get_jiggle_joint_use_gravity);
ClassDB::bind_method(D_METHOD("set_jiggle_joint_gravity", "joint_idx", "gravity"), &SkeletonModification2DJiggle::set_jiggle_joint_gravity);
ClassDB::bind_method(D_METHOD("get_jiggle_joint_gravity", "joint_idx"), &SkeletonModification2DJiggle::get_jiggle_joint_gravity);
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node2D"), "set_target_node", "get_target_node");
ADD_PROPERTY(PropertyInfo(Variant::INT, "jiggle_data_chain_length", PROPERTY_HINT_RANGE, "0,100,1"), "set_jiggle_data_chain_length", "get_jiggle_data_chain_length");
ADD_GROUP("Default Joint Settings", "");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "stiffness"), "set_stiffness", "get_stiffness");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mass"), "set_mass", "get_mass");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "damping", PROPERTY_HINT_RANGE, "0, 1, 0.01"), "set_damping", "get_damping");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_gravity"), "set_use_gravity", "get_use_gravity");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "gravity"), "set_gravity", "get_gravity");
ADD_GROUP("", "");
}
SkeletonModification2DJiggle::SkeletonModification2DJiggle() {
stack = nullptr;
is_setup = false;
jiggle_data_chain = Vector<Jiggle_Joint_Data2D>();
stiffness = 3;
mass = 0.75;
damping = 0.75;
use_gravity = false;
gravity = Vector2(0, 6.0);
enabled = true;
editor_draw_gizmo = false; // Nothing to really show in a gizmo right now.
}
SkeletonModification2DJiggle::~SkeletonModification2DJiggle() {
}

View File

@@ -0,0 +1,136 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#pragma once
#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();
};

View File

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

View File

@@ -0,0 +1,97 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#pragma once
#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();
};

View File

@@ -0,0 +1,298 @@
/**************************************************************************/
/* 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")) {
// Do nothing!
return false;
}
}
#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 = ObjectDB::get_instance<PhysicalBone2D>(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.is_empty()) {
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 = ObjectDB::get_instance<PhysicalBone2D>(physical_bone_chain[i].physical_bone_node_cache);
if (!physical_bone) {
continue;
}
if (_simulation_state_dirty_names.has(physical_bone->get_name())) {
physical_bone->set_simulate_physics(_simulation_state_dirty_process);
}
}
}
}
void SkeletonModification2DPhysicalBones::set_physical_bone_node(int p_joint_idx, const NodePath &p_nodepath) {
ERR_FAIL_INDEX_MSG(p_joint_idx, physical_bone_chain.size(), "Joint index out of range!");
physical_bone_chain.write[p_joint_idx].physical_bone_node = p_nodepath;
_physical_bone_update_cache(p_joint_idx);
}
NodePath SkeletonModification2DPhysicalBones::get_physical_bone_node(int p_joint_idx) const {
ERR_FAIL_INDEX_V_MSG(p_joint_idx, physical_bone_chain.size(), NodePath(), "Joint index out of range!");
return physical_bone_chain[p_joint_idx].physical_bone_node;
}
void SkeletonModification2DPhysicalBones::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_physical_bone_chain_length", "length"), &SkeletonModification2DPhysicalBones::set_physical_bone_chain_length);
ClassDB::bind_method(D_METHOD("get_physical_bone_chain_length"), &SkeletonModification2DPhysicalBones::get_physical_bone_chain_length);
ClassDB::bind_method(D_METHOD("set_physical_bone_node", "joint_idx", "physicalbone2d_node"), &SkeletonModification2DPhysicalBones::set_physical_bone_node);
ClassDB::bind_method(D_METHOD("get_physical_bone_node", "joint_idx"), &SkeletonModification2DPhysicalBones::get_physical_bone_node);
ClassDB::bind_method(D_METHOD("fetch_physical_bones"), &SkeletonModification2DPhysicalBones::fetch_physical_bones);
ClassDB::bind_method(D_METHOD("start_simulation", "bones"), &SkeletonModification2DPhysicalBones::start_simulation, DEFVAL(Array()));
ClassDB::bind_method(D_METHOD("stop_simulation", "bones"), &SkeletonModification2DPhysicalBones::stop_simulation, DEFVAL(Array()));
ADD_PROPERTY(PropertyInfo(Variant::INT, "physical_bone_chain_length", PROPERTY_HINT_RANGE, "0,100,1"), "set_physical_bone_chain_length", "get_physical_bone_chain_length");
}
SkeletonModification2DPhysicalBones::SkeletonModification2DPhysicalBones() {
stack = nullptr;
is_setup = false;
physical_bone_chain = Vector<PhysicalBone_Data2D>();
enabled = true;
editor_draw_gizmo = false; // Nothing to really show in a gizmo right now.
}
SkeletonModification2DPhysicalBones::~SkeletonModification2DPhysicalBones() {
}

View File

@@ -0,0 +1,79 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#pragma once
#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();
};

View File

@@ -0,0 +1,135 @@
/**************************************************************************/
/* skeleton_modification_2d_stackholder.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "skeleton_modification_2d_stackholder.h"
#include "scene/2d/skeleton_2d.h"
bool SkeletonModification2DStackHolder::_set(const StringName &p_path, const Variant &p_value) {
String path = p_path;
if (path == "held_modification_stack") {
set_held_modification_stack(p_value);
}
#ifdef TOOLS_ENABLED
else if (path == "editor/draw_gizmo") {
set_editor_draw_gizmo(p_value);
}
#endif // TOOLS_ENABLED
else {
return false;
}
return true;
}
bool SkeletonModification2DStackHolder::_get(const StringName &p_path, Variant &r_ret) const {
String path = p_path;
if (path == "held_modification_stack") {
r_ret = get_held_modification_stack();
}
#ifdef TOOLS_ENABLED
else if (path == "editor/draw_gizmo") {
r_ret = get_editor_draw_gizmo();
}
#endif // TOOLS_ENABLED
else {
return false;
}
return true;
}
void SkeletonModification2DStackHolder::_get_property_list(List<PropertyInfo> *p_list) const {
p_list->push_back(PropertyInfo(Variant::OBJECT, "held_modification_stack", PROPERTY_HINT_RESOURCE_TYPE, "SkeletonModificationStack2D", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_ALWAYS_DUPLICATE));
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint()) {
p_list->push_back(PropertyInfo(Variant::BOOL, "editor/draw_gizmo", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT));
}
#endif // TOOLS_ENABLED
}
void SkeletonModification2DStackHolder::_execute(float p_delta) {
ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr,
"Modification is not setup and therefore cannot execute!");
if (held_modification_stack.is_valid()) {
held_modification_stack->execute(p_delta, execution_mode);
}
}
void SkeletonModification2DStackHolder::_setup_modification(SkeletonModificationStack2D *p_stack) {
stack = p_stack;
if (stack != nullptr) {
is_setup = true;
if (held_modification_stack.is_valid()) {
held_modification_stack->set_skeleton(stack->get_skeleton());
held_modification_stack->setup();
}
}
}
void SkeletonModification2DStackHolder::_draw_editor_gizmo() {
if (stack) {
if (held_modification_stack.is_valid()) {
held_modification_stack->draw_editor_gizmos();
}
}
}
void SkeletonModification2DStackHolder::set_held_modification_stack(Ref<SkeletonModificationStack2D> p_held_stack) {
held_modification_stack = p_held_stack;
if (is_setup && held_modification_stack.is_valid()) {
held_modification_stack->set_skeleton(stack->get_skeleton());
held_modification_stack->setup();
}
}
Ref<SkeletonModificationStack2D> SkeletonModification2DStackHolder::get_held_modification_stack() const {
return held_modification_stack;
}
void SkeletonModification2DStackHolder::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_held_modification_stack", "held_modification_stack"), &SkeletonModification2DStackHolder::set_held_modification_stack);
ClassDB::bind_method(D_METHOD("get_held_modification_stack"), &SkeletonModification2DStackHolder::get_held_modification_stack);
}
SkeletonModification2DStackHolder::SkeletonModification2DStackHolder() {
stack = nullptr;
is_setup = false;
enabled = true;
}
SkeletonModification2DStackHolder::~SkeletonModification2DStackHolder() {
}

View File

@@ -0,0 +1,61 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#pragma once
#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();
};

View File

@@ -0,0 +1,489 @@
/**************************************************************************/
/* skeleton_modification_2d_twoboneik.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "skeleton_modification_2d_twoboneik.h"
#include "scene/2d/skeleton_2d.h"
#ifdef TOOLS_ENABLED
#include "editor/settings/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 = ObjectDB::get_instance<Node2D>(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 (std::isnan(angle_0) || std::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), std::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), std::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 = ObjectDB::get_instance<Node2D>(target_node_cache);
target_direction = operation_bone_one->get_global_position().direction_to(target->get_global_position());
}
stack->skeleton->draw_circle(target_direction * target_minimum_distance, 8, bone_ik_color);
stack->skeleton->draw_circle(target_direction * target_maximum_distance, 8, bone_ik_color);
stack->skeleton->draw_line(target_direction * target_minimum_distance, target_direction * target_maximum_distance, bone_ik_color, 2.0);
}
}
}
#endif // TOOLS_ENABLED
}
void SkeletonModification2DTwoBoneIK::update_target_cache() {
if (!is_setup || !stack) {
if (is_setup) {
ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!");
}
return;
}
target_node_cache = ObjectID();
if (stack->skeleton) {
if (stack->skeleton->is_inside_tree()) {
if (stack->skeleton->has_node(target_node)) {
Node *node = stack->skeleton->get_node(target_node);
ERR_FAIL_COND_MSG(!node || stack->skeleton == node,
"Cannot update target cache: node is this modification's skeleton or cannot be found!");
ERR_FAIL_COND_MSG(!node->is_inside_tree(),
"Cannot update target cache: node is not in the scene tree!");
target_node_cache = node->get_instance_id();
}
}
}
}
void SkeletonModification2DTwoBoneIK::update_joint_one_bone2d_cache() {
if (!is_setup || !stack) {
if (is_setup) {
ERR_PRINT_ONCE("Cannot update joint one Bone2D cache: modification is not properly setup!");
}
return;
}
joint_one_bone2d_node_cache = ObjectID();
if (stack->skeleton) {
if (stack->skeleton->is_inside_tree()) {
if (stack->skeleton->has_node(joint_one_bone2d_node)) {
Node *node = stack->skeleton->get_node(joint_one_bone2d_node);
ERR_FAIL_COND_MSG(!node || stack->skeleton == node,
"Cannot update joint one Bone2D cache: node is this modification's skeleton or cannot be found!");
ERR_FAIL_COND_MSG(!node->is_inside_tree(),
"Cannot update joint one Bone2D cache: node is not in the scene tree!");
joint_one_bone2d_node_cache = node->get_instance_id();
Bone2D *bone = Object::cast_to<Bone2D>(node);
if (bone) {
joint_one_bone_idx = bone->get_index_in_skeleton();
} else {
ERR_FAIL_MSG("Update joint one Bone2D cache: Nodepath to Bone2D is not a Bone2D node!");
}
}
}
}
}
void SkeletonModification2DTwoBoneIK::update_joint_two_bone2d_cache() {
if (!is_setup || !stack) {
if (is_setup) {
ERR_PRINT_ONCE("Cannot update joint two Bone2D cache: modification is not properly setup!");
}
return;
}
joint_two_bone2d_node_cache = ObjectID();
if (stack->skeleton) {
if (stack->skeleton->is_inside_tree()) {
if (stack->skeleton->has_node(joint_two_bone2d_node)) {
Node *node = stack->skeleton->get_node(joint_two_bone2d_node);
ERR_FAIL_COND_MSG(!node || stack->skeleton == node,
"Cannot update joint two Bone2D cache: node is this modification's skeleton or cannot be found!");
ERR_FAIL_COND_MSG(!node->is_inside_tree(),
"Cannot update joint two Bone2D cache: node is not in scene tree!");
joint_two_bone2d_node_cache = node->get_instance_id();
Bone2D *bone = Object::cast_to<Bone2D>(node);
if (bone) {
joint_two_bone_idx = bone->get_index_in_skeleton();
} else {
ERR_FAIL_MSG("Update joint two Bone2D cache: Nodepath to Bone2D is not a Bone2D node!");
}
}
}
}
}
void SkeletonModification2DTwoBoneIK::set_target_node(const NodePath &p_target_node) {
target_node = p_target_node;
update_target_cache();
}
NodePath SkeletonModification2DTwoBoneIK::get_target_node() const {
return target_node;
}
void SkeletonModification2DTwoBoneIK::set_joint_one_bone2d_node(const NodePath &p_target_node) {
joint_one_bone2d_node = p_target_node;
update_joint_one_bone2d_cache();
notify_property_list_changed();
}
void SkeletonModification2DTwoBoneIK::set_target_minimum_distance(float p_distance) {
ERR_FAIL_COND_MSG(p_distance < 0, "Target minimum distance cannot be less than zero!");
target_minimum_distance = p_distance;
}
float SkeletonModification2DTwoBoneIK::get_target_minimum_distance() const {
return target_minimum_distance;
}
void SkeletonModification2DTwoBoneIK::set_target_maximum_distance(float p_distance) {
ERR_FAIL_COND_MSG(p_distance < 0, "Target maximum distance cannot be less than zero!");
target_maximum_distance = p_distance;
}
float SkeletonModification2DTwoBoneIK::get_target_maximum_distance() const {
return target_maximum_distance;
}
void SkeletonModification2DTwoBoneIK::set_flip_bend_direction(bool p_flip_direction) {
flip_bend_direction = p_flip_direction;
#ifdef TOOLS_ENABLED
if (stack && is_setup) {
stack->set_editor_gizmos_dirty(true);
}
#endif // TOOLS_ENABLED
}
bool SkeletonModification2DTwoBoneIK::get_flip_bend_direction() const {
return flip_bend_direction;
}
NodePath SkeletonModification2DTwoBoneIK::get_joint_one_bone2d_node() const {
return joint_one_bone2d_node;
}
void SkeletonModification2DTwoBoneIK::set_joint_two_bone2d_node(const NodePath &p_target_node) {
joint_two_bone2d_node = p_target_node;
update_joint_two_bone2d_cache();
notify_property_list_changed();
}
NodePath SkeletonModification2DTwoBoneIK::get_joint_two_bone2d_node() const {
return joint_two_bone2d_node;
}
void SkeletonModification2DTwoBoneIK::set_joint_one_bone_idx(int p_bone_idx) {
ERR_FAIL_COND_MSG(p_bone_idx < 0, "Bone index is out of range: The index is too low!");
if (is_setup) {
if (stack->skeleton) {
ERR_FAIL_INDEX_MSG(p_bone_idx, stack->skeleton->get_bone_count(), "Passed-in Bone index is out of range!");
joint_one_bone_idx = p_bone_idx;
joint_one_bone2d_node_cache = stack->skeleton->get_bone(p_bone_idx)->get_instance_id();
joint_one_bone2d_node = stack->skeleton->get_path_to(stack->skeleton->get_bone(p_bone_idx));
} else {
WARN_PRINT("TwoBoneIK: Cannot verify the joint bone index for joint one...");
joint_one_bone_idx = p_bone_idx;
}
} else {
joint_one_bone_idx = p_bone_idx;
}
notify_property_list_changed();
}
int SkeletonModification2DTwoBoneIK::get_joint_one_bone_idx() const {
return joint_one_bone_idx;
}
void SkeletonModification2DTwoBoneIK::set_joint_two_bone_idx(int p_bone_idx) {
ERR_FAIL_COND_MSG(p_bone_idx < 0, "Bone index is out of range: The index is too low!");
if (is_setup) {
if (stack->skeleton) {
ERR_FAIL_INDEX_MSG(p_bone_idx, stack->skeleton->get_bone_count(), "Passed-in Bone index is out of range!");
joint_two_bone_idx = p_bone_idx;
joint_two_bone2d_node_cache = stack->skeleton->get_bone(p_bone_idx)->get_instance_id();
joint_two_bone2d_node = stack->skeleton->get_path_to(stack->skeleton->get_bone(p_bone_idx));
} else {
WARN_PRINT("TwoBoneIK: Cannot verify the joint bone index for joint two...");
joint_two_bone_idx = p_bone_idx;
}
} else {
joint_two_bone_idx = p_bone_idx;
}
notify_property_list_changed();
}
int SkeletonModification2DTwoBoneIK::get_joint_two_bone_idx() const {
return joint_two_bone_idx;
}
#ifdef TOOLS_ENABLED
void SkeletonModification2DTwoBoneIK::set_editor_draw_min_max(bool p_draw) {
editor_draw_min_max = p_draw;
}
bool SkeletonModification2DTwoBoneIK::get_editor_draw_min_max() const {
return editor_draw_min_max;
}
#endif // TOOLS_ENABLED
void SkeletonModification2DTwoBoneIK::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_target_node", "target_nodepath"), &SkeletonModification2DTwoBoneIK::set_target_node);
ClassDB::bind_method(D_METHOD("get_target_node"), &SkeletonModification2DTwoBoneIK::get_target_node);
ClassDB::bind_method(D_METHOD("set_target_minimum_distance", "minimum_distance"), &SkeletonModification2DTwoBoneIK::set_target_minimum_distance);
ClassDB::bind_method(D_METHOD("get_target_minimum_distance"), &SkeletonModification2DTwoBoneIK::get_target_minimum_distance);
ClassDB::bind_method(D_METHOD("set_target_maximum_distance", "maximum_distance"), &SkeletonModification2DTwoBoneIK::set_target_maximum_distance);
ClassDB::bind_method(D_METHOD("get_target_maximum_distance"), &SkeletonModification2DTwoBoneIK::get_target_maximum_distance);
ClassDB::bind_method(D_METHOD("set_flip_bend_direction", "flip_direction"), &SkeletonModification2DTwoBoneIK::set_flip_bend_direction);
ClassDB::bind_method(D_METHOD("get_flip_bend_direction"), &SkeletonModification2DTwoBoneIK::get_flip_bend_direction);
ClassDB::bind_method(D_METHOD("set_joint_one_bone2d_node", "bone2d_node"), &SkeletonModification2DTwoBoneIK::set_joint_one_bone2d_node);
ClassDB::bind_method(D_METHOD("get_joint_one_bone2d_node"), &SkeletonModification2DTwoBoneIK::get_joint_one_bone2d_node);
ClassDB::bind_method(D_METHOD("set_joint_one_bone_idx", "bone_idx"), &SkeletonModification2DTwoBoneIK::set_joint_one_bone_idx);
ClassDB::bind_method(D_METHOD("get_joint_one_bone_idx"), &SkeletonModification2DTwoBoneIK::get_joint_one_bone_idx);
ClassDB::bind_method(D_METHOD("set_joint_two_bone2d_node", "bone2d_node"), &SkeletonModification2DTwoBoneIK::set_joint_two_bone2d_node);
ClassDB::bind_method(D_METHOD("get_joint_two_bone2d_node"), &SkeletonModification2DTwoBoneIK::get_joint_two_bone2d_node);
ClassDB::bind_method(D_METHOD("set_joint_two_bone_idx", "bone_idx"), &SkeletonModification2DTwoBoneIK::set_joint_two_bone_idx);
ClassDB::bind_method(D_METHOD("get_joint_two_bone_idx"), &SkeletonModification2DTwoBoneIK::get_joint_two_bone_idx);
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node2D"), "set_target_node", "get_target_node");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "target_minimum_distance", PROPERTY_HINT_RANGE, "0,100000000,0.01,suffix:px"), "set_target_minimum_distance", "get_target_minimum_distance");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "target_maximum_distance", PROPERTY_HINT_NONE, "0,100000000,0.01,suffix:px"), "set_target_maximum_distance", "get_target_maximum_distance");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_bend_direction", PROPERTY_HINT_NONE, ""), "set_flip_bend_direction", "get_flip_bend_direction");
ADD_GROUP("", "");
}
SkeletonModification2DTwoBoneIK::SkeletonModification2DTwoBoneIK() {
stack = nullptr;
is_setup = false;
enabled = true;
editor_draw_gizmo = true;
}
SkeletonModification2DTwoBoneIK::~SkeletonModification2DTwoBoneIK() {
}

View File

@@ -0,0 +1,104 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#pragma once
#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();
};

View File

@@ -0,0 +1,270 @@
/**************************************************************************/
/* skeleton_modification_stack_2d.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "skeleton_modification_stack_2d.h"
#include "scene/2d/skeleton_2d.h"
void SkeletonModificationStack2D::_get_property_list(List<PropertyInfo> *p_list) const {
for (int i = 0; i < modifications.size(); i++) {
p_list->push_back(
PropertyInfo(Variant::OBJECT, "modifications/" + itos(i),
PROPERTY_HINT_RESOURCE_TYPE,
"SkeletonModification2D",
PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_ALWAYS_DUPLICATE));
}
}
bool SkeletonModificationStack2D::_set(const StringName &p_path, const Variant &p_value) {
String path = p_path;
if (path.begins_with("modifications/")) {
int mod_idx = path.get_slicec('/', 1).to_int();
set_modification(mod_idx, p_value);
return true;
}
return false;
}
bool SkeletonModificationStack2D::_get(const StringName &p_path, Variant &r_ret) const {
String path = p_path;
if (path.begins_with("modifications/")) {
int mod_idx = path.get_slicec('/', 1).to_int();
r_ret = get_modification(mod_idx);
return true;
}
return false;
}
void SkeletonModificationStack2D::setup() {
if (is_setup) {
return;
}
if (skeleton != nullptr) {
is_setup = true;
for (int i = 0; i < modifications.size(); i++) {
if (modifications[i].is_null()) {
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_null()) {
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_null()) {
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_null()) {
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_null());
p_mod->_setup_modification(this);
modifications.push_back(p_mod);
#ifdef TOOLS_ENABLED
set_editor_gizmos_dirty(true);
#endif // TOOLS_ENABLED
}
void SkeletonModificationStack2D::delete_modification(int p_mod_idx) {
ERR_FAIL_INDEX(p_mod_idx, modifications.size());
modifications.remove_at(p_mod_idx);
#ifdef TOOLS_ENABLED
set_editor_gizmos_dirty(true);
#endif // TOOLS_ENABLED
}
void SkeletonModificationStack2D::set_modification(int p_mod_idx, Ref<SkeletonModification2D> p_mod) {
ERR_FAIL_INDEX(p_mod_idx, modifications.size());
if (p_mod.is_null()) {
modifications.write[p_mod_idx] = Ref<SkeletonModification2D>();
} else {
modifications.write[p_mod_idx] = p_mod;
p_mod->_setup_modification(this);
}
#ifdef TOOLS_ENABLED
set_editor_gizmos_dirty(true);
#endif // TOOLS_ENABLED
}
void SkeletonModificationStack2D::set_modification_count(int p_count) {
ERR_FAIL_COND_MSG(p_count < 0, "Modification count cannot be less than zero.");
modifications.resize(p_count);
notify_property_list_changed();
#ifdef TOOLS_ENABLED
set_editor_gizmos_dirty(true);
#endif // TOOLS_ENABLED
}
int SkeletonModificationStack2D::get_modification_count() const {
return modifications.size();
}
void SkeletonModificationStack2D::set_skeleton(Skeleton2D *p_skeleton) {
skeleton = p_skeleton;
}
Skeleton2D *SkeletonModificationStack2D::get_skeleton() const {
return skeleton;
}
bool SkeletonModificationStack2D::get_is_setup() const {
return is_setup;
}
void SkeletonModificationStack2D::set_enabled(bool p_enabled) {
enabled = p_enabled;
}
bool SkeletonModificationStack2D::get_enabled() const {
return enabled;
}
void SkeletonModificationStack2D::set_strength(float p_strength) {
ERR_FAIL_COND_MSG(p_strength < 0, "Strength cannot be less than zero!");
ERR_FAIL_COND_MSG(p_strength > 1, "Strength cannot be more than one!");
strength = p_strength;
}
float SkeletonModificationStack2D::get_strength() const {
return strength;
}
void SkeletonModificationStack2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("setup"), &SkeletonModificationStack2D::setup);
ClassDB::bind_method(D_METHOD("execute", "delta", "execution_mode"), &SkeletonModificationStack2D::execute);
ClassDB::bind_method(D_METHOD("enable_all_modifications", "enabled"), &SkeletonModificationStack2D::enable_all_modifications);
ClassDB::bind_method(D_METHOD("get_modification", "mod_idx"), &SkeletonModificationStack2D::get_modification);
ClassDB::bind_method(D_METHOD("add_modification", "modification"), &SkeletonModificationStack2D::add_modification);
ClassDB::bind_method(D_METHOD("delete_modification", "mod_idx"), &SkeletonModificationStack2D::delete_modification);
ClassDB::bind_method(D_METHOD("set_modification", "mod_idx", "modification"), &SkeletonModificationStack2D::set_modification);
ClassDB::bind_method(D_METHOD("set_modification_count", "count"), &SkeletonModificationStack2D::set_modification_count);
ClassDB::bind_method(D_METHOD("get_modification_count"), &SkeletonModificationStack2D::get_modification_count);
ClassDB::bind_method(D_METHOD("get_is_setup"), &SkeletonModificationStack2D::get_is_setup);
ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &SkeletonModificationStack2D::set_enabled);
ClassDB::bind_method(D_METHOD("get_enabled"), &SkeletonModificationStack2D::get_enabled);
ClassDB::bind_method(D_METHOD("set_strength", "strength"), &SkeletonModificationStack2D::set_strength);
ClassDB::bind_method(D_METHOD("get_strength"), &SkeletonModificationStack2D::get_strength);
ClassDB::bind_method(D_METHOD("get_skeleton"), &SkeletonModificationStack2D::get_skeleton);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "get_enabled");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "strength", PROPERTY_HINT_RANGE, "0, 1, 0.001"), "set_strength", "get_strength");
ADD_PROPERTY(PropertyInfo(Variant::INT, "modification_count", PROPERTY_HINT_RANGE, "0, 100, 1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_ARRAY, "Modifications,modifications/"), "set_modification_count", "get_modification_count");
}
SkeletonModificationStack2D::SkeletonModificationStack2D() {
}

View File

@@ -0,0 +1,95 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#pragma once
#include "core/io/resource.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;
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();
};

View File

@@ -0,0 +1,52 @@
/**************************************************************************/
/* 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"
#ifndef NAVIGATION_2D_DISABLED
Ref<NavigationPolygon> TileData::_get_navigation_polygon_bind_compat_84660(int p_layer_id) const {
return get_navigation_polygon(p_layer_id, false, false, false);
}
#endif // NAVIGATION_2D_DISABLED
Ref<OccluderPolygon2D> TileData::_get_occluder_bind_compat_84660(int p_layer_id) const {
return get_occluder_polygon(p_layer_id, 0, false, false, false);
}
void TileData::_bind_compatibility_methods() {
#ifndef NAVIGATION_2D_DISABLED
ClassDB::bind_compatibility_method(D_METHOD("get_navigation_polygon"), &TileData::_get_navigation_polygon_bind_compat_84660);
#endif // NAVIGATION_2D_DISABLED
ClassDB::bind_compatibility_method(D_METHOD("get_occluder"), &TileData::_get_occluder_bind_compat_84660);
}
#endif

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,160 @@
/**************************************************************************/
/* 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 {
const Vector2 shape_center = distance * normal;
// Orthogonal part of the shape editor gizmo (the flat line).
const Vector2 ortho_segment_a = shape_center - normal.orthogonal() * 100;
const Vector2 ortho_segment_b = shape_center + normal.orthogonal() * 100;
const Vector2 ortho_closest = Geometry2D::get_closest_point_to_segment(p_point, ortho_segment_a, ortho_segment_b);
if (p_point.distance_to(ortho_closest) < p_tolerance) {
return true;
}
// Normal part of the shape editor gizmo (the arrow).
const Vector2 normal_segment_a = shape_center;
const Vector2 normal_segment_b = shape_center + normal * 30;
const Vector2 normal_closest = Geometry2D::get_closest_point_to_segment(p_point, normal_segment_a, normal_segment_b);
if (p_point.distance_to(normal_closest) < p_tolerance) {
return true;
}
return false;
}
void WorldBoundaryShape2D::_update_shape() {
Array arr = { normal, distance };
PhysicsServer2D::get_singleton()->shape_set_data(get_rid(), arr);
emit_changed();
}
void WorldBoundaryShape2D::set_normal(const Vector2 &p_normal) {
// Can be non-unit but prevent zero.
ERR_FAIL_COND(p_normal.is_zero_approx());
if (normal == p_normal) {
return;
}
normal = p_normal;
_update_shape();
}
void WorldBoundaryShape2D::set_distance(real_t p_distance) {
if (distance == p_distance) {
return;
}
distance = p_distance;
_update_shape();
}
Vector2 WorldBoundaryShape2D::get_normal() const {
return normal;
}
real_t WorldBoundaryShape2D::get_distance() const {
return distance;
}
void WorldBoundaryShape2D::draw(const RID &p_to_rid, const Color &p_color) {
Vector2 point = distance * normal;
real_t line_width = 3.0;
// Draw collision shape line.
PackedVector2Array line_points = {
point - normal.orthogonal() * 100,
point - normal.orthogonal() * 60,
point + normal.orthogonal() * 60,
point + normal.orthogonal() * 100
};
Color transparent_color = Color(p_color, 0);
PackedColorArray line_colors = {
transparent_color,
p_color,
p_color,
transparent_color
};
RS::get_singleton()->canvas_item_add_polyline(p_to_rid, line_points, line_colors, line_width);
// Draw arrow.
Color arrow_color = p_color.inverted();
Transform2D xf;
xf.rotate(normal.angle());
Vector<Vector2> arrow_points = {
xf.xform(Vector2(distance + line_width / 2, -2.5)),
xf.xform(Vector2(distance + 20, -2.5)),
xf.xform(Vector2(distance + 20, -10)),
xf.xform(Vector2(distance + 40, 0)),
xf.xform(Vector2(distance + 20, 10)),
xf.xform(Vector2(distance + 20, 2.5)),
xf.xform(Vector2(distance + line_width / 2, 2.5)),
};
RS::get_singleton()->canvas_item_add_polyline(p_to_rid, arrow_points, { arrow_color }, line_width / 2);
}
Rect2 WorldBoundaryShape2D::get_rect() const {
Vector2 point = distance * normal;
Vector2 l1[2] = { point - normal.orthogonal() * 100, point + normal.orthogonal() * 100 };
Vector2 l2[2] = { point, point + normal * 30 };
Rect2 rect;
rect.position = l1[0];
rect.expand_to(l1[1]);
rect.expand_to(l2[0]);
rect.expand_to(l2[1]);
return rect;
}
real_t WorldBoundaryShape2D::get_enclosing_radius() const {
return distance;
}
void WorldBoundaryShape2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_normal", "normal"), &WorldBoundaryShape2D::set_normal);
ClassDB::bind_method(D_METHOD("get_normal"), &WorldBoundaryShape2D::get_normal);
ClassDB::bind_method(D_METHOD("set_distance", "distance"), &WorldBoundaryShape2D::set_distance);
ClassDB::bind_method(D_METHOD("get_distance"), &WorldBoundaryShape2D::get_distance);
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "normal"), "set_normal", "get_normal");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "distance", PROPERTY_HINT_RANGE, "-1024,1024,0.01,or_greater,or_less,suffix:px"), "set_distance", "get_distance");
}
WorldBoundaryShape2D::WorldBoundaryShape2D() :
Shape2D(PhysicsServer2D::get_singleton()->world_boundary_shape_create()) {
_update_shape();
}

View File

@@ -0,0 +1,61 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#pragma once
#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();
};

28
scene/resources/3d/SCsub Normal file
View File

@@ -0,0 +1,28 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")
env.add_source_files(env.scene_sources, "fog_material.cpp")
env.add_source_files(env.scene_sources, "importer_mesh.cpp")
env.add_source_files(env.scene_sources, "mesh_library.cpp")
env.add_source_files(env.scene_sources, "primitive_meshes.cpp")
env.add_source_files(env.scene_sources, "skin.cpp")
env.add_source_files(env.scene_sources, "sky_material.cpp")
env.add_source_files(env.scene_sources, "world_3d.cpp")
env.add_source_files(env.scene_sources, "skeleton/*.cpp")
if not env["disable_physics_3d"]:
env.add_source_files(env.scene_sources, "box_shape_3d.cpp")
env.add_source_files(env.scene_sources, "capsule_shape_3d.cpp")
env.add_source_files(env.scene_sources, "circle_shape_3d.cpp")
env.add_source_files(env.scene_sources, "concave_polygon_shape_3d.cpp")
env.add_source_files(env.scene_sources, "convex_polygon_shape_3d.cpp")
env.add_source_files(env.scene_sources, "cylinder_shape_3d.cpp")
env.add_source_files(env.scene_sources, "height_map_shape_3d.cpp")
env.add_source_files(env.scene_sources, "separation_ray_shape_3d.cpp")
env.add_source_files(env.scene_sources, "shape_3d.cpp")
env.add_source_files(env.scene_sources, "sphere_shape_3d.cpp")
env.add_source_files(env.scene_sources, "world_boundary_shape_3d.cpp")
if not env["disable_navigation_3d"]:
env.add_source_files(env.scene_sources, "navigation_mesh_source_geometry_data_3d.cpp")

View File

@@ -0,0 +1,120 @@
/**************************************************************************/
/* box_shape_3d.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "box_shape_3d.h"
#include "scene/resources/3d/primitive_meshes.h"
#include "servers/physics_server_3d.h"
Vector<Vector3> BoxShape3D::get_debug_mesh_lines() const {
Vector<Vector3> lines;
AABB aabb;
aabb.position = -size / 2;
aabb.size = size;
for (int i = 0; i < 12; i++) {
Vector3 a, b;
aabb.get_edge(i, a, b);
lines.push_back(a);
lines.push_back(b);
}
return lines;
}
Ref<ArrayMesh> BoxShape3D::get_debug_arraymesh_faces(const Color &p_modulate) const {
Array box_array;
box_array.resize(RS::ARRAY_MAX);
BoxMesh::create_mesh_array(box_array, size);
Vector<Color> colors;
const PackedVector3Array &verts = box_array[RS::ARRAY_VERTEX];
const int32_t verts_size = verts.size();
for (int i = 0; i < verts_size; i++) {
colors.append(p_modulate);
}
Ref<ArrayMesh> box_mesh = memnew(ArrayMesh);
box_array[RS::ARRAY_COLOR] = colors;
box_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, box_array);
return box_mesh;
}
real_t BoxShape3D::get_enclosing_radius() const {
return size.length() / 2;
}
void BoxShape3D::_update_shape() {
PhysicsServer3D::get_singleton()->shape_set_data(get_shape(), size / 2);
Shape3D::_update_shape();
}
#ifndef DISABLE_DEPRECATED
bool BoxShape3D::_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((Vector3)p_value * 2);
return true;
}
return false;
}
bool BoxShape3D::_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 BoxShape3D::set_size(const Vector3 &p_size) {
ERR_FAIL_COND_MSG(p_size.x < 0 || p_size.y < 0 || p_size.z < 0, "BoxShape3D size cannot be negative.");
size = p_size;
_update_shape();
emit_changed();
}
Vector3 BoxShape3D::get_size() const {
return size;
}
void BoxShape3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_size", "size"), &BoxShape3D::set_size);
ClassDB::bind_method(D_METHOD("get_size"), &BoxShape3D::get_size);
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "size", PROPERTY_HINT_NONE, "suffix:m"), "set_size", "get_size");
}
BoxShape3D::BoxShape3D() :
Shape3D(PhysicsServer3D::get_singleton()->shape_create(PhysicsServer3D::SHAPE_BOX)) {
set_size(Vector3(1, 1, 1));
}

View File

@@ -0,0 +1,57 @@
/**************************************************************************/
/* box_shape_3d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "scene/resources/3d/shape_3d.h"
class BoxShape3D : public Shape3D {
GDCLASS(BoxShape3D, Shape3D);
Vector3 size;
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
virtual void _update_shape() override;
public:
void set_size(const Vector3 &p_size);
Vector3 get_size() const;
virtual Vector<Vector3> get_debug_mesh_lines() const override;
virtual Ref<ArrayMesh> get_debug_arraymesh_faces(const Color &p_modulate) const override;
virtual real_t get_enclosing_radius() const override;
BoxShape3D();
};

View File

@@ -0,0 +1,158 @@
/**************************************************************************/
/* capsule_shape_3d.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "capsule_shape_3d.h"
#include "scene/resources/3d/primitive_meshes.h"
#include "servers/physics_server_3d.h"
Vector<Vector3> CapsuleShape3D::get_debug_mesh_lines() const {
float c_radius = get_radius();
float c_height = get_height();
Vector<Vector3> points;
Vector3 d(0, c_height * 0.5f - c_radius, 0);
for (int i = 0; i < 360; i++) {
float ra = Math::deg_to_rad((float)i);
float rb = Math::deg_to_rad((float)i + 1);
Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * c_radius;
Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * c_radius;
points.push_back(Vector3(a.x, 0, a.y) + d);
points.push_back(Vector3(b.x, 0, b.y) + d);
points.push_back(Vector3(a.x, 0, a.y) - d);
points.push_back(Vector3(b.x, 0, b.y) - d);
if (i % 90 == 0) {
points.push_back(Vector3(a.x, 0, a.y) + d);
points.push_back(Vector3(a.x, 0, a.y) - d);
}
Vector3 dud = i < 180 ? d : -d;
points.push_back(Vector3(0, a.x, a.y) + dud);
points.push_back(Vector3(0, b.x, b.y) + dud);
points.push_back(Vector3(a.y, a.x, 0) + dud);
points.push_back(Vector3(b.y, b.x, 0) + dud);
}
return points;
}
Ref<ArrayMesh> CapsuleShape3D::get_debug_arraymesh_faces(const Color &p_modulate) const {
Array capsule_array;
capsule_array.resize(RS::ARRAY_MAX);
CapsuleMesh::create_mesh_array(capsule_array, radius, height, 32, 8);
Vector<Color> colors;
const PackedVector3Array &verts = capsule_array[RS::ARRAY_VERTEX];
const int32_t verts_size = verts.size();
for (int i = 0; i < verts_size; i++) {
colors.append(p_modulate);
}
Ref<ArrayMesh> capsule_mesh = memnew(ArrayMesh);
capsule_array[RS::ARRAY_COLOR] = colors;
capsule_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, capsule_array);
return capsule_mesh;
}
real_t CapsuleShape3D::get_enclosing_radius() const {
return height * 0.5f;
}
void CapsuleShape3D::_update_shape() {
Dictionary d;
d["radius"] = radius;
d["height"] = height;
PhysicsServer3D::get_singleton()->shape_set_data(get_shape(), d);
Shape3D::_update_shape();
}
void CapsuleShape3D::set_radius(float p_radius) {
ERR_FAIL_COND_MSG(p_radius < 0.0f, "CapsuleShape3D radius cannot be negative.");
radius = p_radius;
if (height < radius * 2.0f) {
height = radius * 2.0f;
}
_update_shape();
emit_changed();
}
float CapsuleShape3D::get_radius() const {
return radius;
}
void CapsuleShape3D::set_height(float p_height) {
ERR_FAIL_COND_MSG(p_height < 0.0f, "CapsuleShape3D height cannot be negative.");
height = p_height;
if (radius > height * 0.5f) {
radius = height * 0.5f;
}
_update_shape();
emit_changed();
}
float CapsuleShape3D::get_height() const {
return height;
}
void CapsuleShape3D::set_mid_height(real_t p_mid_height) {
ERR_FAIL_COND_MSG(p_mid_height < 0.0f, "CapsuleShape3D mid-height cannot be negative.");
height = p_mid_height + radius * 2.0f;
_update_shape();
emit_changed();
}
real_t CapsuleShape3D::get_mid_height() const {
return height - radius * 2.0f;
}
void CapsuleShape3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_radius", "radius"), &CapsuleShape3D::set_radius);
ClassDB::bind_method(D_METHOD("get_radius"), &CapsuleShape3D::get_radius);
ClassDB::bind_method(D_METHOD("set_height", "height"), &CapsuleShape3D::set_height);
ClassDB::bind_method(D_METHOD("get_height"), &CapsuleShape3D::get_height);
ClassDB::bind_method(D_METHOD("set_mid_height", "mid_height"), &CapsuleShape3D::set_mid_height);
ClassDB::bind_method(D_METHOD("get_mid_height"), &CapsuleShape3D::get_mid_height);
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater,suffix:m"), "set_radius", "get_radius");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater,suffix:m"), "set_height", "get_height");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mid_height", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater,suffix:m", PROPERTY_USAGE_NONE), "set_mid_height", "get_mid_height");
ADD_LINKED_PROPERTY("radius", "height");
ADD_LINKED_PROPERTY("height", "radius");
}
CapsuleShape3D::CapsuleShape3D() :
Shape3D(PhysicsServer3D::get_singleton()->shape_create(PhysicsServer3D::SHAPE_CAPSULE)) {
_update_shape();
}

View File

@@ -0,0 +1,60 @@
/**************************************************************************/
/* capsule_shape_3d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "scene/resources/3d/shape_3d.h"
class ArrayMesh;
class CapsuleShape3D : public Shape3D {
GDCLASS(CapsuleShape3D, Shape3D);
float radius = 0.5;
float height = 2.0;
protected:
static void _bind_methods();
virtual void _update_shape() override;
public:
void set_radius(float p_radius);
float get_radius() const;
void set_height(float p_height);
float get_height() const;
void set_mid_height(real_t p_mid_height);
real_t get_mid_height() const;
virtual Vector<Vector3> get_debug_mesh_lines() const override;
virtual Ref<ArrayMesh> get_debug_arraymesh_faces(const Color &p_modulate) const override;
virtual real_t get_enclosing_radius() const override;
CapsuleShape3D();
};

View File

@@ -0,0 +1,136 @@
/**************************************************************************/
/* concave_polygon_shape_3d.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "concave_polygon_shape_3d.h"
#include "scene/resources/mesh.h"
#include "servers/physics_server_3d.h"
Vector<Vector3> ConcavePolygonShape3D::get_debug_mesh_lines() const {
HashSet<DrawEdge, DrawEdge> edges;
int index_count = faces.size();
ERR_FAIL_COND_V((index_count % 3) != 0, Vector<Vector3>());
const Vector3 *r = faces.ptr();
for (int i = 0; i < index_count; i += 3) {
for (int j = 0; j < 3; j++) {
DrawEdge de(r[i + j], r[i + ((j + 1) % 3)]);
edges.insert(de);
}
}
Vector<Vector3> points;
points.resize(edges.size() * 2);
int idx = 0;
for (const DrawEdge &E : edges) {
points.write[idx + 0] = E.a;
points.write[idx + 1] = E.b;
idx += 2;
}
return points;
}
Ref<ArrayMesh> ConcavePolygonShape3D::get_debug_arraymesh_faces(const Color &p_modulate) const {
Vector<Color> colors;
for (int i = 0; i < faces.size(); i++) {
colors.push_back(p_modulate);
}
Ref<ArrayMesh> mesh = memnew(ArrayMesh);
Array a;
a.resize(Mesh::ARRAY_MAX);
a[RS::ARRAY_VERTEX] = faces;
a[RS::ARRAY_COLOR] = colors;
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, a);
return mesh;
}
real_t ConcavePolygonShape3D::get_enclosing_radius() const {
Vector<Vector3> data = get_faces();
const Vector3 *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 ConcavePolygonShape3D::_update_shape() {
Dictionary d;
d["faces"] = faces;
d["backface_collision"] = backface_collision;
PhysicsServer3D::get_singleton()->shape_set_data(get_shape(), d);
Shape3D::_update_shape();
}
void ConcavePolygonShape3D::set_faces(const Vector<Vector3> &p_faces) {
faces = p_faces;
_update_shape();
emit_changed();
}
Vector<Vector3> ConcavePolygonShape3D::get_faces() const {
return faces;
}
void ConcavePolygonShape3D::set_backface_collision_enabled(bool p_enabled) {
backface_collision = p_enabled;
if (!faces.is_empty()) {
_update_shape();
emit_changed();
}
}
bool ConcavePolygonShape3D::is_backface_collision_enabled() const {
return backface_collision;
}
void ConcavePolygonShape3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_faces", "faces"), &ConcavePolygonShape3D::set_faces);
ClassDB::bind_method(D_METHOD("get_faces"), &ConcavePolygonShape3D::get_faces);
ClassDB::bind_method(D_METHOD("set_backface_collision_enabled", "enabled"), &ConcavePolygonShape3D::set_backface_collision_enabled);
ClassDB::bind_method(D_METHOD("is_backface_collision_enabled"), &ConcavePolygonShape3D::is_backface_collision_enabled);
ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR3_ARRAY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_faces", "get_faces");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "backface_collision"), "set_backface_collision_enabled", "is_backface_collision_enabled");
}
ConcavePolygonShape3D::ConcavePolygonShape3D() :
Shape3D(PhysicsServer3D::get_singleton()->shape_create(PhysicsServer3D::SHAPE_CONCAVE_POLYGON)) {
//set_planes(Vector3(1,1,1));
}

View File

@@ -0,0 +1,80 @@
/**************************************************************************/
/* concave_polygon_shape_3d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "scene/resources/3d/shape_3d.h"
class ArrayMesh;
class ConcavePolygonShape3D : public Shape3D {
GDCLASS(ConcavePolygonShape3D, Shape3D);
Vector<Vector3> faces;
bool backface_collision = false;
struct DrawEdge {
Vector3 a;
Vector3 b;
static uint32_t hash(const DrawEdge &p_edge) {
uint32_t h = hash_murmur3_one_32(HashMapHasherDefault::hash(p_edge.a));
return hash_murmur3_one_32(HashMapHasherDefault::hash(p_edge.b), h);
}
bool operator==(const DrawEdge &p_edge) const {
return (a == p_edge.a && b == p_edge.b);
}
DrawEdge(const Vector3 &p_a = Vector3(), const Vector3 &p_b = Vector3()) {
a = p_a;
b = p_b;
if (a < b) {
SWAP(a, b);
}
}
};
protected:
static void _bind_methods();
virtual void _update_shape() override;
public:
void set_faces(const Vector<Vector3> &p_faces);
Vector<Vector3> get_faces() const;
void set_backface_collision_enabled(bool p_enabled);
bool is_backface_collision_enabled() const;
virtual Vector<Vector3> get_debug_mesh_lines() const override;
virtual Ref<ArrayMesh> get_debug_arraymesh_faces(const Color &p_modulate) const override;
virtual real_t get_enclosing_radius() const override;
ConcavePolygonShape3D();
};

View File

@@ -0,0 +1,129 @@
/**************************************************************************/
/* convex_polygon_shape_3d.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "convex_polygon_shape_3d.h"
#include "core/math/convex_hull.h"
#include "scene/resources/mesh.h"
#include "servers/physics_server_3d.h"
Vector<Vector3> ConvexPolygonShape3D::get_debug_mesh_lines() const {
Vector<Vector3> poly_points = get_points();
if (poly_points.size() > 1) { // Need at least 2 points for a line.
Vector<Vector3> varr = Variant(poly_points);
Geometry3D::MeshData md;
Error err = ConvexHullComputer::convex_hull(varr, md);
if (err == OK) {
Vector<Vector3> lines;
lines.resize(md.edges.size() * 2);
for (uint32_t i = 0; i < md.edges.size(); i++) {
lines.write[i * 2 + 0] = md.vertices[md.edges[i].vertex_a];
lines.write[i * 2 + 1] = md.vertices[md.edges[i].vertex_b];
}
return lines;
}
}
return Vector<Vector3>();
}
Ref<ArrayMesh> ConvexPolygonShape3D::get_debug_arraymesh_faces(const Color &p_modulate) const {
const Vector<Vector3> hull_points = get_points();
Vector<Vector3> verts;
Vector<Color> colors;
Vector<int> indices;
if (hull_points.size() >= 3) {
Geometry3D::MeshData md;
Error err = ConvexHullComputer::convex_hull(hull_points, md);
if (err == OK) {
verts = md.vertices;
for (int i = 0; i < verts.size(); i++) {
colors.push_back(p_modulate);
}
for (const Geometry3D::MeshData::Face &face : md.faces) {
const int first_point = face.indices[0];
const int indices_count = face.indices.size();
for (int i = 1; i < indices_count - 1; i++) {
indices.push_back(first_point);
indices.push_back(face.indices[i]);
indices.push_back(face.indices[i + 1]);
}
}
}
}
Ref<ArrayMesh> mesh = memnew(ArrayMesh);
Array a;
a.resize(Mesh::ARRAY_MAX);
a[RS::ARRAY_VERTEX] = verts;
a[RS::ARRAY_COLOR] = colors;
a[RS::ARRAY_INDEX] = indices;
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, a);
return mesh;
}
real_t ConvexPolygonShape3D::get_enclosing_radius() const {
Vector<Vector3> data = get_points();
const Vector3 *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 ConvexPolygonShape3D::_update_shape() {
PhysicsServer3D::get_singleton()->shape_set_data(get_shape(), points);
Shape3D::_update_shape();
}
void ConvexPolygonShape3D::set_points(const Vector<Vector3> &p_points) {
points = p_points;
_update_shape();
emit_changed();
}
Vector<Vector3> ConvexPolygonShape3D::get_points() const {
return points;
}
void ConvexPolygonShape3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_points", "points"), &ConvexPolygonShape3D::set_points);
ClassDB::bind_method(D_METHOD("get_points"), &ConvexPolygonShape3D::get_points);
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "points"), "set_points", "get_points");
}
ConvexPolygonShape3D::ConvexPolygonShape3D() :
Shape3D(PhysicsServer3D::get_singleton()->shape_create(PhysicsServer3D::SHAPE_CONVEX_POLYGON)) {
}

View File

@@ -0,0 +1,55 @@
/**************************************************************************/
/* convex_polygon_shape_3d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "scene/resources/3d/shape_3d.h"
class ArrayMesh;
class ConvexPolygonShape3D : public Shape3D {
GDCLASS(ConvexPolygonShape3D, Shape3D);
Vector<Vector3> points;
protected:
static void _bind_methods();
virtual void _update_shape() override;
public:
void set_points(const Vector<Vector3> &p_points);
Vector<Vector3> get_points() const;
virtual Vector<Vector3> get_debug_mesh_lines() const override;
virtual Ref<ArrayMesh> get_debug_arraymesh_faces(const Color &p_modulate) const override;
virtual real_t get_enclosing_radius() const override;
ConvexPolygonShape3D();
};

View File

@@ -0,0 +1,129 @@
/**************************************************************************/
/* cylinder_shape_3d.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "cylinder_shape_3d.h"
#include "scene/resources/3d/primitive_meshes.h"
#include "servers/physics_server_3d.h"
Vector<Vector3> CylinderShape3D::get_debug_mesh_lines() const {
float c_radius = get_radius();
float c_height = get_height();
Vector<Vector3> points;
Vector3 d(0, c_height * 0.5, 0);
for (int i = 0; i < 360; i++) {
float ra = Math::deg_to_rad((float)i);
float rb = Math::deg_to_rad((float)i + 1);
Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * c_radius;
Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * c_radius;
points.push_back(Vector3(a.x, 0, a.y) + d);
points.push_back(Vector3(b.x, 0, b.y) + d);
points.push_back(Vector3(a.x, 0, a.y) - d);
points.push_back(Vector3(b.x, 0, b.y) - d);
if (i % 90 == 0) {
points.push_back(Vector3(a.x, 0, a.y) + d);
points.push_back(Vector3(a.x, 0, a.y) - d);
}
}
return points;
}
Ref<ArrayMesh> CylinderShape3D::get_debug_arraymesh_faces(const Color &p_modulate) const {
Array cylinder_array;
cylinder_array.resize(RS::ARRAY_MAX);
CylinderMesh::create_mesh_array(cylinder_array, radius, radius, height, 32);
Vector<Color> colors;
const PackedVector3Array &verts = cylinder_array[RS::ARRAY_VERTEX];
const int32_t verts_size = verts.size();
for (int i = 0; i < verts_size; i++) {
colors.append(p_modulate);
}
Ref<ArrayMesh> cylinder_mesh = memnew(ArrayMesh);
cylinder_array[RS::ARRAY_COLOR] = colors;
cylinder_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, cylinder_array);
return cylinder_mesh;
}
real_t CylinderShape3D::get_enclosing_radius() const {
return Vector2(radius, height * 0.5).length();
}
void CylinderShape3D::_update_shape() {
Dictionary d;
d["radius"] = radius;
d["height"] = height;
PhysicsServer3D::get_singleton()->shape_set_data(get_shape(), d);
Shape3D::_update_shape();
}
void CylinderShape3D::set_radius(float p_radius) {
ERR_FAIL_COND_MSG(p_radius < 0, "CylinderShape3D radius cannot be negative.");
radius = p_radius;
_update_shape();
emit_changed();
}
float CylinderShape3D::get_radius() const {
return radius;
}
void CylinderShape3D::set_height(float p_height) {
ERR_FAIL_COND_MSG(p_height < 0, "CylinderShape3D height cannot be negative.");
height = p_height;
_update_shape();
emit_changed();
}
float CylinderShape3D::get_height() const {
return height;
}
void CylinderShape3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_radius", "radius"), &CylinderShape3D::set_radius);
ClassDB::bind_method(D_METHOD("get_radius"), &CylinderShape3D::get_radius);
ClassDB::bind_method(D_METHOD("set_height", "height"), &CylinderShape3D::set_height);
ClassDB::bind_method(D_METHOD("get_height"), &CylinderShape3D::get_height);
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater,suffix:m"), "set_height", "get_height");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater,suffix:m"), "set_radius", "get_radius");
}
CylinderShape3D::CylinderShape3D() :
Shape3D(PhysicsServer3D::get_singleton()->shape_create(PhysicsServer3D::SHAPE_CYLINDER)) {
_update_shape();
}

View File

@@ -0,0 +1,57 @@
/**************************************************************************/
/* cylinder_shape_3d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "scene/resources/3d/shape_3d.h"
class ArrayMesh;
class CylinderShape3D : public Shape3D {
GDCLASS(CylinderShape3D, Shape3D);
float radius = 0.5;
float height = 2.0;
protected:
static void _bind_methods();
virtual void _update_shape() override;
public:
void set_radius(float p_radius);
float get_radius() const;
void set_height(float p_height);
float get_height() const;
virtual Vector<Vector3> get_debug_mesh_lines() const override;
virtual Ref<ArrayMesh> get_debug_arraymesh_faces(const Color &p_modulate) const override;
virtual real_t get_enclosing_radius() const override;
CylinderShape3D();
};

View File

@@ -0,0 +1,183 @@
/**************************************************************************/
/* fog_material.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "fog_material.h"
#include "core/version.h"
Mutex FogMaterial::shader_mutex;
RID FogMaterial::shader;
void FogMaterial::set_density(float p_density) {
density = p_density;
RS::get_singleton()->material_set_param(_get_material(), "density", density);
}
float FogMaterial::get_density() const {
return density;
}
void FogMaterial::set_albedo(Color p_albedo) {
albedo = p_albedo;
RS::get_singleton()->material_set_param(_get_material(), "albedo", albedo);
}
Color FogMaterial::get_albedo() const {
return albedo;
}
void FogMaterial::set_emission(Color p_emission) {
emission = p_emission;
RS::get_singleton()->material_set_param(_get_material(), "emission", emission);
}
Color FogMaterial::get_emission() const {
return emission;
}
void FogMaterial::set_height_falloff(float p_falloff) {
height_falloff = MAX(p_falloff, 0.0f);
RS::get_singleton()->material_set_param(_get_material(), "height_falloff", height_falloff);
}
float FogMaterial::get_height_falloff() const {
return height_falloff;
}
void FogMaterial::set_edge_fade(float p_edge_fade) {
edge_fade = MAX(p_edge_fade, 0.0f);
RS::get_singleton()->material_set_param(_get_material(), "edge_fade", edge_fade);
}
float FogMaterial::get_edge_fade() const {
return edge_fade;
}
void FogMaterial::set_density_texture(const Ref<Texture3D> &p_texture) {
density_texture = p_texture;
Variant tex_rid = p_texture.is_valid() ? Variant(p_texture->get_rid()) : Variant();
RS::get_singleton()->material_set_param(_get_material(), "density_texture", tex_rid);
}
Ref<Texture3D> FogMaterial::get_density_texture() const {
return density_texture;
}
Shader::Mode FogMaterial::get_shader_mode() const {
return Shader::MODE_FOG;
}
RID FogMaterial::get_shader_rid() const {
_update_shader();
return shader;
}
RID FogMaterial::get_rid() const {
_update_shader();
if (!shader_set) {
RS::get_singleton()->material_set_shader(_get_material(), shader);
shader_set = true;
}
return _get_material();
}
void FogMaterial::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_density", "density"), &FogMaterial::set_density);
ClassDB::bind_method(D_METHOD("get_density"), &FogMaterial::get_density);
ClassDB::bind_method(D_METHOD("set_albedo", "albedo"), &FogMaterial::set_albedo);
ClassDB::bind_method(D_METHOD("get_albedo"), &FogMaterial::get_albedo);
ClassDB::bind_method(D_METHOD("set_emission", "emission"), &FogMaterial::set_emission);
ClassDB::bind_method(D_METHOD("get_emission"), &FogMaterial::get_emission);
ClassDB::bind_method(D_METHOD("set_height_falloff", "height_falloff"), &FogMaterial::set_height_falloff);
ClassDB::bind_method(D_METHOD("get_height_falloff"), &FogMaterial::get_height_falloff);
ClassDB::bind_method(D_METHOD("set_edge_fade", "edge_fade"), &FogMaterial::set_edge_fade);
ClassDB::bind_method(D_METHOD("get_edge_fade"), &FogMaterial::get_edge_fade);
ClassDB::bind_method(D_METHOD("set_density_texture", "density_texture"), &FogMaterial::set_density_texture);
ClassDB::bind_method(D_METHOD("get_density_texture"), &FogMaterial::get_density_texture);
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "density", PROPERTY_HINT_RANGE, "-8.0,8.0,0.0001,or_greater,or_less"), "set_density", "get_density");
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "albedo", PROPERTY_HINT_COLOR_NO_ALPHA), "set_albedo", "get_albedo");
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "emission", PROPERTY_HINT_COLOR_NO_ALPHA), "set_emission", "get_emission");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height_falloff", PROPERTY_HINT_EXP_EASING, "attenuation"), "set_height_falloff", "get_height_falloff");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "edge_fade", PROPERTY_HINT_EXP_EASING), "set_edge_fade", "get_edge_fade");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "density_texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture3D"), "set_density_texture", "get_density_texture");
}
void FogMaterial::cleanup_shader() {
if (shader.is_valid()) {
ERR_FAIL_NULL(RenderingServer::get_singleton());
RS::get_singleton()->free(shader);
}
}
void FogMaterial::_update_shader() {
MutexLock shader_lock(shader_mutex);
if (shader.is_null()) {
shader = RS::get_singleton()->shader_create();
// Add a comment to describe the shader origin (useful when converting to ShaderMaterial).
RS::get_singleton()->shader_set_code(shader, R"(
// NOTE: Shader automatically converted from )" GODOT_VERSION_NAME " " GODOT_VERSION_FULL_CONFIG R"('s FogMaterial.
shader_type fog;
uniform float density : hint_range(0, 1, 0.0001) = 1.0;
uniform vec4 albedo : source_color = vec4(1.0);
uniform vec4 emission : source_color = vec4(0, 0, 0, 1);
uniform float height_falloff = 0.0;
uniform float edge_fade = 0.1;
uniform sampler3D density_texture: hint_default_white;
void fog() {
DENSITY = density * clamp(exp2(-height_falloff * (WORLD_POSITION.y - OBJECT_POSITION.y)), 0.0, 1.0);
DENSITY *= texture(density_texture, UVW).r;
DENSITY *= pow(clamp(-2.0 * SDF / min(min(SIZE.x, SIZE.y), SIZE.z), 0.0, 1.0), edge_fade);
ALBEDO = albedo.rgb;
EMISSION = emission.rgb;
}
)");
}
}
FogMaterial::FogMaterial() {
_set_material(RS::get_singleton()->material_create());
set_density(1.0);
set_albedo(Color(1, 1, 1, 1));
set_emission(Color(0, 0, 0, 1));
set_height_falloff(0.0);
set_edge_fade(0.1);
}
FogMaterial::~FogMaterial() {
RS::get_singleton()->material_set_shader(_get_material(), RID());
}

View File

@@ -0,0 +1,84 @@
/**************************************************************************/
/* fog_material.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "scene/resources/material.h"
class FogMaterial : public Material {
GDCLASS(FogMaterial, Material);
private:
float density = 1.0;
Color albedo = Color(1, 1, 1, 1);
Color emission = Color(0, 0, 0, 0);
float height_falloff = 0.0;
float edge_fade = 0.1;
Ref<Texture3D> density_texture;
static Mutex shader_mutex;
static RID shader;
static void _update_shader();
mutable bool shader_set = false;
protected:
static void _bind_methods();
public:
void set_density(float p_density);
float get_density() const;
void set_albedo(Color p_color);
Color get_albedo() const;
void set_emission(Color p_color);
Color get_emission() const;
void set_height_falloff(float p_falloff);
float get_height_falloff() const;
void set_edge_fade(float p_edge_fade);
float get_edge_fade() const;
void set_density_texture(const Ref<Texture3D> &p_texture);
Ref<Texture3D> get_density_texture() const;
virtual Shader::Mode get_shader_mode() const override;
virtual RID get_shader_rid() const override;
virtual RID get_rid() const override;
static void cleanup_shader();
FogMaterial();
virtual ~FogMaterial();
};

View File

@@ -0,0 +1,371 @@
/**************************************************************************/
/* height_map_shape_3d.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "height_map_shape_3d.h"
#include "core/io/image.h"
#include "scene/resources/mesh.h"
#include "servers/physics_server_3d.h"
Vector<Vector3> HeightMapShape3D::get_debug_mesh_lines() const {
Vector<Vector3> points;
if ((map_width != 0) && (map_depth != 0)) {
// This will be slow for large maps...
// also we'll have to figure out how well bullet centers this shape...
Vector2 size(map_width - 1, map_depth - 1);
Vector2 start = size * -0.5;
const real_t *r = map_data.ptr();
// reserve some memory for our points..
points.resize(((map_width - 1) * map_depth * 2) + (map_width * (map_depth - 1) * 2) + ((map_width - 1) * (map_depth - 1) * 2));
// now set our points
int r_offset = 0;
int w_offset = 0;
for (int d = 0; d < map_depth; d++) {
Vector3 height(start.x, 0.0, start.y);
for (int w = 0; w < map_width; w++) {
height.y = r[r_offset++];
if (w != map_width - 1) {
points.write[w_offset++] = height;
points.write[w_offset++] = Vector3(height.x + 1.0, r[r_offset], height.z);
}
if (d != map_depth - 1) {
points.write[w_offset++] = height;
points.write[w_offset++] = Vector3(height.x, r[r_offset + map_width - 1], height.z + 1.0);
}
if ((w != map_width - 1) && (d != map_depth - 1)) {
points.write[w_offset++] = Vector3(height.x + 1.0, r[r_offset], height.z);
points.write[w_offset++] = Vector3(height.x, r[r_offset + map_width - 1], height.z + 1.0);
}
height.x += 1.0;
}
start.y += 1.0;
}
}
return points;
}
Ref<ArrayMesh> HeightMapShape3D::get_debug_arraymesh_faces(const Color &p_modulate) const {
Vector<Vector3> verts;
Vector<Color> colors;
Vector<int> indices;
// This will be slow for large maps...
if ((map_width != 0) && (map_depth != 0)) {
Vector2 size = Vector2(map_width - 1, map_depth - 1) * -0.5;
const real_t *r = map_data.ptr();
for (int d = 0; d <= map_depth - 2; d++) {
const int this_row_offset = map_width * d;
const int next_row_offset = this_row_offset + map_width;
for (int w = 0; w <= map_width - 2; w++) {
const float height_tl = r[next_row_offset + w];
const float height_bl = r[this_row_offset + w];
const float height_br = r[this_row_offset + w + 1];
const float height_tr = r[next_row_offset + w + 1];
const int index_offset = verts.size();
verts.push_back(Vector3(size.x + w, height_tl, size.y + d + 1));
verts.push_back(Vector3(size.x + w, height_bl, size.y + d));
verts.push_back(Vector3(size.x + w + 1, height_br, size.y + d));
verts.push_back(Vector3(size.x + w + 1, height_tr, size.y + d + 1));
colors.push_back(p_modulate);
colors.push_back(p_modulate);
colors.push_back(p_modulate);
colors.push_back(p_modulate);
indices.push_back(index_offset);
indices.push_back(index_offset + 1);
indices.push_back(index_offset + 2);
indices.push_back(index_offset);
indices.push_back(index_offset + 2);
indices.push_back(index_offset + 3);
}
}
}
Ref<ArrayMesh> mesh = memnew(ArrayMesh);
Array a;
a.resize(Mesh::ARRAY_MAX);
a[RS::ARRAY_VERTEX] = verts;
a[RS::ARRAY_COLOR] = colors;
a[RS::ARRAY_INDEX] = indices;
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, a);
return mesh;
}
real_t HeightMapShape3D::get_enclosing_radius() const {
return Vector3(real_t(map_width), max_height - min_height, real_t(map_depth)).length();
}
void HeightMapShape3D::_update_shape() {
Dictionary d;
d["width"] = map_width;
d["depth"] = map_depth;
d["heights"] = map_data;
d["min_height"] = min_height;
d["max_height"] = max_height;
PhysicsServer3D::get_singleton()->shape_set_data(get_shape(), d);
Shape3D::_update_shape();
}
void HeightMapShape3D::set_map_width(int p_new) {
if (p_new < 1) {
// ignore
} else if (map_width != p_new) {
int was_size = map_width * map_depth;
map_width = p_new;
int new_size = map_width * map_depth;
map_data.resize(map_width * map_depth);
real_t *w = map_data.ptrw();
while (was_size < new_size) {
w[was_size++] = 0.0;
}
_update_shape();
emit_changed();
}
}
int HeightMapShape3D::get_map_width() const {
return map_width;
}
void HeightMapShape3D::set_map_depth(int p_new) {
if (p_new < 1) {
// ignore
} else if (map_depth != p_new) {
int was_size = map_width * map_depth;
map_depth = p_new;
int new_size = map_width * map_depth;
map_data.resize(new_size);
real_t *w = map_data.ptrw();
while (was_size < new_size) {
w[was_size++] = 0.0;
}
_update_shape();
emit_changed();
}
}
int HeightMapShape3D::get_map_depth() const {
return map_depth;
}
void HeightMapShape3D::set_map_data(Vector<real_t> p_new) {
int size = (map_width * map_depth);
if (p_new.size() != size) {
// fail
return;
}
// copy
real_t *w = map_data.ptrw();
const real_t *r = p_new.ptr();
for (int i = 0; i < size; i++) {
real_t val = r[i];
w[i] = val;
if (i == 0) {
min_height = val;
max_height = val;
} else {
if (min_height > val) {
min_height = val;
}
if (max_height < val) {
max_height = val;
}
}
}
_update_shape();
emit_changed();
}
Vector<real_t> HeightMapShape3D::get_map_data() const {
return map_data;
}
real_t HeightMapShape3D::get_min_height() const {
return min_height;
}
real_t HeightMapShape3D::get_max_height() const {
return max_height;
}
void HeightMapShape3D::update_map_data_from_image(const Ref<Image> &p_image, real_t p_height_min, real_t p_height_max) {
ERR_FAIL_COND_MSG(p_image.is_null(), "Heightmap update image requires a valid Image reference.");
ERR_FAIL_COND_MSG(p_image->get_format() != Image::FORMAT_RF && p_image->get_format() != Image::FORMAT_RH && p_image->get_format() != Image::FORMAT_R8, "Heightmap update image requires Image in format FORMAT_RF (32 bit), FORMAT_RH (16 bit), or FORMAT_R8 (8 bit).");
ERR_FAIL_COND_MSG(p_image->get_width() < 2, "Heightmap update image requires a minimum Image width of 2.");
ERR_FAIL_COND_MSG(p_image->get_height() < 2, "Heightmap update image requires a minimum Image height of 2.");
ERR_FAIL_COND_MSG(p_height_min > p_height_max, "Heightmap update image requires height_max to be greater than height_min.");
map_width = p_image->get_width();
map_depth = p_image->get_height();
map_data.resize(map_width * map_depth);
real_t new_min_height = FLT_MAX;
real_t new_max_height = -FLT_MAX;
float remap_height_min = float(p_height_min);
float remap_height_max = float(p_height_max);
real_t *map_data_ptrw = map_data.ptrw();
switch (p_image->get_format()) {
case Image::FORMAT_RF: {
const float *image_data_ptr = (float *)p_image->get_data().ptr();
for (int i = 0; i < map_data.size(); i++) {
float pixel_value = image_data_ptr[i];
DEV_ASSERT(pixel_value >= 0.0 && pixel_value <= 1.0);
real_t height_value = Math::remap(pixel_value, 0.0f, 1.0f, remap_height_min, remap_height_max);
if (height_value < new_min_height) {
new_min_height = height_value;
}
if (height_value > new_max_height) {
new_max_height = height_value;
}
map_data_ptrw[i] = height_value;
}
} break;
case Image::FORMAT_RH: {
const uint16_t *image_data_ptr = (uint16_t *)p_image->get_data().ptr();
for (int i = 0; i < map_data.size(); i++) {
float pixel_value = Math::half_to_float(image_data_ptr[i]);
DEV_ASSERT(pixel_value >= 0.0 && pixel_value <= 1.0);
real_t height_value = Math::remap(pixel_value, 0.0f, 1.0f, remap_height_min, remap_height_max);
if (height_value < new_min_height) {
new_min_height = height_value;
}
if (height_value > new_max_height) {
new_max_height = height_value;
}
map_data_ptrw[i] = height_value;
}
} break;
case Image::FORMAT_R8: {
const uint8_t *image_data_ptr = (uint8_t *)p_image->get_data().ptr();
for (int i = 0; i < map_data.size(); i++) {
float pixel_value = float(image_data_ptr[i] / 255.0);
DEV_ASSERT(pixel_value >= 0.0 && pixel_value <= 1.0);
real_t height_value = Math::remap(pixel_value, 0.0f, 1.0f, remap_height_min, remap_height_max);
if (height_value < new_min_height) {
new_min_height = height_value;
}
if (height_value > new_max_height) {
new_max_height = height_value;
}
map_data_ptrw[i] = height_value;
}
} break;
default: {
return;
}
}
min_height = new_min_height;
max_height = new_max_height;
_update_shape();
emit_changed();
}
void HeightMapShape3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_map_width", "width"), &HeightMapShape3D::set_map_width);
ClassDB::bind_method(D_METHOD("get_map_width"), &HeightMapShape3D::get_map_width);
ClassDB::bind_method(D_METHOD("set_map_depth", "height"), &HeightMapShape3D::set_map_depth);
ClassDB::bind_method(D_METHOD("get_map_depth"), &HeightMapShape3D::get_map_depth);
ClassDB::bind_method(D_METHOD("set_map_data", "data"), &HeightMapShape3D::set_map_data);
ClassDB::bind_method(D_METHOD("get_map_data"), &HeightMapShape3D::get_map_data);
ClassDB::bind_method(D_METHOD("get_min_height"), &HeightMapShape3D::get_min_height);
ClassDB::bind_method(D_METHOD("get_max_height"), &HeightMapShape3D::get_max_height);
ClassDB::bind_method(D_METHOD("update_map_data_from_image", "image", "height_min", "height_max"), &HeightMapShape3D::update_map_data_from_image);
ADD_PROPERTY(PropertyInfo(Variant::INT, "map_width", PROPERTY_HINT_RANGE, "1,100,1,or_greater"), "set_map_width", "get_map_width");
ADD_PROPERTY(PropertyInfo(Variant::INT, "map_depth", PROPERTY_HINT_RANGE, "1,100,1,or_greater"), "set_map_depth", "get_map_depth");
ADD_PROPERTY(PropertyInfo(Variant::PACKED_FLOAT32_ARRAY, "map_data"), "set_map_data", "get_map_data");
}
HeightMapShape3D::HeightMapShape3D() :
Shape3D(PhysicsServer3D::get_singleton()->shape_create(PhysicsServer3D::SHAPE_HEIGHTMAP)) {
map_data.resize(map_width * map_depth);
real_t *w = map_data.ptrw();
w[0] = 0.0;
w[1] = 0.0;
w[2] = 0.0;
w[3] = 0.0;
_update_shape();
}

View File

@@ -0,0 +1,69 @@
/**************************************************************************/
/* height_map_shape_3d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "scene/resources/3d/shape_3d.h"
class ArrayMesh;
class Image;
class HeightMapShape3D : public Shape3D {
GDCLASS(HeightMapShape3D, Shape3D);
int map_width = 2;
int map_depth = 2;
Vector<real_t> map_data;
real_t min_height = 0.0;
real_t max_height = 0.0;
protected:
static void _bind_methods();
virtual void _update_shape() override;
public:
void set_map_width(int p_new);
int get_map_width() const;
void set_map_depth(int p_new);
int get_map_depth() const;
void set_map_data(Vector<real_t> p_new);
Vector<real_t> get_map_data() const;
real_t get_min_height() const;
real_t get_max_height() const;
void update_map_data_from_image(const Ref<Image> &p_image, real_t p_height_min, real_t p_height_max);
virtual Vector<Vector3> get_debug_mesh_lines() const override;
virtual Ref<ArrayMesh> get_debug_arraymesh_faces(const Color &p_modulate) const override;
virtual real_t get_enclosing_radius() const override;
HeightMapShape3D();
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,137 @@
/**************************************************************************/
/* importer_mesh.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "core/io/resource.h"
#include "scene/resources/mesh.h"
#include "scene/resources/navigation_mesh.h"
#ifndef PHYSICS_3D_DISABLED
#include "scene/resources/3d/concave_polygon_shape_3d.h"
#include "scene/resources/3d/convex_polygon_shape_3d.h"
#endif // PHYSICS_3D_DISABLED
// The following classes are used by importers instead of ArrayMesh and MeshInstance3D
// so the data is not registered (hence, quality loss), importing happens faster and
// its easier to modify before saving
class ImporterMesh : public Resource {
GDCLASS(ImporterMesh, Resource)
struct Surface {
Mesh::PrimitiveType primitive;
Array arrays;
struct BlendShape {
Array arrays;
};
Vector<BlendShape> blend_shape_data;
struct LOD {
Vector<int> indices;
float distance = 0.0f;
};
Vector<LOD> lods;
Ref<Material> material;
String name;
uint64_t flags = 0;
struct LODComparator {
_FORCE_INLINE_ bool operator()(const LOD &l, const LOD &r) const {
return l.distance < r.distance;
}
};
};
Vector<Surface> surfaces;
Vector<String> blend_shapes;
Mesh::BlendShapeMode blend_shape_mode = Mesh::BLEND_SHAPE_MODE_NORMALIZED;
Ref<ArrayMesh> mesh;
Ref<ImporterMesh> shadow_mesh;
Size2i lightmap_size_hint;
protected:
void _set_data(const Dictionary &p_data);
Dictionary _get_data() const;
void _generate_lods_bind(float p_normal_merge_angle, float p_normal_split_angle, Array p_skin_pose_transform_array);
static void _bind_methods();
public:
void add_blend_shape(const String &p_name);
int get_blend_shape_count() const;
String get_blend_shape_name(int p_blend_shape) const;
static String validate_blend_shape_name(const String &p_name);
void add_surface(Mesh::PrimitiveType p_primitive, const Array &p_arrays, const TypedArray<Array> &p_blend_shapes = Array(), const Dictionary &p_lods = Dictionary(), const Ref<Material> &p_material = Ref<Material>(), const String &p_name = String(), const uint64_t p_flags = 0);
int get_surface_count() const;
void set_blend_shape_mode(Mesh::BlendShapeMode p_blend_shape_mode);
Mesh::BlendShapeMode get_blend_shape_mode() const;
Mesh::PrimitiveType get_surface_primitive_type(int p_surface);
String get_surface_name(int p_surface) const;
void set_surface_name(int p_surface, const String &p_name);
Array get_surface_arrays(int p_surface) const;
Array get_surface_blend_shape_arrays(int p_surface, int p_blend_shape) const;
int get_surface_lod_count(int p_surface) const;
Vector<int> get_surface_lod_indices(int p_surface, int p_lod) const;
float get_surface_lod_size(int p_surface, int p_lod) const;
Ref<Material> get_surface_material(int p_surface) const;
uint64_t get_surface_format(int p_surface) const;
void set_surface_material(int p_surface, const Ref<Material> &p_material);
void optimize_indices();
void generate_lods(float p_normal_merge_angle, Array p_skin_pose_transform_array);
void create_shadow_mesh();
Ref<ImporterMesh> get_shadow_mesh() const;
Vector<Face3> get_faces() const;
#ifndef PHYSICS_3D_DISABLED
Vector<Ref<Shape3D>> convex_decompose(const Ref<MeshConvexDecompositionSettings> &p_settings) const;
Ref<ConvexPolygonShape3D> create_convex_shape(bool p_clean = true, bool p_simplify = false) const;
Ref<ConcavePolygonShape3D> create_trimesh_shape() const;
#endif // PHYSICS_3D_DISABLED
Ref<NavigationMesh> create_navigation_mesh();
Error lightmap_unwrap_cached(const Transform3D &p_base_transform, float p_texel_size, const Vector<uint8_t> &p_src_cache, Vector<uint8_t> &r_dst_cache);
void set_lightmap_size_hint(const Size2i &p_size);
Size2i get_lightmap_size_hint() const;
bool has_mesh() const;
Ref<ArrayMesh> get_mesh(const Ref<ArrayMesh> &p_base = Ref<ArrayMesh>());
void clear();
};

View File

@@ -0,0 +1,406 @@
/**************************************************************************/
/* mesh_library.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "mesh_library.h"
#ifndef PHYSICS_3D_DISABLED
#include "box_shape_3d.h"
#endif // PHYSICS_3D_DISABLED
bool MeshLibrary::_set(const StringName &p_name, const Variant &p_value) {
String prop_name = p_name;
if (prop_name.begins_with("item/")) {
int idx = prop_name.get_slicec('/', 1).to_int();
String what = prop_name.get_slicec('/', 2);
if (!item_map.has(idx)) {
create_item(idx);
}
if (what == "name") {
set_item_name(idx, p_value);
} else if (what == "mesh") {
set_item_mesh(idx, p_value);
} else if (what == "mesh_transform") {
set_item_mesh_transform(idx, p_value);
} else if (what == "mesh_cast_shadow") {
switch ((int)p_value) {
case 0: {
set_item_mesh_cast_shadow(idx, RS::ShadowCastingSetting::SHADOW_CASTING_SETTING_OFF);
} break;
case 1: {
set_item_mesh_cast_shadow(idx, RS::ShadowCastingSetting::SHADOW_CASTING_SETTING_ON);
} break;
case 2: {
set_item_mesh_cast_shadow(idx, RS::ShadowCastingSetting::SHADOW_CASTING_SETTING_DOUBLE_SIDED);
} break;
case 3: {
set_item_mesh_cast_shadow(idx, RS::ShadowCastingSetting::SHADOW_CASTING_SETTING_SHADOWS_ONLY);
} break;
default: {
set_item_mesh_cast_shadow(idx, RS::ShadowCastingSetting::SHADOW_CASTING_SETTING_ON);
} break;
}
#ifndef PHYSICS_3D_DISABLED
} else if (what == "shape") {
Vector<ShapeData> shapes;
ShapeData sd;
sd.shape = p_value;
shapes.push_back(sd);
set_item_shapes(idx, shapes);
} else if (what == "shapes") {
_set_item_shapes(idx, p_value);
#endif // PHYSICS_3D_DISABLED
} else if (what == "preview") {
set_item_preview(idx, p_value);
} else if (what == "navigation_mesh") {
set_item_navigation_mesh(idx, p_value);
} else if (what == "navigation_mesh_transform") {
set_item_navigation_mesh_transform(idx, p_value);
#ifndef DISABLE_DEPRECATED
} else if (what == "navmesh") { // Renamed in 4.0 beta 9.
set_item_navigation_mesh(idx, p_value);
} else if (what == "navmesh_transform") { // Renamed in 4.0 beta 9.
set_item_navigation_mesh_transform(idx, p_value);
#endif // DISABLE_DEPRECATED
} else if (what == "navigation_layers") {
set_item_navigation_layers(idx, p_value);
} else {
return false;
}
return true;
}
return false;
}
bool MeshLibrary::_get(const StringName &p_name, Variant &r_ret) const {
String prop_name = p_name;
int idx = prop_name.get_slicec('/', 1).to_int();
ERR_FAIL_COND_V(!item_map.has(idx), false);
String what = prop_name.get_slicec('/', 2);
if (what == "name") {
r_ret = get_item_name(idx);
} else if (what == "mesh") {
r_ret = get_item_mesh(idx);
} else if (what == "mesh_transform") {
r_ret = get_item_mesh_transform(idx);
} else if (what == "mesh_cast_shadow") {
r_ret = (int)get_item_mesh_cast_shadow(idx);
#ifndef PHYSICS_3D_DISABLED
} else if (what == "shapes") {
r_ret = _get_item_shapes(idx);
#endif // PHYSICS_3D_DISABLED
} else if (what == "navigation_mesh") {
r_ret = get_item_navigation_mesh(idx);
} else if (what == "navigation_mesh_transform") {
r_ret = get_item_navigation_mesh_transform(idx);
#ifndef DISABLE_DEPRECATED
} else if (what == "navmesh") { // Renamed in 4.0 beta 9.
r_ret = get_item_navigation_mesh(idx);
} else if (what == "navmesh_transform") { // Renamed in 4.0 beta 9.
r_ret = get_item_navigation_mesh_transform(idx);
#endif // DISABLE_DEPRECATED
} else if (what == "navigation_layers") {
r_ret = get_item_navigation_layers(idx);
} else if (what == "preview") {
r_ret = get_item_preview(idx);
} else {
return false;
}
return true;
}
void MeshLibrary::_get_property_list(List<PropertyInfo> *p_list) const {
for (const KeyValue<int, Item> &E : item_map) {
String prop_name = vformat("%s/%d/", PNAME("item"), E.key);
p_list->push_back(PropertyInfo(Variant::STRING, prop_name + PNAME("name")));
p_list->push_back(PropertyInfo(Variant::OBJECT, prop_name + PNAME("mesh"), PROPERTY_HINT_RESOURCE_TYPE, "Mesh"));
p_list->push_back(PropertyInfo(Variant::TRANSFORM3D, prop_name + PNAME("mesh_transform"), PROPERTY_HINT_NONE, "suffix:m"));
p_list->push_back(PropertyInfo(Variant::INT, prop_name + PNAME("mesh_cast_shadow"), PROPERTY_HINT_ENUM, "Off,On,Double-Sided,Shadows Only"));
p_list->push_back(PropertyInfo(Variant::ARRAY, prop_name + PNAME("shapes")));
p_list->push_back(PropertyInfo(Variant::OBJECT, prop_name + PNAME("navigation_mesh"), PROPERTY_HINT_RESOURCE_TYPE, "NavigationMesh"));
p_list->push_back(PropertyInfo(Variant::TRANSFORM3D, prop_name + PNAME("navigation_mesh_transform"), PROPERTY_HINT_NONE, "suffix:m"));
p_list->push_back(PropertyInfo(Variant::INT, prop_name + PNAME("navigation_layers"), PROPERTY_HINT_LAYERS_3D_NAVIGATION));
p_list->push_back(PropertyInfo(Variant::OBJECT, prop_name + PNAME("preview"), PROPERTY_HINT_RESOURCE_TYPE, "Texture2D", PROPERTY_USAGE_DEFAULT));
}
}
void MeshLibrary::create_item(int p_item) {
ERR_FAIL_COND(p_item < 0);
ERR_FAIL_COND(item_map.has(p_item));
item_map[p_item] = Item();
emit_changed();
notify_property_list_changed();
}
void MeshLibrary::set_item_name(int p_item, const String &p_name) {
ERR_FAIL_COND_MSG(!item_map.has(p_item), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'.");
item_map[p_item].name = p_name;
emit_changed();
}
void MeshLibrary::set_item_mesh(int p_item, const Ref<Mesh> &p_mesh) {
ERR_FAIL_COND_MSG(!item_map.has(p_item), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'.");
item_map[p_item].mesh = p_mesh;
emit_changed();
}
void MeshLibrary::set_item_mesh_transform(int p_item, const Transform3D &p_transform) {
ERR_FAIL_COND_MSG(!item_map.has(p_item), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'.");
item_map[p_item].mesh_transform = p_transform;
emit_changed();
}
void MeshLibrary::set_item_mesh_cast_shadow(int p_item, RS::ShadowCastingSetting p_shadow_casting_setting) {
ERR_FAIL_COND_MSG(!item_map.has(p_item), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'.");
item_map[p_item].mesh_cast_shadow = p_shadow_casting_setting;
emit_changed();
}
#ifndef PHYSICS_3D_DISABLED
void MeshLibrary::set_item_shapes(int p_item, const Vector<ShapeData> &p_shapes) {
ERR_FAIL_COND_MSG(!item_map.has(p_item), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'.");
item_map[p_item].shapes = p_shapes;
emit_changed();
notify_property_list_changed();
}
#endif // PHYSICS_3D_DISABLED
void MeshLibrary::set_item_navigation_mesh(int p_item, const Ref<NavigationMesh> &p_navigation_mesh) {
ERR_FAIL_COND_MSG(!item_map.has(p_item), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'.");
item_map[p_item].navigation_mesh = p_navigation_mesh;
emit_changed();
}
void MeshLibrary::set_item_navigation_mesh_transform(int p_item, const Transform3D &p_transform) {
ERR_FAIL_COND_MSG(!item_map.has(p_item), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'.");
item_map[p_item].navigation_mesh_transform = p_transform;
emit_changed();
}
void MeshLibrary::set_item_navigation_layers(int p_item, uint32_t p_navigation_layers) {
ERR_FAIL_COND_MSG(!item_map.has(p_item), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'.");
item_map[p_item].navigation_layers = p_navigation_layers;
emit_changed();
}
void MeshLibrary::set_item_preview(int p_item, const Ref<Texture2D> &p_preview) {
ERR_FAIL_COND_MSG(!item_map.has(p_item), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'.");
item_map[p_item].preview = p_preview;
emit_changed();
}
String MeshLibrary::get_item_name(int p_item) const {
ERR_FAIL_COND_V_MSG(!item_map.has(p_item), "", "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'.");
return item_map[p_item].name;
}
Ref<Mesh> MeshLibrary::get_item_mesh(int p_item) const {
ERR_FAIL_COND_V_MSG(!item_map.has(p_item), Ref<Mesh>(), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'.");
return item_map[p_item].mesh;
}
Transform3D MeshLibrary::get_item_mesh_transform(int p_item) const {
ERR_FAIL_COND_V_MSG(!item_map.has(p_item), Transform3D(), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'.");
return item_map[p_item].mesh_transform;
}
RS::ShadowCastingSetting MeshLibrary::get_item_mesh_cast_shadow(int p_item) const {
ERR_FAIL_COND_V_MSG(!item_map.has(p_item), RS::ShadowCastingSetting::SHADOW_CASTING_SETTING_ON, "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'.");
return item_map[p_item].mesh_cast_shadow;
}
#ifndef PHYSICS_3D_DISABLED
Vector<MeshLibrary::ShapeData> MeshLibrary::get_item_shapes(int p_item) const {
ERR_FAIL_COND_V_MSG(!item_map.has(p_item), Vector<ShapeData>(), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'.");
return item_map[p_item].shapes;
}
#endif // PHYSICS_3D_DISABLED
Ref<NavigationMesh> MeshLibrary::get_item_navigation_mesh(int p_item) const {
ERR_FAIL_COND_V_MSG(!item_map.has(p_item), Ref<NavigationMesh>(), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'.");
return item_map[p_item].navigation_mesh;
}
Transform3D MeshLibrary::get_item_navigation_mesh_transform(int p_item) const {
ERR_FAIL_COND_V_MSG(!item_map.has(p_item), Transform3D(), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'.");
return item_map[p_item].navigation_mesh_transform;
}
uint32_t MeshLibrary::get_item_navigation_layers(int p_item) const {
ERR_FAIL_COND_V_MSG(!item_map.has(p_item), 0, "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'.");
return item_map[p_item].navigation_layers;
}
Ref<Texture2D> MeshLibrary::get_item_preview(int p_item) const {
ERR_FAIL_COND_V_MSG(!item_map.has(p_item), Ref<Texture2D>(), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'.");
return item_map[p_item].preview;
}
bool MeshLibrary::has_item(int p_item) const {
return item_map.has(p_item);
}
void MeshLibrary::remove_item(int p_item) {
ERR_FAIL_COND_MSG(!item_map.has(p_item), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'.");
item_map.erase(p_item);
notify_property_list_changed();
emit_changed();
}
void MeshLibrary::clear() {
item_map.clear();
notify_property_list_changed();
emit_changed();
}
Vector<int> MeshLibrary::get_item_list() const {
Vector<int> ret;
ret.resize(item_map.size());
int idx = 0;
for (const KeyValue<int, Item> &E : item_map) {
ret.write[idx++] = E.key;
}
return ret;
}
int MeshLibrary::find_item_by_name(const String &p_name) const {
for (const KeyValue<int, Item> &E : item_map) {
if (E.value.name == p_name) {
return E.key;
}
}
return -1;
}
int MeshLibrary::get_last_unused_item_id() const {
if (!item_map.size()) {
return 0;
} else {
return item_map.back()->key() + 1;
}
}
#ifndef PHYSICS_3D_DISABLED
void MeshLibrary::_set_item_shapes(int p_item, const Array &p_shapes) {
Array arr_shapes = p_shapes;
int size = p_shapes.size();
if (size & 1) {
ERR_FAIL_COND_MSG(!item_map.has(p_item), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'.");
int prev_size = item_map[p_item].shapes.size() * 2;
if (prev_size < size) {
// Check if last element is a shape.
Ref<Shape3D> shape = arr_shapes[size - 1];
if (shape.is_null()) {
Ref<BoxShape3D> box_shape;
box_shape.instantiate();
arr_shapes[size - 1] = box_shape;
}
// Make sure the added element is a Transform3D.
arr_shapes.push_back(Transform3D());
size++;
} else {
size--;
arr_shapes.resize(size);
}
}
Vector<ShapeData> shapes;
for (int i = 0; i < size; i += 2) {
ShapeData sd;
sd.shape = arr_shapes[i + 0];
sd.local_transform = arr_shapes[i + 1];
if (sd.shape.is_valid()) {
shapes.push_back(sd);
}
}
set_item_shapes(p_item, shapes);
}
Array MeshLibrary::_get_item_shapes(int p_item) const {
Vector<ShapeData> shapes = get_item_shapes(p_item);
Array ret;
for (int i = 0; i < shapes.size(); i++) {
ret.push_back(shapes[i].shape);
ret.push_back(shapes[i].local_transform);
}
return ret;
}
#endif // PHYSICS_3D_DISABLED
void MeshLibrary::reset_state() {
clear();
}
void MeshLibrary::_bind_methods() {
ClassDB::bind_method(D_METHOD("create_item", "id"), &MeshLibrary::create_item);
ClassDB::bind_method(D_METHOD("set_item_name", "id", "name"), &MeshLibrary::set_item_name);
ClassDB::bind_method(D_METHOD("set_item_mesh", "id", "mesh"), &MeshLibrary::set_item_mesh);
ClassDB::bind_method(D_METHOD("set_item_mesh_transform", "id", "mesh_transform"), &MeshLibrary::set_item_mesh_transform);
ClassDB::bind_method(D_METHOD("set_item_mesh_cast_shadow", "id", "shadow_casting_setting"), &MeshLibrary::set_item_mesh_cast_shadow);
ClassDB::bind_method(D_METHOD("set_item_navigation_mesh", "id", "navigation_mesh"), &MeshLibrary::set_item_navigation_mesh);
ClassDB::bind_method(D_METHOD("set_item_navigation_mesh_transform", "id", "navigation_mesh"), &MeshLibrary::set_item_navigation_mesh_transform);
ClassDB::bind_method(D_METHOD("set_item_navigation_layers", "id", "navigation_layers"), &MeshLibrary::set_item_navigation_layers);
#ifndef PHYSICS_3D_DISABLED
ClassDB::bind_method(D_METHOD("set_item_shapes", "id", "shapes"), &MeshLibrary::_set_item_shapes);
#endif // PHYSICS_3D_DISABLED
ClassDB::bind_method(D_METHOD("set_item_preview", "id", "texture"), &MeshLibrary::set_item_preview);
ClassDB::bind_method(D_METHOD("get_item_name", "id"), &MeshLibrary::get_item_name);
ClassDB::bind_method(D_METHOD("get_item_mesh", "id"), &MeshLibrary::get_item_mesh);
ClassDB::bind_method(D_METHOD("get_item_mesh_transform", "id"), &MeshLibrary::get_item_mesh_transform);
ClassDB::bind_method(D_METHOD("get_item_mesh_cast_shadow", "id"), &MeshLibrary::get_item_mesh_cast_shadow);
ClassDB::bind_method(D_METHOD("get_item_navigation_mesh", "id"), &MeshLibrary::get_item_navigation_mesh);
ClassDB::bind_method(D_METHOD("get_item_navigation_mesh_transform", "id"), &MeshLibrary::get_item_navigation_mesh_transform);
ClassDB::bind_method(D_METHOD("get_item_navigation_layers", "id"), &MeshLibrary::get_item_navigation_layers);
#ifndef PHYSICS_3D_DISABLED
ClassDB::bind_method(D_METHOD("get_item_shapes", "id"), &MeshLibrary::_get_item_shapes);
#endif // PHYSICS_3D_DISABLED
ClassDB::bind_method(D_METHOD("get_item_preview", "id"), &MeshLibrary::get_item_preview);
ClassDB::bind_method(D_METHOD("remove_item", "id"), &MeshLibrary::remove_item);
ClassDB::bind_method(D_METHOD("find_item_by_name", "name"), &MeshLibrary::find_item_by_name);
ClassDB::bind_method(D_METHOD("clear"), &MeshLibrary::clear);
ClassDB::bind_method(D_METHOD("get_item_list"), &MeshLibrary::get_item_list);
ClassDB::bind_method(D_METHOD("get_last_unused_item_id"), &MeshLibrary::get_last_unused_item_id);
}
MeshLibrary::MeshLibrary() {
}
MeshLibrary::~MeshLibrary() {
}

View File

@@ -0,0 +1,120 @@
/**************************************************************************/
/* mesh_library.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "core/io/resource.h"
#include "core/templates/rb_map.h"
#include "scene/resources/mesh.h"
#include "scene/resources/navigation_mesh.h"
#include "servers/rendering_server.h"
#ifndef PHYSICS_3D_DISABLED
#include "shape_3d.h"
#endif // PHYSICS_3D_DISABLED
class MeshLibrary : public Resource {
GDCLASS(MeshLibrary, Resource);
RES_BASE_EXTENSION("meshlib");
public:
#ifndef PHYSICS_3D_DISABLED
struct ShapeData {
Ref<Shape3D> shape;
Transform3D local_transform;
};
#endif // PHYSICS_3D_DISABLED
struct Item {
String name;
Ref<Mesh> mesh;
Transform3D mesh_transform;
RS::ShadowCastingSetting mesh_cast_shadow = RS::ShadowCastingSetting::SHADOW_CASTING_SETTING_ON;
#ifndef PHYSICS_3D_DISABLED
Vector<ShapeData> shapes;
#endif // PHYSICS_3D_DISABLED
Ref<Texture2D> preview;
Ref<NavigationMesh> navigation_mesh;
Transform3D navigation_mesh_transform;
uint32_t navigation_layers = 1;
};
RBMap<int, Item> item_map;
#ifndef PHYSICS_3D_DISABLED
void _set_item_shapes(int p_item, const Array &p_shapes);
Array _get_item_shapes(int p_item) const;
#endif // PHYSICS_3D_DISABLED
protected:
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
void _get_property_list(List<PropertyInfo> *p_list) const;
virtual void reset_state() override;
static void _bind_methods();
public:
void create_item(int p_item);
void set_item_name(int p_item, const String &p_name);
void set_item_mesh(int p_item, const Ref<Mesh> &p_mesh);
void set_item_mesh_transform(int p_item, const Transform3D &p_transform);
void set_item_mesh_cast_shadow(int p_item, RS::ShadowCastingSetting p_shadow_casting_setting);
void set_item_navigation_mesh(int p_item, const Ref<NavigationMesh> &p_navigation_mesh);
void set_item_navigation_mesh_transform(int p_item, const Transform3D &p_transform);
void set_item_navigation_layers(int p_item, uint32_t p_navigation_layers);
#ifndef PHYSICS_3D_DISABLED
void set_item_shapes(int p_item, const Vector<ShapeData> &p_shapes);
#endif // PHYSICS_3D_DISABLED
void set_item_preview(int p_item, const Ref<Texture2D> &p_preview);
String get_item_name(int p_item) const;
Ref<Mesh> get_item_mesh(int p_item) const;
Transform3D get_item_mesh_transform(int p_item) const;
RS::ShadowCastingSetting get_item_mesh_cast_shadow(int p_item) const;
Ref<NavigationMesh> get_item_navigation_mesh(int p_item) const;
Transform3D get_item_navigation_mesh_transform(int p_item) const;
uint32_t get_item_navigation_layers(int p_item) const;
#ifndef PHYSICS_3D_DISABLED
Vector<ShapeData> get_item_shapes(int p_item) const;
#endif // PHYSICS_3D_DISABLED
Ref<Texture2D> get_item_preview(int p_item) const;
void remove_item(int p_item);
bool has_item(int p_item) const;
void clear();
int find_item_by_name(const String &p_name) const;
Vector<int> get_item_list() const;
int get_last_unused_item_id() const;
MeshLibrary();
~MeshLibrary();
};

View File

@@ -0,0 +1,425 @@
/**************************************************************************/
/* navigation_mesh_source_geometry_data_3d.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "navigation_mesh_source_geometry_data_3d.h"
void NavigationMeshSourceGeometryData3D::set_vertices(const Vector<float> &p_vertices) {
RWLockWrite write_lock(geometry_rwlock);
vertices = p_vertices;
bounds_dirty = true;
}
const Vector<float> &NavigationMeshSourceGeometryData3D::get_vertices() const {
RWLockRead read_lock(geometry_rwlock);
return vertices;
}
void NavigationMeshSourceGeometryData3D::set_indices(const Vector<int> &p_indices) {
ERR_FAIL_COND(vertices.size() < p_indices.size());
RWLockWrite write_lock(geometry_rwlock);
indices = p_indices;
bounds_dirty = true;
}
const Vector<int> &NavigationMeshSourceGeometryData3D::get_indices() const {
RWLockRead read_lock(geometry_rwlock);
return indices;
}
void NavigationMeshSourceGeometryData3D::append_arrays(const Vector<float> &p_vertices, const Vector<int> &p_indices) {
RWLockWrite write_lock(geometry_rwlock);
const int64_t number_of_vertices_before_merge = vertices.size();
const int64_t number_of_indices_before_merge = indices.size();
vertices.append_array(p_vertices);
indices.append_array(p_indices);
for (int64_t i = number_of_indices_before_merge; i < indices.size(); i++) {
indices.set(i, indices[i] + number_of_vertices_before_merge / 3);
}
bounds_dirty = true;
}
bool NavigationMeshSourceGeometryData3D::has_data() {
RWLockRead read_lock(geometry_rwlock);
return vertices.size() && indices.size();
}
void NavigationMeshSourceGeometryData3D::clear() {
RWLockWrite write_lock(geometry_rwlock);
vertices.clear();
indices.clear();
_projected_obstructions.clear();
bounds_dirty = true;
}
void NavigationMeshSourceGeometryData3D::clear_projected_obstructions() {
RWLockWrite write_lock(geometry_rwlock);
_projected_obstructions.clear();
bounds_dirty = true;
}
void NavigationMeshSourceGeometryData3D::_add_vertex(const Vector3 &p_vec3) {
vertices.push_back(p_vec3.x);
vertices.push_back(p_vec3.y);
vertices.push_back(p_vec3.z);
}
void NavigationMeshSourceGeometryData3D::_add_mesh(const Ref<Mesh> &p_mesh, const Transform3D &p_xform) {
int current_vertex_count;
for (int i = 0; i < p_mesh->get_surface_count(); i++) {
current_vertex_count = vertices.size() / 3;
if (p_mesh->surface_get_primitive_type(i) != Mesh::PRIMITIVE_TRIANGLES) {
continue;
}
int index_count = 0;
if (p_mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_INDEX) {
index_count = p_mesh->surface_get_array_index_len(i);
} else {
index_count = p_mesh->surface_get_array_len(i);
}
ERR_CONTINUE((index_count == 0 || (index_count % 3) != 0));
int face_count = index_count / 3;
Array a = p_mesh->surface_get_arrays(i);
ERR_CONTINUE(a.is_empty() || (a.size() != Mesh::ARRAY_MAX));
Vector<Vector3> mesh_vertices = a[Mesh::ARRAY_VERTEX];
ERR_CONTINUE(mesh_vertices.is_empty());
const Vector3 *vr = mesh_vertices.ptr();
if (p_mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_INDEX) {
Vector<int> mesh_indices = a[Mesh::ARRAY_INDEX];
ERR_CONTINUE(mesh_indices.is_empty() || (mesh_indices.size() != index_count));
const int *ir = mesh_indices.ptr();
for (int j = 0; j < mesh_vertices.size(); j++) {
_add_vertex(p_xform.xform(vr[j]));
}
for (int j = 0; j < face_count; j++) {
// CCW
indices.push_back(current_vertex_count + (ir[j * 3 + 0]));
indices.push_back(current_vertex_count + (ir[j * 3 + 2]));
indices.push_back(current_vertex_count + (ir[j * 3 + 1]));
}
} else {
ERR_CONTINUE(mesh_vertices.size() != index_count);
face_count = mesh_vertices.size() / 3;
for (int j = 0; j < face_count; j++) {
_add_vertex(p_xform.xform(vr[j * 3 + 0]));
_add_vertex(p_xform.xform(vr[j * 3 + 2]));
_add_vertex(p_xform.xform(vr[j * 3 + 1]));
indices.push_back(current_vertex_count + (j * 3 + 0));
indices.push_back(current_vertex_count + (j * 3 + 1));
indices.push_back(current_vertex_count + (j * 3 + 2));
}
}
}
}
void NavigationMeshSourceGeometryData3D::_add_mesh_array(const Array &p_mesh_array, const Transform3D &p_xform) {
ERR_FAIL_COND(p_mesh_array.size() != Mesh::ARRAY_MAX);
Vector<Vector3> mesh_vertices = p_mesh_array[Mesh::ARRAY_VERTEX];
ERR_FAIL_COND(mesh_vertices.is_empty());
const Vector3 *vr = mesh_vertices.ptr();
Vector<int> mesh_indices = p_mesh_array[Mesh::ARRAY_INDEX];
ERR_FAIL_COND(mesh_indices.is_empty());
const int *ir = mesh_indices.ptr();
const int face_count = mesh_indices.size() / 3;
const int current_vertex_count = vertices.size() / 3;
for (int j = 0; j < mesh_vertices.size(); j++) {
_add_vertex(p_xform.xform(vr[j]));
}
for (int j = 0; j < face_count; j++) {
// CCW
indices.push_back(current_vertex_count + (ir[j * 3 + 0]));
indices.push_back(current_vertex_count + (ir[j * 3 + 2]));
indices.push_back(current_vertex_count + (ir[j * 3 + 1]));
}
}
void NavigationMeshSourceGeometryData3D::_add_faces(const PackedVector3Array &p_faces, const Transform3D &p_xform) {
ERR_FAIL_COND(p_faces.is_empty());
ERR_FAIL_COND(p_faces.size() % 3 != 0);
int face_count = p_faces.size() / 3;
int current_vertex_count = vertices.size() / 3;
for (int j = 0; j < face_count; j++) {
_add_vertex(p_xform.xform(p_faces[j * 3 + 0]));
_add_vertex(p_xform.xform(p_faces[j * 3 + 1]));
_add_vertex(p_xform.xform(p_faces[j * 3 + 2]));
indices.push_back(current_vertex_count + (j * 3 + 0));
indices.push_back(current_vertex_count + (j * 3 + 2));
indices.push_back(current_vertex_count + (j * 3 + 1));
}
}
void NavigationMeshSourceGeometryData3D::add_mesh(const Ref<Mesh> &p_mesh, const Transform3D &p_xform) {
ERR_FAIL_COND(p_mesh.is_null());
#ifdef DEBUG_ENABLED
if (!Engine::get_singleton()->is_editor_hint()) {
WARN_PRINT_ONCE("Source geometry parsing for navigation mesh baking had to parse RenderingServer meshes at runtime.\n\
This poses a significant performance issues as visual meshes store geometry data on the GPU and transferring this data back to the CPU blocks the rendering.\n\
For runtime (re)baking navigation meshes use and parse collision shapes as source geometry or create geometry data procedurally in scripts.");
}
#endif
_add_mesh(p_mesh, root_node_transform * p_xform);
}
void NavigationMeshSourceGeometryData3D::add_mesh_array(const Array &p_mesh_array, const Transform3D &p_xform) {
ERR_FAIL_COND(p_mesh_array.size() != Mesh::ARRAY_MAX);
RWLockWrite write_lock(geometry_rwlock);
_add_mesh_array(p_mesh_array, root_node_transform * p_xform);
bounds_dirty = true;
}
void NavigationMeshSourceGeometryData3D::add_faces(const PackedVector3Array &p_faces, const Transform3D &p_xform) {
ERR_FAIL_COND(p_faces.size() % 3 != 0);
RWLockWrite write_lock(geometry_rwlock);
_add_faces(p_faces, root_node_transform * p_xform);
bounds_dirty = true;
}
void NavigationMeshSourceGeometryData3D::merge(const Ref<NavigationMeshSourceGeometryData3D> &p_other_geometry) {
ERR_FAIL_COND(p_other_geometry.is_null());
Vector<float> other_vertices;
Vector<int> other_indices;
Vector<ProjectedObstruction> other_projected_obstructions;
p_other_geometry->get_data(other_vertices, other_indices, other_projected_obstructions);
RWLockWrite write_lock(geometry_rwlock);
const int64_t number_of_vertices_before_merge = vertices.size();
const int64_t number_of_indices_before_merge = indices.size();
vertices.append_array(other_vertices);
indices.append_array(other_indices);
for (int64_t i = number_of_indices_before_merge; i < indices.size(); i++) {
indices.set(i, indices[i] + number_of_vertices_before_merge / 3);
}
_projected_obstructions.append_array(other_projected_obstructions);
bounds_dirty = true;
}
void NavigationMeshSourceGeometryData3D::add_projected_obstruction(const Vector<Vector3> &p_vertices, float p_elevation, float p_height, bool p_carve) {
ERR_FAIL_COND(p_vertices.size() < 3);
ERR_FAIL_COND(p_height < 0.0);
ProjectedObstruction projected_obstruction;
projected_obstruction.vertices.resize(p_vertices.size() * 3);
projected_obstruction.elevation = p_elevation;
projected_obstruction.height = p_height;
projected_obstruction.carve = p_carve;
float *obstruction_vertices_ptrw = projected_obstruction.vertices.ptrw();
int vertex_index = 0;
for (const Vector3 &vertex : p_vertices) {
obstruction_vertices_ptrw[vertex_index++] = vertex.x;
obstruction_vertices_ptrw[vertex_index++] = vertex.y;
obstruction_vertices_ptrw[vertex_index++] = vertex.z;
}
RWLockWrite write_lock(geometry_rwlock);
_projected_obstructions.push_back(projected_obstruction);
bounds_dirty = true;
}
void NavigationMeshSourceGeometryData3D::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("elevation"));
ERR_FAIL_COND(!data.has("height"));
ERR_FAIL_COND(!data.has("carve"));
}
ProjectedObstruction projected_obstruction;
projected_obstruction.vertices = Vector<float>(data["vertices"]);
projected_obstruction.elevation = data["elevation"];
projected_obstruction.height = data["height"];
projected_obstruction.carve = data["carve"];
RWLockWrite write_lock(geometry_rwlock);
_projected_obstructions.push_back(projected_obstruction);
bounds_dirty = true;
}
}
Vector<NavigationMeshSourceGeometryData3D::ProjectedObstruction> NavigationMeshSourceGeometryData3D::_get_projected_obstructions() const {
RWLockRead read_lock(geometry_rwlock);
return _projected_obstructions;
}
Array NavigationMeshSourceGeometryData3D::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["elevation"] = projected_obstruction.elevation;
data["height"] = projected_obstruction.height;
data["carve"] = projected_obstruction.carve;
ret[i] = data;
}
return ret;
}
bool NavigationMeshSourceGeometryData3D::_set(const StringName &p_name, const Variant &p_value) {
if (p_name == "projected_obstructions") {
set_projected_obstructions(p_value);
return true;
}
return false;
}
bool NavigationMeshSourceGeometryData3D::_get(const StringName &p_name, Variant &r_ret) const {
if (p_name == "projected_obstructions") {
r_ret = get_projected_obstructions();
return true;
}
return false;
}
void NavigationMeshSourceGeometryData3D::set_data(const Vector<float> &p_vertices, const Vector<int> &p_indices, Vector<ProjectedObstruction> &p_projected_obstructions) {
RWLockWrite write_lock(geometry_rwlock);
vertices = p_vertices;
indices = p_indices;
_projected_obstructions = p_projected_obstructions;
bounds_dirty = true;
}
void NavigationMeshSourceGeometryData3D::get_data(Vector<float> &r_vertices, Vector<int> &r_indices, Vector<ProjectedObstruction> &r_projected_obstructions) {
RWLockRead read_lock(geometry_rwlock);
r_vertices = vertices;
r_indices = indices;
r_projected_obstructions = _projected_obstructions;
}
AABB NavigationMeshSourceGeometryData3D::get_bounds() {
geometry_rwlock.read_lock();
if (bounds_dirty) {
geometry_rwlock.read_unlock();
RWLockWrite write_lock(geometry_rwlock);
bounds_dirty = false;
bounds = AABB();
bool first_vertex = true;
for (int i = 0; i < vertices.size() / 3; i++) {
const Vector3 vertex = Vector3(vertices[i * 3], vertices[i * 3 + 1], vertices[i * 3 + 2]);
if (first_vertex) {
first_vertex = false;
bounds.position = vertex;
} else {
bounds.expand_to(vertex);
}
}
for (const ProjectedObstruction &projected_obstruction : _projected_obstructions) {
for (int i = 0; i < projected_obstruction.vertices.size() / 3; i++) {
const Vector3 vertex = Vector3(projected_obstruction.vertices[i * 3], projected_obstruction.vertices[i * 3 + 1], projected_obstruction.vertices[i * 3 + 2]);
if (first_vertex) {
first_vertex = false;
bounds.position = vertex;
} else {
bounds.expand_to(vertex);
}
}
}
} else {
geometry_rwlock.read_unlock();
}
RWLockRead read_lock(geometry_rwlock);
return bounds;
}
void NavigationMeshSourceGeometryData3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_vertices", "vertices"), &NavigationMeshSourceGeometryData3D::set_vertices);
ClassDB::bind_method(D_METHOD("get_vertices"), &NavigationMeshSourceGeometryData3D::get_vertices);
ClassDB::bind_method(D_METHOD("set_indices", "indices"), &NavigationMeshSourceGeometryData3D::set_indices);
ClassDB::bind_method(D_METHOD("get_indices"), &NavigationMeshSourceGeometryData3D::get_indices);
ClassDB::bind_method(D_METHOD("append_arrays", "vertices", "indices"), &NavigationMeshSourceGeometryData3D::append_arrays);
ClassDB::bind_method(D_METHOD("clear"), &NavigationMeshSourceGeometryData3D::clear);
ClassDB::bind_method(D_METHOD("has_data"), &NavigationMeshSourceGeometryData3D::has_data);
ClassDB::bind_method(D_METHOD("add_mesh", "mesh", "xform"), &NavigationMeshSourceGeometryData3D::add_mesh);
ClassDB::bind_method(D_METHOD("add_mesh_array", "mesh_array", "xform"), &NavigationMeshSourceGeometryData3D::add_mesh_array);
ClassDB::bind_method(D_METHOD("add_faces", "faces", "xform"), &NavigationMeshSourceGeometryData3D::add_faces);
ClassDB::bind_method(D_METHOD("merge", "other_geometry"), &NavigationMeshSourceGeometryData3D::merge);
ClassDB::bind_method(D_METHOD("add_projected_obstruction", "vertices", "elevation", "height", "carve"), &NavigationMeshSourceGeometryData3D::add_projected_obstruction);
ClassDB::bind_method(D_METHOD("clear_projected_obstructions"), &NavigationMeshSourceGeometryData3D::clear_projected_obstructions);
ClassDB::bind_method(D_METHOD("set_projected_obstructions", "projected_obstructions"), &NavigationMeshSourceGeometryData3D::set_projected_obstructions);
ClassDB::bind_method(D_METHOD("get_projected_obstructions"), &NavigationMeshSourceGeometryData3D::get_projected_obstructions);
ClassDB::bind_method(D_METHOD("get_bounds"), &NavigationMeshSourceGeometryData3D::get_bounds);
ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR3_ARRAY, "vertices", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_vertices", "get_vertices");
ADD_PROPERTY(PropertyInfo(Variant::PACKED_INT32_ARRAY, "indices", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_indices", "get_indices");
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "projected_obstructions", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_projected_obstructions", "get_projected_obstructions");
}

View File

@@ -0,0 +1,109 @@
/**************************************************************************/
/* navigation_mesh_source_geometry_data_3d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "core/os/rw_lock.h"
#include "scene/resources/mesh.h"
class NavigationMeshSourceGeometryData3D : public Resource {
GDCLASS(NavigationMeshSourceGeometryData3D, Resource);
RWLock geometry_rwlock;
Vector<float> vertices;
Vector<int> indices;
AABB bounds;
bool bounds_dirty = true;
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();
private:
void _add_vertex(const Vector3 &p_vec3);
void _add_mesh(const Ref<Mesh> &p_mesh, const Transform3D &p_xform);
void _add_mesh_array(const Array &p_array, const Transform3D &p_xform);
void _add_faces(const PackedVector3Array &p_faces, const Transform3D &p_xform);
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;
float elevation = 0.0;
float height = 0.0;
bool carve = false;
};
// 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
Transform3D root_node_transform;
void set_vertices(const Vector<float> &p_vertices);
const Vector<float> &get_vertices() const;
void set_indices(const Vector<int> &p_indices);
const Vector<int> &get_indices() const;
void append_arrays(const Vector<float> &p_vertices, const Vector<int> &p_indices);
bool has_data();
void clear();
void clear_projected_obstructions();
void add_mesh(const Ref<Mesh> &p_mesh, const Transform3D &p_xform);
void add_mesh_array(const Array &p_mesh_array, const Transform3D &p_xform);
void add_faces(const PackedVector3Array &p_faces, const Transform3D &p_xform);
void merge(const Ref<NavigationMeshSourceGeometryData3D> &p_other_geometry);
void add_projected_obstruction(const Vector<Vector3> &p_vertices, float p_elevation, float p_height, bool p_carve);
Vector<ProjectedObstruction> _get_projected_obstructions() const;
void set_projected_obstructions(const Array &p_array);
Array get_projected_obstructions() const;
void set_data(const Vector<float> &p_vertices, const Vector<int> &p_indices, Vector<ProjectedObstruction> &p_projected_obstructions);
void get_data(Vector<float> &r_vertices, Vector<int> &r_indices, Vector<ProjectedObstruction> &r_projected_obstructions);
AABB get_bounds();
~NavigationMeshSourceGeometryData3D() { clear(); }
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,678 @@
/**************************************************************************/
/* primitive_meshes.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "scene/resources/font.h"
#include "scene/resources/mesh.h"
#include "servers/text_server.h"
///@TODO probably should change a few integers to unsigned integers...
/**
Base class for all the classes in this file, handles a number of code functions that are shared among all meshes.
This class is set apart that it assumes a single surface is always generated for our mesh.
*/
class PrimitiveMesh : public Mesh {
GDCLASS(PrimitiveMesh, Mesh);
private:
RID mesh;
mutable AABB aabb;
AABB custom_aabb;
mutable int array_len = 0;
mutable int index_array_len = 0;
Ref<Material> material;
bool flip_faces = false;
bool add_uv2 = false;
float uv2_padding = 2.0;
// make sure we do an update after we've finished constructing our object
mutable bool pending_request = true;
void _update() const;
protected:
// assume primitive triangles as the type, correct for all but one and it will change this :)
Mesh::PrimitiveType primitive_type = Mesh::PRIMITIVE_TRIANGLES;
// Copy of our texel_size project setting.
float texel_size = 0.2;
static void _bind_methods();
virtual void _create_mesh_array(Array &p_arr) const {}
GDVIRTUAL0RC(Array, _create_mesh_array)
Vector2 get_uv2_scale(Vector2 p_margin_scale = Vector2(1.0, 1.0)) const;
float get_lightmap_texel_size() const;
virtual void _update_lightmap_size() {}
void _on_settings_changed();
public:
virtual int get_surface_count() const override;
virtual int surface_get_array_len(int p_idx) const override;
virtual int surface_get_array_index_len(int p_idx) const override;
virtual Array surface_get_arrays(int p_surface) const override;
virtual TypedArray<Array> surface_get_blend_shape_arrays(int p_surface) const override;
virtual Dictionary surface_get_lods(int p_surface) const override;
virtual BitField<ArrayFormat> surface_get_format(int p_idx) const override;
virtual Mesh::PrimitiveType surface_get_primitive_type(int p_idx) const override;
virtual void surface_set_material(int p_idx, const Ref<Material> &p_material) override;
virtual Ref<Material> surface_get_material(int p_idx) const override;
virtual int get_blend_shape_count() const override;
virtual StringName get_blend_shape_name(int p_index) const override;
virtual void set_blend_shape_name(int p_index, const StringName &p_name) override;
virtual AABB get_aabb() const override;
virtual RID get_rid() const override;
void set_material(const Ref<Material> &p_material);
Ref<Material> get_material() const;
Array get_mesh_arrays() const;
void set_custom_aabb(const AABB &p_custom);
AABB get_custom_aabb() const;
void set_flip_faces(bool p_enable);
bool get_flip_faces() const;
void set_add_uv2(bool p_enable);
bool get_add_uv2() const { return add_uv2; }
void set_uv2_padding(float p_padding);
float get_uv2_padding() const { return uv2_padding; }
void request_update();
PrimitiveMesh();
~PrimitiveMesh();
};
/**
Mesh for a simple capsule
*/
class CapsuleMesh : public PrimitiveMesh {
GDCLASS(CapsuleMesh, PrimitiveMesh);
private:
float radius = 0.5;
float height = 2.0;
int radial_segments = 64;
int rings = 8;
protected:
static void _bind_methods();
virtual void _create_mesh_array(Array &p_arr) const override;
virtual void _update_lightmap_size() override;
public:
static void create_mesh_array(Array &p_arr, float radius, float height, int radial_segments = 64, int rings = 8, bool p_add_uv2 = false, const float p_uv2_padding = 1.0);
void set_radius(const float p_radius);
float get_radius() const;
void set_height(const float p_height);
float get_height() const;
void set_radial_segments(const int p_segments);
int get_radial_segments() const;
void set_rings(const int p_rings);
int get_rings() const;
};
/**
A box
*/
class BoxMesh : public PrimitiveMesh {
GDCLASS(BoxMesh, PrimitiveMesh);
private:
Vector3 size = Vector3(1, 1, 1);
int subdivide_w = 0;
int subdivide_h = 0;
int subdivide_d = 0;
protected:
static void _bind_methods();
virtual void _create_mesh_array(Array &p_arr) const override;
virtual void _update_lightmap_size() override;
public:
static void create_mesh_array(Array &p_arr, Vector3 size, int subdivide_w = 0, int subdivide_h = 0, int subdivide_d = 0, bool p_add_uv2 = false, const float p_uv2_padding = 1.0);
void set_size(const Vector3 &p_size);
Vector3 get_size() const;
void set_subdivide_width(const int p_divisions);
int get_subdivide_width() const;
void set_subdivide_height(const int p_divisions);
int get_subdivide_height() const;
void set_subdivide_depth(const int p_divisions);
int get_subdivide_depth() const;
};
/**
A cylinder
*/
class CylinderMesh : public PrimitiveMesh {
GDCLASS(CylinderMesh, PrimitiveMesh);
private:
float top_radius = 0.5;
float bottom_radius = 0.5;
float height = 2.0;
int radial_segments = 64;
int rings = 4;
bool cap_top = true;
bool cap_bottom = true;
protected:
static void _bind_methods();
virtual void _create_mesh_array(Array &p_arr) const override;
virtual void _update_lightmap_size() override;
public:
static void create_mesh_array(Array &p_arr, float top_radius, float bottom_radius, float height, int radial_segments = 64, int rings = 4, bool cap_top = true, bool cap_bottom = true, bool p_add_uv2 = false, const float p_uv2_padding = 1.0);
void set_top_radius(const float p_radius);
float get_top_radius() const;
void set_bottom_radius(const float p_radius);
float get_bottom_radius() const;
void set_height(const float p_height);
float get_height() const;
void set_radial_segments(const int p_segments);
int get_radial_segments() const;
void set_rings(const int p_rings);
int get_rings() const;
void set_cap_top(bool p_cap_top);
bool is_cap_top() const;
void set_cap_bottom(bool p_cap_bottom);
bool is_cap_bottom() const;
};
/*
A flat rectangle, can be used as quad or heightmap.
*/
class PlaneMesh : public PrimitiveMesh {
GDCLASS(PlaneMesh, PrimitiveMesh);
public:
enum Orientation {
FACE_X,
FACE_Y,
FACE_Z,
};
private:
Size2 size = Size2(2.0, 2.0);
int subdivide_w = 0;
int subdivide_d = 0;
Vector3 center_offset;
Orientation orientation = FACE_Y;
protected:
static void _bind_methods();
virtual void _create_mesh_array(Array &p_arr) const override;
virtual void _update_lightmap_size() override;
public:
void set_size(const Size2 &p_size);
Size2 get_size() const;
void set_subdivide_width(const int p_divisions);
int get_subdivide_width() const;
void set_subdivide_depth(const int p_divisions);
int get_subdivide_depth() const;
void set_center_offset(const Vector3 p_offset);
Vector3 get_center_offset() const;
void set_orientation(const Orientation p_orientation);
Orientation get_orientation() const;
};
VARIANT_ENUM_CAST(PlaneMesh::Orientation)
/*
A flat rectangle, inherits from PlaneMesh but defaults to facing the Z-plane.
*/
class QuadMesh : public PlaneMesh {
GDCLASS(QuadMesh, PlaneMesh);
public:
QuadMesh() {
set_orientation(FACE_Z);
set_size(Size2(1, 1));
}
};
/**
A prism shapen, handy for ramps, triangles, etc.
*/
class PrismMesh : public PrimitiveMesh {
GDCLASS(PrismMesh, PrimitiveMesh);
private:
float left_to_right = 0.5;
Vector3 size = Vector3(1.0, 1.0, 1.0);
int subdivide_w = 0;
int subdivide_h = 0;
int subdivide_d = 0;
protected:
static void _bind_methods();
virtual void _create_mesh_array(Array &p_arr) const override;
virtual void _update_lightmap_size() override;
public:
void set_left_to_right(const float p_left_to_right);
float get_left_to_right() const;
void set_size(const Vector3 &p_size);
Vector3 get_size() const;
void set_subdivide_width(const int p_divisions);
int get_subdivide_width() const;
void set_subdivide_height(const int p_divisions);
int get_subdivide_height() const;
void set_subdivide_depth(const int p_divisions);
int get_subdivide_depth() const;
};
/**
A sphere..
*/
class SphereMesh : public PrimitiveMesh {
GDCLASS(SphereMesh, PrimitiveMesh);
private:
float radius = 0.5;
float height = 1.0;
int radial_segments = 64;
int rings = 32;
bool is_hemisphere = false;
protected:
static void _bind_methods();
virtual void _create_mesh_array(Array &p_arr) const override;
virtual void _update_lightmap_size() override;
public:
static void create_mesh_array(Array &p_arr, float radius, float height, int radial_segments = 64, int rings = 32, bool is_hemisphere = false, bool p_add_uv2 = false, const float p_uv2_padding = 1.0);
void set_radius(const float p_radius);
float get_radius() const;
void set_height(const float p_height);
float get_height() const;
void set_radial_segments(const int p_radial_segments);
int get_radial_segments() const;
void set_rings(const int p_rings);
int get_rings() const;
void set_is_hemisphere(const bool p_is_hemisphere);
bool get_is_hemisphere() const;
};
/**
Big donut
*/
class TorusMesh : public PrimitiveMesh {
GDCLASS(TorusMesh, PrimitiveMesh);
private:
float inner_radius = 0.5;
float outer_radius = 1.0;
int rings = 64;
int ring_segments = 32;
protected:
static void _bind_methods();
virtual void _create_mesh_array(Array &p_arr) const override;
virtual void _update_lightmap_size() override;
public:
void set_inner_radius(const float p_inner_radius);
float get_inner_radius() const;
void set_outer_radius(const float p_outer_radius);
float get_outer_radius() const;
void set_rings(const int p_rings);
int get_rings() const;
void set_ring_segments(const int p_ring_segments);
int get_ring_segments() const;
};
/**
A single point for use in particle systems
*/
class PointMesh : public PrimitiveMesh {
GDCLASS(PointMesh, PrimitiveMesh)
protected:
virtual void _create_mesh_array(Array &p_arr) const override;
public:
PointMesh();
};
class TubeTrailMesh : public PrimitiveMesh {
GDCLASS(TubeTrailMesh, PrimitiveMesh);
private:
float radius = 0.5;
int radial_steps = 8;
int sections = 5;
float section_length = 0.2;
int section_rings = 3;
bool cap_top = true;
bool cap_bottom = true;
Ref<Curve> curve;
void _curve_changed();
protected:
static void _bind_methods();
virtual void _create_mesh_array(Array &p_arr) const override;
public:
void set_radius(const float p_radius);
float get_radius() const;
void set_radial_steps(const int p_radial_steps);
int get_radial_steps() const;
void set_sections(const int p_sections);
int get_sections() const;
void set_section_length(float p_sectionlength);
float get_section_length() const;
void set_section_rings(const int p_section_rings);
int get_section_rings() const;
void set_cap_top(bool p_cap_top);
bool is_cap_top() const;
void set_cap_bottom(bool p_cap_bottom);
bool is_cap_bottom() const;
void set_curve(const Ref<Curve> &p_curve);
Ref<Curve> get_curve() const;
virtual int get_builtin_bind_pose_count() const override;
virtual Transform3D get_builtin_bind_pose(int p_index) const override;
TubeTrailMesh();
};
class RibbonTrailMesh : public PrimitiveMesh {
GDCLASS(RibbonTrailMesh, PrimitiveMesh);
public:
enum Shape {
SHAPE_FLAT,
SHAPE_CROSS
};
private:
float size = 1.0;
int sections = 5;
float section_length = 0.2;
int section_segments = 3;
Shape shape = SHAPE_CROSS;
Ref<Curve> curve;
void _curve_changed();
protected:
static void _bind_methods();
virtual void _create_mesh_array(Array &p_arr) const override;
public:
void set_shape(Shape p_shape);
Shape get_shape() const;
void set_size(const float p_size);
float get_size() const;
void set_sections(const int p_sections);
int get_sections() const;
void set_section_length(float p_sectionlength);
float get_section_length() const;
void set_section_segments(const int p_section_segments);
int get_section_segments() const;
void set_curve(const Ref<Curve> &p_curve);
Ref<Curve> get_curve() const;
virtual int get_builtin_bind_pose_count() const override;
virtual Transform3D get_builtin_bind_pose(int p_index) const override;
RibbonTrailMesh();
};
/**
Text...
*/
class TextMesh : public PrimitiveMesh {
GDCLASS(TextMesh, PrimitiveMesh);
private:
struct ContourPoint {
Vector2 point;
bool sharp = false;
ContourPoint() {}
ContourPoint(const Vector2 &p_pt, bool p_sharp) {
point = p_pt;
sharp = p_sharp;
}
};
struct ContourInfo {
real_t length = 0.0;
bool ccw = true;
ContourInfo() {}
ContourInfo(real_t p_len, bool p_ccw) {
length = p_len;
ccw = p_ccw;
}
};
struct GlyphMeshKey {
uint64_t font_id;
uint32_t gl_id;
bool operator==(const GlyphMeshKey &p_b) const {
return (font_id == p_b.font_id) && (gl_id == p_b.gl_id);
}
GlyphMeshKey(uint64_t p_font_id, uint32_t p_gl_id) {
font_id = p_font_id;
gl_id = p_gl_id;
}
};
struct GlyphMeshKeyHasher {
_FORCE_INLINE_ static uint32_t hash(const GlyphMeshKey &p_a) {
return hash_murmur3_buffer(&p_a, sizeof(GlyphMeshKey));
}
};
struct GlyphMeshData {
Vector<Vector2> triangles;
Vector<Vector<ContourPoint>> contours;
Vector<ContourInfo> contours_info;
Vector2 min_p = Vector2(Math::INF, Math::INF);
Vector2 max_p = Vector2(-Math::INF, -Math::INF);
};
mutable HashMap<GlyphMeshKey, GlyphMeshData, GlyphMeshKeyHasher> cache;
RID text_rid;
mutable Vector<RID> lines_rid;
String text;
String xl_text;
int font_size = 16;
Ref<Font> font_override;
TextServer::AutowrapMode autowrap_mode = TextServer::AUTOWRAP_OFF;
BitField<TextServer::JustificationFlag> jst_flags = TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_SKIP_LAST_LINE | TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE;
float width = 500.0;
float line_spacing = 0.f;
Point2 lbl_offset;
HorizontalAlignment horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER;
VerticalAlignment vertical_alignment = VERTICAL_ALIGNMENT_CENTER;
bool uppercase = false;
String language;
TextServer::Direction text_direction = TextServer::DIRECTION_AUTO;
TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT;
Array st_args;
real_t depth = 0.05;
real_t pixel_size = 0.01;
real_t curve_step = 0.5;
mutable bool dirty_lines = true;
mutable bool dirty_text = true;
mutable bool dirty_font = true;
mutable bool dirty_cache = true;
void _generate_glyph_mesh_data(const GlyphMeshKey &p_key, const Glyph &p_glyph) const;
void _font_changed();
protected:
static void _bind_methods();
void _notification(int p_what);
virtual void _create_mesh_array(Array &p_arr) const override;
public:
GDVIRTUAL2RC(TypedArray<Vector3i>, _structured_text_parser, Array, String)
TextMesh();
~TextMesh();
void set_horizontal_alignment(HorizontalAlignment p_alignment);
HorizontalAlignment get_horizontal_alignment() const;
void set_vertical_alignment(VerticalAlignment p_alignment);
VerticalAlignment get_vertical_alignment() const;
void set_text(const String &p_string);
String get_text() const;
void set_font(const Ref<Font> &p_font);
Ref<Font> get_font() const;
Ref<Font> _get_font_or_default() const;
void set_font_size(int p_size);
int get_font_size() const;
void set_line_spacing(float p_size);
float get_line_spacing() const;
void set_autowrap_mode(TextServer::AutowrapMode p_mode);
TextServer::AutowrapMode get_autowrap_mode() const;
void set_justification_flags(BitField<TextServer::JustificationFlag> p_flags);
BitField<TextServer::JustificationFlag> get_justification_flags() const;
void set_text_direction(TextServer::Direction p_text_direction);
TextServer::Direction get_text_direction() const;
void set_language(const String &p_language);
String get_language() const;
void set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser);
TextServer::StructuredTextParser get_structured_text_bidi_override() const;
void set_structured_text_bidi_override_options(Array p_args);
Array get_structured_text_bidi_override_options() const;
void set_uppercase(bool p_uppercase);
bool is_uppercase() const;
void set_width(real_t p_width);
real_t get_width() const;
void set_depth(real_t p_depth);
real_t get_depth() const;
void set_curve_step(real_t p_step);
real_t get_curve_step() const;
void set_pixel_size(real_t p_amount);
real_t get_pixel_size() const;
void set_offset(const Point2 &p_offset);
Point2 get_offset() const;
};
VARIANT_ENUM_CAST(RibbonTrailMesh::Shape)

View File

@@ -0,0 +1,97 @@
/**************************************************************************/
/* separation_ray_shape_3d.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "separation_ray_shape_3d.h"
#include "scene/resources/mesh.h"
#include "servers/physics_server_3d.h"
Vector<Vector3> SeparationRayShape3D::get_debug_mesh_lines() const {
Vector<Vector3> points = {
Vector3(),
Vector3(0, 0, get_length())
};
return points;
}
Ref<ArrayMesh> SeparationRayShape3D::get_debug_arraymesh_faces(const Color &p_modulate) const {
return memnew(ArrayMesh);
}
real_t SeparationRayShape3D::get_enclosing_radius() const {
return length;
}
void SeparationRayShape3D::_update_shape() {
Dictionary d;
d["length"] = length;
d["slide_on_slope"] = slide_on_slope;
PhysicsServer3D::get_singleton()->shape_set_data(get_shape(), d);
Shape3D::_update_shape();
}
void SeparationRayShape3D::set_length(float p_length) {
length = p_length;
_update_shape();
emit_changed();
}
float SeparationRayShape3D::get_length() const {
return length;
}
void SeparationRayShape3D::set_slide_on_slope(bool p_active) {
slide_on_slope = p_active;
_update_shape();
emit_changed();
}
bool SeparationRayShape3D::get_slide_on_slope() const {
return slide_on_slope;
}
void SeparationRayShape3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_length", "length"), &SeparationRayShape3D::set_length);
ClassDB::bind_method(D_METHOD("get_length"), &SeparationRayShape3D::get_length);
ClassDB::bind_method(D_METHOD("set_slide_on_slope", "active"), &SeparationRayShape3D::set_slide_on_slope);
ClassDB::bind_method(D_METHOD("get_slide_on_slope"), &SeparationRayShape3D::get_slide_on_slope);
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "length", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater,suffix:m"), "set_length", "get_length");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "slide_on_slope"), "set_slide_on_slope", "get_slide_on_slope");
}
SeparationRayShape3D::SeparationRayShape3D() :
Shape3D(PhysicsServer3D::get_singleton()->shape_create(PhysicsServer3D::SHAPE_SEPARATION_RAY)) {
/* Code copied from setters to prevent the use of uninitialized variables */
_update_shape();
emit_changed();
}

View File

@@ -0,0 +1,58 @@
/**************************************************************************/
/* separation_ray_shape_3d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "scene/resources/3d/shape_3d.h"
class ArrayMesh;
class SeparationRayShape3D : public Shape3D {
GDCLASS(SeparationRayShape3D, Shape3D);
float length = 1.0;
bool slide_on_slope = false;
protected:
static void _bind_methods();
virtual void _update_shape() override;
public:
void set_length(float p_length);
float get_length() const;
void set_slide_on_slope(bool p_active);
bool get_slide_on_slope() const;
virtual Vector<Vector3> get_debug_mesh_lines() const override;
virtual Ref<ArrayMesh> get_debug_arraymesh_faces(const Color &p_modulate) const override;
virtual real_t get_enclosing_radius() const override;
SeparationRayShape3D();
};

View File

@@ -0,0 +1,169 @@
/**************************************************************************/
/* shape_3d.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "shape_3d.h"
#include "scene/main/scene_tree.h"
#include "scene/resources/mesh.h"
#include "servers/physics_server_3d.h"
void Shape3D::add_vertices_to_array(Vector<Vector3> &array, const Transform3D &p_xform) {
Vector<Vector3> toadd = get_debug_mesh_lines();
if (toadd.size()) {
int base = array.size();
array.resize(base + toadd.size());
Vector3 *w = array.ptrw();
for (int i = 0; i < toadd.size(); i++) {
w[i + base] = p_xform.xform(toadd[i]);
}
}
}
void Shape3D::set_custom_solver_bias(real_t p_bias) {
custom_bias = p_bias;
PhysicsServer3D::get_singleton()->shape_set_custom_solver_bias(shape, custom_bias);
}
real_t Shape3D::get_custom_solver_bias() const {
return custom_bias;
}
real_t Shape3D::get_margin() const {
return margin;
}
void Shape3D::set_margin(real_t p_margin) {
margin = p_margin;
PhysicsServer3D::get_singleton()->shape_set_margin(shape, margin);
}
void Shape3D::set_debug_color(const Color &p_color) {
if (p_color == debug_color) {
return;
}
debug_color = p_color;
#ifdef DEBUG_ENABLED
debug_properties_edited = true;
#endif // DEBUG_ENABLED
_update_shape();
}
Color Shape3D::get_debug_color() const {
return debug_color;
}
void Shape3D::set_debug_fill(bool p_fill) {
if (p_fill == debug_fill) {
return;
}
debug_fill = p_fill;
#ifdef DEBUG_ENABLED
debug_properties_edited = true;
#endif // DEBUG_ENABLED
_update_shape();
}
bool Shape3D::get_debug_fill() const {
return debug_fill;
}
Ref<ArrayMesh> Shape3D::get_debug_mesh() {
if (debug_mesh_cache.is_valid()) {
return debug_mesh_cache;
}
Vector<Vector3> lines = get_debug_mesh_lines();
debug_mesh_cache.instantiate();
if (!lines.is_empty()) {
Vector<Color> colors;
colors.resize(lines.size());
colors.fill(debug_color);
Array lines_array;
lines_array.resize(Mesh::ARRAY_MAX);
lines_array[Mesh::ARRAY_VERTEX] = lines;
lines_array[Mesh::ARRAY_COLOR] = colors;
debug_mesh_cache->add_surface_from_arrays(Mesh::PRIMITIVE_LINES, lines_array);
SceneTree *scene_tree = SceneTree::get_singleton();
if (scene_tree) {
debug_mesh_cache->surface_set_material(0, scene_tree->get_debug_collision_material());
}
if (debug_fill) {
Ref<ArrayMesh> array_mesh = get_debug_arraymesh_faces(debug_color * Color(1.0, 1.0, 1.0, 0.0625));
if (array_mesh.is_valid() && array_mesh->get_surface_count() > 0) {
Array solid_array = array_mesh->surface_get_arrays(0);
debug_mesh_cache->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, solid_array);
if (scene_tree) {
debug_mesh_cache->surface_set_material(1, scene_tree->get_debug_collision_material());
}
}
}
}
return debug_mesh_cache;
}
void Shape3D::_update_shape() {
emit_changed();
debug_mesh_cache.unref();
}
void Shape3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_custom_solver_bias", "bias"), &Shape3D::set_custom_solver_bias);
ClassDB::bind_method(D_METHOD("get_custom_solver_bias"), &Shape3D::get_custom_solver_bias);
ClassDB::bind_method(D_METHOD("set_margin", "margin"), &Shape3D::set_margin);
ClassDB::bind_method(D_METHOD("get_margin"), &Shape3D::get_margin);
ClassDB::bind_method(D_METHOD("get_debug_mesh"), &Shape3D::get_debug_mesh);
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "custom_solver_bias", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_custom_solver_bias", "get_custom_solver_bias");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "margin", PROPERTY_HINT_RANGE, "0,10,0.001,or_greater,suffix:m"), "set_margin", "get_margin");
}
Shape3D::Shape3D() {
ERR_PRINT("Default constructor must not be called!");
}
Shape3D::Shape3D(RID p_shape) :
shape(p_shape) {}
Shape3D::~Shape3D() {
ERR_FAIL_NULL(PhysicsServer3D::get_singleton());
PhysicsServer3D::get_singleton()->free(shape);
}

View File

@@ -0,0 +1,92 @@
/**************************************************************************/
/* shape_3d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "core/io/resource.h"
class ArrayMesh;
class Material;
class Shape3D : public Resource {
GDCLASS(Shape3D, Resource);
OBJ_SAVE_TYPE(Shape3D);
RES_BASE_EXTENSION("shape");
RID shape;
real_t custom_bias = 0.0;
real_t margin = 0.04;
Ref<ArrayMesh> debug_mesh_cache;
// Not wrapped in `#ifdef DEBUG_ENABLED` as it is used for rendering.
Color debug_color = Color(0.0, 0.0, 0.0, 0.0);
bool debug_fill = true;
#ifdef DEBUG_ENABLED
bool debug_properties_edited = false;
#endif // DEBUG_ENABLED
protected:
static void _bind_methods();
_FORCE_INLINE_ RID get_shape() const { return shape; }
Shape3D(RID p_shape);
virtual void _update_shape();
public:
virtual RID get_rid() const override { return shape; }
Ref<ArrayMesh> get_debug_mesh();
virtual Vector<Vector3> get_debug_mesh_lines() const = 0; // { return Vector<Vector3>(); }
virtual Ref<ArrayMesh> get_debug_arraymesh_faces(const Color &p_modulate) const = 0;
/// Returns the radius of a sphere that fully enclose this shape
virtual real_t get_enclosing_radius() const = 0;
void add_vertices_to_array(Vector<Vector3> &array, const Transform3D &p_xform);
void set_custom_solver_bias(real_t p_bias);
real_t get_custom_solver_bias() const;
real_t get_margin() const;
void set_margin(real_t p_margin);
void set_debug_color(const Color &p_color);
Color get_debug_color() const;
void set_debug_fill(bool p_fill);
bool get_debug_fill() const;
#ifdef DEBUG_ENABLED
_FORCE_INLINE_ bool are_debug_properties_edited() const { return debug_properties_edited; }
#endif // DEBUG_ENABLED
Shape3D();
~Shape3D();
};

162
scene/resources/3d/skin.cpp Normal file
View File

@@ -0,0 +1,162 @@
/**************************************************************************/
/* skin.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 "skin.h"
void Skin::set_bind_count(int p_size) {
ERR_FAIL_COND(p_size < 0);
binds.resize(p_size);
binds_ptr = binds.ptrw();
bind_count = p_size;
emit_changed();
}
void Skin::add_bind(int p_bone, const Transform3D &p_pose) {
uint32_t index = bind_count;
set_bind_count(bind_count + 1);
set_bind_bone(index, p_bone);
set_bind_pose(index, p_pose);
}
void Skin::add_named_bind(const String &p_name, const Transform3D &p_pose) {
uint32_t index = bind_count;
set_bind_count(bind_count + 1);
set_bind_name(index, p_name);
set_bind_pose(index, p_pose);
}
void Skin::set_bind_name(int p_index, const StringName &p_name) {
ERR_FAIL_INDEX(p_index, bind_count);
bool notify_change = (binds_ptr[p_index].name != StringName()) != (p_name != StringName());
binds_ptr[p_index].name = p_name;
emit_changed();
if (notify_change) {
notify_property_list_changed();
}
}
void Skin::set_bind_bone(int p_index, int p_bone) {
ERR_FAIL_INDEX(p_index, bind_count);
binds_ptr[p_index].bone = p_bone;
emit_changed();
}
void Skin::set_bind_pose(int p_index, const Transform3D &p_pose) {
ERR_FAIL_INDEX(p_index, bind_count);
binds_ptr[p_index].pose = p_pose;
emit_changed();
}
void Skin::clear_binds() {
binds.clear();
binds_ptr = nullptr;
bind_count = 0;
emit_changed();
}
void Skin::reset_state() {
clear_binds();
}
bool Skin::_set(const StringName &p_name, const Variant &p_value) {
String prop_name = p_name;
if (prop_name == "bind_count") {
set_bind_count(p_value);
return true;
} else if (prop_name.begins_with("bind/")) {
int index = prop_name.get_slicec('/', 1).to_int();
String what = prop_name.get_slicec('/', 2);
if (what == "bone") {
set_bind_bone(index, p_value);
return true;
} else if (what == "name") {
set_bind_name(index, p_value);
return true;
} else if (what == "pose") {
set_bind_pose(index, p_value);
return true;
}
}
return false;
}
bool Skin::_get(const StringName &p_name, Variant &r_ret) const {
String prop_name = p_name;
if (prop_name == "bind_count") {
r_ret = get_bind_count();
return true;
} else if (prop_name.begins_with("bind/")) {
int index = prop_name.get_slicec('/', 1).to_int();
String what = prop_name.get_slicec('/', 2);
if (what == "bone") {
r_ret = get_bind_bone(index);
return true;
} else if (what == "name") {
r_ret = get_bind_name(index);
return true;
} else if (what == "pose") {
r_ret = get_bind_pose(index);
return true;
}
}
return false;
}
void Skin::_get_property_list(List<PropertyInfo> *p_list) const {
p_list->push_back(PropertyInfo(Variant::INT, PNAME("bind_count"), PROPERTY_HINT_RANGE, "0,16384,1,or_greater"));
for (int i = 0; i < get_bind_count(); i++) {
const String prefix = vformat("%s/%d/", PNAME("bind"), i);
p_list->push_back(PropertyInfo(Variant::STRING_NAME, prefix + PNAME("name")));
p_list->push_back(PropertyInfo(Variant::INT, prefix + PNAME("bone"), PROPERTY_HINT_RANGE, "0,16384,1,or_greater", get_bind_name(i) != StringName() ? PROPERTY_USAGE_NO_EDITOR : PROPERTY_USAGE_DEFAULT));
p_list->push_back(PropertyInfo(Variant::TRANSFORM3D, prefix + PNAME("pose")));
}
}
void Skin::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_bind_count", "bind_count"), &Skin::set_bind_count);
ClassDB::bind_method(D_METHOD("get_bind_count"), &Skin::get_bind_count);
ClassDB::bind_method(D_METHOD("add_bind", "bone", "pose"), &Skin::add_bind);
ClassDB::bind_method(D_METHOD("add_named_bind", "name", "pose"), &Skin::add_named_bind);
ClassDB::bind_method(D_METHOD("set_bind_pose", "bind_index", "pose"), &Skin::set_bind_pose);
ClassDB::bind_method(D_METHOD("get_bind_pose", "bind_index"), &Skin::get_bind_pose);
ClassDB::bind_method(D_METHOD("set_bind_name", "bind_index", "name"), &Skin::set_bind_name);
ClassDB::bind_method(D_METHOD("get_bind_name", "bind_index"), &Skin::get_bind_name);
ClassDB::bind_method(D_METHOD("set_bind_bone", "bind_index", "bone"), &Skin::set_bind_bone);
ClassDB::bind_method(D_METHOD("get_bind_bone", "bind_index"), &Skin::get_bind_bone);
ClassDB::bind_method(D_METHOD("clear_binds"), &Skin::clear_binds);
}
Skin::Skin() {
}

86
scene/resources/3d/skin.h Normal file
View File

@@ -0,0 +1,86 @@
/**************************************************************************/
/* skin.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "core/io/resource.h"
class Skin : public Resource {
GDCLASS(Skin, Resource)
struct Bind {
int bone = -1;
StringName name;
Transform3D pose;
};
Vector<Bind> binds;
Bind *binds_ptr = nullptr;
int bind_count = 0;
protected:
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
void _get_property_list(List<PropertyInfo> *p_list) const;
virtual void reset_state() override;
static void _bind_methods();
public:
void set_bind_count(int p_size);
inline int get_bind_count() const { return bind_count; }
void add_bind(int p_bone, const Transform3D &p_pose);
void add_named_bind(const String &p_name, const Transform3D &p_pose);
void set_bind_bone(int p_index, int p_bone);
void set_bind_pose(int p_index, const Transform3D &p_pose);
void set_bind_name(int p_index, const StringName &p_name);
inline int get_bind_bone(int p_index) const {
ERR_FAIL_INDEX_V(p_index, bind_count, -1);
return binds_ptr[p_index].bone;
}
inline StringName get_bind_name(int p_index) const {
ERR_FAIL_INDEX_V(p_index, bind_count, StringName());
return binds_ptr[p_index].name;
}
inline Transform3D get_bind_pose(int p_index) const {
ERR_FAIL_INDEX_V(p_index, bind_count, Transform3D());
return binds_ptr[p_index].pose;
}
void clear_binds();
Skin();
};

View File

@@ -0,0 +1,838 @@
/**************************************************************************/
/* sky_material.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 "sky_material.h"
#include "core/config/project_settings.h"
#include "core/version.h"
Mutex ProceduralSkyMaterial::shader_mutex;
RID ProceduralSkyMaterial::shader_cache[4];
void ProceduralSkyMaterial::set_sky_top_color(const Color &p_sky_top) {
sky_top_color = p_sky_top;
RS::get_singleton()->material_set_param(_get_material(), "sky_top_color", sky_top_color * sky_energy_multiplier);
}
Color ProceduralSkyMaterial::get_sky_top_color() const {
return sky_top_color;
}
void ProceduralSkyMaterial::set_sky_horizon_color(const Color &p_sky_horizon) {
sky_horizon_color = p_sky_horizon;
RS::get_singleton()->material_set_param(_get_material(), "sky_horizon_color", sky_horizon_color * sky_energy_multiplier);
}
Color ProceduralSkyMaterial::get_sky_horizon_color() const {
return sky_horizon_color;
}
void ProceduralSkyMaterial::set_sky_curve(float p_curve) {
sky_curve = p_curve;
// Actual curve passed to shader includes an ad hoc adjustment because the curve used to be
// in calculated in angles and now uses cosines.
RS::get_singleton()->material_set_param(_get_material(), "inv_sky_curve", 0.6 / sky_curve);
}
float ProceduralSkyMaterial::get_sky_curve() const {
return sky_curve;
}
void ProceduralSkyMaterial::set_sky_energy_multiplier(float p_multiplier) {
sky_energy_multiplier = p_multiplier;
RS::get_singleton()->material_set_param(_get_material(), "sky_top_color", sky_top_color * sky_energy_multiplier);
RS::get_singleton()->material_set_param(_get_material(), "sky_horizon_color", sky_horizon_color * sky_energy_multiplier);
RS::get_singleton()->material_set_param(_get_material(), "sky_cover_modulate", Color(sky_cover_modulate.r, sky_cover_modulate.g, sky_cover_modulate.b, sky_cover_modulate.a * sky_energy_multiplier));
}
float ProceduralSkyMaterial::get_sky_energy_multiplier() const {
return sky_energy_multiplier;
}
void ProceduralSkyMaterial::set_sky_cover(const Ref<Texture2D> &p_sky_cover) {
sky_cover = p_sky_cover;
if (p_sky_cover.is_valid()) {
RS::get_singleton()->material_set_param(_get_material(), "sky_cover", p_sky_cover->get_rid());
} else {
RS::get_singleton()->material_set_param(_get_material(), "sky_cover", Variant());
}
_update_shader(use_debanding, sky_cover.is_valid());
if (shader_set) {
RS::get_singleton()->material_set_shader(_get_material(), get_shader_cache());
}
}
Ref<Texture2D> ProceduralSkyMaterial::get_sky_cover() const {
return sky_cover;
}
void ProceduralSkyMaterial::set_sky_cover_modulate(const Color &p_sky_cover_modulate) {
sky_cover_modulate = p_sky_cover_modulate;
RS::get_singleton()->material_set_param(_get_material(), "sky_cover_modulate", Color(sky_cover_modulate.r, sky_cover_modulate.g, sky_cover_modulate.b, sky_cover_modulate.a * sky_energy_multiplier));
}
Color ProceduralSkyMaterial::get_sky_cover_modulate() const {
return sky_cover_modulate;
}
void ProceduralSkyMaterial::set_ground_bottom_color(const Color &p_ground_bottom) {
ground_bottom_color = p_ground_bottom;
RS::get_singleton()->material_set_param(_get_material(), "ground_bottom_color", ground_bottom_color * ground_energy_multiplier);
}
Color ProceduralSkyMaterial::get_ground_bottom_color() const {
return ground_bottom_color;
}
void ProceduralSkyMaterial::set_ground_horizon_color(const Color &p_ground_horizon) {
ground_horizon_color = p_ground_horizon;
RS::get_singleton()->material_set_param(_get_material(), "ground_horizon_color", ground_horizon_color * ground_energy_multiplier);
}
Color ProceduralSkyMaterial::get_ground_horizon_color() const {
return ground_horizon_color;
}
void ProceduralSkyMaterial::set_ground_curve(float p_curve) {
ground_curve = p_curve;
// Actual curve passed to shader includes an ad hoc adjustment because the curve used to be
// in calculated in angles and now uses cosines.
RS::get_singleton()->material_set_param(_get_material(), "inv_ground_curve", 0.6 / ground_curve);
}
float ProceduralSkyMaterial::get_ground_curve() const {
return ground_curve;
}
void ProceduralSkyMaterial::set_ground_energy_multiplier(float p_multiplier) {
ground_energy_multiplier = p_multiplier;
RS::get_singleton()->material_set_param(_get_material(), "ground_bottom_color", ground_bottom_color * ground_energy_multiplier);
RS::get_singleton()->material_set_param(_get_material(), "ground_horizon_color", ground_horizon_color * ground_energy_multiplier);
}
float ProceduralSkyMaterial::get_ground_energy_multiplier() const {
return ground_energy_multiplier;
}
void ProceduralSkyMaterial::set_sun_angle_max(float p_angle) {
sun_angle_max = p_angle;
RS::get_singleton()->material_set_param(_get_material(), "sun_angle_max", Math::cos(Math::deg_to_rad(sun_angle_max)));
}
float ProceduralSkyMaterial::get_sun_angle_max() const {
return sun_angle_max;
}
void ProceduralSkyMaterial::set_sun_curve(float p_curve) {
sun_curve = p_curve;
// Actual curve passed to shader includes an ad hoc adjustment because the curve used to be
// in calculated in angles and now uses cosines.
RS::get_singleton()->material_set_param(_get_material(), "inv_sun_curve", 1.6f / Math::pow(sun_curve, 1.4f));
}
float ProceduralSkyMaterial::get_sun_curve() const {
return sun_curve;
}
void ProceduralSkyMaterial::set_use_debanding(bool p_use_debanding) {
use_debanding = p_use_debanding;
_update_shader(use_debanding, sky_cover.is_valid());
// Only set if shader already compiled
if (shader_set) {
RS::get_singleton()->material_set_shader(_get_material(), get_shader_cache());
}
}
bool ProceduralSkyMaterial::get_use_debanding() const {
return use_debanding;
}
void ProceduralSkyMaterial::set_energy_multiplier(float p_multiplier) {
global_energy_multiplier = p_multiplier;
RS::get_singleton()->material_set_param(_get_material(), "exposure", global_energy_multiplier);
}
float ProceduralSkyMaterial::get_energy_multiplier() const {
return global_energy_multiplier;
}
Shader::Mode ProceduralSkyMaterial::get_shader_mode() const {
return Shader::MODE_SKY;
}
// Internal function to grab the current shader RID.
// Must only be called if the shader is initialized.
RID ProceduralSkyMaterial::get_shader_cache() const {
return shader_cache[int(use_debanding) + (sky_cover.is_valid() ? 2 : 0)];
}
RID ProceduralSkyMaterial::get_rid() const {
_update_shader(use_debanding, sky_cover.is_valid());
if (!shader_set) {
RS::get_singleton()->material_set_shader(_get_material(), get_shader_cache());
shader_set = true;
}
return _get_material();
}
RID ProceduralSkyMaterial::get_shader_rid() const {
_update_shader(use_debanding, sky_cover.is_valid());
return get_shader_cache();
}
void ProceduralSkyMaterial::_validate_property(PropertyInfo &p_property) const {
if (!Engine::get_singleton()->is_editor_hint()) {
return;
}
if ((p_property.name == "sky_luminance" || p_property.name == "ground_luminance") && !GLOBAL_GET_CACHED(bool, "rendering/lights_and_shadows/use_physical_light_units")) {
p_property.usage = PROPERTY_USAGE_NO_EDITOR;
}
}
void ProceduralSkyMaterial::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_sky_top_color", "color"), &ProceduralSkyMaterial::set_sky_top_color);
ClassDB::bind_method(D_METHOD("get_sky_top_color"), &ProceduralSkyMaterial::get_sky_top_color);
ClassDB::bind_method(D_METHOD("set_sky_horizon_color", "color"), &ProceduralSkyMaterial::set_sky_horizon_color);
ClassDB::bind_method(D_METHOD("get_sky_horizon_color"), &ProceduralSkyMaterial::get_sky_horizon_color);
ClassDB::bind_method(D_METHOD("set_sky_curve", "curve"), &ProceduralSkyMaterial::set_sky_curve);
ClassDB::bind_method(D_METHOD("get_sky_curve"), &ProceduralSkyMaterial::get_sky_curve);
ClassDB::bind_method(D_METHOD("set_sky_energy_multiplier", "multiplier"), &ProceduralSkyMaterial::set_sky_energy_multiplier);
ClassDB::bind_method(D_METHOD("get_sky_energy_multiplier"), &ProceduralSkyMaterial::get_sky_energy_multiplier);
ClassDB::bind_method(D_METHOD("set_sky_cover", "sky_cover"), &ProceduralSkyMaterial::set_sky_cover);
ClassDB::bind_method(D_METHOD("get_sky_cover"), &ProceduralSkyMaterial::get_sky_cover);
ClassDB::bind_method(D_METHOD("set_sky_cover_modulate", "color"), &ProceduralSkyMaterial::set_sky_cover_modulate);
ClassDB::bind_method(D_METHOD("get_sky_cover_modulate"), &ProceduralSkyMaterial::get_sky_cover_modulate);
ClassDB::bind_method(D_METHOD("set_ground_bottom_color", "color"), &ProceduralSkyMaterial::set_ground_bottom_color);
ClassDB::bind_method(D_METHOD("get_ground_bottom_color"), &ProceduralSkyMaterial::get_ground_bottom_color);
ClassDB::bind_method(D_METHOD("set_ground_horizon_color", "color"), &ProceduralSkyMaterial::set_ground_horizon_color);
ClassDB::bind_method(D_METHOD("get_ground_horizon_color"), &ProceduralSkyMaterial::get_ground_horizon_color);
ClassDB::bind_method(D_METHOD("set_ground_curve", "curve"), &ProceduralSkyMaterial::set_ground_curve);
ClassDB::bind_method(D_METHOD("get_ground_curve"), &ProceduralSkyMaterial::get_ground_curve);
ClassDB::bind_method(D_METHOD("set_ground_energy_multiplier", "energy"), &ProceduralSkyMaterial::set_ground_energy_multiplier);
ClassDB::bind_method(D_METHOD("get_ground_energy_multiplier"), &ProceduralSkyMaterial::get_ground_energy_multiplier);
ClassDB::bind_method(D_METHOD("set_sun_angle_max", "degrees"), &ProceduralSkyMaterial::set_sun_angle_max);
ClassDB::bind_method(D_METHOD("get_sun_angle_max"), &ProceduralSkyMaterial::get_sun_angle_max);
ClassDB::bind_method(D_METHOD("set_sun_curve", "curve"), &ProceduralSkyMaterial::set_sun_curve);
ClassDB::bind_method(D_METHOD("get_sun_curve"), &ProceduralSkyMaterial::get_sun_curve);
ClassDB::bind_method(D_METHOD("set_use_debanding", "use_debanding"), &ProceduralSkyMaterial::set_use_debanding);
ClassDB::bind_method(D_METHOD("get_use_debanding"), &ProceduralSkyMaterial::get_use_debanding);
ClassDB::bind_method(D_METHOD("set_energy_multiplier", "multiplier"), &ProceduralSkyMaterial::set_energy_multiplier);
ClassDB::bind_method(D_METHOD("get_energy_multiplier"), &ProceduralSkyMaterial::get_energy_multiplier);
ADD_GROUP("Sky", "sky_");
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "sky_top_color", PROPERTY_HINT_COLOR_NO_ALPHA), "set_sky_top_color", "get_sky_top_color");
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "sky_horizon_color", PROPERTY_HINT_COLOR_NO_ALPHA), "set_sky_horizon_color", "get_sky_horizon_color");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "sky_curve", PROPERTY_HINT_EXP_EASING), "set_sky_curve", "get_sky_curve");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "sky_energy_multiplier", PROPERTY_HINT_RANGE, "0,64,0.01"), "set_sky_energy_multiplier", "get_sky_energy_multiplier");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "sky_cover", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_sky_cover", "get_sky_cover");
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "sky_cover_modulate"), "set_sky_cover_modulate", "get_sky_cover_modulate");
ADD_GROUP("Ground", "ground_");
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "ground_bottom_color", PROPERTY_HINT_COLOR_NO_ALPHA), "set_ground_bottom_color", "get_ground_bottom_color");
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "ground_horizon_color", PROPERTY_HINT_COLOR_NO_ALPHA), "set_ground_horizon_color", "get_ground_horizon_color");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "ground_curve", PROPERTY_HINT_EXP_EASING), "set_ground_curve", "get_ground_curve");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "ground_energy_multiplier", PROPERTY_HINT_RANGE, "0,64,0.01"), "set_ground_energy_multiplier", "get_ground_energy_multiplier");
ADD_GROUP("Sun", "sun_");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "sun_angle_max", PROPERTY_HINT_RANGE, "0,360,0.01,degrees"), "set_sun_angle_max", "get_sun_angle_max");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "sun_curve", PROPERTY_HINT_EXP_EASING), "set_sun_curve", "get_sun_curve");
ADD_GROUP("", "");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_debanding"), "set_use_debanding", "get_use_debanding");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "energy_multiplier", PROPERTY_HINT_RANGE, "0,128,0.01"), "set_energy_multiplier", "get_energy_multiplier");
}
void ProceduralSkyMaterial::cleanup_shader() {
for (int i = 0; i < 4; i++) {
if (shader_cache[i].is_valid()) {
RS::get_singleton()->free(shader_cache[i]);
}
}
}
void ProceduralSkyMaterial::_update_shader(bool p_use_debanding, bool p_use_sky_cover) {
MutexLock shader_lock(shader_mutex);
int index = int(p_use_debanding) + int(p_use_sky_cover) * 2;
if (shader_cache[index].is_null()) {
shader_cache[index] = RS::get_singleton()->shader_create();
// Add a comment to describe the shader origin (useful when converting to ShaderMaterial).
RS::get_singleton()->shader_set_code(shader_cache[index], vformat(R"(
// NOTE: Shader automatically converted from )" GODOT_VERSION_NAME " " GODOT_VERSION_FULL_CONFIG R"('s ProceduralSkyMaterial.
shader_type sky;
%s
uniform vec4 sky_top_color : source_color = vec4(0.385, 0.454, 0.55, 1.0);
uniform vec4 sky_horizon_color : source_color = vec4(0.646, 0.656, 0.67, 1.0);
uniform float inv_sky_curve : hint_range(1, 100) = 4.0;
uniform vec4 ground_bottom_color : source_color = vec4(0.2, 0.169, 0.133, 1.0);
uniform vec4 ground_horizon_color : source_color = vec4(0.646, 0.656, 0.67, 1.0);
uniform float inv_ground_curve : hint_range(1, 100) = 30.0;
uniform float sun_angle_max = 0.877;
uniform float inv_sun_curve : hint_range(1, 100) = 22.78;
uniform float exposure : hint_range(0, 128) = 1.0;
uniform sampler2D sky_cover : filter_linear, source_color, hint_default_black;
uniform vec4 sky_cover_modulate : source_color = vec4(1.0, 1.0, 1.0, 1.0);
void sky() {
float v_angle = clamp(EYEDIR.y, -1.0, 1.0);
vec3 sky = mix(sky_top_color.rgb, sky_horizon_color.rgb, clamp(pow(1.0 - v_angle, inv_sky_curve), 0.0, 1.0));
if (LIGHT0_ENABLED) {
float sun_angle = dot(LIGHT0_DIRECTION, EYEDIR);
float sun_size = cos(LIGHT0_SIZE);
if (sun_angle > sun_size) {
sky = LIGHT0_COLOR * LIGHT0_ENERGY;
} else if (sun_angle > sun_angle_max) {
float c2 = (sun_size - sun_angle) / (sun_size - sun_angle_max);
sky = mix(sky, LIGHT0_COLOR * LIGHT0_ENERGY, clamp(pow(1.0 - c2, inv_sun_curve), 0.0, 1.0));
}
}
if (LIGHT1_ENABLED) {
float sun_angle = dot(LIGHT1_DIRECTION, EYEDIR);
float sun_size = cos(LIGHT1_SIZE);
if (sun_angle > sun_size) {
sky = LIGHT1_COLOR * LIGHT1_ENERGY;
} else if (sun_angle > sun_angle_max) {
float c2 = (sun_size - sun_angle) / (sun_size - sun_angle_max);
sky = mix(sky, LIGHT1_COLOR * LIGHT1_ENERGY, clamp(pow(1.0 - c2, inv_sun_curve), 0.0, 1.0));
}
}
if (LIGHT2_ENABLED) {
float sun_angle = dot(LIGHT2_DIRECTION, EYEDIR);
float sun_size = cos(LIGHT2_SIZE);
if (sun_angle > sun_size) {
sky = LIGHT2_COLOR * LIGHT2_ENERGY;
} else if (sun_angle > sun_angle_max) {
float c2 = (sun_size - sun_angle) / (sun_size - sun_angle_max);
sky = mix(sky, LIGHT2_COLOR * LIGHT2_ENERGY, clamp(pow(1.0 - c2, inv_sun_curve), 0.0, 1.0));
}
}
if (LIGHT3_ENABLED) {
float sun_angle = dot(LIGHT3_DIRECTION, EYEDIR);
float sun_size = cos(LIGHT3_SIZE);
if (sun_angle > sun_size) {
sky = LIGHT3_COLOR * LIGHT3_ENERGY;
} else if (sun_angle > sun_angle_max) {
float c2 = (sun_size - sun_angle) / (sun_size - sun_angle_max);
sky = mix(sky, LIGHT3_COLOR * LIGHT3_ENERGY, clamp(pow(1.0 - c2, inv_sun_curve), 0.0, 1.0));
}
}
%s
%s
vec3 ground = mix(ground_bottom_color.rgb, ground_horizon_color.rgb, clamp(pow(1.0 + v_angle, inv_ground_curve), 0.0, 1.0));
COLOR = mix(ground, sky, step(0.0, EYEDIR.y)) * exposure;
}
)",
p_use_debanding ? "render_mode use_debanding;" : "", p_use_sky_cover ? "vec4 sky_cover_texture = texture(sky_cover, SKY_COORDS);" : "", p_use_sky_cover ? "sky += (sky_cover_texture.rgb * sky_cover_modulate.rgb) * sky_cover_texture.a * sky_cover_modulate.a;" : ""));
}
}
ProceduralSkyMaterial::ProceduralSkyMaterial() {
_set_material(RS::get_singleton()->material_create());
set_sky_top_color(Color(0.385, 0.454, 0.55));
set_sky_horizon_color(Color(0.6463, 0.6558, 0.6708));
set_sky_curve(0.15);
set_sky_energy_multiplier(1.0);
set_sky_cover_modulate(Color(1, 1, 1));
set_ground_bottom_color(Color(0.2, 0.169, 0.133));
set_ground_horizon_color(Color(0.6463, 0.6558, 0.6708));
set_ground_curve(0.02);
set_ground_energy_multiplier(1.0);
set_sun_angle_max(30.0);
set_sun_curve(0.15);
set_use_debanding(true);
set_energy_multiplier(1.0);
}
ProceduralSkyMaterial::~ProceduralSkyMaterial() {
}
/////////////////////////////////////////
/* PanoramaSkyMaterial */
void PanoramaSkyMaterial::set_panorama(const Ref<Texture2D> &p_panorama) {
panorama = p_panorama;
if (p_panorama.is_valid()) {
RS::get_singleton()->material_set_param(_get_material(), "source_panorama", p_panorama->get_rid());
} else {
RS::get_singleton()->material_set_param(_get_material(), "source_panorama", Variant());
}
}
Ref<Texture2D> PanoramaSkyMaterial::get_panorama() const {
return panorama;
}
void PanoramaSkyMaterial::set_filtering_enabled(bool p_enabled) {
filter = p_enabled;
notify_property_list_changed();
_update_shader(filter);
// Only set if shader already compiled
if (shader_set) {
RS::get_singleton()->material_set_shader(_get_material(), shader_cache[int(filter)]);
}
}
bool PanoramaSkyMaterial::is_filtering_enabled() const {
return filter;
}
void PanoramaSkyMaterial::set_energy_multiplier(float p_multiplier) {
energy_multiplier = p_multiplier;
RS::get_singleton()->material_set_param(_get_material(), "exposure", energy_multiplier);
}
float PanoramaSkyMaterial::get_energy_multiplier() const {
return energy_multiplier;
}
Shader::Mode PanoramaSkyMaterial::get_shader_mode() const {
return Shader::MODE_SKY;
}
RID PanoramaSkyMaterial::get_rid() const {
_update_shader(filter);
if (!shader_set) {
RS::get_singleton()->material_set_shader(_get_material(), shader_cache[int(filter)]);
shader_set = true;
}
return _get_material();
}
RID PanoramaSkyMaterial::get_shader_rid() const {
_update_shader(filter);
return shader_cache[int(filter)];
}
void PanoramaSkyMaterial::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_panorama", "texture"), &PanoramaSkyMaterial::set_panorama);
ClassDB::bind_method(D_METHOD("get_panorama"), &PanoramaSkyMaterial::get_panorama);
ClassDB::bind_method(D_METHOD("set_filtering_enabled", "enabled"), &PanoramaSkyMaterial::set_filtering_enabled);
ClassDB::bind_method(D_METHOD("is_filtering_enabled"), &PanoramaSkyMaterial::is_filtering_enabled);
ClassDB::bind_method(D_METHOD("set_energy_multiplier", "multiplier"), &PanoramaSkyMaterial::set_energy_multiplier);
ClassDB::bind_method(D_METHOD("get_energy_multiplier"), &PanoramaSkyMaterial::get_energy_multiplier);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "panorama", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_panorama", "get_panorama");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "filter"), "set_filtering_enabled", "is_filtering_enabled");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "energy_multiplier", PROPERTY_HINT_RANGE, "0,128,0.01"), "set_energy_multiplier", "get_energy_multiplier");
}
Mutex PanoramaSkyMaterial::shader_mutex;
RID PanoramaSkyMaterial::shader_cache[2];
void PanoramaSkyMaterial::cleanup_shader() {
for (int i = 0; i < 2; i++) {
if (shader_cache[i].is_valid()) {
RS::get_singleton()->free(shader_cache[i]);
}
}
}
void PanoramaSkyMaterial::_update_shader(bool p_filter) {
MutexLock shader_lock(shader_mutex);
int index = int(p_filter);
if (shader_cache[index].is_null()) {
shader_cache[index] = RS::get_singleton()->shader_create();
// Add a comment to describe the shader origin (useful when converting to ShaderMaterial).
RS::get_singleton()->shader_set_code(shader_cache[index], vformat(R"(
// NOTE: Shader automatically converted from )" GODOT_VERSION_NAME " " GODOT_VERSION_FULL_CONFIG R"('s PanoramaSkyMaterial.
shader_type sky;
uniform sampler2D source_panorama : %s, source_color, hint_default_black;
uniform float exposure : hint_range(0, 128) = 1.0;
void sky() {
COLOR = texture(source_panorama, SKY_COORDS).rgb * exposure;
}
)",
p_filter ? "filter_linear" : "filter_nearest"));
}
}
PanoramaSkyMaterial::PanoramaSkyMaterial() {
_set_material(RS::get_singleton()->material_create());
set_energy_multiplier(1.0);
}
PanoramaSkyMaterial::~PanoramaSkyMaterial() {
}
//////////////////////////////////
/* PhysicalSkyMaterial */
void PhysicalSkyMaterial::set_rayleigh_coefficient(float p_rayleigh) {
rayleigh = p_rayleigh;
RS::get_singleton()->material_set_param(_get_material(), "rayleigh", rayleigh);
}
float PhysicalSkyMaterial::get_rayleigh_coefficient() const {
return rayleigh;
}
void PhysicalSkyMaterial::set_rayleigh_color(Color p_rayleigh_color) {
rayleigh_color = p_rayleigh_color;
RS::get_singleton()->material_set_param(_get_material(), "rayleigh_color", rayleigh_color);
}
Color PhysicalSkyMaterial::get_rayleigh_color() const {
return rayleigh_color;
}
void PhysicalSkyMaterial::set_mie_coefficient(float p_mie) {
mie = p_mie;
RS::get_singleton()->material_set_param(_get_material(), "mie", mie);
}
float PhysicalSkyMaterial::get_mie_coefficient() const {
return mie;
}
void PhysicalSkyMaterial::set_mie_eccentricity(float p_eccentricity) {
mie_eccentricity = p_eccentricity;
RS::get_singleton()->material_set_param(_get_material(), "mie_eccentricity", mie_eccentricity);
}
float PhysicalSkyMaterial::get_mie_eccentricity() const {
return mie_eccentricity;
}
void PhysicalSkyMaterial::set_mie_color(Color p_mie_color) {
mie_color = p_mie_color;
RS::get_singleton()->material_set_param(_get_material(), "mie_color", mie_color);
}
Color PhysicalSkyMaterial::get_mie_color() const {
return mie_color;
}
void PhysicalSkyMaterial::set_turbidity(float p_turbidity) {
turbidity = p_turbidity;
RS::get_singleton()->material_set_param(_get_material(), "turbidity", turbidity);
}
float PhysicalSkyMaterial::get_turbidity() const {
return turbidity;
}
void PhysicalSkyMaterial::set_sun_disk_scale(float p_sun_disk_scale) {
sun_disk_scale = p_sun_disk_scale;
RS::get_singleton()->material_set_param(_get_material(), "sun_disk_scale", sun_disk_scale);
}
float PhysicalSkyMaterial::get_sun_disk_scale() const {
return sun_disk_scale;
}
void PhysicalSkyMaterial::set_ground_color(Color p_ground_color) {
ground_color = p_ground_color;
RS::get_singleton()->material_set_param(_get_material(), "ground_color", ground_color);
}
Color PhysicalSkyMaterial::get_ground_color() const {
return ground_color;
}
void PhysicalSkyMaterial::set_energy_multiplier(float p_multiplier) {
energy_multiplier = p_multiplier;
RS::get_singleton()->material_set_param(_get_material(), "exposure", energy_multiplier);
}
float PhysicalSkyMaterial::get_energy_multiplier() const {
return energy_multiplier;
}
void PhysicalSkyMaterial::set_use_debanding(bool p_use_debanding) {
use_debanding = p_use_debanding;
_update_shader(use_debanding, night_sky.is_valid());
// Only set if shader already compiled
if (shader_set) {
RS::get_singleton()->material_set_shader(_get_material(), get_shader_cache());
}
}
bool PhysicalSkyMaterial::get_use_debanding() const {
return use_debanding;
}
void PhysicalSkyMaterial::set_night_sky(const Ref<Texture2D> &p_night_sky) {
night_sky = p_night_sky;
if (p_night_sky.is_valid()) {
RS::get_singleton()->material_set_param(_get_material(), "night_sky", p_night_sky->get_rid());
} else {
RS::get_singleton()->material_set_param(_get_material(), "night_sky", Variant());
}
_update_shader(use_debanding, night_sky.is_valid());
if (shader_set) {
RS::get_singleton()->material_set_shader(_get_material(), get_shader_cache());
}
}
Ref<Texture2D> PhysicalSkyMaterial::get_night_sky() const {
return night_sky;
}
Shader::Mode PhysicalSkyMaterial::get_shader_mode() const {
return Shader::MODE_SKY;
}
// Internal function to grab the current shader RID.
// Must only be called if the shader is initialized.
RID PhysicalSkyMaterial::get_shader_cache() const {
return shader_cache[int(use_debanding) + (night_sky.is_valid() ? 2 : 0)];
}
RID PhysicalSkyMaterial::get_rid() const {
_update_shader(use_debanding, night_sky.is_valid());
if (!shader_set) {
RS::get_singleton()->material_set_shader(_get_material(), get_shader_cache());
shader_set = true;
}
return _get_material();
}
RID PhysicalSkyMaterial::get_shader_rid() const {
_update_shader(use_debanding, night_sky.is_valid());
return get_shader_cache();
}
void PhysicalSkyMaterial::_validate_property(PropertyInfo &p_property) const {
if (!Engine::get_singleton()->is_editor_hint()) {
return;
}
if (p_property.name == "exposure_value" && !GLOBAL_GET_CACHED(bool, "rendering/lights_and_shadows/use_physical_light_units")) {
p_property.usage = PROPERTY_USAGE_NO_EDITOR;
}
}
Mutex PhysicalSkyMaterial::shader_mutex;
RID PhysicalSkyMaterial::shader_cache[4];
void PhysicalSkyMaterial::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_rayleigh_coefficient", "rayleigh"), &PhysicalSkyMaterial::set_rayleigh_coefficient);
ClassDB::bind_method(D_METHOD("get_rayleigh_coefficient"), &PhysicalSkyMaterial::get_rayleigh_coefficient);
ClassDB::bind_method(D_METHOD("set_rayleigh_color", "color"), &PhysicalSkyMaterial::set_rayleigh_color);
ClassDB::bind_method(D_METHOD("get_rayleigh_color"), &PhysicalSkyMaterial::get_rayleigh_color);
ClassDB::bind_method(D_METHOD("set_mie_coefficient", "mie"), &PhysicalSkyMaterial::set_mie_coefficient);
ClassDB::bind_method(D_METHOD("get_mie_coefficient"), &PhysicalSkyMaterial::get_mie_coefficient);
ClassDB::bind_method(D_METHOD("set_mie_eccentricity", "eccentricity"), &PhysicalSkyMaterial::set_mie_eccentricity);
ClassDB::bind_method(D_METHOD("get_mie_eccentricity"), &PhysicalSkyMaterial::get_mie_eccentricity);
ClassDB::bind_method(D_METHOD("set_mie_color", "color"), &PhysicalSkyMaterial::set_mie_color);
ClassDB::bind_method(D_METHOD("get_mie_color"), &PhysicalSkyMaterial::get_mie_color);
ClassDB::bind_method(D_METHOD("set_turbidity", "turbidity"), &PhysicalSkyMaterial::set_turbidity);
ClassDB::bind_method(D_METHOD("get_turbidity"), &PhysicalSkyMaterial::get_turbidity);
ClassDB::bind_method(D_METHOD("set_sun_disk_scale", "scale"), &PhysicalSkyMaterial::set_sun_disk_scale);
ClassDB::bind_method(D_METHOD("get_sun_disk_scale"), &PhysicalSkyMaterial::get_sun_disk_scale);
ClassDB::bind_method(D_METHOD("set_ground_color", "color"), &PhysicalSkyMaterial::set_ground_color);
ClassDB::bind_method(D_METHOD("get_ground_color"), &PhysicalSkyMaterial::get_ground_color);
ClassDB::bind_method(D_METHOD("set_energy_multiplier", "multiplier"), &PhysicalSkyMaterial::set_energy_multiplier);
ClassDB::bind_method(D_METHOD("get_energy_multiplier"), &PhysicalSkyMaterial::get_energy_multiplier);
ClassDB::bind_method(D_METHOD("set_use_debanding", "use_debanding"), &PhysicalSkyMaterial::set_use_debanding);
ClassDB::bind_method(D_METHOD("get_use_debanding"), &PhysicalSkyMaterial::get_use_debanding);
ClassDB::bind_method(D_METHOD("set_night_sky", "night_sky"), &PhysicalSkyMaterial::set_night_sky);
ClassDB::bind_method(D_METHOD("get_night_sky"), &PhysicalSkyMaterial::get_night_sky);
ADD_GROUP("Rayleigh", "rayleigh_");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "rayleigh_coefficient", PROPERTY_HINT_RANGE, "0,64,0.01"), "set_rayleigh_coefficient", "get_rayleigh_coefficient");
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "rayleigh_color", PROPERTY_HINT_COLOR_NO_ALPHA), "set_rayleigh_color", "get_rayleigh_color");
ADD_GROUP("Mie", "mie_");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mie_coefficient", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_mie_coefficient", "get_mie_coefficient");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mie_eccentricity", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_mie_eccentricity", "get_mie_eccentricity");
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "mie_color", PROPERTY_HINT_COLOR_NO_ALPHA), "set_mie_color", "get_mie_color");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "turbidity", PROPERTY_HINT_RANGE, "0,1000,0.01"), "set_turbidity", "get_turbidity");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "sun_disk_scale", PROPERTY_HINT_RANGE, "0,360,0.01"), "set_sun_disk_scale", "get_sun_disk_scale");
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "ground_color", PROPERTY_HINT_COLOR_NO_ALPHA), "set_ground_color", "get_ground_color");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "energy_multiplier", PROPERTY_HINT_RANGE, "0,128,0.01"), "set_energy_multiplier", "get_energy_multiplier");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_debanding"), "set_use_debanding", "get_use_debanding");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "night_sky", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_night_sky", "get_night_sky");
}
void PhysicalSkyMaterial::cleanup_shader() {
for (int i = 0; i < 4; i++) {
if (shader_cache[i].is_valid()) {
RS::get_singleton()->free(shader_cache[i]);
}
}
}
void PhysicalSkyMaterial::_update_shader(bool p_use_debanding, bool p_use_night_sky) {
MutexLock shader_lock(shader_mutex);
int index = int(p_use_debanding) + int(p_use_night_sky) * 2;
if (shader_cache[index].is_null()) {
shader_cache[index] = RS::get_singleton()->shader_create();
// Add a comment to describe the shader origin (useful when converting to ShaderMaterial).
RS::get_singleton()->shader_set_code(shader_cache[index], vformat(R"(
// NOTE: Shader automatically converted from )" GODOT_VERSION_NAME " " GODOT_VERSION_FULL_CONFIG R"('s PhysicalSkyMaterial.
shader_type sky;
%s
uniform float rayleigh : hint_range(0, 64) = 2.0;
uniform vec4 rayleigh_color : source_color = vec4(0.3, 0.405, 0.6, 1.0);
uniform float mie : hint_range(0, 1) = 0.005;
uniform float mie_eccentricity : hint_range(-1, 1) = 0.8;
uniform vec4 mie_color : source_color = vec4(0.69, 0.729, 0.812, 1.0);
uniform float turbidity : hint_range(0, 1000) = 10.0;
uniform float sun_disk_scale : hint_range(0, 360) = 1.0;
uniform vec4 ground_color : source_color = vec4(0.1, 0.07, 0.034, 1.0);
uniform float exposure : hint_range(0, 128) = 1.0;
uniform sampler2D night_sky : filter_linear, source_color, hint_default_black;
const vec3 UP = vec3( 0.0, 1.0, 0.0 );
// Optical length at zenith for molecules.
const float rayleigh_zenith_size = 8.4e3;
const float mie_zenith_size = 1.25e3;
float henyey_greenstein(float cos_theta, float g) {
const float k = 0.0795774715459;
return k * (1.0 - g * g) / (pow(1.0 + g * g - 2.0 * g * cos_theta, 1.5));
}
void sky() {
if (LIGHT0_ENABLED) {
float zenith_angle = clamp( dot(UP, normalize(LIGHT0_DIRECTION)), -1.0, 1.0 );
float sun_energy = max(0.0, 0.757 * zenith_angle) * LIGHT0_ENERGY;
float sun_fade = 1.0 - clamp(1.0 - exp(LIGHT0_DIRECTION.y), 0.0, 1.0);
// Rayleigh coefficients.
float rayleigh_coefficient = rayleigh - ( 1.0 * ( 1.0 - sun_fade ) );
vec3 rayleigh_beta = rayleigh_coefficient * rayleigh_color.rgb * 0.0001;
// mie coefficients from Preetham
vec3 mie_beta = turbidity * mie * mie_color.rgb * 0.000434;
// Optical length.
float zenith = max(0.0, dot(UP, EYEDIR));
float optical_mass = 1.0 / (zenith + 0.15 * pow(3.885 + 54.5 * zenith, -1.253));
float rayleigh_scatter = rayleigh_zenith_size * optical_mass;
float mie_scatter = mie_zenith_size * optical_mass;
// Light extinction based on thickness of atmosphere.
vec3 extinction = exp(-(rayleigh_beta * rayleigh_scatter + mie_beta * mie_scatter));
// In scattering.
float cos_theta = dot(EYEDIR, normalize(LIGHT0_DIRECTION));
float rayleigh_phase = (3.0 / (16.0 * PI)) * (1.0 + pow(cos_theta * 0.5 + 0.5, 2.0));
vec3 betaRTheta = rayleigh_beta * rayleigh_phase;
float mie_phase = henyey_greenstein(cos_theta, mie_eccentricity);
vec3 betaMTheta = mie_beta * mie_phase;
vec3 Lin = pow(sun_energy * ((betaRTheta + betaMTheta) / (rayleigh_beta + mie_beta)) * (1.0 - extinction), vec3(1.5));
// Hack from https://github.com/mrdoob/three.js/blob/master/examples/jsm/objects/Sky.js
Lin *= mix(vec3(1.0), pow(sun_energy * ((betaRTheta + betaMTheta) / (rayleigh_beta + mie_beta)) * extinction, vec3(0.5)), clamp(pow(1.0 - zenith_angle, 5.0), 0.0, 1.0));
// Hack in the ground color.
Lin *= mix(ground_color.rgb, vec3(1.0), smoothstep(-0.1, 0.1, dot(UP, EYEDIR)));
// Solar disk and out-scattering.
float sunAngularDiameterCos = cos(LIGHT0_SIZE * sun_disk_scale);
float sunAngularDiameterCos2 = cos(LIGHT0_SIZE * sun_disk_scale * 0.5);
float sundisk = smoothstep(sunAngularDiameterCos, sunAngularDiameterCos2, cos_theta);
vec3 L0 = (sun_energy * extinction) * sundisk * LIGHT0_COLOR;
%s
vec3 color = Lin + L0;
COLOR = pow(color, vec3(1.0 / (1.2 + (1.2 * sun_fade))));
COLOR *= exposure;
} else {
// There is no sun, so display night_sky and nothing else.
%s
COLOR *= exposure;
}
}
)",
p_use_debanding ? "render_mode use_debanding;" : "", p_use_night_sky ? "L0 += texture(night_sky, SKY_COORDS).xyz * extinction;" : "", p_use_night_sky ? "COLOR = texture(night_sky, SKY_COORDS).xyz;" : ""));
}
}
PhysicalSkyMaterial::PhysicalSkyMaterial() {
_set_material(RS::get_singleton()->material_create());
set_rayleigh_coefficient(2.0);
set_rayleigh_color(Color(0.3, 0.405, 0.6));
set_mie_coefficient(0.005);
set_mie_eccentricity(0.8);
set_mie_color(Color(0.69, 0.729, 0.812));
set_turbidity(10.0);
set_sun_disk_scale(1.0);
set_ground_color(Color(0.1, 0.07, 0.034));
set_energy_multiplier(1.0);
set_use_debanding(true);
}
PhysicalSkyMaterial::~PhysicalSkyMaterial() {
}

View File

@@ -0,0 +1,236 @@
/**************************************************************************/
/* sky_material.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "core/templates/rid.h"
#include "scene/resources/material.h"
class ProceduralSkyMaterial : public Material {
GDCLASS(ProceduralSkyMaterial, Material);
private:
Color sky_top_color;
Color sky_horizon_color;
float sky_curve = 0.0f;
float sky_energy_multiplier = 0.0f;
Ref<Texture2D> sky_cover;
Color sky_cover_modulate;
Color ground_bottom_color;
Color ground_horizon_color;
float ground_curve = 0.0f;
float ground_energy_multiplier = 0.0f;
float sun_angle_max = 0.0f;
float sun_curve = 0.0f;
bool use_debanding = true;
float global_energy_multiplier = 1.0f;
static Mutex shader_mutex;
static RID shader_cache[4];
static void _update_shader(bool p_use_debanding, bool p_use_sky_cover);
mutable bool shader_set = false;
RID get_shader_cache() const;
protected:
static void _bind_methods();
void _validate_property(PropertyInfo &property) const;
public:
void set_sky_top_color(const Color &p_sky_top);
Color get_sky_top_color() const;
void set_sky_horizon_color(const Color &p_sky_horizon);
Color get_sky_horizon_color() const;
void set_sky_curve(float p_curve);
float get_sky_curve() const;
void set_sky_energy_multiplier(float p_multiplier);
float get_sky_energy_multiplier() const;
void set_sky_cover(const Ref<Texture2D> &p_sky_cover);
Ref<Texture2D> get_sky_cover() const;
void set_sky_cover_modulate(const Color &p_sky_cover_modulate);
Color get_sky_cover_modulate() const;
void set_ground_bottom_color(const Color &p_ground_bottom);
Color get_ground_bottom_color() const;
void set_ground_horizon_color(const Color &p_ground_horizon);
Color get_ground_horizon_color() const;
void set_ground_curve(float p_curve);
float get_ground_curve() const;
void set_ground_energy_multiplier(float p_energy);
float get_ground_energy_multiplier() const;
void set_sun_angle_max(float p_angle);
float get_sun_angle_max() const;
void set_sun_curve(float p_curve);
float get_sun_curve() const;
void set_use_debanding(bool p_use_debanding);
bool get_use_debanding() const;
void set_energy_multiplier(float p_multiplier);
float get_energy_multiplier() const;
virtual Shader::Mode get_shader_mode() const override;
virtual RID get_shader_rid() const override;
virtual RID get_rid() const override;
static void cleanup_shader();
ProceduralSkyMaterial();
~ProceduralSkyMaterial();
};
//////////////////////////////////////////////////////
/* PanoramaSkyMaterial */
class PanoramaSkyMaterial : public Material {
GDCLASS(PanoramaSkyMaterial, Material);
private:
Ref<Texture2D> panorama;
float energy_multiplier = 1.0f;
static Mutex shader_mutex;
static RID shader_cache[2];
static void _update_shader(bool p_filter);
mutable bool shader_set = false;
bool filter = true;
protected:
static void _bind_methods();
public:
void set_panorama(const Ref<Texture2D> &p_panorama);
Ref<Texture2D> get_panorama() const;
void set_filtering_enabled(bool p_enabled);
bool is_filtering_enabled() const;
void set_energy_multiplier(float p_multiplier);
float get_energy_multiplier() const;
virtual Shader::Mode get_shader_mode() const override;
virtual RID get_shader_rid() const override;
virtual RID get_rid() const override;
static void cleanup_shader();
PanoramaSkyMaterial();
~PanoramaSkyMaterial();
};
//////////////////////////////////////////////////////
/* PanoramaSkyMaterial */
class PhysicalSkyMaterial : public Material {
GDCLASS(PhysicalSkyMaterial, Material);
private:
static Mutex shader_mutex;
static RID shader_cache[4];
RID get_shader_cache() const;
float rayleigh = 0.0f;
Color rayleigh_color;
float mie = 0.0f;
float mie_eccentricity = 0.0f;
Color mie_color;
float turbidity = 0.0f;
float sun_disk_scale = 0.0f;
Color ground_color;
float energy_multiplier = 1.0f;
bool use_debanding = true;
Ref<Texture2D> night_sky;
static void _update_shader(bool p_use_debanding, bool p_use_night_sky);
mutable bool shader_set = false;
protected:
static void _bind_methods();
void _validate_property(PropertyInfo &property) const;
public:
void set_rayleigh_coefficient(float p_rayleigh);
float get_rayleigh_coefficient() const;
void set_rayleigh_color(Color p_rayleigh_color);
Color get_rayleigh_color() const;
void set_turbidity(float p_turbidity);
float get_turbidity() const;
void set_mie_coefficient(float p_mie);
float get_mie_coefficient() const;
void set_mie_eccentricity(float p_eccentricity);
float get_mie_eccentricity() const;
void set_mie_color(Color p_mie_color);
Color get_mie_color() const;
void set_sun_disk_scale(float p_sun_disk_scale);
float get_sun_disk_scale() const;
void set_ground_color(Color p_ground_color);
Color get_ground_color() const;
void set_energy_multiplier(float p_multiplier);
float get_energy_multiplier() const;
void set_exposure_value(float p_exposure);
float get_exposure_value() const;
void set_use_debanding(bool p_use_debanding);
bool get_use_debanding() const;
void set_night_sky(const Ref<Texture2D> &p_night_sky);
Ref<Texture2D> get_night_sky() const;
virtual Shader::Mode get_shader_mode() const override;
virtual RID get_shader_rid() const override;
static void cleanup_shader();
virtual RID get_rid() const override;
PhysicalSkyMaterial();
~PhysicalSkyMaterial();
};

View File

@@ -0,0 +1,106 @@
/**************************************************************************/
/* sphere_shape_3d.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "sphere_shape_3d.h"
#include "scene/resources/3d/primitive_meshes.h"
#include "servers/physics_server_3d.h"
Vector<Vector3> SphereShape3D::get_debug_mesh_lines() const {
float r = get_radius();
Vector<Vector3> points;
for (int i = 0; i <= 360; i++) {
float ra = Math::deg_to_rad((float)i);
float rb = Math::deg_to_rad((float)i + 1);
Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * r;
Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * r;
points.push_back(Vector3(a.x, 0, a.y));
points.push_back(Vector3(b.x, 0, b.y));
points.push_back(Vector3(0, a.x, a.y));
points.push_back(Vector3(0, b.x, b.y));
points.push_back(Vector3(a.x, a.y, 0));
points.push_back(Vector3(b.x, b.y, 0));
}
return points;
}
Ref<ArrayMesh> SphereShape3D::get_debug_arraymesh_faces(const Color &p_modulate) const {
Array sphere_array;
sphere_array.resize(RS::ARRAY_MAX);
SphereMesh::create_mesh_array(sphere_array, radius, radius * 2, 32);
Vector<Color> colors;
const PackedVector3Array &verts = sphere_array[RS::ARRAY_VERTEX];
const int32_t verts_size = verts.size();
for (int i = 0; i < verts_size; i++) {
colors.append(p_modulate);
}
Ref<ArrayMesh> sphere_mesh = memnew(ArrayMesh);
sphere_array[RS::ARRAY_COLOR] = colors;
sphere_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, sphere_array);
return sphere_mesh;
}
real_t SphereShape3D::get_enclosing_radius() const {
return radius;
}
void SphereShape3D::_update_shape() {
PhysicsServer3D::get_singleton()->shape_set_data(get_shape(), radius);
Shape3D::_update_shape();
}
void SphereShape3D::set_radius(float p_radius) {
ERR_FAIL_COND_MSG(p_radius < 0, "SphereShape3D radius cannot be negative.");
radius = p_radius;
_update_shape();
emit_changed();
}
float SphereShape3D::get_radius() const {
return radius;
}
void SphereShape3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_radius", "radius"), &SphereShape3D::set_radius);
ClassDB::bind_method(D_METHOD("get_radius"), &SphereShape3D::get_radius);
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater,suffix:m"), "set_radius", "get_radius");
}
SphereShape3D::SphereShape3D() :
Shape3D(PhysicsServer3D::get_singleton()->shape_create(PhysicsServer3D::SHAPE_SPHERE)) {
set_radius(0.5);
}

View File

@@ -0,0 +1,55 @@
/**************************************************************************/
/* sphere_shape_3d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "scene/resources/3d/shape_3d.h"
class ArrayMesh;
class SphereShape3D : public Shape3D {
GDCLASS(SphereShape3D, Shape3D);
float radius = 0.5f;
protected:
static void _bind_methods();
virtual void _update_shape() override;
public:
void set_radius(float p_radius);
float get_radius() const;
virtual Vector<Vector3> get_debug_mesh_lines() const override;
virtual Ref<ArrayMesh> get_debug_arraymesh_faces(const Color &p_modulate) const override;
virtual real_t get_enclosing_radius() const override;
SphereShape3D();
};

View File

@@ -0,0 +1,210 @@
/**************************************************************************/
/* world_3d.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "world_3d.h"
#include "core/config/project_settings.h"
#include "scene/3d/camera_3d.h"
#include "scene/resources/camera_attributes.h"
#include "scene/resources/environment.h"
#ifndef NAVIGATION_3D_DISABLED
#include "servers/navigation_server_3d.h"
#endif // NAVIGATION_3D_DISABLED
void World3D::_register_camera(Camera3D *p_camera) {
cameras.insert(p_camera);
}
void World3D::_remove_camera(Camera3D *p_camera) {
cameras.erase(p_camera);
}
RID World3D::get_space() const {
#ifndef PHYSICS_3D_DISABLED
if (space.is_null()) {
space = PhysicsServer3D::get_singleton()->space_create();
PhysicsServer3D::get_singleton()->space_set_active(space, true);
PhysicsServer3D::get_singleton()->area_set_param(space, PhysicsServer3D::AREA_PARAM_GRAVITY, GLOBAL_GET("physics/3d/default_gravity"));
PhysicsServer3D::get_singleton()->area_set_param(space, PhysicsServer3D::AREA_PARAM_GRAVITY_VECTOR, GLOBAL_GET("physics/3d/default_gravity_vector"));
PhysicsServer3D::get_singleton()->area_set_param(space, PhysicsServer3D::AREA_PARAM_LINEAR_DAMP, GLOBAL_GET("physics/3d/default_linear_damp"));
PhysicsServer3D::get_singleton()->area_set_param(space, PhysicsServer3D::AREA_PARAM_ANGULAR_DAMP, GLOBAL_GET("physics/3d/default_angular_damp"));
}
#endif // PHYSICS_3D_DISABLED
return space;
}
#ifndef NAVIGATION_3D_DISABLED
RID World3D::get_navigation_map() const {
if (navigation_map.is_null()) {
navigation_map = NavigationServer3D::get_singleton()->map_create();
NavigationServer3D::get_singleton()->map_set_active(navigation_map, true);
NavigationServer3D::get_singleton()->map_set_cell_size(navigation_map, GLOBAL_GET("navigation/3d/default_cell_size"));
NavigationServer3D::get_singleton()->map_set_cell_height(navigation_map, GLOBAL_GET("navigation/3d/default_cell_height"));
NavigationServer3D::get_singleton()->map_set_up(navigation_map, GLOBAL_GET("navigation/3d/default_up"));
NavigationServer3D::get_singleton()->map_set_merge_rasterizer_cell_scale(navigation_map, GLOBAL_GET("navigation/3d/merge_rasterizer_cell_scale"));
NavigationServer3D::get_singleton()->map_set_use_edge_connections(navigation_map, GLOBAL_GET("navigation/3d/use_edge_connections"));
NavigationServer3D::get_singleton()->map_set_edge_connection_margin(navigation_map, GLOBAL_GET("navigation/3d/default_edge_connection_margin"));
NavigationServer3D::get_singleton()->map_set_link_connection_radius(navigation_map, GLOBAL_GET("navigation/3d/default_link_connection_radius"));
}
return navigation_map;
}
#endif // NAVIGATION_3D_DISABLED
RID World3D::get_scenario() const {
return scenario;
}
void World3D::set_environment(const Ref<Environment> &p_environment) {
if (environment == p_environment) {
return;
}
environment = p_environment;
if (environment.is_valid()) {
RS::get_singleton()->scenario_set_environment(scenario, environment->get_rid());
} else {
RS::get_singleton()->scenario_set_environment(scenario, RID());
}
emit_changed();
}
Ref<Environment> World3D::get_environment() const {
return environment;
}
void World3D::set_fallback_environment(const Ref<Environment> &p_environment) {
if (fallback_environment == p_environment) {
return;
}
fallback_environment = p_environment;
if (fallback_environment.is_valid()) {
RS::get_singleton()->scenario_set_fallback_environment(scenario, p_environment->get_rid());
} else {
RS::get_singleton()->scenario_set_fallback_environment(scenario, RID());
}
emit_changed();
}
Ref<Environment> World3D::get_fallback_environment() const {
return fallback_environment;
}
void World3D::set_camera_attributes(const Ref<CameraAttributes> &p_camera_attributes) {
camera_attributes = p_camera_attributes;
if (camera_attributes.is_valid()) {
RS::get_singleton()->scenario_set_camera_attributes(scenario, camera_attributes->get_rid());
} else {
RS::get_singleton()->scenario_set_camera_attributes(scenario, RID());
}
}
Ref<CameraAttributes> World3D::get_camera_attributes() const {
return camera_attributes;
}
void World3D::set_compositor(const Ref<Compositor> &p_compositor) {
compositor = p_compositor;
if (compositor.is_valid()) {
RS::get_singleton()->scenario_set_compositor(scenario, compositor->get_rid());
} else {
RS::get_singleton()->scenario_set_compositor(scenario, RID());
}
}
Ref<Compositor> World3D::get_compositor() const {
return compositor;
}
#ifndef PHYSICS_3D_DISABLED
PhysicsDirectSpaceState3D *World3D::get_direct_space_state() {
return PhysicsServer3D::get_singleton()->space_get_direct_state(get_space());
}
#endif // PHYSICS_3D_DISABLED
void World3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_space"), &World3D::get_space);
#ifndef NAVIGATION_3D_DISABLED
ClassDB::bind_method(D_METHOD("get_navigation_map"), &World3D::get_navigation_map);
#endif // NAVIGATION_3D_DISABLED
ClassDB::bind_method(D_METHOD("get_scenario"), &World3D::get_scenario);
ClassDB::bind_method(D_METHOD("set_environment", "env"), &World3D::set_environment);
ClassDB::bind_method(D_METHOD("get_environment"), &World3D::get_environment);
ClassDB::bind_method(D_METHOD("set_fallback_environment", "env"), &World3D::set_fallback_environment);
ClassDB::bind_method(D_METHOD("get_fallback_environment"), &World3D::get_fallback_environment);
ClassDB::bind_method(D_METHOD("set_camera_attributes", "attributes"), &World3D::set_camera_attributes);
ClassDB::bind_method(D_METHOD("get_camera_attributes"), &World3D::get_camera_attributes);
#ifndef PHYSICS_3D_DISABLED
ClassDB::bind_method(D_METHOD("get_direct_space_state"), &World3D::get_direct_space_state);
#endif // PHYSICS_3D_DISABLED
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "environment", PROPERTY_HINT_RESOURCE_TYPE, "Environment"), "set_environment", "get_environment");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "fallback_environment", PROPERTY_HINT_RESOURCE_TYPE, "Environment"), "set_fallback_environment", "get_fallback_environment");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "camera_attributes", PROPERTY_HINT_RESOURCE_TYPE, "CameraAttributesPractical,CameraAttributesPhysical"), "set_camera_attributes", "get_camera_attributes");
ADD_PROPERTY(PropertyInfo(Variant::RID, "space", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "", "get_space");
#ifndef NAVIGATION_3D_DISABLED
ADD_PROPERTY(PropertyInfo(Variant::RID, "navigation_map", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "", "get_navigation_map");
#endif // NAVIGATION_3D_DISABLED
ADD_PROPERTY(PropertyInfo(Variant::RID, "scenario", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "", "get_scenario");
#ifndef PHYSICS_3D_DISABLED
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "direct_space_state", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsDirectSpaceState3D", PROPERTY_USAGE_NONE), "", "get_direct_space_state");
#endif // PHYSICS_3D_DISABLED
}
World3D::World3D() {
scenario = RenderingServer::get_singleton()->scenario_create();
}
World3D::~World3D() {
ERR_FAIL_NULL(RenderingServer::get_singleton());
#ifndef PHYSICS_3D_DISABLED
ERR_FAIL_NULL(PhysicsServer3D::get_singleton());
#endif // PHYSICS_3D_DISABLED
#ifndef NAVIGATION_3D_DISABLED
ERR_FAIL_NULL(NavigationServer3D::get_singleton());
#endif // NAVIGATION_3D_DISABLED
RenderingServer::get_singleton()->free(scenario);
#ifndef PHYSICS_3D_DISABLED
if (space.is_valid()) {
PhysicsServer3D::get_singleton()->free(space);
}
#endif // PHYSICS_3D_DISABLED
#ifndef NAVIGATION_3D_DISABLED
if (navigation_map.is_valid()) {
NavigationServer3D::get_singleton()->free(navigation_map);
}
#endif // NAVIGATION_3D_DISABLED
}

View File

@@ -0,0 +1,97 @@
/**************************************************************************/
/* world_3d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "core/io/resource.h"
#include "scene/resources/compositor.h"
#include "scene/resources/environment.h"
#ifndef PHYSICS_3D_DISABLED
#include "servers/physics_server_3d.h"
#endif // PHYSICS_3D_DISABLED
class CameraAttributes;
class Camera3D;
class VisibleOnScreenNotifier3D;
struct SpatialIndexer;
class World3D : public Resource {
GDCLASS(World3D, Resource);
private:
RID scenario;
mutable RID space;
#ifndef NAVIGATION_3D_DISABLED
mutable RID navigation_map;
#endif // NAVIGATION_3D_DISABLED
Ref<Environment> environment;
Ref<Environment> fallback_environment;
Ref<CameraAttributes> camera_attributes;
Ref<Compositor> compositor;
HashSet<Camera3D *> cameras;
protected:
static void _bind_methods();
friend class Camera3D;
void _register_camera(Camera3D *p_camera);
void _remove_camera(Camera3D *p_camera);
public:
RID get_space() const;
#ifndef NAVIGATION_3D_DISABLED
RID get_navigation_map() const;
#endif // NAVIGATION_3D_DISABLED
RID get_scenario() const;
void set_environment(const Ref<Environment> &p_environment);
Ref<Environment> get_environment() const;
void set_fallback_environment(const Ref<Environment> &p_environment);
Ref<Environment> get_fallback_environment() const;
void set_camera_attributes(const Ref<CameraAttributes> &p_camera_attributes);
Ref<CameraAttributes> get_camera_attributes() const;
void set_compositor(const Ref<Compositor> &p_compositor);
Ref<Compositor> get_compositor() const;
_FORCE_INLINE_ const HashSet<Camera3D *> &get_cameras() const { return cameras; }
#ifndef PHYSICS_3D_DISABLED
PhysicsDirectSpaceState3D *get_direct_space_state();
#endif // PHYSICS_3D_DISABLED
World3D();
~World3D();
};

View File

@@ -0,0 +1,137 @@
/**************************************************************************/
/* world_boundary_shape_3d.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "world_boundary_shape_3d.h"
#include "scene/resources/mesh.h"
#include "servers/physics_server_3d.h"
Vector<Vector3> WorldBoundaryShape3D::get_debug_mesh_lines() const {
Plane p = get_plane();
Vector3 n1 = p.get_any_perpendicular_normal();
Vector3 n2 = p.normal.cross(n1).normalized();
Vector3 pface[4] = {
p.normal * p.d + n1 * 10.0 + n2 * 10.0,
p.normal * p.d + n1 * 10.0 + n2 * -10.0,
p.normal * p.d + n1 * -10.0 + n2 * -10.0,
p.normal * p.d + n1 * -10.0 + n2 * 10.0,
};
Vector<Vector3> points = {
pface[0],
pface[1],
pface[1],
pface[2],
pface[2],
pface[3],
pface[3],
pface[0],
p.normal * p.d,
p.normal * p.d + p.normal * 3
};
return points;
}
Ref<ArrayMesh> WorldBoundaryShape3D::get_debug_arraymesh_faces(const Color &p_modulate) const {
Plane p = get_plane();
Vector3 n1 = p.get_any_perpendicular_normal();
Vector3 n2 = p.normal.cross(n1).normalized();
Vector3 pface[4] = {
p.normal * p.d + n1 * 10.0 + n2 * 10.0,
p.normal * p.d + n1 * 10.0 + n2 * -10.0,
p.normal * p.d + n1 * -10.0 + n2 * -10.0,
p.normal * p.d + n1 * -10.0 + n2 * 10.0,
};
Vector<Vector3> points = {
pface[0],
pface[1],
pface[2],
pface[3],
};
Vector<Color> colors = {
p_modulate,
p_modulate,
p_modulate,
p_modulate,
};
Vector<int> indices = {
0,
1,
2,
0,
2,
3,
};
Ref<ArrayMesh> mesh = memnew(ArrayMesh);
Array a;
a.resize(Mesh::ARRAY_MAX);
a[RS::ARRAY_VERTEX] = points;
a[RS::ARRAY_COLOR] = colors;
a[RS::ARRAY_INDEX] = indices;
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, a);
return mesh;
}
void WorldBoundaryShape3D::_update_shape() {
PhysicsServer3D::get_singleton()->shape_set_data(get_shape(), plane);
Shape3D::_update_shape();
}
void WorldBoundaryShape3D::set_plane(const Plane &p_plane) {
plane = p_plane;
_update_shape();
emit_changed();
}
const Plane &WorldBoundaryShape3D::get_plane() const {
return plane;
}
void WorldBoundaryShape3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_plane", "plane"), &WorldBoundaryShape3D::set_plane);
ClassDB::bind_method(D_METHOD("get_plane"), &WorldBoundaryShape3D::get_plane);
ADD_PROPERTY(PropertyInfo(Variant::PLANE, "plane", PROPERTY_HINT_NONE, "suffix:m"), "set_plane", "get_plane");
}
WorldBoundaryShape3D::WorldBoundaryShape3D() :
Shape3D(PhysicsServer3D::get_singleton()->shape_create(PhysicsServer3D::SHAPE_WORLD_BOUNDARY)) {
set_plane(Plane(0, 1, 0, 0));
}

View File

@@ -0,0 +1,57 @@
/**************************************************************************/
/* world_boundary_shape_3d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "scene/resources/3d/shape_3d.h"
class ArrayMesh;
class WorldBoundaryShape3D : public Shape3D {
GDCLASS(WorldBoundaryShape3D, Shape3D);
Plane plane;
protected:
static void _bind_methods();
virtual void _update_shape() override;
public:
void set_plane(const Plane &p_plane);
const Plane &get_plane() const;
virtual Vector<Vector3> get_debug_mesh_lines() const override;
virtual Ref<ArrayMesh> get_debug_arraymesh_faces(const Color &p_modulate) const override;
virtual real_t get_enclosing_radius() const override {
// Should be infinite?
return 0;
}
WorldBoundaryShape3D();
};

35
scene/resources/SCsub Normal file
View File

@@ -0,0 +1,35 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")
# Thirdparty code
thirdparty_obj = []
thirdparty_dir = "#thirdparty/misc/"
thirdparty_sources = [
"mikktspace.c",
"qoa.c",
]
thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources]
env_thirdparty = env.Clone()
env_thirdparty.disable_warnings()
env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources)
env.scene_sources += thirdparty_obj
# Godot source files
scene_obj = []
env.add_source_files(scene_obj, "*.cpp")
env.scene_sources += scene_obj
# Needed to force rebuilding the scene files when the thirdparty code is updated.
env.Depends(scene_obj, thirdparty_obj)
SConscript("2d/SCsub")
if not env["disable_3d"]:
SConscript("3d/SCsub")

View File

@@ -0,0 +1,291 @@
/**************************************************************************/
/* animated_texture.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "animated_texture.h"
void AnimatedTexture::_update_proxy() {
RWLockRead r(rw_lock);
float delta;
if (prev_ticks == 0) {
delta = 0;
prev_ticks = OS::get_singleton()->get_ticks_usec();
} else {
uint64_t ticks = OS::get_singleton()->get_ticks_usec();
delta = float(double(ticks - prev_ticks) / 1000000.0);
prev_ticks = ticks;
}
time += delta;
float speed = speed_scale == 0 ? 0 : std::abs(1.0 / speed_scale);
int iter_max = frame_count;
while (iter_max && !pause) {
float frame_limit = frames[current_frame].duration * speed;
if (time > frame_limit) {
if (speed_scale > 0.0) {
current_frame++;
} else {
current_frame--;
}
if (current_frame >= frame_count) {
if (one_shot) {
current_frame = frame_count - 1;
} else {
current_frame = 0;
}
} else if (current_frame < 0) {
if (one_shot) {
current_frame = 0;
} else {
current_frame = frame_count - 1;
}
}
time -= frame_limit;
} else {
break;
}
iter_max--;
}
if (frames[current_frame].texture.is_valid()) {
RenderingServer::get_singleton()->texture_proxy_update(proxy, frames[current_frame].texture->get_rid());
}
}
void AnimatedTexture::set_frames(int p_frames) {
ERR_FAIL_COND(p_frames < 1 || p_frames > MAX_FRAMES);
RWLockWrite r(rw_lock);
frame_count = p_frames;
}
int AnimatedTexture::get_frames() const {
return frame_count;
}
void AnimatedTexture::set_current_frame(int p_frame) {
ERR_FAIL_COND(p_frame < 0 || p_frame >= frame_count);
RWLockWrite r(rw_lock);
current_frame = p_frame;
time = 0;
}
int AnimatedTexture::get_current_frame() const {
return current_frame;
}
void AnimatedTexture::set_pause(bool p_pause) {
RWLockWrite r(rw_lock);
pause = p_pause;
}
bool AnimatedTexture::get_pause() const {
return pause;
}
void AnimatedTexture::set_one_shot(bool p_one_shot) {
RWLockWrite r(rw_lock);
one_shot = p_one_shot;
}
bool AnimatedTexture::get_one_shot() const {
return one_shot;
}
void AnimatedTexture::set_frame_texture(int p_frame, const Ref<Texture2D> &p_texture) {
ERR_FAIL_COND(p_texture == this);
ERR_FAIL_INDEX(p_frame, MAX_FRAMES);
RWLockWrite w(rw_lock);
frames[p_frame].texture = p_texture;
}
Ref<Texture2D> AnimatedTexture::get_frame_texture(int p_frame) const {
ERR_FAIL_INDEX_V(p_frame, MAX_FRAMES, Ref<Texture2D>());
RWLockRead r(rw_lock);
return frames[p_frame].texture;
}
void AnimatedTexture::set_frame_duration(int p_frame, float p_duration) {
ERR_FAIL_INDEX(p_frame, MAX_FRAMES);
RWLockWrite r(rw_lock);
frames[p_frame].duration = p_duration;
}
float AnimatedTexture::get_frame_duration(int p_frame) const {
ERR_FAIL_INDEX_V(p_frame, MAX_FRAMES, 0);
RWLockRead r(rw_lock);
return frames[p_frame].duration;
}
void AnimatedTexture::set_speed_scale(float p_scale) {
ERR_FAIL_COND(p_scale < -1000 || p_scale >= 1000);
RWLockWrite r(rw_lock);
speed_scale = p_scale;
}
float AnimatedTexture::get_speed_scale() const {
return speed_scale;
}
int AnimatedTexture::get_width() const {
RWLockRead r(rw_lock);
if (frames[current_frame].texture.is_null()) {
return 1;
}
return frames[current_frame].texture->get_width();
}
int AnimatedTexture::get_height() const {
RWLockRead r(rw_lock);
if (frames[current_frame].texture.is_null()) {
return 1;
}
return frames[current_frame].texture->get_height();
}
RID AnimatedTexture::get_rid() const {
return proxy;
}
bool AnimatedTexture::has_alpha() const {
RWLockRead r(rw_lock);
if (frames[current_frame].texture.is_null()) {
return false;
}
return frames[current_frame].texture->has_alpha();
}
Ref<Image> AnimatedTexture::get_image() const {
RWLockRead r(rw_lock);
if (frames[current_frame].texture.is_null()) {
return Ref<Image>();
}
return frames[current_frame].texture->get_image();
}
bool AnimatedTexture::is_pixel_opaque(int p_x, int p_y) const {
RWLockRead r(rw_lock);
if (frames[current_frame].texture.is_valid()) {
return frames[current_frame].texture->is_pixel_opaque(p_x, p_y);
}
return true;
}
void AnimatedTexture::_validate_property(PropertyInfo &p_property) const {
String prop = p_property.name;
if (prop.begins_with("frame_")) {
int frame = prop.get_slicec('/', 0).get_slicec('_', 1).to_int();
if (frame >= frame_count) {
p_property.usage = PROPERTY_USAGE_NONE;
}
}
}
void AnimatedTexture::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_frames", "frames"), &AnimatedTexture::set_frames);
ClassDB::bind_method(D_METHOD("get_frames"), &AnimatedTexture::get_frames);
ClassDB::bind_method(D_METHOD("set_current_frame", "frame"), &AnimatedTexture::set_current_frame);
ClassDB::bind_method(D_METHOD("get_current_frame"), &AnimatedTexture::get_current_frame);
ClassDB::bind_method(D_METHOD("set_pause", "pause"), &AnimatedTexture::set_pause);
ClassDB::bind_method(D_METHOD("get_pause"), &AnimatedTexture::get_pause);
ClassDB::bind_method(D_METHOD("set_one_shot", "one_shot"), &AnimatedTexture::set_one_shot);
ClassDB::bind_method(D_METHOD("get_one_shot"), &AnimatedTexture::get_one_shot);
ClassDB::bind_method(D_METHOD("set_speed_scale", "scale"), &AnimatedTexture::set_speed_scale);
ClassDB::bind_method(D_METHOD("get_speed_scale"), &AnimatedTexture::get_speed_scale);
ClassDB::bind_method(D_METHOD("set_frame_texture", "frame", "texture"), &AnimatedTexture::set_frame_texture);
ClassDB::bind_method(D_METHOD("get_frame_texture", "frame"), &AnimatedTexture::get_frame_texture);
ClassDB::bind_method(D_METHOD("set_frame_duration", "frame", "duration"), &AnimatedTexture::set_frame_duration);
ClassDB::bind_method(D_METHOD("get_frame_duration", "frame"), &AnimatedTexture::get_frame_duration);
ADD_PROPERTY(PropertyInfo(Variant::INT, "frames", PROPERTY_HINT_RANGE, "1," + itos(MAX_FRAMES), PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_frames", "get_frames");
ADD_PROPERTY(PropertyInfo(Variant::INT, "current_frame", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_current_frame", "get_current_frame");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "pause"), "set_pause", "get_pause");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "one_shot"), "set_one_shot", "get_one_shot");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "speed_scale", PROPERTY_HINT_RANGE, "-60,60,0.1,or_less,or_greater"), "set_speed_scale", "get_speed_scale");
for (int i = 0; i < MAX_FRAMES; i++) {
ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "frame_" + itos(i) + "/texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL), "set_frame_texture", "get_frame_texture", i);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "frame_" + itos(i) + "/duration", PROPERTY_HINT_RANGE, "0.0,16.0,0.01,or_greater,suffix:s", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL), "set_frame_duration", "get_frame_duration", i);
}
BIND_CONSTANT(MAX_FRAMES);
}
void AnimatedTexture::_finish_non_thread_safe_setup() {
RenderingServer::get_singleton()->connect("frame_pre_draw", callable_mp(this, &AnimatedTexture::_update_proxy));
}
AnimatedTexture::AnimatedTexture() {
//proxy = RS::get_singleton()->texture_create();
proxy_ph = RS::get_singleton()->texture_2d_placeholder_create();
proxy = RS::get_singleton()->texture_proxy_create(proxy_ph);
RenderingServer::get_singleton()->texture_set_force_redraw_if_visible(proxy, true);
MessageQueue::get_main_singleton()->push_callable(callable_mp(this, &AnimatedTexture::_finish_non_thread_safe_setup));
}
AnimatedTexture::~AnimatedTexture() {
ERR_FAIL_NULL(RenderingServer::get_singleton());
RS::get_singleton()->free(proxy);
RS::get_singleton()->free(proxy_ph);
}

View File

@@ -0,0 +1,107 @@
/**************************************************************************/
/* animated_texture.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "scene/resources/texture.h"
class AnimatedTexture : public Texture2D {
GDCLASS(AnimatedTexture, Texture2D);
// Use readers writers lock for this, since its far more times read than written to.
RWLock rw_lock;
public:
enum {
MAX_FRAMES = 256
};
private:
RID proxy_ph;
RID proxy;
struct Frame {
Ref<Texture2D> texture;
float duration = 1.0;
};
Frame frames[MAX_FRAMES];
int frame_count = 1.0;
int current_frame = 0;
bool pause = false;
bool one_shot = false;
float speed_scale = 1.0;
float time = 0.0;
uint64_t prev_ticks = 0;
void _update_proxy();
void _finish_non_thread_safe_setup();
protected:
static void _bind_methods();
void _validate_property(PropertyInfo &p_property) const;
public:
void set_frames(int p_frames);
int get_frames() const;
void set_current_frame(int p_frame);
int get_current_frame() const;
void set_pause(bool p_pause);
bool get_pause() const;
void set_one_shot(bool p_one_shot);
bool get_one_shot() const;
void set_frame_texture(int p_frame, const Ref<Texture2D> &p_texture);
Ref<Texture2D> get_frame_texture(int p_frame) const;
void set_frame_duration(int p_frame, float p_duration);
float get_frame_duration(int p_frame) const;
void set_speed_scale(float p_scale);
float get_speed_scale() const;
virtual int get_width() const override;
virtual int get_height() const override;
virtual RID get_rid() const override;
virtual bool has_alpha() const override;
virtual Ref<Image> get_image() const override;
bool is_pixel_opaque(int p_x, int p_y) const override;
AnimatedTexture();
~AnimatedTexture();
};

View File

@@ -0,0 +1,66 @@
/**************************************************************************/
/* animation.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
Vector3 Animation::_position_track_interpolate_bind_compat_86629(int p_track, double p_time) const {
return position_track_interpolate(p_track, p_time, false);
}
Quaternion Animation::_rotation_track_interpolate_bind_compat_86629(int p_track, double p_time) const {
return rotation_track_interpolate(p_track, p_time, false);
}
Vector3 Animation::_scale_track_interpolate_bind_compat_86629(int p_track, double p_time) const {
return scale_track_interpolate(p_track, p_time, false);
}
float Animation::_blend_shape_track_interpolate_bind_compat_86629(int p_track, double p_time) const {
return blend_shape_track_interpolate(p_track, p_time, false);
}
Variant Animation::_value_track_interpolate_bind_compat_86629(int p_track, double p_time) const {
return value_track_interpolate(p_track, p_time, false);
}
int Animation::_track_find_key_bind_compat_92861(int p_track, double p_time, FindMode p_find_mode) const {
return track_find_key(p_track, p_time, p_find_mode, false, false);
}
void Animation::_bind_compatibility_methods() {
ClassDB::bind_compatibility_method(D_METHOD("position_track_interpolate", "track_idx", "time_sec"), &Animation::_position_track_interpolate_bind_compat_86629);
ClassDB::bind_compatibility_method(D_METHOD("rotation_track_interpolate", "track_idx", "time_sec"), &Animation::_rotation_track_interpolate_bind_compat_86629);
ClassDB::bind_compatibility_method(D_METHOD("scale_track_interpolate", "track_idx", "time_sec"), &Animation::_scale_track_interpolate_bind_compat_86629);
ClassDB::bind_compatibility_method(D_METHOD("blend_shape_track_interpolate", "track_idx", "time_sec"), &Animation::_blend_shape_track_interpolate_bind_compat_86629);
ClassDB::bind_compatibility_method(D_METHOD("value_track_interpolate", "track_idx", "time_sec"), &Animation::_value_track_interpolate_bind_compat_86629);
ClassDB::bind_compatibility_method(D_METHOD("track_find_key", "track_idx", "time", "find_mode"), &Animation::_track_find_key_bind_compat_92861, DEFVAL(FIND_MODE_NEAREST));
}
#endif // DISABLE_DEPRECATED

File diff suppressed because it is too large Load Diff

590
scene/resources/animation.h Normal file
View File

@@ -0,0 +1,590 @@
/**************************************************************************/
/* animation.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "core/io/resource.h"
#include "core/templates/local_vector.h"
#define ANIM_MIN_LENGTH 0.001
class Animation : public Resource {
GDCLASS(Animation, Resource);
RES_BASE_EXTENSION("anim");
public:
typedef uint32_t TypeHash;
static inline String PARAMETERS_BASE_PATH = "parameters/";
static constexpr real_t DEFAULT_STEP = 1.0 / 30;
enum TrackType : uint8_t {
TYPE_VALUE, // Set a value in a property, can be interpolated.
TYPE_POSITION_3D, // Position 3D track, can be compressed.
TYPE_ROTATION_3D, // Rotation 3D track, can be compressed.
TYPE_SCALE_3D, // Scale 3D track, can be compressed.
TYPE_BLEND_SHAPE, // Blend Shape track, can be compressed.
TYPE_METHOD, // Call any method on a specific node.
TYPE_BEZIER, // Bezier curve.
TYPE_AUDIO,
TYPE_ANIMATION,
};
enum InterpolationType : uint8_t {
INTERPOLATION_NEAREST,
INTERPOLATION_LINEAR,
INTERPOLATION_CUBIC,
INTERPOLATION_LINEAR_ANGLE,
INTERPOLATION_CUBIC_ANGLE,
};
enum UpdateMode : uint8_t {
UPDATE_CONTINUOUS,
UPDATE_DISCRETE,
UPDATE_CAPTURE,
};
enum LoopMode : uint8_t {
LOOP_NONE,
LOOP_LINEAR,
LOOP_PINGPONG,
};
// LoopedFlag is used in Animataion to "process the keys at both ends correct".
enum LoopedFlag : uint8_t {
LOOPED_FLAG_NONE,
LOOPED_FLAG_END,
LOOPED_FLAG_START,
};
enum FindMode : uint8_t {
FIND_MODE_NEAREST,
FIND_MODE_APPROX,
FIND_MODE_EXACT,
};
#ifdef TOOLS_ENABLED
enum HandleMode {
HANDLE_MODE_FREE,
HANDLE_MODE_LINEAR,
HANDLE_MODE_BALANCED,
HANDLE_MODE_MIRRORED,
};
enum HandleSetMode {
HANDLE_SET_MODE_NONE,
HANDLE_SET_MODE_RESET,
HANDLE_SET_MODE_AUTO,
};
#endif // TOOLS_ENABLED
struct Track {
TrackType type = TrackType::TYPE_ANIMATION;
InterpolationType interpolation = INTERPOLATION_LINEAR;
bool loop_wrap = true;
NodePath path; // Path to something.
TypeHash thash = 0; // Hash by Path + SubPath + TrackType.
bool imported = false;
bool enabled = true;
virtual ~Track() {}
};
private:
struct Key {
real_t transition = 1.0;
double time = 0.0; // Time in secs.
};
// Transform key holds either Vector3 or Quaternion.
template <typename T>
struct TKey : public Key {
T value;
};
const int32_t POSITION_TRACK_SIZE = 5;
const int32_t ROTATION_TRACK_SIZE = 6;
const int32_t SCALE_TRACK_SIZE = 5;
const int32_t BLEND_SHAPE_TRACK_SIZE = 3;
/* POSITION TRACK */
struct PositionTrack : public Track {
Vector<TKey<Vector3>> positions;
int32_t compressed_track = -1;
PositionTrack() { type = TYPE_POSITION_3D; }
};
/* ROTATION TRACK */
struct RotationTrack : public Track {
Vector<TKey<Quaternion>> rotations;
int32_t compressed_track = -1;
RotationTrack() { type = TYPE_ROTATION_3D; }
};
/* SCALE TRACK */
struct ScaleTrack : public Track {
Vector<TKey<Vector3>> scales;
int32_t compressed_track = -1;
ScaleTrack() { type = TYPE_SCALE_3D; }
};
/* BLEND SHAPE TRACK */
struct BlendShapeTrack : public Track {
Vector<TKey<float>> blend_shapes;
int32_t compressed_track = -1;
BlendShapeTrack() { type = TYPE_BLEND_SHAPE; }
};
/* PROPERTY VALUE TRACK */
struct ValueTrack : public Track {
UpdateMode update_mode = UPDATE_CONTINUOUS;
bool update_on_seek = false;
Vector<TKey<Variant>> values;
ValueTrack() {
type = TYPE_VALUE;
}
};
/* METHOD TRACK */
struct MethodKey : public Key {
StringName method;
Vector<Variant> params;
};
struct MethodTrack : public Track {
Vector<MethodKey> methods;
MethodTrack() { type = TYPE_METHOD; }
};
/* BEZIER TRACK */
struct BezierKey {
Vector2 in_handle; // Relative (x always <0)
Vector2 out_handle; // Relative (x always >0)
real_t value = 0.0;
#ifdef TOOLS_ENABLED
HandleMode handle_mode = HANDLE_MODE_FREE;
#endif // TOOLS_ENABLED
};
struct BezierTrack : public Track {
Vector<TKey<BezierKey>> values;
BezierTrack() {
type = TYPE_BEZIER;
}
};
/* AUDIO TRACK */
struct AudioKey {
Ref<Resource> stream;
real_t start_offset = 0.0; // Offset from start.
real_t end_offset = 0.0; // Offset from end, if 0 then full length or infinite.
AudioKey() {
}
};
struct AudioTrack : public Track {
Vector<TKey<AudioKey>> values;
bool use_blend = true;
AudioTrack() {
type = TYPE_AUDIO;
}
};
/* ANIMATION TRACK */
struct AnimationTrack : public Track {
Vector<TKey<StringName>> values;
AnimationTrack() {
type = TYPE_ANIMATION;
}
};
/* Marker */
struct MarkerKey {
double time;
StringName name;
MarkerKey(double p_time, const StringName &p_name) :
time(p_time), name(p_name) {}
MarkerKey() = default;
};
Vector<MarkerKey> marker_names; // time -> name
HashMap<StringName, double> marker_times; // name -> time
HashMap<StringName, Color> marker_colors; // name -> color
Vector<Track *> tracks;
template <typename T, typename V>
int _insert(double p_time, T &p_keys, const V &p_value);
int _marker_insert(double p_time, Vector<MarkerKey> &p_keys, const MarkerKey &p_value);
template <typename K>
inline int _find(const Vector<K> &p_keys, double p_time, bool p_backward = false, bool p_limit = false) const;
_FORCE_INLINE_ Vector3 _interpolate(const Vector3 &p_a, const Vector3 &p_b, real_t p_c) const;
_FORCE_INLINE_ Quaternion _interpolate(const Quaternion &p_a, const Quaternion &p_b, real_t p_c) const;
_FORCE_INLINE_ Variant _interpolate(const Variant &p_a, const Variant &p_b, real_t p_c) const;
_FORCE_INLINE_ real_t _interpolate(const real_t &p_a, const real_t &p_b, real_t p_c) const;
_FORCE_INLINE_ Variant _interpolate_angle(const Variant &p_a, const Variant &p_b, real_t p_c) const;
_FORCE_INLINE_ Vector3 _cubic_interpolate_in_time(const Vector3 &p_pre_a, const Vector3 &p_a, const Vector3 &p_b, const Vector3 &p_post_b, real_t p_c, real_t p_pre_a_t, real_t p_b_t, real_t p_post_b_t) const;
_FORCE_INLINE_ Quaternion _cubic_interpolate_in_time(const Quaternion &p_pre_a, const Quaternion &p_a, const Quaternion &p_b, const Quaternion &p_post_b, real_t p_c, real_t p_pre_a_t, real_t p_b_t, real_t p_post_b_t) const;
_FORCE_INLINE_ Variant _cubic_interpolate_in_time(const Variant &p_pre_a, const Variant &p_a, const Variant &p_b, const Variant &p_post_b, real_t p_c, real_t p_pre_a_t, real_t p_b_t, real_t p_post_b_t) const;
_FORCE_INLINE_ real_t _cubic_interpolate_in_time(const real_t &p_pre_a, const real_t &p_a, const real_t &p_b, const real_t &p_post_b, real_t p_c, real_t p_pre_a_t, real_t p_b_t, real_t p_post_b_t) const;
_FORCE_INLINE_ Variant _cubic_interpolate_angle_in_time(const Variant &p_pre_a, const Variant &p_a, const Variant &p_b, const Variant &p_post_b, real_t p_c, real_t p_pre_a_t, real_t p_b_t, real_t p_post_b_t) const;
template <typename T>
_FORCE_INLINE_ T _interpolate(const Vector<TKey<T>> &p_keys, double p_time, InterpolationType p_interp, bool p_loop_wrap, bool *p_ok, bool p_backward = false) const;
template <typename T>
_FORCE_INLINE_ void _track_get_key_indices_in_range(const Vector<T> &p_array, double from_time, double to_time, List<int> *p_indices, bool p_is_backward) const;
double length = 1.0;
real_t step = DEFAULT_STEP;
LoopMode loop_mode = LOOP_NONE;
bool capture_included = false;
void _check_capture_included();
void _track_update_hash(int p_track);
/* Animation compression page format (version 1):
*
* Animation uses bitwidth based compression separated into small pages. The intention is that pages fit easily in the cache, so decoding is cache efficient.
* The page-based nature also makes future animation streaming from disk possible.
*
* Actual format:
*
* num_compressed_tracks = bounds.size()
* header : (x num_compressed_tracks)
* -------
* timeline_keys_offset : uint32_t - offset to time keys
* timeline_size : uint32_t - amount of time keys
* data_keys_offset : uint32_t offset to key data
*
* time key (uint32_t):
* ------------------
* frame : bits 0-15 - time offset of key, computed as: page.time_offset + frame * (1.0/fps)
* data_key_offset : bits 16-27 - offset to key data, computed as: data_keys_offset * 4 + data_key_offset
* data_key_count : bits 28-31 - amount of data keys pointed to, computed as: data_key_count+1 (max 16)
*
* data key:
* ---------
* X / Blend Shape : uint16_t - X coordinate of XYZ vector key, or Blend Shape value. If Blend shape, Y and Z are not present and can be ignored.
* Y : uint16_t
* Z : uint16_t
* If data_key_count+1 > 1 (if more than 1 key is stored):
* data_bitwidth : uint16_t - This is only present if data_key_count > 1. Contains delta bitwidth information.
* X / Blend Shape delta bitwidth: bits 0-3 -
* if 0, nothing is present for X (use the first key-value for subsequent keys),
* else assume the number of bits present for each element (+ 1 for sign). Assumed always 16 bits, delta max signed 15 bits, with underflow and overflow supported.
* Y delta bitwidth : bits 4-7
* Z delta bitwidth : bits 8-11
* FRAME delta bitwidth : 12-15 bits - always present (obviously), actual bitwidth is FRAME+1
* Data key is 4 bytes long for Blend Shapes, 8 bytes long for pos/rot/scale.
*
* delta keys:
* -----------
* Compressed format is packed in the following format after the data key, containing delta keys one after the next in a tightly bit packed fashion.
* FRAME bits -> X / Blend Shape Bits (if bitwidth > 0) -> Y Bits (if not Blend Shape and Y Bitwidth > 0) -> Z Bits (if not Blend Shape and Z Bitwidth > 0)
*
* data key format:
* ----------------
* Decoding keys means starting from the base key and going key by key applying deltas until the proper position is reached needed for interpolation.
* Resulting values are uint32_t
* data for X / Blend Shape, Y and Z must be normalized first: unorm = float(data) / 65535.0
* **Blend Shape**: (unorm * 2.0 - 1.0) * Compression::BLEND_SHAPE_RANGE
* **Pos/Scale**: unorm_vec3 * bounds[track].size + bounds[track].position
* **Rotation**: Quaternion(Vector3::octahedron_decode(unorm_vec3.xy),unorm_vec3.z * Math::PI * 2.0)
* **Frame**: page.time_offset + frame * (1.0/fps)
*/
struct Compression {
enum {
MAX_DATA_TRACK_SIZE = 16384,
BLEND_SHAPE_RANGE = 8, // -8.0 to 8.0.
FORMAT_VERSION = 1
};
struct Page {
Vector<uint8_t> data;
double time_offset;
};
uint32_t fps = 120;
LocalVector<Page> pages;
LocalVector<AABB> bounds; // Used by position and scale tracks (which contain index to track and index to bounds).
bool enabled = false;
} compression;
Vector3i _compress_key(uint32_t p_track, const AABB &p_bounds, int32_t p_key = -1, float p_time = 0.0);
bool _rotation_interpolate_compressed(uint32_t p_compressed_track, double p_time, Quaternion &r_ret) const;
bool _pos_scale_interpolate_compressed(uint32_t p_compressed_track, double p_time, Vector3 &r_ret) const;
bool _blend_shape_interpolate_compressed(uint32_t p_compressed_track, double p_time, float &r_ret) const;
template <uint32_t COMPONENTS>
bool _fetch_compressed(uint32_t p_compressed_track, double p_time, Vector3i &r_current_value, double &r_current_time, Vector3i &r_next_value, double &r_next_time, uint32_t *key_index = nullptr) const;
template <uint32_t COMPONENTS>
bool _fetch_compressed_by_index(uint32_t p_compressed_track, int p_index, Vector3i &r_value, double &r_time) const;
int _get_compressed_key_count(uint32_t p_compressed_track) const;
template <uint32_t COMPONENTS>
void _get_compressed_key_indices_in_range(uint32_t p_compressed_track, double p_time, double p_delta, List<int> *r_indices) const;
_FORCE_INLINE_ Quaternion _uncompress_quaternion(const Vector3i &p_value) const;
_FORCE_INLINE_ Vector3 _uncompress_pos_scale(uint32_t p_compressed_track, const Vector3i &p_value) const;
_FORCE_INLINE_ float _uncompress_blend_shape(const Vector3i &p_value) const;
// bind helpers
private:
bool _float_track_optimize_key(const TKey<float> t0, const TKey<float> t1, const TKey<float> t2, real_t p_allowed_velocity_err, real_t p_allowed_precision_error, bool p_is_nearest);
bool _vector2_track_optimize_key(const TKey<Vector2> t0, const TKey<Vector2> t1, const TKey<Vector2> t2, real_t p_allowed_velocity_err, real_t p_allowed_angular_error, real_t p_allowed_precision_error, bool p_is_nearest);
bool _vector3_track_optimize_key(const TKey<Vector3> t0, const TKey<Vector3> t1, const TKey<Vector3> t2, real_t p_allowed_velocity_err, real_t p_allowed_angular_error, real_t p_allowed_precision_error, bool p_is_nearest);
bool _quaternion_track_optimize_key(const TKey<Quaternion> t0, const TKey<Quaternion> t1, const TKey<Quaternion> t2, real_t p_allowed_velocity_err, real_t p_allowed_angular_error, real_t p_allowed_precision_error, bool p_is_nearest);
void _position_track_optimize(int p_idx, real_t p_allowed_velocity_err, real_t p_allowed_angular_err, real_t p_allowed_precision_error);
void _rotation_track_optimize(int p_idx, real_t p_allowed_velocity_err, real_t p_allowed_angular_error, real_t p_allowed_precision_error);
void _scale_track_optimize(int p_idx, real_t p_allowed_velocity_err, real_t p_allowed_angular_err, real_t p_allowed_precision_error);
void _blend_shape_track_optimize(int p_idx, real_t p_allowed_velocity_err, real_t p_allowed_precision_error);
void _value_track_optimize(int p_idx, real_t p_allowed_velocity_err, real_t p_allowed_angular_err, real_t p_allowed_precision_error);
protected:
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
void _get_property_list(List<PropertyInfo> *p_list) const;
virtual void reset_state() override;
static void _bind_methods();
static bool inform_variant_array(int &r_min, int &r_max); // Returns true if max and min are swapped.
#ifndef DISABLE_DEPRECATED
Vector3 _position_track_interpolate_bind_compat_86629(int p_track, double p_time) const;
Quaternion _rotation_track_interpolate_bind_compat_86629(int p_track, double p_time) const;
Vector3 _scale_track_interpolate_bind_compat_86629(int p_track, double p_time) const;
float _blend_shape_track_interpolate_bind_compat_86629(int p_track, double p_time) const;
Variant _value_track_interpolate_bind_compat_86629(int p_track, double p_time) const;
int _track_find_key_bind_compat_92861(int p_track, double p_time, FindMode p_find_mode = FIND_MODE_NEAREST) const;
static void _bind_compatibility_methods();
#endif // DISABLE_DEPRECATED
public:
int add_track(TrackType p_type, int p_at_pos = -1);
void remove_track(int p_track);
_FORCE_INLINE_ const Vector<Track *> get_tracks() {
return tracks;
}
bool is_capture_included() const;
int get_track_count() const;
TrackType track_get_type(int p_track) const;
void track_set_path(int p_track, const NodePath &p_path);
NodePath track_get_path(int p_track) const;
int find_track(const NodePath &p_path, const TrackType p_type) const;
TypeHash track_get_type_hash(int p_track) const;
void track_move_up(int p_track);
void track_move_down(int p_track);
void track_move_to(int p_track, int p_to_index);
void track_swap(int p_track, int p_with_track);
void track_set_imported(int p_track, bool p_imported);
bool track_is_imported(int p_track) const;
void track_set_enabled(int p_track, bool p_enabled);
bool track_is_enabled(int p_track) const;
int track_insert_key(int p_track, double p_time, const Variant &p_key, real_t p_transition = 1);
void track_set_key_transition(int p_track, int p_key_idx, real_t p_transition);
void track_set_key_value(int p_track, int p_key_idx, const Variant &p_value);
void track_set_key_time(int p_track, int p_key_idx, double p_time);
int track_find_key(int p_track, double p_time, FindMode p_find_mode = FIND_MODE_NEAREST, bool p_limit = false, bool p_backward = false) const;
void track_remove_key(int p_track, int p_idx);
void track_remove_key_at_time(int p_track, double p_time);
int track_get_key_count(int p_track) const;
Variant track_get_key_value(int p_track, int p_key_idx) const;
double track_get_key_time(int p_track, int p_key_idx) const;
real_t track_get_key_transition(int p_track, int p_key_idx) const;
bool track_is_compressed(int p_track) const;
int position_track_insert_key(int p_track, double p_time, const Vector3 &p_position);
Error position_track_get_key(int p_track, int p_key, Vector3 *r_position) const;
Error try_position_track_interpolate(int p_track, double p_time, Vector3 *r_interpolation, bool p_backward = false) const;
Vector3 position_track_interpolate(int p_track, double p_time, bool p_backward = false) const;
int rotation_track_insert_key(int p_track, double p_time, const Quaternion &p_rotation);
Error rotation_track_get_key(int p_track, int p_key, Quaternion *r_rotation) const;
Error try_rotation_track_interpolate(int p_track, double p_time, Quaternion *r_interpolation, bool p_backward = false) const;
Quaternion rotation_track_interpolate(int p_track, double p_time, bool p_backward = false) const;
int scale_track_insert_key(int p_track, double p_time, const Vector3 &p_scale);
Error scale_track_get_key(int p_track, int p_key, Vector3 *r_scale) const;
Error try_scale_track_interpolate(int p_track, double p_time, Vector3 *r_interpolation, bool p_backward = false) const;
Vector3 scale_track_interpolate(int p_track, double p_time, bool p_backward = false) const;
int blend_shape_track_insert_key(int p_track, double p_time, float p_blend);
Error blend_shape_track_get_key(int p_track, int p_key, float *r_blend) const;
Error try_blend_shape_track_interpolate(int p_track, double p_time, float *r_blend, bool p_backward = false) const;
float blend_shape_track_interpolate(int p_track, double p_time, bool p_backward = false) const;
void track_set_interpolation_type(int p_track, InterpolationType p_interp);
InterpolationType track_get_interpolation_type(int p_track) const;
Array make_default_bezier_key(float p_value);
int bezier_track_insert_key(int p_track, double p_time, real_t p_value, const Vector2 &p_in_handle, const Vector2 &p_out_handle);
void bezier_track_set_key_value(int p_track, int p_index, real_t p_value);
void bezier_track_set_key_in_handle(int p_track, int p_index, const Vector2 &p_handle, real_t p_balanced_value_time_ratio = 1.0);
void bezier_track_set_key_out_handle(int p_track, int p_index, const Vector2 &p_handle, real_t p_balanced_value_time_ratio = 1.0);
real_t bezier_track_get_key_value(int p_track, int p_index) const;
Vector2 bezier_track_get_key_in_handle(int p_track, int p_index) const;
Vector2 bezier_track_get_key_out_handle(int p_track, int p_index) const;
#ifdef TOOLS_ENABLED
void bezier_track_set_key_handle_mode(int p_track, int p_index, HandleMode p_mode, HandleSetMode p_set_mode = HANDLE_SET_MODE_NONE);
HandleMode bezier_track_get_key_handle_mode(int p_track, int p_index) const;
bool bezier_track_calculate_handles(int p_track, int p_index, HandleMode p_mode, HandleSetMode p_set_mode, Vector2 *r_in_handle, Vector2 *r_out_handle);
bool bezier_track_calculate_handles(float p_time, float p_prev_time, float p_prev_value, float p_next_time, float p_next_value, HandleMode p_mode, HandleSetMode p_set_mode, Vector2 *r_in_handle, Vector2 *r_out_handle);
#endif // TOOLS_ENABLED
real_t bezier_track_interpolate(int p_track, double p_time) const;
int audio_track_insert_key(int p_track, double p_time, const Ref<Resource> &p_stream, real_t p_start_offset = 0, real_t p_end_offset = 0);
void audio_track_set_key_stream(int p_track, int p_key, const Ref<Resource> &p_stream);
void audio_track_set_key_start_offset(int p_track, int p_key, real_t p_offset);
void audio_track_set_key_end_offset(int p_track, int p_key, real_t p_offset);
Ref<Resource> audio_track_get_key_stream(int p_track, int p_key) const;
real_t audio_track_get_key_start_offset(int p_track, int p_key) const;
real_t audio_track_get_key_end_offset(int p_track, int p_key) const;
void audio_track_set_use_blend(int p_track, bool p_enable);
bool audio_track_is_use_blend(int p_track) const;
int animation_track_insert_key(int p_track, double p_time, const StringName &p_animation);
void animation_track_set_key_animation(int p_track, int p_key, const StringName &p_animation);
StringName animation_track_get_key_animation(int p_track, int p_key) const;
void track_set_interpolation_loop_wrap(int p_track, bool p_enable);
bool track_get_interpolation_loop_wrap(int p_track) const;
Variant value_track_interpolate(int p_track, double p_time, bool p_backward = false) const;
void value_track_set_update_mode(int p_track, UpdateMode p_mode);
UpdateMode value_track_get_update_mode(int p_track) const;
Vector<Variant> method_track_get_params(int p_track, int p_key_idx) const;
StringName method_track_get_name(int p_track, int p_key_idx) const;
void copy_track(int p_track, Ref<Animation> p_to_animation);
void track_get_key_indices_in_range(int p_track, double p_time, double p_delta, List<int> *p_indices, Animation::LoopedFlag p_looped_flag = Animation::LOOPED_FLAG_NONE) const;
void add_marker(const StringName &p_name, double p_time);
void remove_marker(const StringName &p_name);
bool has_marker(const StringName &p_name) const;
StringName get_marker_at_time(double p_time) const;
StringName get_next_marker(double p_time) const;
StringName get_prev_marker(double p_time) const;
double get_marker_time(const StringName &p_time) const;
PackedStringArray get_marker_names() const;
Color get_marker_color(const StringName &p_name) const;
void set_marker_color(const StringName &p_name, const Color &p_color);
void set_length(real_t p_length);
real_t get_length() const;
void set_loop_mode(LoopMode p_loop_mode);
LoopMode get_loop_mode() const;
void set_step(real_t p_step);
real_t get_step() const;
void clear();
void optimize(real_t p_allowed_velocity_err = 0.01, real_t p_allowed_angular_err = 0.01, int p_precision = 3);
void compress(uint32_t p_page_size = 8192, uint32_t p_fps = 120, float p_split_tolerance = 4.0); // 4.0 seems to be the split tolerance sweet spot from many tests.
// Helper functions for Variant.
static bool is_variant_interpolatable(const Variant p_value);
static bool validate_type_match(const Variant &p_from, Variant &r_to);
static Variant cast_to_blendwise(const Variant p_value);
static Variant cast_from_blendwise(const Variant p_value, const Variant::Type p_type);
static Variant string_to_array(const Variant p_value);
static Variant array_to_string(const Variant p_value);
static Variant add_variant(const Variant &a, const Variant &b);
static Variant subtract_variant(const Variant &a, const Variant &b);
static Variant blend_variant(const Variant &a, const Variant &b, float c);
static Variant interpolate_variant(const Variant &a, const Variant &b, float c, bool p_snap_array_element = false);
static Variant cubic_interpolate_in_time_variant(const Variant &pre_a, const Variant &a, const Variant &b, const Variant &post_b, float c, real_t p_pre_a_t, real_t p_b_t, real_t p_post_b_t, bool p_snap_array_element = false);
static bool is_less_or_equal_approx(double a, double b) {
return a < b || Math::is_equal_approx(a, b);
}
static bool is_less_approx(double a, double b) {
return a < b && !Math::is_equal_approx(a, b);
}
static bool is_greater_or_equal_approx(double a, double b) {
return a > b || Math::is_equal_approx(a, b);
}
static bool is_greater_approx(double a, double b) {
return a > b && !Math::is_equal_approx(a, b);
}
static TrackType get_cache_type(TrackType p_type);
Animation();
~Animation();
};
VARIANT_ENUM_CAST(Animation::TrackType);
VARIANT_ENUM_CAST(Animation::InterpolationType);
VARIANT_ENUM_CAST(Animation::UpdateMode);
VARIANT_ENUM_CAST(Animation::LoopMode);
VARIANT_ENUM_CAST(Animation::LoopedFlag);
VARIANT_ENUM_CAST(Animation::FindMode);
#ifdef TOOLS_ENABLED
VARIANT_ENUM_CAST(Animation::HandleMode);
VARIANT_ENUM_CAST(Animation::HandleSetMode);
#endif // TOOLS_ENABLED

View File

@@ -0,0 +1,179 @@
/**************************************************************************/
/* animation_library.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 "animation_library.h"
#include "scene/scene_string_names.h"
bool AnimationLibrary::is_valid_animation_name(const String &p_name) {
return !(p_name.is_empty() || p_name.contains_char('/') || p_name.contains_char(':') || p_name.contains_char(',') || p_name.contains_char('['));
}
bool AnimationLibrary::is_valid_library_name(const String &p_name) {
return !(p_name.contains_char('/') || p_name.contains_char(':') || p_name.contains_char(',') || p_name.contains_char('['));
}
String AnimationLibrary::validate_library_name(const String &p_name) {
return p_name.replace_chars("/:,[", '_');
}
Error AnimationLibrary::add_animation(const StringName &p_name, const Ref<Animation> &p_animation) {
ERR_FAIL_COND_V_MSG(!is_valid_animation_name(p_name), ERR_INVALID_PARAMETER, "Invalid animation name: '" + String(p_name) + "'.");
ERR_FAIL_COND_V(p_animation.is_null(), ERR_INVALID_PARAMETER);
if (animations.has(p_name)) {
animations.get(p_name)->disconnect_changed(callable_mp(this, &AnimationLibrary::_animation_changed));
animations.erase(p_name);
emit_signal(SNAME("animation_removed"), p_name);
}
animations.insert(p_name, p_animation);
animations.get(p_name)->connect_changed(callable_mp(this, &AnimationLibrary::_animation_changed).bind(p_name));
emit_signal(SNAME("animation_added"), p_name);
notify_property_list_changed();
return OK;
}
void AnimationLibrary::remove_animation(const StringName &p_name) {
ERR_FAIL_COND_MSG(!animations.has(p_name), vformat("Animation not found: %s.", p_name));
animations.get(p_name)->disconnect_changed(callable_mp(this, &AnimationLibrary::_animation_changed));
animations.erase(p_name);
emit_signal(SNAME("animation_removed"), p_name);
notify_property_list_changed();
}
void AnimationLibrary::rename_animation(const StringName &p_name, const StringName &p_new_name) {
ERR_FAIL_COND_MSG(!animations.has(p_name), vformat("Animation not found: %s.", p_name));
ERR_FAIL_COND_MSG(!is_valid_animation_name(p_new_name), "Invalid animation name: '" + String(p_new_name) + "'.");
ERR_FAIL_COND_MSG(animations.has(p_new_name), vformat("Animation name \"%s\" already exists in library.", p_new_name));
animations.get(p_name)->disconnect_changed(callable_mp(this, &AnimationLibrary::_animation_changed));
animations.get(p_name)->connect_changed(callable_mp(this, &AnimationLibrary::_animation_changed).bind(p_new_name));
animations.insert(p_new_name, animations[p_name]);
animations.erase(p_name);
emit_signal(SNAME("animation_renamed"), p_name, p_new_name);
}
bool AnimationLibrary::has_animation(const StringName &p_name) const {
return animations.has(p_name);
}
Ref<Animation> AnimationLibrary::get_animation(const StringName &p_name) const {
ERR_FAIL_COND_V_MSG(!animations.has(p_name), Ref<Animation>(), vformat("Animation not found: \"%s\".", p_name));
return animations[p_name];
}
TypedArray<StringName> AnimationLibrary::_get_animation_list() const {
TypedArray<StringName> ret;
List<StringName> names;
get_animation_list(&names);
for (const StringName &K : names) {
ret.push_back(K);
}
return ret;
}
void AnimationLibrary::_animation_changed(const StringName &p_name) {
emit_signal(SceneStringName(animation_changed), p_name);
}
void AnimationLibrary::get_animation_list(List<StringName> *p_animations) const {
List<StringName> anims;
for (const KeyValue<StringName, Ref<Animation>> &E : animations) {
anims.push_back(E.key);
}
anims.sort_custom<StringName::AlphCompare>();
for (const StringName &E : anims) {
p_animations->push_back(E);
}
}
int AnimationLibrary::get_animation_list_size() const {
return animations.size();
}
void AnimationLibrary::_set_data(const Dictionary &p_data) {
for (KeyValue<StringName, Ref<Animation>> &K : animations) {
K.value->disconnect_changed(callable_mp(this, &AnimationLibrary::_animation_changed));
}
animations.clear();
for (const KeyValue<Variant, Variant> &kv : p_data) {
add_animation(kv.key, kv.value);
}
}
Dictionary AnimationLibrary::_get_data() const {
Dictionary ret;
for (const KeyValue<StringName, Ref<Animation>> &K : animations) {
ret[K.key] = K.value;
}
return ret;
}
#ifdef TOOLS_ENABLED
void AnimationLibrary::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const {
const String pf = p_function;
if (p_idx == 0 && (pf == "get_animation" || pf == "has_animation" || pf == "rename_animation" || pf == "remove_animation")) {
List<StringName> names;
get_animation_list(&names);
for (const StringName &E : names) {
r_options->push_back(E.operator String().quote());
}
}
Resource::get_argument_options(p_function, p_idx, r_options);
}
#endif
void AnimationLibrary::_bind_methods() {
ClassDB::bind_method(D_METHOD("add_animation", "name", "animation"), &AnimationLibrary::add_animation);
ClassDB::bind_method(D_METHOD("remove_animation", "name"), &AnimationLibrary::remove_animation);
ClassDB::bind_method(D_METHOD("rename_animation", "name", "newname"), &AnimationLibrary::rename_animation);
ClassDB::bind_method(D_METHOD("has_animation", "name"), &AnimationLibrary::has_animation);
ClassDB::bind_method(D_METHOD("get_animation", "name"), &AnimationLibrary::get_animation);
ClassDB::bind_method(D_METHOD("get_animation_list"), &AnimationLibrary::_get_animation_list);
ClassDB::bind_method(D_METHOD("get_animation_list_size"), &AnimationLibrary::get_animation_list_size);
ClassDB::bind_method(D_METHOD("_set_data", "data"), &AnimationLibrary::_set_data);
ClassDB::bind_method(D_METHOD("_get_data"), &AnimationLibrary::_get_data);
ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_data", "_get_data");
ADD_SIGNAL(MethodInfo("animation_added", PropertyInfo(Variant::STRING_NAME, "name")));
ADD_SIGNAL(MethodInfo("animation_removed", PropertyInfo(Variant::STRING_NAME, "name")));
ADD_SIGNAL(MethodInfo("animation_renamed", PropertyInfo(Variant::STRING_NAME, "name"), PropertyInfo(Variant::STRING_NAME, "to_name")));
ADD_SIGNAL(MethodInfo("animation_changed", PropertyInfo(Variant::STRING_NAME, "name")));
}
AnimationLibrary::AnimationLibrary() {
}

View File

@@ -0,0 +1,70 @@
/**************************************************************************/
/* animation_library.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "core/variant/typed_array.h"
#include "scene/resources/animation.h"
class AnimationLibrary : public Resource {
GDCLASS(AnimationLibrary, Resource)
void _set_data(const Dictionary &p_data);
Dictionary _get_data() const;
TypedArray<StringName> _get_animation_list() const;
void _animation_changed(const StringName &p_name);
friend class AnimationMixer; // For faster access.
HashMap<StringName, Ref<Animation>> animations;
protected:
static void _bind_methods();
public:
static bool is_valid_animation_name(const String &p_name);
static bool is_valid_library_name(const String &p_name);
static String validate_library_name(const String &p_name);
Error add_animation(const StringName &p_name, const Ref<Animation> &p_animation);
void remove_animation(const StringName &p_name);
void rename_animation(const StringName &p_name, const StringName &p_new_name);
bool has_animation(const StringName &p_name) const;
Ref<Animation> get_animation(const StringName &p_name) const;
void get_animation_list(List<StringName> *p_animations) const;
int get_animation_list_size() const;
#ifdef TOOLS_ENABLED
virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override;
#endif
AnimationLibrary();
};

View File

@@ -0,0 +1,255 @@
/**************************************************************************/
/* atlas_texture.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 "atlas_texture.h"
int AtlasTexture::get_width() const {
if (region.size.width == 0) {
if (atlas.is_valid()) {
return atlas->get_width();
}
return 1;
} else {
return region.size.width + margin.size.width;
}
}
int AtlasTexture::get_height() const {
if (region.size.height == 0) {
if (atlas.is_valid()) {
return atlas->get_height();
}
return 1;
} else {
return region.size.height + margin.size.height;
}
}
RID AtlasTexture::get_rid() const {
if (atlas.is_valid()) {
return atlas->get_rid();
}
return RID();
}
bool AtlasTexture::has_alpha() const {
if (atlas.is_valid()) {
return atlas->has_alpha();
}
return false;
}
void AtlasTexture::set_atlas(const Ref<Texture2D> &p_atlas) {
ERR_FAIL_COND(p_atlas == this);
if (atlas == p_atlas) {
return;
}
// Support recursive AtlasTextures.
if (Ref<AtlasTexture>(atlas).is_valid()) {
atlas->disconnect_changed(callable_mp((Resource *)this, &AtlasTexture::emit_changed));
}
atlas = p_atlas;
if (Ref<AtlasTexture>(atlas).is_valid()) {
atlas->connect_changed(callable_mp((Resource *)this, &AtlasTexture::emit_changed));
}
emit_changed();
}
Ref<Texture2D> AtlasTexture::get_atlas() const {
return atlas;
}
void AtlasTexture::set_region(const Rect2 &p_region) {
if (region == p_region) {
return;
}
region = p_region;
emit_changed();
}
Rect2 AtlasTexture::get_region() const {
return region;
}
void AtlasTexture::set_margin(const Rect2 &p_margin) {
if (margin == p_margin) {
return;
}
margin = p_margin;
emit_changed();
}
Rect2 AtlasTexture::get_margin() const {
return margin;
}
void AtlasTexture::set_filter_clip(const bool p_enable) {
filter_clip = p_enable;
emit_changed();
}
bool AtlasTexture::has_filter_clip() const {
return filter_clip;
}
Rect2 AtlasTexture::_get_region_rect() const {
Rect2 rc = region;
if (atlas.is_valid()) {
if (rc.size.width == 0) {
rc.size.width = atlas->get_width();
}
if (rc.size.height == 0) {
rc.size.height = atlas->get_height();
}
}
return rc;
}
void AtlasTexture::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_atlas", "atlas"), &AtlasTexture::set_atlas);
ClassDB::bind_method(D_METHOD("get_atlas"), &AtlasTexture::get_atlas);
ClassDB::bind_method(D_METHOD("set_region", "region"), &AtlasTexture::set_region);
ClassDB::bind_method(D_METHOD("get_region"), &AtlasTexture::get_region);
ClassDB::bind_method(D_METHOD("set_margin", "margin"), &AtlasTexture::set_margin);
ClassDB::bind_method(D_METHOD("get_margin"), &AtlasTexture::get_margin);
ClassDB::bind_method(D_METHOD("set_filter_clip", "enable"), &AtlasTexture::set_filter_clip);
ClassDB::bind_method(D_METHOD("has_filter_clip"), &AtlasTexture::has_filter_clip);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "atlas", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_atlas", "get_atlas");
ADD_PROPERTY(PropertyInfo(Variant::RECT2, "region", PROPERTY_HINT_NONE, "suffix:px"), "set_region", "get_region");
ADD_PROPERTY(PropertyInfo(Variant::RECT2, "margin", PROPERTY_HINT_NONE, "suffix:px"), "set_margin", "get_margin");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "filter_clip"), "set_filter_clip", "has_filter_clip");
}
void AtlasTexture::draw(RID p_canvas_item, const Point2 &p_pos, const Color &p_modulate, bool p_transpose) const {
if (atlas.is_null()) {
return;
}
const Rect2 rc = _get_region_rect();
atlas->draw_rect_region(p_canvas_item, Rect2(p_pos + margin.position, rc.size), rc, p_modulate, p_transpose, filter_clip);
}
void AtlasTexture::draw_rect(RID p_canvas_item, const Rect2 &p_rect, bool p_tile, const Color &p_modulate, bool p_transpose) const {
if (atlas.is_null()) {
return;
}
Rect2 src_rect = Rect2(0, 0, get_width(), get_height());
Rect2 dr;
Rect2 src_c;
if (get_rect_region(p_rect, src_rect, dr, src_c)) {
atlas->draw_rect_region(p_canvas_item, dr, src_c, p_modulate, p_transpose, filter_clip);
}
}
void AtlasTexture::draw_rect_region(RID p_canvas_item, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate, bool p_transpose, bool p_clip_uv) const {
// This might not necessarily work well if using a rect, needs to be fixed properly.
if (atlas.is_null()) {
return;
}
Rect2 dr;
Rect2 src_c;
if (get_rect_region(p_rect, p_src_rect, dr, src_c)) {
atlas->draw_rect_region(p_canvas_item, dr, src_c, p_modulate, p_transpose, filter_clip);
}
}
bool AtlasTexture::get_rect_region(const Rect2 &p_rect, const Rect2 &p_src_rect, Rect2 &r_rect, Rect2 &r_src_rect) const {
if (atlas.is_null()) {
return false;
}
Rect2 src = p_src_rect;
if (src.size == Size2()) {
src.size = region.size;
}
if (src.size == Size2() && atlas.is_valid()) {
src.size = atlas->get_size();
}
Vector2 scale = p_rect.size / src.size;
src.position += (region.position - margin.position);
Rect2 src_clipped = _get_region_rect().intersection(src);
if (src_clipped.size == Size2()) {
return false;
}
Vector2 ofs = (src_clipped.position - src.position);
if (scale.x < 0) {
ofs.x += (src_clipped.size.x - src.size.x);
}
if (scale.y < 0) {
ofs.y += (src_clipped.size.y - src.size.y);
}
r_rect = Rect2(p_rect.position + ofs * scale, src_clipped.size * scale);
r_src_rect = src_clipped;
return true;
}
bool AtlasTexture::is_pixel_opaque(int p_x, int p_y) const {
if (atlas.is_null()) {
return true;
}
int x = p_x + region.position.x - margin.position.x;
int y = p_y + region.position.y - margin.position.y;
// Margin edge may outside of atlas.
if (x < 0 || x >= atlas->get_width()) {
return false;
}
if (y < 0 || y >= atlas->get_height()) {
return false;
}
return atlas->is_pixel_opaque(x, y);
}
Ref<Image> AtlasTexture::get_image() const {
if (atlas.is_null()) {
return Ref<Image>();
}
const Ref<Image> &atlas_image = atlas->get_image();
if (atlas_image.is_null()) {
return Ref<Image>();
}
return atlas_image->get_region(_get_region_rect());
}

View File

@@ -0,0 +1,76 @@
/**************************************************************************/
/* atlas_texture.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "scene/resources/texture.h"
class AtlasTexture : public Texture2D {
GDCLASS(AtlasTexture, Texture2D);
RES_BASE_EXTENSION("atlastex");
Rect2 _get_region_rect() const;
protected:
Ref<Texture2D> atlas;
Rect2 region;
Rect2 margin;
bool filter_clip = false;
static void _bind_methods();
public:
virtual int get_width() const override;
virtual int get_height() const override;
virtual RID get_rid() const override;
virtual bool has_alpha() const override;
void set_atlas(const Ref<Texture2D> &p_atlas);
Ref<Texture2D> get_atlas() const;
void set_region(const Rect2 &p_region);
Rect2 get_region() const;
void set_margin(const Rect2 &p_margin);
Rect2 get_margin() const;
void set_filter_clip(const bool p_enable);
bool has_filter_clip() const;
virtual void draw(RID p_canvas_item, const Point2 &p_pos, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false) const override;
virtual void draw_rect(RID p_canvas_item, const Rect2 &p_rect, bool p_tile = false, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false) const override;
virtual void draw_rect_region(RID p_canvas_item, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, bool p_clip_uv = true) const override;
virtual bool get_rect_region(const Rect2 &p_rect, const Rect2 &p_src_rect, Rect2 &r_rect, Rect2 &r_src_rect) const override;
bool is_pixel_opaque(int p_x, int p_y) const override;
virtual Ref<Image> get_image() const override;
};

View File

@@ -0,0 +1,41 @@
/**************************************************************************/
/* audio_stream_polyphonic.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
AudioStreamPlaybackPolyphonic::ID AudioStreamPlaybackPolyphonic::_play_stream_bind_compat_91382(const Ref<AudioStream> &p_stream, float p_from_offset, float p_volume_db, float p_pitch_scale) {
return play_stream(p_stream, p_from_offset, p_volume_db, p_pitch_scale, AudioServer::PlaybackType::PLAYBACK_TYPE_DEFAULT, SceneStringName(Master));
}
void AudioStreamPlaybackPolyphonic::_bind_compatibility_methods() {
ClassDB::bind_compatibility_method(D_METHOD("play_stream", "stream", "from_offset", "volume_db", "pitch_scale"), &AudioStreamPlaybackPolyphonic::_play_stream_bind_compat_91382, DEFVAL(0), DEFVAL(0), DEFVAL(1.0));
}
#endif

View File

@@ -0,0 +1,360 @@
/**************************************************************************/
/* audio_stream_polyphonic.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "audio_stream_polyphonic.h"
#include "audio_stream_polyphonic.compat.inc"
#include "servers/audio_server.h"
constexpr uint64_t ID_MASK = 0xFFFFFFFF;
constexpr uint64_t INDEX_SHIFT = 32;
Ref<AudioStreamPlayback> AudioStreamPolyphonic::instantiate_playback() {
Ref<AudioStreamPlaybackPolyphonic> playback;
playback.instantiate();
playback->streams.resize(polyphony);
return playback;
}
String AudioStreamPolyphonic::get_stream_name() const {
return "AudioStreamPolyphonic";
}
bool AudioStreamPolyphonic::is_monophonic() const {
return true; // This avoids stream players to instantiate more than one of these.
}
void AudioStreamPolyphonic::set_polyphony(int p_voices) {
ERR_FAIL_COND(p_voices < 0 || p_voices > 128);
polyphony = p_voices;
}
int AudioStreamPolyphonic::get_polyphony() const {
return polyphony;
}
void AudioStreamPolyphonic::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_polyphony", "voices"), &AudioStreamPolyphonic::set_polyphony);
ClassDB::bind_method(D_METHOD("get_polyphony"), &AudioStreamPolyphonic::get_polyphony);
ADD_PROPERTY(PropertyInfo(Variant::INT, "polyphony", PROPERTY_HINT_RANGE, "1,128,1"), "set_polyphony", "get_polyphony");
}
AudioStreamPolyphonic::AudioStreamPolyphonic() {
}
////////////////////////
void AudioStreamPlaybackPolyphonic::start(double p_from_pos) {
if (active) {
stop();
}
active = true;
}
void AudioStreamPlaybackPolyphonic::stop() {
if (!active) {
return;
}
bool locked = false;
for (Stream &s : streams) {
if (s.active.is_set()) {
// Need locking because something may still be mixing.
locked = true;
AudioServer::get_singleton()->lock();
}
s.active.clear();
s.finish_request.clear();
s.stream_playback.unref();
s.stream.unref();
}
if (locked) {
AudioServer::get_singleton()->unlock();
}
active = false;
}
bool AudioStreamPlaybackPolyphonic::is_playing() const {
return active;
}
int AudioStreamPlaybackPolyphonic::get_loop_count() const {
return 0;
}
double AudioStreamPlaybackPolyphonic::get_playback_position() const {
return 0;
}
void AudioStreamPlaybackPolyphonic::seek(double p_time) {
// Ignored.
}
void AudioStreamPlaybackPolyphonic::tag_used_streams() {
for (Stream &s : streams) {
if (s.active.is_set()) {
s.stream_playback->tag_used_streams();
}
}
}
int AudioStreamPlaybackPolyphonic::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) {
if (!active) {
return 0;
}
// Pre-clear buffer.
for (int i = 0; i < p_frames; i++) {
p_buffer[i] = AudioFrame(0, 0);
}
for (Stream &s : streams) {
if (!s.active.is_set()) {
continue;
}
if (s.stream_playback->get_is_sample()) {
if (s.finish_request.is_set()) {
s.active.clear();
AudioServer::get_singleton()->stop_sample_playback(s.stream_playback->get_sample_playback());
}
continue;
}
float volume_db = s.volume_db; // Copy because it can be overridden at any time.
float next_volume = Math::db_to_linear(volume_db);
s.prev_volume_db = volume_db;
if (s.finish_request.is_set()) {
if (s.pending_play.is_set()) {
// Did not get the chance to play, was finalized too soon.
s.active.clear();
continue;
}
next_volume = 0;
}
if (s.pending_play.is_set()) {
s.stream_playback->start(s.play_offset);
s.pending_play.clear();
}
float prev_volume = Math::db_to_linear(s.prev_volume_db);
float volume_inc = (next_volume - prev_volume) / float(p_frames);
int todo = p_frames;
int offset = 0;
float volume = prev_volume;
bool stream_done = false;
while (todo) {
int to_mix = MIN(todo, int(INTERNAL_BUFFER_LEN));
int mixed = s.stream_playback->mix(internal_buffer, s.pitch_scale, to_mix);
for (int i = 0; i < to_mix; i++) {
p_buffer[offset + i] += internal_buffer[i] * volume;
volume += volume_inc;
}
if (mixed < to_mix) {
// Stream is done.
s.active.clear();
stream_done = true;
break;
}
todo -= to_mix;
offset += to_mix;
}
if (stream_done) {
continue;
}
if (s.finish_request.is_set()) {
s.active.clear();
}
}
return p_frames;
}
AudioStreamPlaybackPolyphonic::ID AudioStreamPlaybackPolyphonic::play_stream(const Ref<AudioStream> &p_stream, float p_from_offset, float p_volume_db, float p_pitch_scale, AudioServer::PlaybackType p_playback_type, const StringName &p_bus) {
ERR_FAIL_COND_V(p_stream.is_null(), INVALID_ID);
AudioServer::PlaybackType playback_type = p_playback_type == AudioServer::PlaybackType::PLAYBACK_TYPE_DEFAULT
? AudioServer::get_singleton()->get_default_playback_type()
: p_playback_type;
for (uint32_t i = 0; i < streams.size(); i++) {
if (streams[i].active.is_set() && streams[i].stream_playback->get_is_sample()) {
Ref<AudioSamplePlayback> active_sample_playback = streams[i].stream_playback->get_sample_playback();
if (active_sample_playback.is_null() || !AudioServer::get_singleton()->is_sample_playback_active(active_sample_playback)) {
streams[i].active.clear();
}
}
if (!streams[i].active.is_set()) {
// Can use this stream, as it's not active.
streams[i].stream = p_stream;
streams[i].stream_playback = streams[i].stream->instantiate_playback();
streams[i].play_offset = p_from_offset;
streams[i].volume_db = p_volume_db;
streams[i].prev_volume_db = p_volume_db;
streams[i].pitch_scale = p_pitch_scale;
streams[i].id = id_counter++;
streams[i].finish_request.clear();
streams[i].pending_play.set();
streams[i].active.set();
// Sample playback.
if (playback_type == AudioServer::PlaybackType::PLAYBACK_TYPE_SAMPLE && p_stream->can_be_sampled()) {
streams[i].stream_playback->set_is_sample(true);
if (!AudioServer::get_singleton()->is_stream_registered_as_sample(p_stream)) {
AudioServer::get_singleton()->register_stream_as_sample(p_stream);
}
float linear_volume = Math::db_to_linear(p_volume_db);
Ref<AudioSamplePlayback> sp;
sp.instantiate();
sp->stream = streams[i].stream;
sp->offset = p_from_offset;
sp->volume_vector.resize(4);
sp->volume_vector.write[0] = AudioFrame(linear_volume, linear_volume);
sp->volume_vector.write[1] = AudioFrame(linear_volume, /* LFE= */ 1.0f);
sp->volume_vector.write[2] = AudioFrame(linear_volume, linear_volume);
sp->volume_vector.write[3] = AudioFrame(linear_volume, linear_volume);
sp->bus = p_bus;
if (streams[i].stream_playback->get_sample_playback().is_valid()) {
AudioServer::get_singleton()->stop_playback_stream(sp);
}
streams[i].stream_playback->set_sample_playback(sp);
AudioServer::get_singleton()->start_sample_playback(sp);
}
return (ID(i) << INDEX_SHIFT) | ID(streams[i].id);
}
}
return INVALID_ID;
}
AudioStreamPlaybackPolyphonic::Stream *AudioStreamPlaybackPolyphonic::_find_stream(int64_t p_id) {
uint32_t index = static_cast<uint64_t>(p_id) >> INDEX_SHIFT;
if (index >= streams.size()) {
return nullptr;
}
if (!streams[index].active.is_set()) {
return nullptr; // Not active, no longer exists.
}
int64_t id = static_cast<uint64_t>(p_id) & ID_MASK;
if (streams[index].id != id) {
return nullptr;
}
return &streams[index];
}
const AudioStreamPlaybackPolyphonic::Stream *AudioStreamPlaybackPolyphonic::_find_stream(int64_t p_id) const {
uint32_t index = static_cast<uint64_t>(p_id) >> INDEX_SHIFT;
if (index >= streams.size()) {
return nullptr;
}
if (!streams[index].active.is_set()) {
return nullptr; // Not active, no longer exists.
}
int64_t id = static_cast<uint64_t>(p_id) & ID_MASK;
if (streams[index].id != id) {
return nullptr;
}
return &streams[index];
}
void AudioStreamPlaybackPolyphonic::set_stream_volume(ID p_stream_id, float p_volume_db) {
Stream *s = _find_stream(p_stream_id);
if (!s) {
return;
}
s->volume_db = p_volume_db;
}
void AudioStreamPlaybackPolyphonic::set_stream_pitch_scale(ID p_stream_id, float p_pitch_scale) {
Stream *s = _find_stream(p_stream_id);
if (!s) {
return;
}
s->pitch_scale = p_pitch_scale;
}
bool AudioStreamPlaybackPolyphonic::is_stream_playing(ID p_stream_id) const {
return _find_stream(p_stream_id) != nullptr;
}
void AudioStreamPlaybackPolyphonic::stop_stream(ID p_stream_id) {
Stream *s = _find_stream(p_stream_id);
if (!s) {
return;
}
s->finish_request.set();
}
void AudioStreamPlaybackPolyphonic::set_is_sample(bool p_is_sample) {
_is_sample = p_is_sample;
}
bool AudioStreamPlaybackPolyphonic::get_is_sample() const {
return _is_sample;
}
Ref<AudioSamplePlayback> AudioStreamPlaybackPolyphonic::get_sample_playback() const {
return sample_playback;
}
void AudioStreamPlaybackPolyphonic::set_sample_playback(const Ref<AudioSamplePlayback> &p_playback) {
sample_playback = p_playback;
if (sample_playback.is_valid()) {
sample_playback->stream_playback = Ref<AudioStreamPlayback>(this);
}
}
void AudioStreamPlaybackPolyphonic::_bind_methods() {
ClassDB::bind_method(D_METHOD("play_stream", "stream", "from_offset", "volume_db", "pitch_scale", "playback_type", "bus"), &AudioStreamPlaybackPolyphonic::play_stream, DEFVAL(0), DEFVAL(0), DEFVAL(1.0), DEFVAL(0), DEFVAL(SceneStringName(Master)));
ClassDB::bind_method(D_METHOD("set_stream_volume", "stream", "volume_db"), &AudioStreamPlaybackPolyphonic::set_stream_volume);
ClassDB::bind_method(D_METHOD("set_stream_pitch_scale", "stream", "pitch_scale"), &AudioStreamPlaybackPolyphonic::set_stream_pitch_scale);
ClassDB::bind_method(D_METHOD("is_stream_playing", "stream"), &AudioStreamPlaybackPolyphonic::is_stream_playing);
ClassDB::bind_method(D_METHOD("stop_stream", "stream"), &AudioStreamPlaybackPolyphonic::stop_stream);
BIND_CONSTANT(INVALID_ID);
}
AudioStreamPlaybackPolyphonic::AudioStreamPlaybackPolyphonic() {
}

View File

@@ -0,0 +1,135 @@
/**************************************************************************/
/* audio_stream_polyphonic.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "core/templates/local_vector.h"
#include "scene/scene_string_names.h"
#include "servers/audio/audio_stream.h"
#include "servers/audio_server.h"
class AudioStreamPolyphonic : public AudioStream {
GDCLASS(AudioStreamPolyphonic, AudioStream)
int polyphony = 32;
AudioServer::PlaybackType playback_type;
static void _bind_methods();
public:
virtual Ref<AudioStreamPlayback> instantiate_playback() override;
virtual String get_stream_name() const override;
virtual bool is_monophonic() const override;
void set_polyphony(int p_voices);
int get_polyphony() const;
virtual bool is_meta_stream() const override { return true; }
AudioStreamPolyphonic();
};
class AudioStreamPlaybackPolyphonic : public AudioStreamPlayback {
GDCLASS(AudioStreamPlaybackPolyphonic, AudioStreamPlayback)
constexpr static uint32_t INTERNAL_BUFFER_LEN = 128;
struct Stream {
SafeFlag active;
SafeFlag pending_play;
SafeFlag finish_request;
float play_offset = 0;
float pitch_scale = 1.0;
Ref<AudioStream> stream;
Ref<AudioStreamPlayback> stream_playback;
float prev_volume_db = 0;
float volume_db = 0;
uint32_t id = 0;
Stream() :
active(false), pending_play(false), finish_request(false) {}
};
LocalVector<Stream> streams;
AudioFrame internal_buffer[INTERNAL_BUFFER_LEN];
bool active = false;
uint32_t id_counter = 1;
bool _is_sample = false;
Ref<AudioSamplePlayback> sample_playback;
_FORCE_INLINE_ Stream *_find_stream(int64_t p_id);
_FORCE_INLINE_ const Stream *_find_stream(int64_t p_id) const;
friend class AudioStreamPolyphonic;
protected:
static void _bind_methods();
public:
typedef int64_t ID;
enum {
INVALID_ID = -1
};
virtual void start(double p_from_pos = 0.0) override;
virtual void stop() override;
virtual bool is_playing() const override;
virtual int get_loop_count() const override; //times it looped
virtual double get_playback_position() const override;
virtual void seek(double p_time) override;
virtual void tag_used_streams() override;
virtual int mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) override;
ID play_stream(const Ref<AudioStream> &p_stream, float p_from_offset = 0, float p_volume_db = 0, float p_pitch_scale = 1.0, AudioServer::PlaybackType p_playback_type = AudioServer::PlaybackType::PLAYBACK_TYPE_DEFAULT, const StringName &p_bus = SceneStringName(Master));
void set_stream_volume(ID p_stream_id, float p_volume_db);
void set_stream_pitch_scale(ID p_stream_id, float p_pitch_scale);
bool is_stream_playing(ID p_stream_id) const;
void stop_stream(ID p_stream_id);
virtual void set_is_sample(bool p_is_sample) override;
virtual bool get_is_sample() const override;
virtual Ref<AudioSamplePlayback> get_sample_playback() const override;
virtual void set_sample_playback(const Ref<AudioSamplePlayback> &p_playback) override;
private:
#ifndef DISABLE_DEPRECATED
ID _play_stream_bind_compat_91382(const Ref<AudioStream> &p_stream, float p_from_offset = 0, float p_volume_db = 0, float p_pitch_scale = 1.0);
static void _bind_compatibility_methods();
#endif // DISABLE_DEPRECATED
public:
AudioStreamPlaybackPolyphonic();
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,295 @@
/**************************************************************************/
/* audio_stream_wav.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "servers/audio/audio_stream.h"
#include "thirdparty/misc/qoa.h"
class AudioStreamWAV;
class AudioStreamPlaybackWAV : public AudioStreamPlaybackResampled {
GDCLASS(AudioStreamPlaybackWAV, AudioStreamPlaybackResampled);
struct IMA_ADPCM_State {
int16_t step_index = 0;
int32_t predictor = 0;
/* values at loop point */
int16_t loop_step_index = 0;
int32_t loop_predictor = 0;
int32_t last_nibble = 0;
int32_t loop_pos = 0;
int32_t window_ofs = 0;
} ima_adpcm[2];
struct QOA_State {
qoa_desc desc = {};
uint32_t data_ofs = 0;
uint32_t frame_len = 0;
TightLocalVector<int16_t> dec;
uint32_t dec_len = 0;
} qoa;
int64_t offset = 0;
int8_t sign = 1;
bool active = false;
friend class AudioStreamWAV;
Ref<AudioStreamWAV> base;
template <typename Depth, bool is_stereo, bool is_ima_adpcm, bool is_qoa>
void decode_samples(const Depth *p_src, AudioFrame *p_dst, int64_t &p_offset, int8_t &p_increment, uint32_t p_amount, IMA_ADPCM_State *p_ima_adpcm, QOA_State *p_qoa);
bool _is_sample = false;
Ref<AudioSamplePlayback> sample_playback;
protected:
virtual int _mix_internal(AudioFrame *p_buffer, int p_frames) override;
virtual float get_stream_sampling_rate() override;
public:
virtual void start(double p_from_pos = 0.0) override;
virtual void stop() override;
virtual bool is_playing() const override;
virtual int get_loop_count() const override; //times it looped
virtual double get_playback_position() const override;
virtual void seek(double p_time) override;
virtual void tag_used_streams() override;
virtual void set_is_sample(bool p_is_sample) override;
virtual bool get_is_sample() const override;
virtual Ref<AudioSamplePlayback> get_sample_playback() const override;
virtual void set_sample_playback(const Ref<AudioSamplePlayback> &p_playback) override;
};
class AudioStreamWAV : public AudioStream {
GDCLASS(AudioStreamWAV, AudioStream);
RES_BASE_EXTENSION("sample")
public:
enum Format {
FORMAT_8_BITS,
FORMAT_16_BITS,
FORMAT_IMA_ADPCM,
FORMAT_QOA,
};
// Keep the ResourceImporterWAV `edit/loop_mode` enum hint in sync with these options.
enum LoopMode {
LOOP_DISABLED,
LOOP_FORWARD,
LOOP_PINGPONG,
LOOP_BACKWARD
};
private:
friend class AudioStreamPlaybackWAV;
Format format = FORMAT_8_BITS;
LoopMode loop_mode = LOOP_DISABLED;
bool stereo = false;
int loop_begin = 0;
int loop_end = 0;
int mix_rate = 44100;
TightLocalVector<uint8_t> data;
uint32_t data_bytes = 0;
Dictionary tags;
protected:
static void _bind_methods();
public:
static Ref<AudioStreamWAV> load_from_buffer(const Vector<uint8_t> &p_stream_data, const Dictionary &p_options);
static Ref<AudioStreamWAV> load_from_file(const String &p_path, const Dictionary &p_options);
void set_format(Format p_format);
Format get_format() const;
void set_loop_mode(LoopMode p_loop_mode);
LoopMode get_loop_mode() const;
void set_loop_begin(int p_frame);
int get_loop_begin() const;
void set_loop_end(int p_frame);
int get_loop_end() const;
void set_mix_rate(int p_hz);
int get_mix_rate() const;
void set_stereo(bool p_enable);
bool is_stereo() const;
void set_tags(const Dictionary &p_tags);
virtual Dictionary get_tags() const override;
virtual double get_length() const override; //if supported, otherwise return 0
virtual bool is_monophonic() const override;
void set_data(const Vector<uint8_t> &p_data);
Vector<uint8_t> get_data() const;
Error save_to_wav(const String &p_path);
virtual Ref<AudioStreamPlayback> instantiate_playback() override;
virtual String get_stream_name() const override;
virtual bool can_be_sampled() const override {
return true;
}
virtual Ref<AudioSample> generate_sample() const override;
static void _compress_ima_adpcm(const Vector<float> &p_data, Vector<uint8_t> &r_dst_data) {
static const int16_t _ima_adpcm_step_table[89] = {
7, 8, 9, 10, 11, 12, 13, 14, 16, 17,
19, 21, 23, 25, 28, 31, 34, 37, 41, 45,
50, 55, 60, 66, 73, 80, 88, 97, 107, 118,
130, 143, 157, 173, 190, 209, 230, 253, 279, 307,
337, 371, 408, 449, 494, 544, 598, 658, 724, 796,
876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066,
2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358,
5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,
15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767
};
static const int8_t _ima_adpcm_index_table[16] = {
-1, -1, -1, -1, 2, 4, 6, 8,
-1, -1, -1, -1, 2, 4, 6, 8
};
int datalen = p_data.size();
int datamax = datalen;
if (datalen & 1) {
datalen++;
}
r_dst_data.resize(datalen / 2 + 4);
uint8_t *w = r_dst_data.ptrw();
int i, step_idx = 0, prev = 0;
uint8_t *out = w;
const float *in = p_data.ptr();
// Initial value is zero.
*(out++) = 0;
*(out++) = 0;
// Table index initial value.
*(out++) = 0;
// Unused.
*(out++) = 0;
for (i = 0; i < datalen; i++) {
int step, diff, vpdiff, mask;
uint8_t nibble;
int16_t xm_sample;
if (i >= datamax) {
xm_sample = 0;
} else {
xm_sample = CLAMP(in[i] * 32767.0, -32768, 32767);
}
diff = (int)xm_sample - prev;
nibble = 0;
step = _ima_adpcm_step_table[step_idx];
vpdiff = step >> 3;
if (diff < 0) {
nibble = 8;
diff = -diff;
}
mask = 4;
while (mask) {
if (diff >= step) {
nibble |= mask;
diff -= step;
vpdiff += step;
}
step >>= 1;
mask >>= 1;
}
if (nibble & 8) {
prev -= vpdiff;
} else {
prev += vpdiff;
}
prev = CLAMP(prev, -32768, 32767);
step_idx += _ima_adpcm_index_table[nibble];
step_idx = CLAMP(step_idx, 0, 88);
if (i & 1) {
*out |= nibble << 4;
out++;
} else {
*out = nibble;
}
}
}
static void _compress_qoa(const Vector<float> &p_data, Vector<uint8_t> &dst_data, qoa_desc *p_desc) {
uint32_t frames_len = (p_desc->samples + QOA_FRAME_LEN - 1) / QOA_FRAME_LEN * (QOA_LMS_LEN * 4 * p_desc->channels + 8);
uint32_t slices_len = (p_desc->samples + QOA_SLICE_LEN - 1) / QOA_SLICE_LEN * 8 * p_desc->channels;
dst_data.resize(8 + frames_len + slices_len);
for (uint32_t c = 0; c < p_desc->channels; c++) {
memset(p_desc->lms[c].history, 0, sizeof(p_desc->lms[c].history));
memset(p_desc->lms[c].weights, 0, sizeof(p_desc->lms[c].weights));
p_desc->lms[c].weights[2] = -(1 << 13);
p_desc->lms[c].weights[3] = (1 << 14);
}
TightLocalVector<int16_t> data16;
data16.resize(QOA_FRAME_LEN * p_desc->channels);
uint8_t *dst_ptr = dst_data.ptrw();
dst_ptr += qoa_encode_header(p_desc, dst_data.ptrw());
uint32_t frame_len = QOA_FRAME_LEN;
for (uint32_t s = 0; s < p_desc->samples; s += frame_len) {
frame_len = MIN(frame_len, p_desc->samples - s);
for (uint32_t i = 0; i < frame_len * p_desc->channels; i++) {
data16[i] = CLAMP(p_data[s * p_desc->channels + i] * 32767.0, -32768, 32767);
}
dst_ptr += qoa_encode_frame(data16.ptr(), p_desc, frame_len, dst_ptr);
}
}
};
VARIANT_ENUM_CAST(AudioStreamWAV::Format)
VARIANT_ENUM_CAST(AudioStreamWAV::LoopMode)

729
scene/resources/bit_map.cpp Normal file
View File

@@ -0,0 +1,729 @@
/**************************************************************************/
/* bit_map.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 "bit_map.h"
#include "core/variant/typed_array.h"
void BitMap::create(const Size2i &p_size) {
ERR_FAIL_COND(p_size.width < 1);
ERR_FAIL_COND(p_size.height < 1);
ERR_FAIL_COND(static_cast<int64_t>(p_size.width) * static_cast<int64_t>(p_size.height) > INT32_MAX);
Error err = bitmask.resize(Math::division_round_up(p_size.width * p_size.height, 8));
ERR_FAIL_COND(err != OK);
width = p_size.width;
height = p_size.height;
memset(bitmask.ptrw(), 0, bitmask.size());
}
void BitMap::create_from_image_alpha(const Ref<Image> &p_image, float p_threshold) {
ERR_FAIL_COND(p_image.is_null() || p_image->is_empty());
Ref<Image> img = p_image->duplicate();
img->convert(Image::FORMAT_LA8);
ERR_FAIL_COND(img->get_format() != Image::FORMAT_LA8);
create(Size2i(img->get_width(), img->get_height()));
const uint8_t *r = img->get_data().ptr();
uint8_t *w = bitmask.ptrw();
for (int i = 0; i < width * height; i++) {
int bbyte = i / 8;
int bbit = i % 8;
if (r[i * 2 + 1] / 255.0 > p_threshold) {
w[bbyte] |= (1 << bbit);
}
}
}
void BitMap::set_bit_rect(const Rect2i &p_rect, bool p_value) {
Rect2i current = Rect2i(0, 0, width, height).intersection(p_rect);
uint8_t *data = bitmask.ptrw();
for (int i = current.position.x; i < current.position.x + current.size.x; i++) {
for (int j = current.position.y; j < current.position.y + current.size.y; j++) {
int ofs = width * j + i;
int bbyte = ofs / 8;
int bbit = ofs % 8;
uint8_t b = data[bbyte];
if (p_value) {
b |= (1 << bbit);
} else {
b &= ~(1 << bbit);
}
data[bbyte] = b;
}
}
}
int BitMap::get_true_bit_count() const {
int ds = bitmask.size();
const uint8_t *d = bitmask.ptr();
int c = 0;
// Fast, almost branchless version.
for (int i = 0; i < ds; i++) {
c += (d[i] & (1 << 7)) >> 7;
c += (d[i] & (1 << 6)) >> 6;
c += (d[i] & (1 << 5)) >> 5;
c += (d[i] & (1 << 4)) >> 4;
c += (d[i] & (1 << 3)) >> 3;
c += (d[i] & (1 << 2)) >> 2;
c += (d[i] & (1 << 1)) >> 1;
c += d[i] & 1;
}
return c;
}
void BitMap::set_bitv(const Point2i &p_pos, bool p_value) {
set_bit(p_pos.x, p_pos.y, p_value);
}
void BitMap::set_bit(int p_x, int p_y, bool p_value) {
ERR_FAIL_INDEX(p_x, width);
ERR_FAIL_INDEX(p_y, height);
int ofs = width * p_y + p_x;
int bbyte = ofs / 8;
int bbit = ofs % 8;
uint8_t b = bitmask[bbyte];
if (p_value) {
b |= (1 << bbit);
} else {
b &= ~(1 << bbit);
}
bitmask.write[bbyte] = b;
}
bool BitMap::get_bitv(const Point2i &p_pos) const {
return get_bit(p_pos.x, p_pos.y);
}
bool BitMap::get_bit(int p_x, int p_y) const {
ERR_FAIL_INDEX_V(p_x, width, false);
ERR_FAIL_INDEX_V(p_y, height, false);
int ofs = width * p_y + p_x;
int bbyte = ofs / 8;
int bbit = ofs % 8;
return (bitmask[bbyte] & (1 << bbit)) != 0;
}
Size2i BitMap::get_size() const {
return Size2i(width, height);
}
void BitMap::_set_data(const Dictionary &p_d) {
ERR_FAIL_COND(!p_d.has("size"));
ERR_FAIL_COND(!p_d.has("data"));
create(p_d["size"]);
bitmask = p_d["data"];
}
Dictionary BitMap::_get_data() const {
Dictionary d;
d["size"] = get_size();
d["data"] = bitmask;
return d;
}
Vector<Vector<Vector2>> BitMap::_march_square(const Rect2i &p_rect, const Point2i &p_start) const {
int stepx = 0;
int stepy = 0;
int prevx = 0;
int prevy = 0;
int startx = p_start.x;
int starty = p_start.y;
int curx = startx;
int cury = starty;
unsigned int count = 0;
HashMap<Point2i, int> cross_map;
Vector<Vector2> _points;
int points_size = 0;
Vector<Vector<Vector2>> ret;
// Add starting entry at start of return.
ret.resize(1);
do {
int sv = 0;
{ // Square value
/*
checking the 2x2 pixel grid, assigning these values to each pixel, if not transparent
+---+---+
| 1 | 2 |
+---+---+
| 4 | 8 | <- current pixel (curx,cury)
+---+---+
*/
Point2i tl = Point2i(curx - 1, cury - 1);
sv += (p_rect.has_point(tl) && get_bitv(tl)) ? 1 : 0;
Point2i tr = Point2i(curx, cury - 1);
sv += (p_rect.has_point(tr) && get_bitv(tr)) ? 2 : 0;
Point2i bl = Point2i(curx - 1, cury);
sv += (p_rect.has_point(bl) && get_bitv(bl)) ? 4 : 0;
Point2i br = Point2i(curx, cury);
sv += (p_rect.has_point(br) && get_bitv(br)) ? 8 : 0;
ERR_FAIL_COND_V(sv == 0 || sv == 15, Vector<Vector<Vector2>>());
}
switch (sv) {
case 1:
case 5:
case 13:
/* going UP with these cases:
1 5 13
+---+---+ +---+---+ +---+---+
| 1 | | | 1 | | | 1 | |
+---+---+ +---+---+ +---+---+
| | | | 4 | | | 4 | 8 |
+---+---+ +---+---+ +---+---+
*/
stepx = 0;
stepy = -1;
break;
case 8:
case 10:
case 11:
/* going DOWN with these cases:
8 10 11
+---+---+ +---+---+ +---+---+
| | | | | 2 | | 1 | 2 |
+---+---+ +---+---+ +---+---+
| | 8 | | | 8 | | | 8 |
+---+---+ +---+---+ +---+---+
*/
stepx = 0;
stepy = 1;
break;
case 4:
case 12:
case 14:
/* going LEFT with these cases:
4 12 14
+---+---+ +---+---+ +---+---+
| | | | | | | | 2 |
+---+---+ +---+---+ +---+---+
| 4 | | | 4 | 8 | | 4 | 8 |
+---+---+ +---+---+ +---+---+
*/
stepx = -1;
stepy = 0;
break;
case 2:
case 3:
case 7:
/* going RIGHT with these cases:
2 3 7
+---+---+ +---+---+ +---+---+
| | 2 | | 1 | 2 | | 1 | 2 |
+---+---+ +---+---+ +---+---+
| | | | | | | 4 | |
+---+---+ +---+---+ +---+---+
*/
stepx = 1;
stepy = 0;
break;
case 9:
/* Going DOWN if coming from the LEFT, otherwise go UP.
9
+---+---+
| 1 | |
+---+---+
| | 8 |
+---+---+
*/
if (prevx == 1) {
stepx = 0;
stepy = 1;
} else {
stepx = 0;
stepy = -1;
}
break;
case 6:
/* Going RIGHT if coming from BELOW, otherwise go LEFT.
6
+---+---+
| | 2 |
+---+---+
| 4 | |
+---+---+
*/
if (prevy == -1) {
stepx = 1;
stepy = 0;
} else {
stepx = -1;
stepy = 0;
}
break;
default:
ERR_PRINT("this shouldn't happen.");
}
// Handle crossing points.
if (sv == 6 || sv == 9) {
const Point2i cur_pos(curx, cury);
// Find if this point has occurred before.
if (HashMap<Point2i, int>::Iterator found = cross_map.find(cur_pos)) {
// Add points after the previous crossing to the result.
ret.push_back(_points.slice(found->value + 1, points_size));
// Remove points after crossing point.
points_size = found->value + 1;
// Erase trailing map elements.
while (cross_map.last() != found) {
cross_map.remove(cross_map.last());
}
cross_map.erase(cur_pos);
} else {
// Add crossing point to map.
cross_map.insert(cur_pos, points_size - 1);
}
}
// Small optimization:
// If the previous direction is same as the current direction,
// then we should modify the last vector to current.
curx += stepx;
cury += stepy;
if (stepx == prevx && stepy == prevy) {
_points.set(points_size - 1, Vector2(curx, cury) - p_rect.position);
} else {
_points.resize(MAX(points_size + 1, _points.size()));
_points.set(points_size, Vector2(curx, cury) - p_rect.position);
points_size++;
}
count++;
prevx = stepx;
prevy = stepy;
ERR_FAIL_COND_V((int)count > 2 * (width * height + 1), Vector<Vector<Vector2>>());
} while (curx != startx || cury != starty);
// Add remaining points to result.
_points.resize(points_size);
ret.set(0, _points);
return ret;
}
static float perpendicular_distance(const Vector2 &i, const Vector2 &start, const Vector2 &end) {
float res;
float slope;
float intercept;
if (start.x == end.x) {
res = Math::abs(i.x - end.x);
} else if (start.y == end.y) {
res = Math::abs(i.y - end.y);
} else {
slope = (end.y - start.y) / (end.x - start.x);
intercept = start.y - (slope * start.x);
res = Math::abs(slope * i.x - i.y + intercept) / Math::sqrt(Math::pow(slope, 2.0f) + 1.0);
}
return res;
}
static Vector<Vector2> rdp(const Vector<Vector2> &v, float optimization) {
if (v.size() < 3) {
return v;
}
int index = -1;
float dist = 0.0;
// Not looping first and last point.
for (size_t i = 1, size = v.size(); i < size - 1; ++i) {
float cdist = perpendicular_distance(v[i], v[0], v[v.size() - 1]);
if (cdist > dist) {
dist = cdist;
index = static_cast<int>(i);
}
}
if (dist > optimization) {
Vector<Vector2> left, right;
left.resize(index);
for (int i = 0; i < index; i++) {
left.write[i] = v[i];
}
right.resize(v.size() - index);
for (int i = 0; i < right.size(); i++) {
right.write[i] = v[index + i];
}
Vector<Vector2> r1 = rdp(left, optimization);
Vector<Vector2> r2 = rdp(right, optimization);
int middle = r1.size();
r1.resize(r1.size() + r2.size());
for (int i = 0; i < r2.size(); i++) {
r1.write[middle + i] = r2[i];
}
return r1;
} else {
Vector<Vector2> ret;
ret.push_back(v[0]);
ret.push_back(v[v.size() - 1]);
return ret;
}
}
static Vector<Vector2> reduce(const Vector<Vector2> &points, const Rect2i &rect, float epsilon) {
int size = points.size();
// If there are less than 3 points, then we have nothing.
ERR_FAIL_COND_V(size < 3, Vector<Vector2>());
// If there are less than 9 points (but more than 3), then we don't need to reduce it.
if (size < 9) {
return points;
}
float maxEp = MIN(rect.size.width, rect.size.height);
float ep = CLAMP(epsilon, 0.0, maxEp / 2);
Vector<Vector2> result = rdp(points, ep);
Vector2 last = result[result.size() - 1];
if (last.y > result[0].y && last.distance_to(result[0]) < ep * 0.5f) {
result.write[0].y = last.y;
result.resize(result.size() - 1);
}
return result;
}
struct FillBitsStackEntry {
Point2i pos;
int i = 0;
int j = 0;
};
static void fill_bits(const BitMap *p_src, Ref<BitMap> &p_map, const Point2i &p_pos, const Rect2i &rect) {
// Using a custom stack to work iteratively to avoid stack overflow on big bitmaps.
Vector<FillBitsStackEntry> stack;
// Tracking size since we won't be shrinking the stack vector.
int stack_size = 0;
Point2i pos = p_pos;
int next_i = 0;
int next_j = 0;
bool reenter = true;
bool popped = false;
do {
if (reenter) {
next_i = pos.x - 1;
next_j = pos.y - 1;
reenter = false;
}
for (int i = next_i; i <= pos.x + 1; i++) {
for (int j = next_j; j <= pos.y + 1; j++) {
if (popped) {
// The next loop over j must start normally.
next_j = pos.y - 1;
popped = false;
// Skip because an iteration was already executed with current counter values.
continue;
}
if (i < rect.position.x || i >= rect.position.x + rect.size.x) {
continue;
}
if (j < rect.position.y || j >= rect.position.y + rect.size.y) {
continue;
}
if (p_map->get_bit(i, j)) {
continue;
} else if (p_src->get_bit(i, j)) {
p_map->set_bit(i, j, true);
FillBitsStackEntry se = { pos, i, j };
stack.resize(MAX(stack_size + 1, stack.size()));
stack.set(stack_size, se);
stack_size++;
pos = Point2i(i, j);
reenter = true;
break;
}
}
if (reenter) {
break;
}
}
if (!reenter) {
if (stack_size) {
FillBitsStackEntry se = stack.get(stack_size - 1);
stack_size--;
pos = se.pos;
next_i = se.i;
next_j = se.j;
popped = true;
}
}
} while (reenter || popped);
}
Vector<Vector<Vector2>> BitMap::clip_opaque_to_polygons(const Rect2i &p_rect, float p_epsilon) const {
Rect2i r = Rect2i(0, 0, width, height).intersection(p_rect);
Ref<BitMap> fill;
fill.instantiate();
fill->create(get_size());
Vector<Vector<Vector2>> polygons;
for (int i = r.position.y; i < r.position.y + r.size.height; i++) {
for (int j = r.position.x; j < r.position.x + r.size.width; j++) {
if (!fill->get_bit(j, i) && get_bit(j, i)) {
fill_bits(this, fill, Point2i(j, i), r);
for (Vector<Vector2> polygon : _march_square(r, Point2i(j, i))) {
polygon = reduce(polygon, r, p_epsilon);
if (polygon.size() < 3) {
print_verbose("Invalid polygon, skipped");
continue;
}
polygons.push_back(polygon);
}
}
}
}
return polygons;
}
void BitMap::grow_mask(int p_pixels, const Rect2i &p_rect) {
if (p_pixels == 0) {
return;
}
bool bit_value = p_pixels > 0;
p_pixels = Math::abs(p_pixels);
const int pixels2 = p_pixels * p_pixels;
Rect2i r = Rect2i(0, 0, width, height).intersection(p_rect);
Ref<BitMap> copy;
copy.instantiate();
copy->create(get_size());
copy->bitmask = bitmask;
for (int i = r.position.y; i < r.position.y + r.size.height; i++) {
for (int j = r.position.x; j < r.position.x + r.size.width; j++) {
if (bit_value == get_bit(j, i)) {
continue;
}
bool found = false;
for (int y = i - p_pixels; y <= i + p_pixels; y++) {
for (int x = j - p_pixels; x <= j + p_pixels; x++) {
bool outside = false;
if ((x < p_rect.position.x) || (x >= p_rect.position.x + p_rect.size.x) || (y < p_rect.position.y) || (y >= p_rect.position.y + p_rect.size.y)) {
// Outside of rectangle counts as bit not set.
if (!bit_value) {
outside = true;
} else {
continue;
}
}
float d = Point2(j, i).distance_squared_to(Point2(x, y)) - CMP_EPSILON2;
if (d > pixels2) {
continue;
}
if (outside || (bit_value == copy->get_bit(x, y))) {
found = true;
break;
}
}
if (found) {
break;
}
}
if (found) {
set_bit(j, i, bit_value);
}
}
}
}
void BitMap::shrink_mask(int p_pixels, const Rect2i &p_rect) {
grow_mask(-p_pixels, p_rect);
}
TypedArray<PackedVector2Array> BitMap::_opaque_to_polygons_bind(const Rect2i &p_rect, float p_epsilon) const {
Vector<Vector<Vector2>> result = clip_opaque_to_polygons(p_rect, p_epsilon);
// Convert result to bindable types.
TypedArray<PackedVector2Array> result_array;
result_array.resize(result.size());
for (int i = 0; i < result.size(); i++) {
const Vector<Vector2> &polygon = result[i];
PackedVector2Array polygon_array;
polygon_array.resize(polygon.size());
{
Vector2 *w = polygon_array.ptrw();
for (int j = 0; j < polygon.size(); j++) {
w[j] = polygon[j];
}
}
result_array[i] = polygon_array;
}
return result_array;
}
void BitMap::resize(const Size2i &p_new_size) {
ERR_FAIL_COND(p_new_size.width < 0 || p_new_size.height < 0);
if (p_new_size == get_size()) {
return;
}
Ref<BitMap> new_bitmap;
new_bitmap.instantiate();
new_bitmap->create(p_new_size);
// also allow for upscaling
int lw = (width == 0) ? 0 : p_new_size.width;
int lh = (height == 0) ? 0 : p_new_size.height;
float scale_x = ((float)width / p_new_size.width);
float scale_y = ((float)height / p_new_size.height);
for (int x = 0; x < lw; x++) {
for (int y = 0; y < lh; y++) {
bool new_bit = get_bit(x * scale_x, y * scale_y);
new_bitmap->set_bit(x, y, new_bit);
}
}
width = new_bitmap->width;
height = new_bitmap->height;
bitmask = new_bitmap->bitmask;
}
Ref<Image> BitMap::convert_to_image() const {
Ref<Image> image = Image::create_empty(width, height, false, Image::FORMAT_L8);
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
image->set_pixel(i, j, get_bit(i, j) ? Color(1, 1, 1) : Color(0, 0, 0));
}
}
return image;
}
void BitMap::blit(const Vector2i &p_pos, const Ref<BitMap> &p_bitmap) {
ERR_FAIL_COND_MSG(p_bitmap.is_null(), "It's not a reference to a valid BitMap object.");
int x = p_pos.x;
int y = p_pos.y;
int w = p_bitmap->get_size().width;
int h = p_bitmap->get_size().height;
for (int i = 0; i < w; i++) {
for (int j = 0; j < h; j++) {
int px = x + i;
int py = y + j;
if (px < 0 || px >= width) {
continue;
}
if (py < 0 || py >= height) {
continue;
}
if (p_bitmap->get_bit(i, j)) {
set_bit(px, py, true);
}
}
}
}
void BitMap::_bind_methods() {
ClassDB::bind_method(D_METHOD("create", "size"), &BitMap::create);
ClassDB::bind_method(D_METHOD("create_from_image_alpha", "image", "threshold"), &BitMap::create_from_image_alpha, DEFVAL(0.1));
ClassDB::bind_method(D_METHOD("set_bitv", "position", "bit"), &BitMap::set_bitv);
ClassDB::bind_method(D_METHOD("set_bit", "x", "y", "bit"), &BitMap::set_bit);
ClassDB::bind_method(D_METHOD("get_bitv", "position"), &BitMap::get_bitv);
ClassDB::bind_method(D_METHOD("get_bit", "x", "y"), &BitMap::get_bit);
ClassDB::bind_method(D_METHOD("set_bit_rect", "rect", "bit"), &BitMap::set_bit_rect);
ClassDB::bind_method(D_METHOD("get_true_bit_count"), &BitMap::get_true_bit_count);
ClassDB::bind_method(D_METHOD("get_size"), &BitMap::get_size);
ClassDB::bind_method(D_METHOD("resize", "new_size"), &BitMap::resize);
ClassDB::bind_method(D_METHOD("_set_data", "data"), &BitMap::_set_data);
ClassDB::bind_method(D_METHOD("_get_data"), &BitMap::_get_data);
ClassDB::bind_method(D_METHOD("grow_mask", "pixels", "rect"), &BitMap::grow_mask);
ClassDB::bind_method(D_METHOD("convert_to_image"), &BitMap::convert_to_image);
ClassDB::bind_method(D_METHOD("opaque_to_polygons", "rect", "epsilon"), &BitMap::_opaque_to_polygons_bind, DEFVAL(2.0));
ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_data", "_get_data");
}

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