pandemonium_engine/modules/steering_ai/gsai_path.cpp

202 lines
6.9 KiB
C++

/*************************************************************************/
/* gsai_path.cpp */
/*************************************************************************/
/* This file is part of: */
/* PANDEMONIUM ENGINE */
/* https://github.com/Relintai/pandemonium_engine */
/*************************************************************************/
/* Copyright (c) 2022-present Péter Magyar. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 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 "gsai_path.h"
bool GSAIPath::get_is_open() const {
return is_open;
}
void GSAIPath::set_is_open(const bool val) {
is_open = val;
}
float GSAIPath::get_length() const {
return length;
}
void GSAIPath::set_length(const float val) {
length = val;
}
void GSAIPath::initialize(const PoolVector3Array &waypoints, const bool _is_open) {
is_open = _is_open;
create_path(waypoints);
if (waypoints.size() > 0) {
_nearest_point_on_segment = waypoints[0];
_nearest_point_on_path = waypoints[0];
} else {
_nearest_point_on_segment = Vector3();
_nearest_point_on_path = Vector3();
}
}
void GSAIPath::create_path(const PoolVector3Array &waypoints) {
ERR_FAIL_COND_MSG(waypoints.size() < 2, "Waypoints must contain at least two (2) waypoints.");
_segments.clear();
length = 0;
Vector3 current = waypoints[0];
Vector3 previous;
for (int i = 1; i < waypoints.size(); ++i) {
previous = current;
if (i < waypoints.size()) {
current = waypoints[i];
} else if (is_open) {
break;
} else {
current = waypoints[0];
}
GSAISegment segment(previous, current);
length += segment.length;
segment.cumulative_length = length;
_segments.push_back(segment);
}
}
float GSAIPath::calculate_distance(const Vector3 &agent_current_position) {
if (_segments.size() == 0) {
return 0;
}
float smallest_distance_squared = Math_INF;
const GSAISegment *nearest_segment = NULL;
for (int i = 0; i < _segments.size(); ++i) {
const GSAISegment &segment = _segments[i];
float distance_squared = _calculate_point_segment_distance_squared(segment.begin, segment.end, agent_current_position);
if (distance_squared < smallest_distance_squared) {
_nearest_point_on_path = _nearest_point_on_segment;
smallest_distance_squared = distance_squared;
nearest_segment = &segment;
}
}
float length_on_path = nearest_segment->cumulative_length - _nearest_point_on_path.distance_to(nearest_segment->end);
return length_on_path;
}
Vector3 GSAIPath::calculate_target_position(float target_distance) {
if (_segments.size() == 0) {
return Vector3();
}
if (is_open) {
target_distance = CLAMP(target_distance, 0, length);
} else {
if (target_distance < 0) {
target_distance = length + fmod(target_distance, length);
} else if (target_distance > length) {
target_distance = fmod(target_distance, length);
}
}
const GSAISegment *desired_segment = NULL;
for (int i = 0; i < _segments.size(); ++i) {
const GSAISegment &segment = _segments[i];
if (segment.cumulative_length >= target_distance) {
desired_segment = &segment;
break;
}
}
if (!desired_segment) {
desired_segment = &_segments[_segments.size() - 1];
}
float distance = desired_segment->cumulative_length - target_distance;
return (((desired_segment->begin - desired_segment->end) * (distance / desired_segment->length)) + desired_segment->end);
}
Vector3 GSAIPath::get_start_point() {
if (_segments.size() == 0) {
return Vector3();
}
return _segments[0].begin;
}
Vector3 GSAIPath::get_end_point() {
if (_segments.size() == 0) {
return Vector3();
}
return _segments[_segments.size() - 1].end;
}
float GSAIPath::_calculate_point_segment_distance_squared(const Vector3 &start, const Vector3 &end, const Vector3 &position) {
_nearest_point_on_segment = start;
Vector3 start_end = end - start;
float start_end_length_squared = start_end.length_squared();
if (start_end_length_squared != 0) {
float t = (position - start).dot(start_end) / start_end_length_squared;
_nearest_point_on_segment += start_end * CLAMP(t, 0, 1);
}
return _nearest_point_on_segment.distance_squared_to(position);
}
GSAIPath::GSAIPath() {
is_open = false;
length = 0;
}
GSAIPath::~GSAIPath() {
}
void GSAIPath::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_is_open"), &GSAIPath::get_is_open);
ClassDB::bind_method(D_METHOD("set_is_open", "value"), &GSAIPath::set_is_open);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "is_open"), "set_is_open", "get_is_open");
ClassDB::bind_method(D_METHOD("get_length"), &GSAIPath::get_length);
ClassDB::bind_method(D_METHOD("set_length", "value"), &GSAIPath::set_length);
ADD_PROPERTY(PropertyInfo(Variant::REAL, "length"), "set_length", "get_length");
ClassDB::bind_method(D_METHOD("initialize", "waypoints", "is_open"), &GSAIPath::initialize, false);
ClassDB::bind_method(D_METHOD("create_path", "waypoints"), &GSAIPath::create_path);
ClassDB::bind_method(D_METHOD("calculate_distance", "agent_current_position"), &GSAIPath::calculate_distance);
ClassDB::bind_method(D_METHOD("calculate_target_position", "target_distance"), &GSAIPath::calculate_target_position);
ClassDB::bind_method(D_METHOD("get_start_point"), &GSAIPath::get_start_point);
ClassDB::bind_method(D_METHOD("get_end_point"), &GSAIPath::get_end_point);
}