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
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:
23
scene/resources/2d/SCsub
Normal file
23
scene/resources/2d/SCsub
Normal 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")
|
||||
144
scene/resources/2d/capsule_shape_2d.cpp
Normal file
144
scene/resources/2d/capsule_shape_2d.cpp
Normal 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();
|
||||
}
|
||||
64
scene/resources/2d/capsule_shape_2d.h
Normal file
64
scene/resources/2d/capsule_shape_2d.h
Normal file
@@ -0,0 +1,64 @@
|
||||
/**************************************************************************/
|
||||
/* capsule_shape_2d.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#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();
|
||||
};
|
||||
98
scene/resources/2d/circle_shape_2d.cpp
Normal file
98
scene/resources/2d/circle_shape_2d.cpp
Normal 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();
|
||||
}
|
||||
55
scene/resources/2d/circle_shape_2d.h
Normal file
55
scene/resources/2d/circle_shape_2d.h
Normal 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();
|
||||
};
|
||||
119
scene/resources/2d/concave_polygon_shape_2d.cpp
Normal file
119
scene/resources/2d/concave_polygon_shape_2d.cpp
Normal file
@@ -0,0 +1,119 @@
|
||||
/**************************************************************************/
|
||||
/* concave_polygon_shape_2d.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#include "concave_polygon_shape_2d.h"
|
||||
|
||||
#include "core/math/geometry_2d.h"
|
||||
#include "servers/physics_server_2d.h"
|
||||
#include "servers/rendering_server.h"
|
||||
|
||||
bool ConcavePolygonShape2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const {
|
||||
Vector<Vector2> s = get_segments();
|
||||
int len = s.size();
|
||||
if (len == 0 || (len % 2) == 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const Vector2 *r = s.ptr();
|
||||
for (int i = 0; i < len; i += 2) {
|
||||
Vector2 closest = Geometry2D::get_closest_point_to_segment(p_point, r[i], 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);
|
||||
}
|
||||
52
scene/resources/2d/concave_polygon_shape_2d.h
Normal file
52
scene/resources/2d/concave_polygon_shape_2d.h
Normal 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();
|
||||
};
|
||||
143
scene/resources/2d/convex_polygon_shape_2d.cpp
Normal file
143
scene/resources/2d/convex_polygon_shape_2d.cpp
Normal file
@@ -0,0 +1,143 @@
|
||||
/**************************************************************************/
|
||||
/* convex_polygon_shape_2d.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#include "convex_polygon_shape_2d.h"
|
||||
|
||||
#include "core/math/geometry_2d.h"
|
||||
#include "servers/physics_server_2d.h"
|
||||
#include "servers/rendering_server.h"
|
||||
|
||||
bool ConvexPolygonShape2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const {
|
||||
return Geometry2D::is_point_in_polygon(p_point, points);
|
||||
}
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
// Check if point p3 is to the left of [p1, p2] segment or on it.
|
||||
bool left_test(const Vector2 &p1, const Vector2 &p2, const Vector2 &p3) {
|
||||
Vector2 p12 = p2 - p1;
|
||||
Vector2 p13 = p3 - p1;
|
||||
// If the value of the cross product is positive or zero; p3 is to the left or on the segment, respectively.
|
||||
return p12.cross(p13) >= 0;
|
||||
}
|
||||
|
||||
bool is_convex(const Vector<Vector2> &p_points) {
|
||||
// Pre-condition: Polygon is in counter-clockwise order.
|
||||
int polygon_size = p_points.size();
|
||||
for (int i = 0; i < polygon_size && polygon_size > 3; i++) {
|
||||
int j = (i + 1) % polygon_size;
|
||||
int k = (j + 1) % polygon_size;
|
||||
// If any consecutive three points fail left-test, then there is a concavity.
|
||||
if (!left_test(p_points[i], p_points[j], p_points[k])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
void ConvexPolygonShape2D::_update_shape() {
|
||||
Vector<Vector2> final_points = points;
|
||||
if (Geometry2D::is_polygon_clockwise(final_points)) { //needs to be counter clockwise
|
||||
final_points.reverse();
|
||||
}
|
||||
#ifdef DEBUG_ENABLED
|
||||
if (!is_convex(final_points)) {
|
||||
WARN_PRINT("Concave polygon is assigned to ConvexPolygonShape2D.");
|
||||
}
|
||||
#endif
|
||||
PhysicsServer2D::get_singleton()->shape_set_data(get_rid(), final_points);
|
||||
emit_changed();
|
||||
}
|
||||
|
||||
void ConvexPolygonShape2D::set_point_cloud(const Vector<Vector2> &p_points) {
|
||||
Vector<Point2> hull = Geometry2D::convex_hull(p_points);
|
||||
ERR_FAIL_COND(hull.size() < 3);
|
||||
set_points(hull);
|
||||
}
|
||||
|
||||
void ConvexPolygonShape2D::set_points(const Vector<Vector2> &p_points) {
|
||||
points = p_points;
|
||||
|
||||
_update_shape();
|
||||
}
|
||||
|
||||
Vector<Vector2> ConvexPolygonShape2D::get_points() const {
|
||||
return points;
|
||||
}
|
||||
|
||||
void ConvexPolygonShape2D::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_point_cloud", "point_cloud"), &ConvexPolygonShape2D::set_point_cloud);
|
||||
ClassDB::bind_method(D_METHOD("set_points", "points"), &ConvexPolygonShape2D::set_points);
|
||||
ClassDB::bind_method(D_METHOD("get_points"), &ConvexPolygonShape2D::get_points);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "points"), "set_points", "get_points");
|
||||
}
|
||||
|
||||
void ConvexPolygonShape2D::draw(const RID &p_to_rid, const Color &p_color) {
|
||||
if (points.size() < 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
Vector<Color> col = { p_color };
|
||||
RenderingServer::get_singleton()->canvas_item_add_polygon(p_to_rid, points, col);
|
||||
|
||||
if (is_collision_outline_enabled()) {
|
||||
col = { Color(p_color, 1.0) };
|
||||
RenderingServer::get_singleton()->canvas_item_add_polyline(p_to_rid, points, col);
|
||||
// Draw the last segment.
|
||||
RenderingServer::get_singleton()->canvas_item_add_line(p_to_rid, points[points.size() - 1], points[0], Color(p_color, 1.0));
|
||||
}
|
||||
}
|
||||
|
||||
Rect2 ConvexPolygonShape2D::get_rect() const {
|
||||
Rect2 rect;
|
||||
for (int i = 0; i < points.size(); i++) {
|
||||
if (i == 0) {
|
||||
rect.position = points[i];
|
||||
} else {
|
||||
rect.expand_to(points[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
real_t ConvexPolygonShape2D::get_enclosing_radius() const {
|
||||
real_t r = 0.0;
|
||||
for (int i(0); i < get_points().size(); i++) {
|
||||
r = MAX(get_points()[i].length_squared(), r);
|
||||
}
|
||||
return Math::sqrt(r);
|
||||
}
|
||||
|
||||
ConvexPolygonShape2D::ConvexPolygonShape2D() :
|
||||
Shape2D(PhysicsServer2D::get_singleton()->convex_polygon_shape_create()) {
|
||||
}
|
||||
56
scene/resources/2d/convex_polygon_shape_2d.h
Normal file
56
scene/resources/2d/convex_polygon_shape_2d.h
Normal 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();
|
||||
};
|
||||
374
scene/resources/2d/navigation_mesh_source_geometry_data_2d.cpp
Normal file
374
scene/resources/2d/navigation_mesh_source_geometry_data_2d.cpp
Normal 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");
|
||||
}
|
||||
112
scene/resources/2d/navigation_mesh_source_geometry_data_2d.h
Normal file
112
scene/resources/2d/navigation_mesh_source_geometry_data_2d.h
Normal 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(); }
|
||||
};
|
||||
638
scene/resources/2d/navigation_polygon.cpp
Normal file
638
scene/resources/2d/navigation_polygon.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
174
scene/resources/2d/navigation_polygon.h
Normal file
174
scene/resources/2d/navigation_polygon.h
Normal 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);
|
||||
553
scene/resources/2d/polygon_path_finder.cpp
Normal file
553
scene/resources/2d/polygon_path_finder.cpp
Normal 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() {
|
||||
}
|
||||
95
scene/resources/2d/polygon_path_finder.h
Normal file
95
scene/resources/2d/polygon_path_finder.h
Normal 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();
|
||||
};
|
||||
110
scene/resources/2d/rectangle_shape_2d.cpp
Normal file
110
scene/resources/2d/rectangle_shape_2d.cpp
Normal 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();
|
||||
}
|
||||
57
scene/resources/2d/rectangle_shape_2d.h
Normal file
57
scene/resources/2d/rectangle_shape_2d.h
Normal 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();
|
||||
};
|
||||
105
scene/resources/2d/segment_shape_2d.cpp
Normal file
105
scene/resources/2d/segment_shape_2d.cpp
Normal 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();
|
||||
}
|
||||
60
scene/resources/2d/segment_shape_2d.h
Normal file
60
scene/resources/2d/segment_shape_2d.h
Normal 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();
|
||||
};
|
||||
123
scene/resources/2d/separation_ray_shape_2d.cpp
Normal file
123
scene/resources/2d/separation_ray_shape_2d.cpp
Normal 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();
|
||||
}
|
||||
58
scene/resources/2d/separation_ray_shape_2d.h
Normal file
58
scene/resources/2d/separation_ray_shape_2d.h
Normal 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();
|
||||
};
|
||||
123
scene/resources/2d/shape_2d.cpp
Normal file
123
scene/resources/2d/shape_2d.cpp
Normal 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);
|
||||
}
|
||||
67
scene/resources/2d/shape_2d.h
Normal file
67
scene/resources/2d/shape_2d.h
Normal 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();
|
||||
};
|
||||
16
scene/resources/2d/skeleton/SCsub
Normal file
16
scene/resources/2d/skeleton/SCsub
Normal 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")
|
||||
240
scene/resources/2d/skeleton/skeleton_modification_2d.cpp
Normal file
240
scene/resources/2d/skeleton/skeleton_modification_2d.cpp
Normal 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;
|
||||
}
|
||||
86
scene/resources/2d/skeleton/skeleton_modification_2d.h
Normal file
86
scene/resources/2d/skeleton/skeleton_modification_2d.h
Normal 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();
|
||||
};
|
||||
550
scene/resources/2d/skeleton/skeleton_modification_2d_ccdik.cpp
Normal file
550
scene/resources/2d/skeleton/skeleton_modification_2d_ccdik.cpp
Normal 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() {
|
||||
}
|
||||
113
scene/resources/2d/skeleton/skeleton_modification_2d_ccdik.h
Normal file
113
scene/resources/2d/skeleton/skeleton_modification_2d_ccdik.h
Normal 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();
|
||||
};
|
||||
457
scene/resources/2d/skeleton/skeleton_modification_2d_fabrik.cpp
Normal file
457
scene/resources/2d/skeleton/skeleton_modification_2d_fabrik.cpp
Normal 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() {
|
||||
}
|
||||
105
scene/resources/2d/skeleton/skeleton_modification_2d_fabrik.h
Normal file
105
scene/resources/2d/skeleton/skeleton_modification_2d_fabrik.h
Normal 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();
|
||||
};
|
||||
577
scene/resources/2d/skeleton/skeleton_modification_2d_jiggle.cpp
Normal file
577
scene/resources/2d/skeleton/skeleton_modification_2d_jiggle.cpp
Normal 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() {
|
||||
}
|
||||
136
scene/resources/2d/skeleton/skeleton_modification_2d_jiggle.h
Normal file
136
scene/resources/2d/skeleton/skeleton_modification_2d_jiggle.h
Normal 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();
|
||||
};
|
||||
410
scene/resources/2d/skeleton/skeleton_modification_2d_lookat.cpp
Normal file
410
scene/resources/2d/skeleton/skeleton_modification_2d_lookat.cpp
Normal 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() {
|
||||
}
|
||||
@@ -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();
|
||||
};
|
||||
@@ -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() {
|
||||
}
|
||||
@@ -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();
|
||||
};
|
||||
@@ -0,0 +1,135 @@
|
||||
/**************************************************************************/
|
||||
/* skeleton_modification_2d_stackholder.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#include "skeleton_modification_2d_stackholder.h"
|
||||
#include "scene/2d/skeleton_2d.h"
|
||||
|
||||
bool SkeletonModification2DStackHolder::_set(const StringName &p_path, const Variant &p_value) {
|
||||
String path = p_path;
|
||||
|
||||
if (path == "held_modification_stack") {
|
||||
set_held_modification_stack(p_value);
|
||||
}
|
||||
#ifdef TOOLS_ENABLED
|
||||
else if (path == "editor/draw_gizmo") {
|
||||
set_editor_draw_gizmo(p_value);
|
||||
}
|
||||
#endif // TOOLS_ENABLED
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SkeletonModification2DStackHolder::_get(const StringName &p_path, Variant &r_ret) const {
|
||||
String path = p_path;
|
||||
|
||||
if (path == "held_modification_stack") {
|
||||
r_ret = get_held_modification_stack();
|
||||
}
|
||||
#ifdef TOOLS_ENABLED
|
||||
else if (path == "editor/draw_gizmo") {
|
||||
r_ret = get_editor_draw_gizmo();
|
||||
}
|
||||
#endif // TOOLS_ENABLED
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SkeletonModification2DStackHolder::_get_property_list(List<PropertyInfo> *p_list) const {
|
||||
p_list->push_back(PropertyInfo(Variant::OBJECT, "held_modification_stack", PROPERTY_HINT_RESOURCE_TYPE, "SkeletonModificationStack2D", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_ALWAYS_DUPLICATE));
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (Engine::get_singleton()->is_editor_hint()) {
|
||||
p_list->push_back(PropertyInfo(Variant::BOOL, "editor/draw_gizmo", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT));
|
||||
}
|
||||
#endif // TOOLS_ENABLED
|
||||
}
|
||||
|
||||
void SkeletonModification2DStackHolder::_execute(float p_delta) {
|
||||
ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr,
|
||||
"Modification is not setup and therefore cannot execute!");
|
||||
|
||||
if (held_modification_stack.is_valid()) {
|
||||
held_modification_stack->execute(p_delta, execution_mode);
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonModification2DStackHolder::_setup_modification(SkeletonModificationStack2D *p_stack) {
|
||||
stack = p_stack;
|
||||
|
||||
if (stack != nullptr) {
|
||||
is_setup = true;
|
||||
|
||||
if (held_modification_stack.is_valid()) {
|
||||
held_modification_stack->set_skeleton(stack->get_skeleton());
|
||||
held_modification_stack->setup();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonModification2DStackHolder::_draw_editor_gizmo() {
|
||||
if (stack) {
|
||||
if (held_modification_stack.is_valid()) {
|
||||
held_modification_stack->draw_editor_gizmos();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonModification2DStackHolder::set_held_modification_stack(Ref<SkeletonModificationStack2D> p_held_stack) {
|
||||
held_modification_stack = p_held_stack;
|
||||
|
||||
if (is_setup && held_modification_stack.is_valid()) {
|
||||
held_modification_stack->set_skeleton(stack->get_skeleton());
|
||||
held_modification_stack->setup();
|
||||
}
|
||||
}
|
||||
|
||||
Ref<SkeletonModificationStack2D> SkeletonModification2DStackHolder::get_held_modification_stack() const {
|
||||
return held_modification_stack;
|
||||
}
|
||||
|
||||
void SkeletonModification2DStackHolder::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_held_modification_stack", "held_modification_stack"), &SkeletonModification2DStackHolder::set_held_modification_stack);
|
||||
ClassDB::bind_method(D_METHOD("get_held_modification_stack"), &SkeletonModification2DStackHolder::get_held_modification_stack);
|
||||
}
|
||||
|
||||
SkeletonModification2DStackHolder::SkeletonModification2DStackHolder() {
|
||||
stack = nullptr;
|
||||
is_setup = false;
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
SkeletonModification2DStackHolder::~SkeletonModification2DStackHolder() {
|
||||
}
|
||||
@@ -0,0 +1,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();
|
||||
};
|
||||
@@ -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() {
|
||||
}
|
||||
104
scene/resources/2d/skeleton/skeleton_modification_2d_twoboneik.h
Normal file
104
scene/resources/2d/skeleton/skeleton_modification_2d_twoboneik.h
Normal 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();
|
||||
};
|
||||
270
scene/resources/2d/skeleton/skeleton_modification_stack_2d.cpp
Normal file
270
scene/resources/2d/skeleton/skeleton_modification_stack_2d.cpp
Normal 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() {
|
||||
}
|
||||
95
scene/resources/2d/skeleton/skeleton_modification_stack_2d.h
Normal file
95
scene/resources/2d/skeleton/skeleton_modification_stack_2d.h
Normal 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();
|
||||
};
|
||||
52
scene/resources/2d/tile_set.compat.inc
Normal file
52
scene/resources/2d/tile_set.compat.inc
Normal 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
|
||||
7172
scene/resources/2d/tile_set.cpp
Normal file
7172
scene/resources/2d/tile_set.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1041
scene/resources/2d/tile_set.h
Normal file
1041
scene/resources/2d/tile_set.h
Normal file
File diff suppressed because it is too large
Load Diff
160
scene/resources/2d/world_boundary_shape_2d.cpp
Normal file
160
scene/resources/2d/world_boundary_shape_2d.cpp
Normal 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();
|
||||
}
|
||||
61
scene/resources/2d/world_boundary_shape_2d.h
Normal file
61
scene/resources/2d/world_boundary_shape_2d.h
Normal 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
28
scene/resources/3d/SCsub
Normal 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")
|
||||
120
scene/resources/3d/box_shape_3d.cpp
Normal file
120
scene/resources/3d/box_shape_3d.cpp
Normal 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));
|
||||
}
|
||||
57
scene/resources/3d/box_shape_3d.h
Normal file
57
scene/resources/3d/box_shape_3d.h
Normal 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();
|
||||
};
|
||||
158
scene/resources/3d/capsule_shape_3d.cpp
Normal file
158
scene/resources/3d/capsule_shape_3d.cpp
Normal 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();
|
||||
}
|
||||
60
scene/resources/3d/capsule_shape_3d.h
Normal file
60
scene/resources/3d/capsule_shape_3d.h
Normal 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();
|
||||
};
|
||||
136
scene/resources/3d/concave_polygon_shape_3d.cpp
Normal file
136
scene/resources/3d/concave_polygon_shape_3d.cpp
Normal 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));
|
||||
}
|
||||
80
scene/resources/3d/concave_polygon_shape_3d.h
Normal file
80
scene/resources/3d/concave_polygon_shape_3d.h
Normal 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();
|
||||
};
|
||||
129
scene/resources/3d/convex_polygon_shape_3d.cpp
Normal file
129
scene/resources/3d/convex_polygon_shape_3d.cpp
Normal 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)) {
|
||||
}
|
||||
55
scene/resources/3d/convex_polygon_shape_3d.h
Normal file
55
scene/resources/3d/convex_polygon_shape_3d.h
Normal 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();
|
||||
};
|
||||
129
scene/resources/3d/cylinder_shape_3d.cpp
Normal file
129
scene/resources/3d/cylinder_shape_3d.cpp
Normal 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();
|
||||
}
|
||||
57
scene/resources/3d/cylinder_shape_3d.h
Normal file
57
scene/resources/3d/cylinder_shape_3d.h
Normal 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();
|
||||
};
|
||||
183
scene/resources/3d/fog_material.cpp
Normal file
183
scene/resources/3d/fog_material.cpp
Normal 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());
|
||||
}
|
||||
84
scene/resources/3d/fog_material.h
Normal file
84
scene/resources/3d/fog_material.h
Normal 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();
|
||||
};
|
||||
371
scene/resources/3d/height_map_shape_3d.cpp
Normal file
371
scene/resources/3d/height_map_shape_3d.cpp
Normal 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();
|
||||
}
|
||||
69
scene/resources/3d/height_map_shape_3d.h
Normal file
69
scene/resources/3d/height_map_shape_3d.h
Normal 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();
|
||||
};
|
||||
1240
scene/resources/3d/importer_mesh.cpp
Normal file
1240
scene/resources/3d/importer_mesh.cpp
Normal file
File diff suppressed because it is too large
Load Diff
137
scene/resources/3d/importer_mesh.h
Normal file
137
scene/resources/3d/importer_mesh.h
Normal 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();
|
||||
};
|
||||
406
scene/resources/3d/mesh_library.cpp
Normal file
406
scene/resources/3d/mesh_library.cpp
Normal 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() {
|
||||
}
|
||||
120
scene/resources/3d/mesh_library.h
Normal file
120
scene/resources/3d/mesh_library.h
Normal 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();
|
||||
};
|
||||
425
scene/resources/3d/navigation_mesh_source_geometry_data_3d.cpp
Normal file
425
scene/resources/3d/navigation_mesh_source_geometry_data_3d.cpp
Normal 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");
|
||||
}
|
||||
109
scene/resources/3d/navigation_mesh_source_geometry_data_3d.h
Normal file
109
scene/resources/3d/navigation_mesh_source_geometry_data_3d.h
Normal 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(); }
|
||||
};
|
||||
3977
scene/resources/3d/primitive_meshes.cpp
Normal file
3977
scene/resources/3d/primitive_meshes.cpp
Normal file
File diff suppressed because it is too large
Load Diff
678
scene/resources/3d/primitive_meshes.h
Normal file
678
scene/resources/3d/primitive_meshes.h
Normal 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)
|
||||
97
scene/resources/3d/separation_ray_shape_3d.cpp
Normal file
97
scene/resources/3d/separation_ray_shape_3d.cpp
Normal 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();
|
||||
}
|
||||
58
scene/resources/3d/separation_ray_shape_3d.h
Normal file
58
scene/resources/3d/separation_ray_shape_3d.h
Normal 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();
|
||||
};
|
||||
169
scene/resources/3d/shape_3d.cpp
Normal file
169
scene/resources/3d/shape_3d.cpp
Normal 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);
|
||||
}
|
||||
92
scene/resources/3d/shape_3d.h
Normal file
92
scene/resources/3d/shape_3d.h
Normal 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
162
scene/resources/3d/skin.cpp
Normal 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
86
scene/resources/3d/skin.h
Normal 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();
|
||||
};
|
||||
838
scene/resources/3d/sky_material.cpp
Normal file
838
scene/resources/3d/sky_material.cpp
Normal 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() {
|
||||
}
|
||||
236
scene/resources/3d/sky_material.h
Normal file
236
scene/resources/3d/sky_material.h
Normal 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();
|
||||
};
|
||||
106
scene/resources/3d/sphere_shape_3d.cpp
Normal file
106
scene/resources/3d/sphere_shape_3d.cpp
Normal 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);
|
||||
}
|
||||
55
scene/resources/3d/sphere_shape_3d.h
Normal file
55
scene/resources/3d/sphere_shape_3d.h
Normal 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();
|
||||
};
|
||||
210
scene/resources/3d/world_3d.cpp
Normal file
210
scene/resources/3d/world_3d.cpp
Normal 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
|
||||
}
|
||||
97
scene/resources/3d/world_3d.h
Normal file
97
scene/resources/3d/world_3d.h
Normal 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();
|
||||
};
|
||||
137
scene/resources/3d/world_boundary_shape_3d.cpp
Normal file
137
scene/resources/3d/world_boundary_shape_3d.cpp
Normal 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));
|
||||
}
|
||||
57
scene/resources/3d/world_boundary_shape_3d.h
Normal file
57
scene/resources/3d/world_boundary_shape_3d.h
Normal 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
35
scene/resources/SCsub
Normal 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")
|
||||
291
scene/resources/animated_texture.cpp
Normal file
291
scene/resources/animated_texture.cpp
Normal 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);
|
||||
}
|
||||
107
scene/resources/animated_texture.h
Normal file
107
scene/resources/animated_texture.h
Normal 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();
|
||||
};
|
||||
66
scene/resources/animation.compat.inc
Normal file
66
scene/resources/animation.compat.inc
Normal 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
|
||||
6517
scene/resources/animation.cpp
Normal file
6517
scene/resources/animation.cpp
Normal file
File diff suppressed because it is too large
Load Diff
590
scene/resources/animation.h
Normal file
590
scene/resources/animation.h
Normal 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
|
||||
179
scene/resources/animation_library.cpp
Normal file
179
scene/resources/animation_library.cpp
Normal 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() {
|
||||
}
|
||||
70
scene/resources/animation_library.h
Normal file
70
scene/resources/animation_library.h
Normal 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();
|
||||
};
|
||||
255
scene/resources/atlas_texture.cpp
Normal file
255
scene/resources/atlas_texture.cpp
Normal 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());
|
||||
}
|
||||
76
scene/resources/atlas_texture.h
Normal file
76
scene/resources/atlas_texture.h
Normal 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;
|
||||
};
|
||||
41
scene/resources/audio_stream_polyphonic.compat.inc
Normal file
41
scene/resources/audio_stream_polyphonic.compat.inc
Normal 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
|
||||
360
scene/resources/audio_stream_polyphonic.cpp
Normal file
360
scene/resources/audio_stream_polyphonic.cpp
Normal 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() {
|
||||
}
|
||||
135
scene/resources/audio_stream_polyphonic.h
Normal file
135
scene/resources/audio_stream_polyphonic.h
Normal 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();
|
||||
};
|
||||
1255
scene/resources/audio_stream_wav.cpp
Normal file
1255
scene/resources/audio_stream_wav.cpp
Normal file
File diff suppressed because it is too large
Load Diff
295
scene/resources/audio_stream_wav.h
Normal file
295
scene/resources/audio_stream_wav.h
Normal 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
729
scene/resources/bit_map.cpp
Normal 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
Reference in New Issue
Block a user