2014-02-10 01:10:30 +00:00
|
|
|
/*************************************************************************/
|
2021-10-18 19:24:30 +00:00
|
|
|
/* godot_shape_2d.cpp */
|
2014-02-10 01:10:30 +00:00
|
|
|
/*************************************************************************/
|
|
|
|
/* This file is part of: */
|
|
|
|
/* GODOT ENGINE */
|
2017-08-27 12:16:55 +00:00
|
|
|
/* https://godotengine.org */
|
2014-02-10 01:10:30 +00:00
|
|
|
/*************************************************************************/
|
2022-01-03 20:27:34 +00:00
|
|
|
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
|
|
|
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
2014-02-10 01:10:30 +00:00
|
|
|
/* */
|
|
|
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
|
|
|
/* a copy of this software and associated documentation files (the */
|
|
|
|
/* "Software"), to deal in the Software without restriction, including */
|
|
|
|
/* without limitation the rights to use, copy, modify, merge, publish, */
|
|
|
|
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
|
|
|
/* permit persons to whom the Software is furnished to do so, subject to */
|
|
|
|
/* the following conditions: */
|
|
|
|
/* */
|
|
|
|
/* The above copyright notice and this permission notice shall be */
|
|
|
|
/* included in all copies or substantial portions of the Software. */
|
|
|
|
/* */
|
|
|
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
|
|
|
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
|
|
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
|
|
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
|
|
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
|
|
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
|
|
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
|
|
/*************************************************************************/
|
2018-01-04 23:50:27 +00:00
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
#include "godot_shape_2d.h"
|
2017-08-27 19:07:15 +00:00
|
|
|
|
2020-05-25 17:20:45 +00:00
|
|
|
#include "core/math/geometry_2d.h"
|
2020-11-07 22:33:38 +00:00
|
|
|
#include "core/templates/sort_array.h"
|
2014-02-19 14:57:14 +00:00
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
void GodotShape2D::configure(const Rect2 &p_aabb) {
|
2017-03-05 15:44:50 +00:00
|
|
|
aabb = p_aabb;
|
|
|
|
configured = true;
|
2021-10-18 19:24:30 +00:00
|
|
|
for (const KeyValue<GodotShapeOwner2D *, int> &E : owners) {
|
|
|
|
GodotShapeOwner2D *co = (GodotShapeOwner2D *)E.key;
|
2014-02-19 14:57:14 +00:00
|
|
|
co->_shape_changed();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
Vector2 GodotShape2D::get_support(const Vector2 &p_normal) const {
|
2014-02-19 14:57:14 +00:00
|
|
|
Vector2 res[2];
|
|
|
|
int amnt;
|
2017-03-05 15:44:50 +00:00
|
|
|
get_supports(p_normal, res, amnt);
|
2014-02-19 14:57:14 +00:00
|
|
|
return res[0];
|
|
|
|
}
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
void GodotShape2D::add_owner(GodotShapeOwner2D *p_owner) {
|
|
|
|
Map<GodotShapeOwner2D *, int>::Element *E = owners.find(p_owner);
|
2014-02-19 14:57:14 +00:00
|
|
|
if (E) {
|
|
|
|
E->get()++;
|
|
|
|
} else {
|
2017-03-05 15:44:50 +00:00
|
|
|
owners[p_owner] = 1;
|
2014-02-19 14:57:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
void GodotShape2D::remove_owner(GodotShapeOwner2D *p_owner) {
|
|
|
|
Map<GodotShapeOwner2D *, int>::Element *E = owners.find(p_owner);
|
2014-02-19 14:57:14 +00:00
|
|
|
ERR_FAIL_COND(!E);
|
|
|
|
E->get()--;
|
2017-03-05 15:44:50 +00:00
|
|
|
if (E->get() == 0) {
|
2014-02-19 14:57:14 +00:00
|
|
|
owners.erase(E);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
bool GodotShape2D::is_owner(GodotShapeOwner2D *p_owner) const {
|
2014-02-19 14:57:14 +00:00
|
|
|
return owners.has(p_owner);
|
|
|
|
}
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
const Map<GodotShapeOwner2D *, int> &GodotShape2D::get_owners() const {
|
2014-02-19 14:57:14 +00:00
|
|
|
return owners;
|
|
|
|
}
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
GodotShape2D::~GodotShape2D() {
|
2014-02-19 14:57:14 +00:00
|
|
|
ERR_FAIL_COND(owners.size());
|
|
|
|
}
|
|
|
|
|
|
|
|
/*********************************************************/
|
|
|
|
/*********************************************************/
|
|
|
|
/*********************************************************/
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
void GodotWorldBoundaryShape2D::get_supports(const Vector2 &p_normal, Vector2 *r_supports, int &r_amount) const {
|
2017-03-05 15:44:50 +00:00
|
|
|
r_amount = 0;
|
2014-02-19 14:57:14 +00:00
|
|
|
}
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
bool GodotWorldBoundaryShape2D::contains_point(const Vector2 &p_point) const {
|
2015-03-22 04:46:18 +00:00
|
|
|
return normal.dot(p_point) < d;
|
|
|
|
}
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
bool GodotWorldBoundaryShape2D::intersect_segment(const Vector2 &p_begin, const Vector2 &p_end, Vector2 &r_point, Vector2 &r_normal) const {
|
2017-03-05 15:44:50 +00:00
|
|
|
Vector2 segment = p_begin - p_end;
|
|
|
|
real_t den = normal.dot(segment);
|
2014-02-19 14:57:14 +00:00
|
|
|
|
|
|
|
//printf("den is %i\n",den);
|
2017-03-05 15:44:50 +00:00
|
|
|
if (Math::abs(den) <= CMP_EPSILON) {
|
2014-02-19 14:57:14 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-03-05 15:44:50 +00:00
|
|
|
real_t dist = (normal.dot(p_begin) - d) / den;
|
2014-02-19 14:57:14 +00:00
|
|
|
//printf("dist is %i\n",dist);
|
|
|
|
|
2017-03-05 15:44:50 +00:00
|
|
|
if (dist < -CMP_EPSILON || dist > (1.0 + CMP_EPSILON)) {
|
2014-02-19 14:57:14 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
r_point = p_begin + segment * -dist;
|
2017-03-05 15:44:50 +00:00
|
|
|
r_normal = normal;
|
2014-02-19 14:57:14 +00:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
real_t GodotWorldBoundaryShape2D::get_moment_of_inertia(real_t p_mass, const Size2 &p_scale) const {
|
2014-02-19 14:57:14 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
void GodotWorldBoundaryShape2D::set_data(const Variant &p_data) {
|
2017-03-05 15:44:50 +00:00
|
|
|
ERR_FAIL_COND(p_data.get_type() != Variant::ARRAY);
|
2014-02-19 14:57:14 +00:00
|
|
|
Array arr = p_data;
|
2017-03-05 15:44:50 +00:00
|
|
|
ERR_FAIL_COND(arr.size() != 2);
|
|
|
|
normal = arr[0];
|
|
|
|
d = arr[1];
|
|
|
|
configure(Rect2(Vector2(-1e4, -1e4), Vector2(1e4 * 2, 1e4 * 2)));
|
2014-02-19 14:57:14 +00:00
|
|
|
}
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
Variant GodotWorldBoundaryShape2D::get_data() const {
|
2014-02-19 14:57:14 +00:00
|
|
|
Array arr;
|
|
|
|
arr.resize(2);
|
2017-03-05 15:44:50 +00:00
|
|
|
arr[0] = normal;
|
|
|
|
arr[1] = d;
|
2014-02-19 14:57:14 +00:00
|
|
|
return arr;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*********************************************************/
|
|
|
|
/*********************************************************/
|
|
|
|
/*********************************************************/
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
void GodotSeparationRayShape2D::get_supports(const Vector2 &p_normal, Vector2 *r_supports, int &r_amount) const {
|
2021-08-17 17:15:11 +00:00
|
|
|
r_amount = 1;
|
|
|
|
|
|
|
|
if (p_normal.y > 0) {
|
|
|
|
*r_supports = Vector2(0, length);
|
|
|
|
} else {
|
|
|
|
*r_supports = Vector2();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
bool GodotSeparationRayShape2D::contains_point(const Vector2 &p_point) const {
|
2021-08-17 17:15:11 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
bool GodotSeparationRayShape2D::intersect_segment(const Vector2 &p_begin, const Vector2 &p_end, Vector2 &r_point, Vector2 &r_normal) const {
|
2021-08-17 17:15:11 +00:00
|
|
|
return false; //rays can't be intersected
|
|
|
|
}
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
real_t GodotSeparationRayShape2D::get_moment_of_inertia(real_t p_mass, const Size2 &p_scale) const {
|
2021-08-17 17:15:11 +00:00
|
|
|
return 0; //rays are mass-less
|
|
|
|
}
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
void GodotSeparationRayShape2D::set_data(const Variant &p_data) {
|
2021-08-17 17:15:11 +00:00
|
|
|
Dictionary d = p_data;
|
|
|
|
length = d["length"];
|
2021-08-19 18:02:40 +00:00
|
|
|
slide_on_slope = d["slide_on_slope"];
|
2021-08-17 17:15:11 +00:00
|
|
|
configure(Rect2(0, 0, 0.001, length));
|
|
|
|
}
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
Variant GodotSeparationRayShape2D::get_data() const {
|
2021-08-17 17:15:11 +00:00
|
|
|
Dictionary d;
|
|
|
|
d["length"] = length;
|
2021-08-19 18:02:40 +00:00
|
|
|
d["slide_on_slope"] = slide_on_slope;
|
2021-08-17 17:15:11 +00:00
|
|
|
return d;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*********************************************************/
|
|
|
|
/*********************************************************/
|
|
|
|
/*********************************************************/
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
void GodotSegmentShape2D::get_supports(const Vector2 &p_normal, Vector2 *r_supports, int &r_amount) const {
|
2017-07-08 15:12:18 +00:00
|
|
|
if (Math::abs(p_normal.dot(n)) > _SEGMENT_IS_VALID_SUPPORT_THRESHOLD) {
|
2017-03-05 15:44:50 +00:00
|
|
|
r_supports[0] = a;
|
|
|
|
r_supports[1] = b;
|
|
|
|
r_amount = 2;
|
2014-02-19 14:57:14 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-03-05 15:44:50 +00:00
|
|
|
real_t dp = p_normal.dot(b - a);
|
2020-05-14 14:41:43 +00:00
|
|
|
if (dp > 0) {
|
2017-03-05 15:44:50 +00:00
|
|
|
*r_supports = b;
|
2020-05-14 14:41:43 +00:00
|
|
|
} else {
|
2017-03-05 15:44:50 +00:00
|
|
|
*r_supports = a;
|
2020-05-14 14:41:43 +00:00
|
|
|
}
|
2017-03-05 15:44:50 +00:00
|
|
|
r_amount = 1;
|
2014-02-19 14:57:14 +00:00
|
|
|
}
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
bool GodotSegmentShape2D::contains_point(const Vector2 &p_point) const {
|
2015-03-22 04:46:18 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
bool GodotSegmentShape2D::intersect_segment(const Vector2 &p_begin, const Vector2 &p_end, Vector2 &r_point, Vector2 &r_normal) const {
|
2020-05-25 17:20:45 +00:00
|
|
|
if (!Geometry2D::segment_intersects_segment(p_begin, p_end, a, b, &r_point)) {
|
2014-02-19 14:57:14 +00:00
|
|
|
return false;
|
2020-05-14 14:41:43 +00:00
|
|
|
}
|
2014-02-19 14:57:14 +00:00
|
|
|
|
|
|
|
if (n.dot(p_begin) > n.dot(a)) {
|
2017-03-05 15:44:50 +00:00
|
|
|
r_normal = n;
|
2014-02-19 14:57:14 +00:00
|
|
|
} else {
|
2017-03-05 15:44:50 +00:00
|
|
|
r_normal = -n;
|
2014-02-19 14:57:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
real_t GodotSegmentShape2D::get_moment_of_inertia(real_t p_mass, const Size2 &p_scale) const {
|
2019-05-20 04:19:14 +00:00
|
|
|
return p_mass * ((a * p_scale).distance_squared_to(b * p_scale)) / 12;
|
2014-02-19 14:57:14 +00:00
|
|
|
}
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
void GodotSegmentShape2D::set_data(const Variant &p_data) {
|
2017-03-05 15:44:50 +00:00
|
|
|
ERR_FAIL_COND(p_data.get_type() != Variant::RECT2);
|
2014-02-19 14:57:14 +00:00
|
|
|
|
|
|
|
Rect2 r = p_data;
|
2017-06-03 22:25:13 +00:00
|
|
|
a = r.position;
|
2017-03-05 15:44:50 +00:00
|
|
|
b = r.size;
|
2020-12-06 18:16:06 +00:00
|
|
|
n = (b - a).orthogonal();
|
2014-02-19 14:57:14 +00:00
|
|
|
|
|
|
|
Rect2 aabb;
|
2017-06-03 22:25:13 +00:00
|
|
|
aabb.position = a;
|
2014-02-19 14:57:14 +00:00
|
|
|
aabb.expand_to(b);
|
2020-05-14 14:41:43 +00:00
|
|
|
if (aabb.size.x == 0) {
|
2017-03-05 15:44:50 +00:00
|
|
|
aabb.size.x = 0.001;
|
2020-05-14 14:41:43 +00:00
|
|
|
}
|
|
|
|
if (aabb.size.y == 0) {
|
2017-03-05 15:44:50 +00:00
|
|
|
aabb.size.y = 0.001;
|
2020-05-14 14:41:43 +00:00
|
|
|
}
|
2014-02-19 14:57:14 +00:00
|
|
|
configure(aabb);
|
|
|
|
}
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
Variant GodotSegmentShape2D::get_data() const {
|
2014-02-19 14:57:14 +00:00
|
|
|
Rect2 r;
|
2017-06-03 22:25:13 +00:00
|
|
|
r.position = a;
|
2017-03-05 15:44:50 +00:00
|
|
|
r.size = b;
|
2014-02-19 14:57:14 +00:00
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*********************************************************/
|
|
|
|
/*********************************************************/
|
|
|
|
/*********************************************************/
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
void GodotCircleShape2D::get_supports(const Vector2 &p_normal, Vector2 *r_supports, int &r_amount) const {
|
2017-03-05 15:44:50 +00:00
|
|
|
r_amount = 1;
|
|
|
|
*r_supports = p_normal * radius;
|
2014-02-19 14:57:14 +00:00
|
|
|
}
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
bool GodotCircleShape2D::contains_point(const Vector2 &p_point) const {
|
2017-03-05 15:44:50 +00:00
|
|
|
return p_point.length_squared() < radius * radius;
|
2015-03-22 04:46:18 +00:00
|
|
|
}
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
bool GodotCircleShape2D::intersect_segment(const Vector2 &p_begin, const Vector2 &p_end, Vector2 &r_point, Vector2 &r_normal) const {
|
2014-02-19 14:57:14 +00:00
|
|
|
Vector2 line_vec = p_end - p_begin;
|
|
|
|
|
|
|
|
real_t a, b, c;
|
|
|
|
|
|
|
|
a = line_vec.dot(line_vec);
|
|
|
|
b = 2 * p_begin.dot(line_vec);
|
|
|
|
c = p_begin.dot(p_begin) - radius * radius;
|
|
|
|
|
2017-03-05 15:44:50 +00:00
|
|
|
real_t sqrtterm = b * b - 4 * a * c;
|
2014-02-19 14:57:14 +00:00
|
|
|
|
2020-05-14 14:41:43 +00:00
|
|
|
if (sqrtterm < 0) {
|
2014-02-19 14:57:14 +00:00
|
|
|
return false;
|
2020-05-14 14:41:43 +00:00
|
|
|
}
|
2014-02-19 14:57:14 +00:00
|
|
|
sqrtterm = Math::sqrt(sqrtterm);
|
2017-03-05 15:44:50 +00:00
|
|
|
real_t res = (-b - sqrtterm) / (2 * a);
|
2014-02-19 14:57:14 +00:00
|
|
|
|
2017-03-05 15:44:50 +00:00
|
|
|
if (res < 0 || res > 1 + CMP_EPSILON) {
|
2014-02-19 14:57:14 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-03-05 15:44:50 +00:00
|
|
|
r_point = p_begin + line_vec * res;
|
|
|
|
r_normal = r_point.normalized();
|
2014-02-19 14:57:14 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
real_t GodotCircleShape2D::get_moment_of_inertia(real_t p_mass, const Size2 &p_scale) const {
|
2019-05-20 04:19:14 +00:00
|
|
|
real_t a = radius * p_scale.x;
|
|
|
|
real_t b = radius * p_scale.y;
|
|
|
|
return p_mass * (a * a + b * b) / 4;
|
2014-02-19 14:57:14 +00:00
|
|
|
}
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
void GodotCircleShape2D::set_data(const Variant &p_data) {
|
2014-02-19 14:57:14 +00:00
|
|
|
ERR_FAIL_COND(!p_data.is_num());
|
2017-03-05 15:44:50 +00:00
|
|
|
radius = p_data;
|
|
|
|
configure(Rect2(-radius, -radius, radius * 2, radius * 2));
|
2014-02-19 14:57:14 +00:00
|
|
|
}
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
Variant GodotCircleShape2D::get_data() const {
|
2014-02-19 14:57:14 +00:00
|
|
|
return radius;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*********************************************************/
|
|
|
|
/*********************************************************/
|
|
|
|
/*********************************************************/
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
void GodotRectangleShape2D::get_supports(const Vector2 &p_normal, Vector2 *r_supports, int &r_amount) const {
|
2017-03-05 15:44:50 +00:00
|
|
|
for (int i = 0; i < 2; i++) {
|
2014-02-19 14:57:14 +00:00
|
|
|
Vector2 ag;
|
2017-03-05 15:44:50 +00:00
|
|
|
ag[i] = 1.0;
|
2017-02-13 23:25:05 +00:00
|
|
|
real_t dp = ag.dot(p_normal);
|
2020-05-14 14:41:43 +00:00
|
|
|
if (Math::abs(dp) < _SEGMENT_IS_VALID_SUPPORT_THRESHOLD) {
|
2014-02-19 14:57:14 +00:00
|
|
|
continue;
|
2020-05-14 14:41:43 +00:00
|
|
|
}
|
2014-02-19 14:57:14 +00:00
|
|
|
|
2017-03-05 15:44:50 +00:00
|
|
|
real_t sgn = dp > 0 ? 1.0 : -1.0;
|
2014-02-19 14:57:14 +00:00
|
|
|
|
2017-03-05 15:44:50 +00:00
|
|
|
r_amount = 2;
|
2014-02-19 14:57:14 +00:00
|
|
|
|
2017-03-05 15:44:50 +00:00
|
|
|
r_supports[0][i] = half_extents[i] * sgn;
|
|
|
|
r_supports[0][i ^ 1] = half_extents[i ^ 1];
|
2014-02-19 14:57:14 +00:00
|
|
|
|
2017-03-05 15:44:50 +00:00
|
|
|
r_supports[1][i] = half_extents[i] * sgn;
|
|
|
|
r_supports[1][i ^ 1] = -half_extents[i ^ 1];
|
2014-02-19 14:57:14 +00:00
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* USE POINT */
|
|
|
|
|
2017-03-05 15:44:50 +00:00
|
|
|
r_amount = 1;
|
|
|
|
r_supports[0] = Vector2(
|
|
|
|
(p_normal.x < 0) ? -half_extents.x : half_extents.x,
|
|
|
|
(p_normal.y < 0) ? -half_extents.y : half_extents.y);
|
2014-02-19 14:57:14 +00:00
|
|
|
}
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
bool GodotRectangleShape2D::contains_point(const Vector2 &p_point) const {
|
2021-01-28 06:34:26 +00:00
|
|
|
real_t x = p_point.x;
|
|
|
|
real_t y = p_point.y;
|
|
|
|
real_t edge_x = half_extents.x;
|
|
|
|
real_t edge_y = half_extents.y;
|
2019-02-20 22:25:39 +00:00
|
|
|
return (x >= -edge_x) && (x < edge_x) && (y >= -edge_y) && (y < edge_y);
|
2015-03-22 04:46:18 +00:00
|
|
|
}
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
bool GodotRectangleShape2D::intersect_segment(const Vector2 &p_begin, const Vector2 &p_end, Vector2 &r_point, Vector2 &r_normal) const {
|
2017-03-05 15:44:50 +00:00
|
|
|
return get_aabb().intersects_segment(p_begin, p_end, &r_point, &r_normal);
|
2014-02-19 14:57:14 +00:00
|
|
|
}
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
real_t GodotRectangleShape2D::get_moment_of_inertia(real_t p_mass, const Size2 &p_scale) const {
|
2017-03-05 15:44:50 +00:00
|
|
|
Vector2 he2 = half_extents * 2 * p_scale;
|
|
|
|
return p_mass * he2.dot(he2) / 12.0;
|
2014-02-19 14:57:14 +00:00
|
|
|
}
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
void GodotRectangleShape2D::set_data(const Variant &p_data) {
|
2017-03-05 15:44:50 +00:00
|
|
|
ERR_FAIL_COND(p_data.get_type() != Variant::VECTOR2);
|
2014-02-19 14:57:14 +00:00
|
|
|
|
2017-03-05 15:44:50 +00:00
|
|
|
half_extents = p_data;
|
|
|
|
configure(Rect2(-half_extents, half_extents * 2.0));
|
2014-02-19 14:57:14 +00:00
|
|
|
}
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
Variant GodotRectangleShape2D::get_data() const {
|
2014-02-19 14:57:14 +00:00
|
|
|
return half_extents;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*********************************************************/
|
|
|
|
/*********************************************************/
|
|
|
|
/*********************************************************/
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
void GodotCapsuleShape2D::get_supports(const Vector2 &p_normal, Vector2 *r_supports, int &r_amount) const {
|
2017-03-05 15:44:50 +00:00
|
|
|
Vector2 n = p_normal;
|
2014-02-19 14:57:14 +00:00
|
|
|
|
2017-02-13 23:25:05 +00:00
|
|
|
real_t d = n.y;
|
2014-02-19 14:57:14 +00:00
|
|
|
|
2017-07-08 15:12:18 +00:00
|
|
|
if (Math::abs(d) < (1.0 - _SEGMENT_IS_VALID_SUPPORT_THRESHOLD)) {
|
2014-02-19 14:57:14 +00:00
|
|
|
// make it flat
|
2017-03-05 15:44:50 +00:00
|
|
|
n.y = 0.0;
|
2014-02-19 14:57:14 +00:00
|
|
|
n.normalize();
|
2017-03-05 15:44:50 +00:00
|
|
|
n *= radius;
|
2014-02-19 14:57:14 +00:00
|
|
|
|
2017-03-05 15:44:50 +00:00
|
|
|
r_amount = 2;
|
|
|
|
r_supports[0] = n;
|
2020-08-30 21:15:00 +00:00
|
|
|
r_supports[0].y += height * 0.5 - radius;
|
2017-03-05 15:44:50 +00:00
|
|
|
r_supports[1] = n;
|
2020-08-30 21:15:00 +00:00
|
|
|
r_supports[1].y -= height * 0.5 - radius;
|
2014-02-19 14:57:14 +00:00
|
|
|
|
|
|
|
} else {
|
2020-08-30 21:15:00 +00:00
|
|
|
real_t h = height * 0.5 - radius;
|
2014-02-19 14:57:14 +00:00
|
|
|
|
2017-03-05 15:44:50 +00:00
|
|
|
n *= radius;
|
2020-08-30 21:15:00 +00:00
|
|
|
n.y += (d > 0) ? h : -h;
|
2017-03-05 15:44:50 +00:00
|
|
|
r_amount = 1;
|
|
|
|
*r_supports = n;
|
2014-02-19 14:57:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
bool GodotCapsuleShape2D::contains_point(const Vector2 &p_point) const {
|
2015-03-22 04:46:18 +00:00
|
|
|
Vector2 p = p_point;
|
2017-03-05 15:44:50 +00:00
|
|
|
p.y = Math::abs(p.y);
|
2020-08-30 21:15:00 +00:00
|
|
|
p.y -= height * 0.5 - radius;
|
2020-05-14 14:41:43 +00:00
|
|
|
if (p.y < 0) {
|
2017-03-05 15:44:50 +00:00
|
|
|
p.y = 0;
|
2020-05-14 14:41:43 +00:00
|
|
|
}
|
2015-03-22 04:46:18 +00:00
|
|
|
|
2017-03-05 15:44:50 +00:00
|
|
|
return p.length_squared() < radius * radius;
|
2015-03-22 04:46:18 +00:00
|
|
|
}
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
bool GodotCapsuleShape2D::intersect_segment(const Vector2 &p_begin, const Vector2 &p_end, Vector2 &r_point, Vector2 &r_normal) const {
|
2017-02-13 23:25:05 +00:00
|
|
|
real_t d = 1e10;
|
2017-03-05 15:44:50 +00:00
|
|
|
Vector2 n = (p_end - p_begin).normalized();
|
|
|
|
bool collided = false;
|
2014-02-19 14:57:14 +00:00
|
|
|
|
|
|
|
//try spheres
|
2017-03-05 15:44:50 +00:00
|
|
|
for (int i = 0; i < 2; i++) {
|
2014-02-19 14:57:14 +00:00
|
|
|
Vector2 begin = p_begin;
|
|
|
|
Vector2 end = p_end;
|
2020-08-30 21:15:00 +00:00
|
|
|
real_t ofs = (i == 0) ? -height * 0.5 + radius : height * 0.5 - radius;
|
2017-03-05 15:44:50 +00:00
|
|
|
begin.y += ofs;
|
|
|
|
end.y += ofs;
|
2014-02-19 14:57:14 +00:00
|
|
|
|
|
|
|
Vector2 line_vec = end - begin;
|
|
|
|
|
|
|
|
real_t a, b, c;
|
|
|
|
|
|
|
|
a = line_vec.dot(line_vec);
|
|
|
|
b = 2 * begin.dot(line_vec);
|
|
|
|
c = begin.dot(begin) - radius * radius;
|
|
|
|
|
2017-03-05 15:44:50 +00:00
|
|
|
real_t sqrtterm = b * b - 4 * a * c;
|
2014-02-19 14:57:14 +00:00
|
|
|
|
2020-05-14 14:41:43 +00:00
|
|
|
if (sqrtterm < 0) {
|
2014-02-19 14:57:14 +00:00
|
|
|
continue;
|
2020-05-14 14:41:43 +00:00
|
|
|
}
|
2014-02-19 14:57:14 +00:00
|
|
|
|
|
|
|
sqrtterm = Math::sqrt(sqrtterm);
|
2017-03-05 15:44:50 +00:00
|
|
|
real_t res = (-b - sqrtterm) / (2 * a);
|
2014-02-19 14:57:14 +00:00
|
|
|
|
2017-03-05 15:44:50 +00:00
|
|
|
if (res < 0 || res > 1 + CMP_EPSILON) {
|
2014-02-19 14:57:14 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2017-03-05 15:44:50 +00:00
|
|
|
Vector2 point = begin + line_vec * res;
|
|
|
|
Vector2 pointf(point.x, point.y - ofs);
|
2014-02-19 14:57:14 +00:00
|
|
|
real_t pd = n.dot(pointf);
|
2017-03-05 15:44:50 +00:00
|
|
|
if (pd < d) {
|
|
|
|
r_point = pointf;
|
|
|
|
r_normal = point.normalized();
|
|
|
|
d = pd;
|
|
|
|
collided = true;
|
2014-02-19 14:57:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-05 15:44:50 +00:00
|
|
|
Vector2 rpos, rnorm;
|
2020-08-30 21:15:00 +00:00
|
|
|
if (Rect2(Point2(-radius, -height * 0.5 + radius), Size2(radius * 2.0, height - radius * 2)).intersects_segment(p_begin, p_end, &rpos, &rnorm)) {
|
2014-02-19 14:57:14 +00:00
|
|
|
real_t pd = n.dot(rpos);
|
2017-03-05 15:44:50 +00:00
|
|
|
if (pd < d) {
|
|
|
|
r_point = rpos;
|
|
|
|
r_normal = rnorm;
|
|
|
|
d = pd;
|
|
|
|
collided = true;
|
2014-02-19 14:57:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//return get_aabb().intersects_segment(p_begin,p_end,&r_point,&r_normal);
|
|
|
|
return collided; //todo
|
|
|
|
}
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
real_t GodotCapsuleShape2D::get_moment_of_inertia(real_t p_mass, const Size2 &p_scale) const {
|
2020-08-30 21:15:00 +00:00
|
|
|
Vector2 he2 = Vector2(radius * 2, height) * p_scale;
|
2017-03-05 15:44:50 +00:00
|
|
|
return p_mass * he2.dot(he2) / 12.0;
|
2014-02-19 14:57:14 +00:00
|
|
|
}
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
void GodotCapsuleShape2D::set_data(const Variant &p_data) {
|
2017-03-05 15:44:50 +00:00
|
|
|
ERR_FAIL_COND(p_data.get_type() != Variant::ARRAY && p_data.get_type() != Variant::VECTOR2);
|
2014-02-19 14:57:14 +00:00
|
|
|
|
2017-03-05 15:44:50 +00:00
|
|
|
if (p_data.get_type() == Variant::ARRAY) {
|
|
|
|
Array arr = p_data;
|
|
|
|
ERR_FAIL_COND(arr.size() != 2);
|
|
|
|
height = arr[0];
|
|
|
|
radius = arr[1];
|
2014-02-19 14:57:14 +00:00
|
|
|
} else {
|
|
|
|
Point2 p = p_data;
|
2017-03-05 15:44:50 +00:00
|
|
|
radius = p.x;
|
|
|
|
height = p.y;
|
2014-02-19 14:57:14 +00:00
|
|
|
}
|
|
|
|
|
2020-08-30 21:15:00 +00:00
|
|
|
Point2 he(radius, height * 0.5);
|
2017-03-05 15:44:50 +00:00
|
|
|
configure(Rect2(-he, he * 2));
|
2014-02-19 14:57:14 +00:00
|
|
|
}
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
Variant GodotCapsuleShape2D::get_data() const {
|
2017-03-05 15:44:50 +00:00
|
|
|
return Point2(height, radius);
|
2014-02-19 14:57:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*********************************************************/
|
|
|
|
/*********************************************************/
|
|
|
|
/*********************************************************/
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
void GodotConvexPolygonShape2D::get_supports(const Vector2 &p_normal, Vector2 *r_supports, int &r_amount) const {
|
2017-03-05 15:44:50 +00:00
|
|
|
int support_idx = -1;
|
|
|
|
real_t d = -1e10;
|
2021-04-06 15:18:16 +00:00
|
|
|
r_amount = 0;
|
2014-02-19 14:57:14 +00:00
|
|
|
|
2017-03-05 15:44:50 +00:00
|
|
|
for (int i = 0; i < point_count; i++) {
|
2014-02-19 14:57:14 +00:00
|
|
|
//test point
|
|
|
|
real_t ld = p_normal.dot(points[i].pos);
|
2017-03-05 15:44:50 +00:00
|
|
|
if (ld > d) {
|
|
|
|
support_idx = i;
|
|
|
|
d = ld;
|
2014-02-19 14:57:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//test segment
|
2017-07-08 15:12:18 +00:00
|
|
|
if (points[i].normal.dot(p_normal) > _SEGMENT_IS_VALID_SUPPORT_THRESHOLD) {
|
2017-03-05 15:44:50 +00:00
|
|
|
r_amount = 2;
|
|
|
|
r_supports[0] = points[i].pos;
|
|
|
|
r_supports[1] = points[(i + 1) % point_count].pos;
|
2014-02-19 14:57:14 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-06 15:18:16 +00:00
|
|
|
ERR_FAIL_COND_MSG(support_idx == -1, "Convex polygon shape support not found.");
|
2014-02-19 14:57:14 +00:00
|
|
|
|
2017-03-05 15:44:50 +00:00
|
|
|
r_amount = 1;
|
|
|
|
r_supports[0] = points[support_idx].pos;
|
2014-02-19 14:57:14 +00:00
|
|
|
}
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
bool GodotConvexPolygonShape2D::contains_point(const Vector2 &p_point) const {
|
2017-03-05 15:44:50 +00:00
|
|
|
bool out = false;
|
|
|
|
bool in = false;
|
2015-03-22 04:46:18 +00:00
|
|
|
|
2017-03-05 15:44:50 +00:00
|
|
|
for (int i = 0; i < point_count; i++) {
|
2017-02-13 23:25:05 +00:00
|
|
|
real_t d = points[i].normal.dot(p_point) - points[i].normal.dot(points[i].pos);
|
2020-05-14 14:41:43 +00:00
|
|
|
if (d > 0) {
|
2017-03-05 15:44:50 +00:00
|
|
|
out = true;
|
2020-05-14 14:41:43 +00:00
|
|
|
} else {
|
2017-03-05 15:44:50 +00:00
|
|
|
in = true;
|
2020-05-14 14:41:43 +00:00
|
|
|
}
|
2015-03-22 04:46:18 +00:00
|
|
|
}
|
|
|
|
|
2019-04-08 09:03:37 +00:00
|
|
|
return in != out;
|
2015-03-22 04:46:18 +00:00
|
|
|
}
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
bool GodotConvexPolygonShape2D::intersect_segment(const Vector2 &p_begin, const Vector2 &p_end, Vector2 &r_point, Vector2 &r_normal) const {
|
2017-03-05 15:44:50 +00:00
|
|
|
Vector2 n = (p_end - p_begin).normalized();
|
|
|
|
real_t d = 1e10;
|
|
|
|
bool inters = false;
|
2014-02-19 14:57:14 +00:00
|
|
|
|
2017-03-05 15:44:50 +00:00
|
|
|
for (int i = 0; i < point_count; i++) {
|
2014-02-19 14:57:14 +00:00
|
|
|
Vector2 res;
|
|
|
|
|
2020-05-25 17:20:45 +00:00
|
|
|
if (!Geometry2D::segment_intersects_segment(p_begin, p_end, points[i].pos, points[(i + 1) % point_count].pos, &res)) {
|
2014-02-19 14:57:14 +00:00
|
|
|
continue;
|
2020-05-14 14:41:43 +00:00
|
|
|
}
|
2014-02-19 14:57:14 +00:00
|
|
|
|
2017-02-13 23:25:05 +00:00
|
|
|
real_t nd = n.dot(res);
|
2017-03-05 15:44:50 +00:00
|
|
|
if (nd < d) {
|
|
|
|
d = nd;
|
|
|
|
r_point = res;
|
|
|
|
r_normal = points[i].normal;
|
|
|
|
inters = true;
|
2014-02-19 14:57:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-18 01:41:21 +00:00
|
|
|
return inters;
|
2014-02-19 14:57:14 +00:00
|
|
|
}
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
real_t GodotConvexPolygonShape2D::get_moment_of_inertia(real_t p_mass, const Size2 &p_scale) const {
|
2021-04-06 18:11:40 +00:00
|
|
|
ERR_FAIL_COND_V_MSG(point_count == 0, 0, "Convex polygon shape has no points.");
|
2014-02-19 14:57:14 +00:00
|
|
|
Rect2 aabb;
|
2017-06-03 22:25:13 +00:00
|
|
|
aabb.position = points[0].pos * p_scale;
|
2017-03-05 15:44:50 +00:00
|
|
|
for (int i = 0; i < point_count; i++) {
|
|
|
|
aabb.expand_to(points[i].pos * p_scale);
|
2014-02-19 14:57:14 +00:00
|
|
|
}
|
|
|
|
|
2019-05-20 04:19:14 +00:00
|
|
|
return p_mass * aabb.size.dot(aabb.size) / 12.0;
|
2014-02-19 14:57:14 +00:00
|
|
|
}
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
void GodotConvexPolygonShape2D::set_data(const Variant &p_data) {
|
2021-01-28 06:34:26 +00:00
|
|
|
#ifdef REAL_T_IS_DOUBLE
|
|
|
|
ERR_FAIL_COND(p_data.get_type() != Variant::PACKED_VECTOR2_ARRAY && p_data.get_type() != Variant::PACKED_FLOAT64_ARRAY);
|
|
|
|
#else
|
Variant: Added 64-bit packed arrays, renamed Variant::REAL to FLOAT.
- Renames PackedIntArray to PackedInt32Array.
- Renames PackedFloatArray to PackedFloat32Array.
- Adds PackedInt64Array and PackedFloat64Array.
- Renames Variant::REAL to Variant::FLOAT for consistency.
Packed arrays are for storing large amount of data and creating stuff like
meshes, buffers. textures, etc. Forcing them to be 64 is a huge waste of
memory. That said, many users requested the ability to have 64 bits packed
arrays for their games, so this is just an optional added type.
For Variant, the float datatype is always 64 bits, and exposed as `float`.
We still have `real_t` which is the datatype that can change from 32 to 64
bits depending on a compile flag (not entirely working right now, but that's
the idea). It affects math related datatypes and code only.
Neither Variant nor PackedArray make use of real_t, which is only intended
for math precision, so the term is removed from there to keep only float.
2020-02-24 18:20:53 +00:00
|
|
|
ERR_FAIL_COND(p_data.get_type() != Variant::PACKED_VECTOR2_ARRAY && p_data.get_type() != Variant::PACKED_FLOAT32_ARRAY);
|
2021-01-28 06:34:26 +00:00
|
|
|
#endif
|
2014-02-19 14:57:14 +00:00
|
|
|
|
2020-05-14 14:41:43 +00:00
|
|
|
if (points) {
|
2014-02-19 14:57:14 +00:00
|
|
|
memdelete_arr(points);
|
2020-05-14 14:41:43 +00:00
|
|
|
}
|
2020-04-01 23:20:12 +00:00
|
|
|
points = nullptr;
|
2017-03-05 15:44:50 +00:00
|
|
|
point_count = 0;
|
|
|
|
|
2020-02-17 21:06:54 +00:00
|
|
|
if (p_data.get_type() == Variant::PACKED_VECTOR2_ARRAY) {
|
|
|
|
Vector<Vector2> arr = p_data;
|
2017-03-05 15:44:50 +00:00
|
|
|
ERR_FAIL_COND(arr.size() == 0);
|
|
|
|
point_count = arr.size();
|
|
|
|
points = memnew_arr(Point, point_count);
|
2020-02-17 21:06:54 +00:00
|
|
|
const Vector2 *r = arr.ptr();
|
2014-02-19 14:57:14 +00:00
|
|
|
|
2017-03-05 15:44:50 +00:00
|
|
|
for (int i = 0; i < point_count; i++) {
|
|
|
|
points[i].pos = r[i];
|
2014-02-19 14:57:14 +00:00
|
|
|
}
|
|
|
|
|
2017-03-05 15:44:50 +00:00
|
|
|
for (int i = 0; i < point_count; i++) {
|
2014-02-19 14:57:14 +00:00
|
|
|
Vector2 p = points[i].pos;
|
2017-03-05 15:44:50 +00:00
|
|
|
Vector2 pn = points[(i + 1) % point_count].pos;
|
2020-12-06 18:16:06 +00:00
|
|
|
points[i].normal = (pn - p).orthogonal().normalized();
|
2014-02-19 14:57:14 +00:00
|
|
|
}
|
|
|
|
} else {
|
2020-02-17 21:06:54 +00:00
|
|
|
Vector<real_t> dvr = p_data;
|
2017-03-05 15:44:50 +00:00
|
|
|
point_count = dvr.size() / 4;
|
|
|
|
ERR_FAIL_COND(point_count == 0);
|
2014-02-19 14:57:14 +00:00
|
|
|
|
2017-03-05 15:44:50 +00:00
|
|
|
points = memnew_arr(Point, point_count);
|
2020-02-17 21:06:54 +00:00
|
|
|
const real_t *r = dvr.ptr();
|
2014-02-19 14:57:14 +00:00
|
|
|
|
2017-03-05 15:44:50 +00:00
|
|
|
for (int i = 0; i < point_count; i++) {
|
|
|
|
int idx = i << 2;
|
|
|
|
points[i].pos.x = r[idx + 0];
|
|
|
|
points[i].pos.y = r[idx + 1];
|
|
|
|
points[i].normal.x = r[idx + 2];
|
|
|
|
points[i].normal.y = r[idx + 3];
|
2014-02-19 14:57:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-05 15:44:50 +00:00
|
|
|
ERR_FAIL_COND(point_count == 0);
|
2014-02-19 14:57:14 +00:00
|
|
|
Rect2 aabb;
|
2017-06-03 22:25:13 +00:00
|
|
|
aabb.position = points[0].pos;
|
2020-05-14 14:41:43 +00:00
|
|
|
for (int i = 1; i < point_count; i++) {
|
2014-02-19 14:57:14 +00:00
|
|
|
aabb.expand_to(points[i].pos);
|
2020-05-14 14:41:43 +00:00
|
|
|
}
|
2014-02-19 14:57:14 +00:00
|
|
|
|
|
|
|
configure(aabb);
|
|
|
|
}
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
Variant GodotConvexPolygonShape2D::get_data() const {
|
2020-02-17 21:06:54 +00:00
|
|
|
Vector<Vector2> dvr;
|
2014-02-19 14:57:14 +00:00
|
|
|
|
|
|
|
dvr.resize(point_count);
|
|
|
|
|
2017-03-05 15:44:50 +00:00
|
|
|
for (int i = 0; i < point_count; i++) {
|
|
|
|
dvr.set(i, points[i].pos);
|
2014-02-19 14:57:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return dvr;
|
|
|
|
}
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
GodotConvexPolygonShape2D::~GodotConvexPolygonShape2D() {
|
2020-05-14 14:41:43 +00:00
|
|
|
if (points) {
|
2014-02-19 14:57:14 +00:00
|
|
|
memdelete_arr(points);
|
2020-05-14 14:41:43 +00:00
|
|
|
}
|
2014-02-19 14:57:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
void GodotConcavePolygonShape2D::get_supports(const Vector2 &p_normal, Vector2 *r_supports, int &r_amount) const {
|
2017-03-05 15:44:50 +00:00
|
|
|
real_t d = -1e10;
|
|
|
|
int idx = -1;
|
|
|
|
for (int i = 0; i < points.size(); i++) {
|
2014-02-19 14:57:14 +00:00
|
|
|
real_t ld = p_normal.dot(points[i]);
|
2017-03-05 15:44:50 +00:00
|
|
|
if (ld > d) {
|
|
|
|
d = ld;
|
|
|
|
idx = i;
|
2014-02-19 14:57:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-05 15:44:50 +00:00
|
|
|
r_amount = 1;
|
|
|
|
ERR_FAIL_COND(idx == -1);
|
|
|
|
*r_supports = points[idx];
|
2014-02-19 14:57:14 +00:00
|
|
|
}
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
bool GodotConcavePolygonShape2D::contains_point(const Vector2 &p_point) const {
|
2015-03-22 04:46:18 +00:00
|
|
|
return false; //sorry
|
|
|
|
}
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
bool GodotConcavePolygonShape2D::intersect_segment(const Vector2 &p_begin, const Vector2 &p_end, Vector2 &r_point, Vector2 &r_normal) const {
|
2021-04-06 18:43:51 +00:00
|
|
|
if (segments.size() == 0 || points.size() == 0) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-03-05 15:44:50 +00:00
|
|
|
uint32_t *stack = (uint32_t *)alloca(sizeof(int) * bvh_depth);
|
2014-02-19 14:57:14 +00:00
|
|
|
|
|
|
|
enum {
|
2017-03-05 15:44:50 +00:00
|
|
|
TEST_AABB_BIT = 0,
|
|
|
|
VISIT_LEFT_BIT = 1,
|
|
|
|
VISIT_RIGHT_BIT = 2,
|
|
|
|
VISIT_DONE_BIT = 3,
|
|
|
|
VISITED_BIT_SHIFT = 29,
|
|
|
|
NODE_IDX_MASK = (1 << VISITED_BIT_SHIFT) - 1,
|
|
|
|
VISITED_BIT_MASK = ~NODE_IDX_MASK,
|
2014-02-19 14:57:14 +00:00
|
|
|
|
|
|
|
};
|
|
|
|
|
2017-03-05 15:44:50 +00:00
|
|
|
Vector2 n = (p_end - p_begin).normalized();
|
|
|
|
real_t d = 1e10;
|
|
|
|
bool inters = false;
|
2014-02-19 14:57:14 +00:00
|
|
|
|
2017-01-14 11:26:56 +00:00
|
|
|
/*
|
|
|
|
for(int i=0;i<bvh_depth;i++)
|
|
|
|
stack[i]=0;
|
|
|
|
*/
|
2014-02-19 14:57:14 +00:00
|
|
|
|
2017-03-05 15:44:50 +00:00
|
|
|
int level = 0;
|
2014-02-19 14:57:14 +00:00
|
|
|
|
2017-03-05 15:44:50 +00:00
|
|
|
const Segment *segmentptr = &segments[0];
|
|
|
|
const Vector2 *pointptr = &points[0];
|
2014-02-19 14:57:14 +00:00
|
|
|
const BVH *bvhptr = &bvh[0];
|
|
|
|
|
2017-03-05 15:44:50 +00:00
|
|
|
stack[0] = 0;
|
|
|
|
while (true) {
|
|
|
|
uint32_t node = stack[level] & NODE_IDX_MASK;
|
2019-02-12 20:10:08 +00:00
|
|
|
const BVH &bvh = bvhptr[node];
|
2017-03-05 15:44:50 +00:00
|
|
|
bool done = false;
|
2014-02-19 14:57:14 +00:00
|
|
|
|
2017-03-05 15:44:50 +00:00
|
|
|
switch (stack[level] >> VISITED_BIT_SHIFT) {
|
2014-02-19 14:57:14 +00:00
|
|
|
case TEST_AABB_BIT: {
|
2019-02-12 20:10:08 +00:00
|
|
|
bool valid = bvh.aabb.intersects_segment(p_begin, p_end);
|
2014-02-19 14:57:14 +00:00
|
|
|
if (!valid) {
|
2017-03-05 15:44:50 +00:00
|
|
|
stack[level] = (VISIT_DONE_BIT << VISITED_BIT_SHIFT) | node;
|
2014-02-19 14:57:14 +00:00
|
|
|
|
|
|
|
} else {
|
2019-02-12 20:10:08 +00:00
|
|
|
if (bvh.left < 0) {
|
|
|
|
const Segment &s = segmentptr[bvh.right];
|
2017-03-05 15:44:50 +00:00
|
|
|
Vector2 a = pointptr[s.points[0]];
|
|
|
|
Vector2 b = pointptr[s.points[1]];
|
2014-02-19 14:57:14 +00:00
|
|
|
|
|
|
|
Vector2 res;
|
|
|
|
|
2020-05-25 17:20:45 +00:00
|
|
|
if (Geometry2D::segment_intersects_segment(p_begin, p_end, a, b, &res)) {
|
2017-02-13 23:25:05 +00:00
|
|
|
real_t nd = n.dot(res);
|
2017-03-05 15:44:50 +00:00
|
|
|
if (nd < d) {
|
|
|
|
d = nd;
|
|
|
|
r_point = res;
|
2020-12-06 18:16:06 +00:00
|
|
|
r_normal = (b - a).orthogonal().normalized();
|
2017-03-05 15:44:50 +00:00
|
|
|
inters = true;
|
2014-02-19 14:57:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-05 15:44:50 +00:00
|
|
|
stack[level] = (VISIT_DONE_BIT << VISITED_BIT_SHIFT) | node;
|
2014-02-19 14:57:14 +00:00
|
|
|
|
|
|
|
} else {
|
2017-03-05 15:44:50 +00:00
|
|
|
stack[level] = (VISIT_LEFT_BIT << VISITED_BIT_SHIFT) | node;
|
2014-02-19 14:57:14 +00:00
|
|
|
}
|
|
|
|
}
|
2017-03-05 15:44:50 +00:00
|
|
|
}
|
|
|
|
continue;
|
2014-02-19 14:57:14 +00:00
|
|
|
case VISIT_LEFT_BIT: {
|
2017-03-05 15:44:50 +00:00
|
|
|
stack[level] = (VISIT_RIGHT_BIT << VISITED_BIT_SHIFT) | node;
|
2019-02-12 20:10:08 +00:00
|
|
|
stack[level + 1] = bvh.left | TEST_AABB_BIT;
|
2014-02-19 14:57:14 +00:00
|
|
|
level++;
|
2017-03-05 15:44:50 +00:00
|
|
|
}
|
|
|
|
continue;
|
2014-02-19 14:57:14 +00:00
|
|
|
case VISIT_RIGHT_BIT: {
|
2017-03-05 15:44:50 +00:00
|
|
|
stack[level] = (VISIT_DONE_BIT << VISITED_BIT_SHIFT) | node;
|
2019-02-12 20:10:08 +00:00
|
|
|
stack[level + 1] = bvh.right | TEST_AABB_BIT;
|
2014-02-19 14:57:14 +00:00
|
|
|
level++;
|
2017-03-05 15:44:50 +00:00
|
|
|
}
|
|
|
|
continue;
|
2014-02-19 14:57:14 +00:00
|
|
|
case VISIT_DONE_BIT: {
|
2017-03-05 15:44:50 +00:00
|
|
|
if (level == 0) {
|
|
|
|
done = true;
|
2014-02-19 14:57:14 +00:00
|
|
|
break;
|
2020-05-14 14:41:43 +00:00
|
|
|
} else {
|
2014-02-19 14:57:14 +00:00
|
|
|
level--;
|
2020-05-14 14:41:43 +00:00
|
|
|
}
|
2017-03-05 15:44:50 +00:00
|
|
|
}
|
|
|
|
continue;
|
2014-02-19 14:57:14 +00:00
|
|
|
}
|
|
|
|
|
2020-05-14 14:41:43 +00:00
|
|
|
if (done) {
|
2014-02-19 14:57:14 +00:00
|
|
|
break;
|
2020-05-14 14:41:43 +00:00
|
|
|
}
|
2014-02-19 14:57:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (inters) {
|
2020-05-14 14:41:43 +00:00
|
|
|
if (n.dot(r_normal) > 0) {
|
2017-03-05 15:44:50 +00:00
|
|
|
r_normal = -r_normal;
|
2020-05-14 14:41:43 +00:00
|
|
|
}
|
2014-02-19 14:57:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return inters;
|
|
|
|
}
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
int GodotConcavePolygonShape2D::_generate_bvh(BVH *p_bvh, int p_len, int p_depth) {
|
2017-03-05 15:44:50 +00:00
|
|
|
if (p_len == 1) {
|
|
|
|
bvh_depth = MAX(p_depth, bvh_depth);
|
2014-02-19 14:57:14 +00:00
|
|
|
bvh.push_back(*p_bvh);
|
2017-03-05 15:44:50 +00:00
|
|
|
return bvh.size() - 1;
|
2014-02-19 14:57:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//else sort best
|
|
|
|
|
2017-03-05 15:44:50 +00:00
|
|
|
Rect2 global_aabb = p_bvh[0].aabb;
|
|
|
|
for (int i = 1; i < p_len; i++) {
|
|
|
|
global_aabb = global_aabb.merge(p_bvh[i].aabb);
|
2014-02-19 14:57:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (global_aabb.size.x > global_aabb.size.y) {
|
2017-03-05 15:44:50 +00:00
|
|
|
SortArray<BVH, BVH_CompareX> sort;
|
|
|
|
sort.sort(p_bvh, p_len);
|
2014-02-19 14:57:14 +00:00
|
|
|
|
|
|
|
} else {
|
2017-03-05 15:44:50 +00:00
|
|
|
SortArray<BVH, BVH_CompareY> sort;
|
|
|
|
sort.sort(p_bvh, p_len);
|
2014-02-19 14:57:14 +00:00
|
|
|
}
|
|
|
|
|
2017-03-05 15:44:50 +00:00
|
|
|
int median = p_len / 2;
|
2014-02-19 14:57:14 +00:00
|
|
|
|
|
|
|
BVH node;
|
2017-03-05 15:44:50 +00:00
|
|
|
node.aabb = global_aabb;
|
2014-02-19 14:57:14 +00:00
|
|
|
int node_idx = bvh.size();
|
|
|
|
bvh.push_back(node);
|
|
|
|
|
2017-03-05 15:44:50 +00:00
|
|
|
int l = _generate_bvh(p_bvh, median, p_depth + 1);
|
|
|
|
int r = _generate_bvh(&p_bvh[median], p_len - median, p_depth + 1);
|
2018-07-25 01:11:03 +00:00
|
|
|
bvh.write[node_idx].left = l;
|
|
|
|
bvh.write[node_idx].right = r;
|
2014-02-19 14:57:14 +00:00
|
|
|
|
|
|
|
return node_idx;
|
|
|
|
}
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
void GodotConcavePolygonShape2D::set_data(const Variant &p_data) {
|
2021-01-28 06:34:26 +00:00
|
|
|
#ifdef REAL_T_IS_DOUBLE
|
|
|
|
ERR_FAIL_COND(p_data.get_type() != Variant::PACKED_VECTOR2_ARRAY && p_data.get_type() != Variant::PACKED_FLOAT64_ARRAY);
|
|
|
|
#else
|
Variant: Added 64-bit packed arrays, renamed Variant::REAL to FLOAT.
- Renames PackedIntArray to PackedInt32Array.
- Renames PackedFloatArray to PackedFloat32Array.
- Adds PackedInt64Array and PackedFloat64Array.
- Renames Variant::REAL to Variant::FLOAT for consistency.
Packed arrays are for storing large amount of data and creating stuff like
meshes, buffers. textures, etc. Forcing them to be 64 is a huge waste of
memory. That said, many users requested the ability to have 64 bits packed
arrays for their games, so this is just an optional added type.
For Variant, the float datatype is always 64 bits, and exposed as `float`.
We still have `real_t` which is the datatype that can change from 32 to 64
bits depending on a compile flag (not entirely working right now, but that's
the idea). It affects math related datatypes and code only.
Neither Variant nor PackedArray make use of real_t, which is only intended
for math precision, so the term is removed from there to keep only float.
2020-02-24 18:20:53 +00:00
|
|
|
ERR_FAIL_COND(p_data.get_type() != Variant::PACKED_VECTOR2_ARRAY && p_data.get_type() != Variant::PACKED_FLOAT32_ARRAY);
|
2021-01-28 06:34:26 +00:00
|
|
|
#endif
|
2014-02-19 14:57:14 +00:00
|
|
|
|
|
|
|
Rect2 aabb;
|
|
|
|
|
2020-02-17 21:06:54 +00:00
|
|
|
if (p_data.get_type() == Variant::PACKED_VECTOR2_ARRAY) {
|
|
|
|
Vector<Vector2> p2arr = p_data;
|
2014-02-19 14:57:14 +00:00
|
|
|
int len = p2arr.size();
|
2017-03-05 15:44:50 +00:00
|
|
|
ERR_FAIL_COND(len % 2);
|
2014-02-19 14:57:14 +00:00
|
|
|
|
2016-03-09 18:52:15 +00:00
|
|
|
segments.clear();
|
|
|
|
points.clear();
|
|
|
|
bvh.clear();
|
2017-03-05 15:44:50 +00:00
|
|
|
bvh_depth = 1;
|
2016-03-09 18:52:15 +00:00
|
|
|
|
2017-03-05 15:44:50 +00:00
|
|
|
if (len == 0) {
|
2016-03-09 18:52:15 +00:00
|
|
|
configure(aabb);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-02-17 21:06:54 +00:00
|
|
|
const Vector2 *arr = p2arr.ptr();
|
2014-02-19 14:57:14 +00:00
|
|
|
|
2017-03-05 15:44:50 +00:00
|
|
|
Map<Point2, int> pointmap;
|
|
|
|
for (int i = 0; i < len; i += 2) {
|
|
|
|
Point2 p1 = arr[i];
|
|
|
|
Point2 p2 = arr[i + 1];
|
|
|
|
int idx_p1, idx_p2;
|
2014-02-19 14:57:14 +00:00
|
|
|
|
|
|
|
if (pointmap.has(p1)) {
|
2017-03-05 15:44:50 +00:00
|
|
|
idx_p1 = pointmap[p1];
|
2014-02-19 14:57:14 +00:00
|
|
|
} else {
|
2017-03-05 15:44:50 +00:00
|
|
|
idx_p1 = pointmap.size();
|
|
|
|
pointmap[p1] = idx_p1;
|
2014-02-19 14:57:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (pointmap.has(p2)) {
|
2017-03-05 15:44:50 +00:00
|
|
|
idx_p2 = pointmap[p2];
|
2014-02-19 14:57:14 +00:00
|
|
|
} else {
|
2017-03-05 15:44:50 +00:00
|
|
|
idx_p2 = pointmap.size();
|
|
|
|
pointmap[p2] = idx_p2;
|
2014-02-19 14:57:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Segment s;
|
2017-03-05 15:44:50 +00:00
|
|
|
s.points[0] = idx_p1;
|
|
|
|
s.points[1] = idx_p2;
|
2014-02-19 14:57:14 +00:00
|
|
|
segments.push_back(s);
|
|
|
|
}
|
|
|
|
|
|
|
|
points.resize(pointmap.size());
|
2017-06-03 22:25:13 +00:00
|
|
|
aabb.position = pointmap.front()->key();
|
2021-08-09 20:13:42 +00:00
|
|
|
for (const KeyValue<Point2, int> &E : pointmap) {
|
|
|
|
aabb.expand_to(E.key);
|
|
|
|
points.write[E.value] = E.key;
|
2014-02-19 14:57:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Vector<BVH> main_vbh;
|
|
|
|
main_vbh.resize(segments.size());
|
2017-03-05 15:44:50 +00:00
|
|
|
for (int i = 0; i < main_vbh.size(); i++) {
|
2018-07-25 01:11:03 +00:00
|
|
|
main_vbh.write[i].aabb.position = points[segments[i].points[0]];
|
|
|
|
main_vbh.write[i].aabb.expand_to(points[segments[i].points[1]]);
|
|
|
|
main_vbh.write[i].left = -1;
|
|
|
|
main_vbh.write[i].right = i;
|
2014-02-19 14:57:14 +00:00
|
|
|
}
|
|
|
|
|
2018-07-25 01:11:03 +00:00
|
|
|
_generate_bvh(main_vbh.ptrw(), main_vbh.size(), 1);
|
2014-02-19 14:57:14 +00:00
|
|
|
|
|
|
|
} else {
|
|
|
|
//dictionary with arrays
|
|
|
|
}
|
|
|
|
|
|
|
|
configure(aabb);
|
|
|
|
}
|
2020-05-14 12:29:06 +00:00
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
Variant GodotConcavePolygonShape2D::get_data() const {
|
2020-02-17 21:06:54 +00:00
|
|
|
Vector<Vector2> rsegments;
|
2014-02-19 14:57:14 +00:00
|
|
|
int len = segments.size();
|
2017-03-05 15:44:50 +00:00
|
|
|
rsegments.resize(len * 2);
|
2020-02-17 21:06:54 +00:00
|
|
|
Vector2 *w = rsegments.ptrw();
|
2017-03-05 15:44:50 +00:00
|
|
|
for (int i = 0; i < len; i++) {
|
|
|
|
w[(i << 1) + 0] = points[segments[i].points[0]];
|
|
|
|
w[(i << 1) + 1] = points[segments[i].points[1]];
|
2014-02-19 14:57:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return rsegments;
|
|
|
|
}
|
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
void GodotConcavePolygonShape2D::cull(const Rect2 &p_local_aabb, QueryCallback p_callback, void *p_userdata) const {
|
2017-03-05 15:44:50 +00:00
|
|
|
uint32_t *stack = (uint32_t *)alloca(sizeof(int) * bvh_depth);
|
2014-02-19 14:57:14 +00:00
|
|
|
|
|
|
|
enum {
|
2017-03-05 15:44:50 +00:00
|
|
|
TEST_AABB_BIT = 0,
|
|
|
|
VISIT_LEFT_BIT = 1,
|
|
|
|
VISIT_RIGHT_BIT = 2,
|
|
|
|
VISIT_DONE_BIT = 3,
|
|
|
|
VISITED_BIT_SHIFT = 29,
|
|
|
|
NODE_IDX_MASK = (1 << VISITED_BIT_SHIFT) - 1,
|
|
|
|
VISITED_BIT_MASK = ~NODE_IDX_MASK,
|
2014-02-19 14:57:14 +00:00
|
|
|
|
|
|
|
};
|
|
|
|
|
2017-01-14 11:26:56 +00:00
|
|
|
/*
|
|
|
|
for(int i=0;i<bvh_depth;i++)
|
|
|
|
stack[i]=0;
|
|
|
|
*/
|
2014-02-19 14:57:14 +00:00
|
|
|
|
2018-04-12 10:15:34 +00:00
|
|
|
if (segments.size() == 0 || points.size() == 0 || bvh.size() == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-03-05 15:44:50 +00:00
|
|
|
int level = 0;
|
2014-02-19 14:57:14 +00:00
|
|
|
|
2017-03-05 15:44:50 +00:00
|
|
|
const Segment *segmentptr = &segments[0];
|
|
|
|
const Vector2 *pointptr = &points[0];
|
2014-02-19 14:57:14 +00:00
|
|
|
const BVH *bvhptr = &bvh[0];
|
|
|
|
|
2017-03-05 15:44:50 +00:00
|
|
|
stack[0] = 0;
|
|
|
|
while (true) {
|
|
|
|
uint32_t node = stack[level] & NODE_IDX_MASK;
|
2019-02-12 20:10:08 +00:00
|
|
|
const BVH &bvh = bvhptr[node];
|
2014-02-19 14:57:14 +00:00
|
|
|
|
2017-03-05 15:44:50 +00:00
|
|
|
switch (stack[level] >> VISITED_BIT_SHIFT) {
|
2014-02-19 14:57:14 +00:00
|
|
|
case TEST_AABB_BIT: {
|
2019-02-12 20:10:08 +00:00
|
|
|
bool valid = p_local_aabb.intersects(bvh.aabb);
|
2014-02-19 14:57:14 +00:00
|
|
|
if (!valid) {
|
2017-03-05 15:44:50 +00:00
|
|
|
stack[level] = (VISIT_DONE_BIT << VISITED_BIT_SHIFT) | node;
|
2014-02-19 14:57:14 +00:00
|
|
|
|
|
|
|
} else {
|
2019-02-12 20:10:08 +00:00
|
|
|
if (bvh.left < 0) {
|
|
|
|
const Segment &s = segmentptr[bvh.right];
|
2017-03-05 15:44:50 +00:00
|
|
|
Vector2 a = pointptr[s.points[0]];
|
|
|
|
Vector2 b = pointptr[s.points[1]];
|
2014-02-19 14:57:14 +00:00
|
|
|
|
2021-10-18 19:24:30 +00:00
|
|
|
GodotSegmentShape2D ss(a, b, (b - a).orthogonal().normalized());
|
2014-02-19 14:57:14 +00:00
|
|
|
|
2021-05-08 02:16:04 +00:00
|
|
|
if (p_callback(p_userdata, &ss)) {
|
|
|
|
return;
|
|
|
|
}
|
2017-03-05 15:44:50 +00:00
|
|
|
stack[level] = (VISIT_DONE_BIT << VISITED_BIT_SHIFT) | node;
|
2014-02-19 14:57:14 +00:00
|
|
|
|
|
|
|
} else {
|
2017-03-05 15:44:50 +00:00
|
|
|
stack[level] = (VISIT_LEFT_BIT << VISITED_BIT_SHIFT) | node;
|
2014-02-19 14:57:14 +00:00
|
|
|
}
|
|
|
|
}
|
2017-03-05 15:44:50 +00:00
|
|
|
}
|
|
|
|
continue;
|
2014-02-19 14:57:14 +00:00
|
|
|
case VISIT_LEFT_BIT: {
|
2017-03-05 15:44:50 +00:00
|
|
|
stack[level] = (VISIT_RIGHT_BIT << VISITED_BIT_SHIFT) | node;
|
2019-02-12 20:10:08 +00:00
|
|
|
stack[level + 1] = bvh.left | TEST_AABB_BIT;
|
2014-02-19 14:57:14 +00:00
|
|
|
level++;
|
2017-03-05 15:44:50 +00:00
|
|
|
}
|
|
|
|
continue;
|
2014-02-19 14:57:14 +00:00
|
|
|
case VISIT_RIGHT_BIT: {
|
2017-03-05 15:44:50 +00:00
|
|
|
stack[level] = (VISIT_DONE_BIT << VISITED_BIT_SHIFT) | node;
|
2019-02-12 20:10:08 +00:00
|
|
|
stack[level + 1] = bvh.right | TEST_AABB_BIT;
|
2014-02-19 14:57:14 +00:00
|
|
|
level++;
|
2017-03-05 15:44:50 +00:00
|
|
|
}
|
|
|
|
continue;
|
2014-02-19 14:57:14 +00:00
|
|
|
case VISIT_DONE_BIT: {
|
2020-05-14 14:41:43 +00:00
|
|
|
if (level == 0) {
|
2014-02-19 14:57:14 +00:00
|
|
|
return;
|
2020-05-14 14:41:43 +00:00
|
|
|
} else {
|
2014-02-19 14:57:14 +00:00
|
|
|
level--;
|
2020-05-14 14:41:43 +00:00
|
|
|
}
|
2017-03-05 15:44:50 +00:00
|
|
|
}
|
|
|
|
continue;
|
2014-02-19 14:57:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|