Path: blob/master/modules/godot_physics_3d/godot_collision_solver_3d.cpp
10277 views
/**************************************************************************/1/* godot_collision_solver_3d.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "godot_collision_solver_3d.h"3132#include "godot_collision_solver_3d_sat.h"33#include "godot_soft_body_3d.h"3435#include "gjk_epa.h"3637#define collision_solver sat_calculate_penetration38//#define collision_solver gjk_epa_calculate_penetration3940bool GodotCollisionSolver3D::solve_static_world_boundary(const GodotShape3D *p_shape_A, const Transform3D &p_transform_A, const GodotShape3D *p_shape_B, const Transform3D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result, real_t p_margin) {41const GodotWorldBoundaryShape3D *world_boundary = static_cast<const GodotWorldBoundaryShape3D *>(p_shape_A);42if (p_shape_B->get_type() == PhysicsServer3D::SHAPE_WORLD_BOUNDARY) {43return false;44}45Plane p = p_transform_A.xform(world_boundary->get_plane());4647static const int max_supports = 16;48Vector3 supports[max_supports];49int support_count;50GodotShape3D::FeatureType support_type = GodotShape3D::FeatureType::FEATURE_POINT;51p_shape_B->get_supports(p_transform_B.basis.xform_inv(-p.normal).normalized(), max_supports, supports, support_count, support_type);5253if (support_type == GodotShape3D::FEATURE_CIRCLE) {54ERR_FAIL_COND_V(support_count != 3, false);5556Vector3 circle_pos = supports[0];57Vector3 circle_axis_1 = supports[1] - circle_pos;58Vector3 circle_axis_2 = supports[2] - circle_pos;5960// Use 3 equidistant points on the circle.61for (int i = 0; i < 3; ++i) {62Vector3 vertex_pos = circle_pos;63vertex_pos += circle_axis_1 * Math::cos(2.0 * Math::PI * i / 3.0);64vertex_pos += circle_axis_2 * Math::sin(2.0 * Math::PI * i / 3.0);65supports[i] = vertex_pos;66}67}6869bool found = false;7071for (int i = 0; i < support_count; i++) {72supports[i] += p_margin * supports[i].normalized();73supports[i] = p_transform_B.xform(supports[i]);74if (p.distance_to(supports[i]) >= 0) {75continue;76}77found = true;7879Vector3 support_A = p.project(supports[i]);8081if (p_result_callback) {82if (p_swap_result) {83Vector3 normal = (support_A - supports[i]).normalized();84p_result_callback(supports[i], 0, support_A, 0, normal, p_userdata);85} else {86Vector3 normal = (supports[i] - support_A).normalized();87p_result_callback(support_A, 0, supports[i], 0, normal, p_userdata);88}89}90}9192return found;93}9495bool GodotCollisionSolver3D::solve_separation_ray(const GodotShape3D *p_shape_A, const Transform3D &p_transform_A, const GodotShape3D *p_shape_B, const Transform3D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result, real_t p_margin) {96const GodotSeparationRayShape3D *ray = static_cast<const GodotSeparationRayShape3D *>(p_shape_A);9798Vector3 from = p_transform_A.origin;99Vector3 to = from + p_transform_A.basis.get_column(2) * (ray->get_length() + p_margin);100Vector3 support_A = to;101102Transform3D ai = p_transform_B.affine_inverse();103104from = ai.xform(from);105to = ai.xform(to);106107Vector3 p, n;108int fi = -1;109if (!p_shape_B->intersect_segment(from, to, p, n, fi, true)) {110return false;111}112113// Discard contacts when the ray is fully contained inside the shape.114if (n == Vector3()) {115return false;116}117118// Discard contacts in the wrong direction.119if (n.dot(from - to) < CMP_EPSILON) {120return false;121}122123Vector3 support_B = p_transform_B.xform(p);124if (ray->get_slide_on_slope()) {125Vector3 global_n = ai.basis.xform_inv(n).normalized();126support_B = support_A + (support_B - support_A).length() * global_n;127}128129if (p_result_callback) {130Vector3 normal = (support_B - support_A).normalized();131if (p_swap_result) {132p_result_callback(support_B, 0, support_A, 0, -normal, p_userdata);133} else {134p_result_callback(support_A, 0, support_B, 0, normal, p_userdata);135}136}137return true;138}139140struct _SoftBodyContactCollisionInfo {141int node_index = 0;142GodotCollisionSolver3D::CallbackResult result_callback = nullptr;143void *userdata = nullptr;144bool swap_result = false;145int contact_count = 0;146};147148void GodotCollisionSolver3D::soft_body_contact_callback(const Vector3 &p_point_A, int p_index_A, const Vector3 &p_point_B, int p_index_B, const Vector3 &normal, void *p_userdata) {149_SoftBodyContactCollisionInfo &cinfo = *(static_cast<_SoftBodyContactCollisionInfo *>(p_userdata));150151++cinfo.contact_count;152153if (!cinfo.result_callback) {154return;155}156157if (cinfo.swap_result) {158cinfo.result_callback(p_point_B, cinfo.node_index, p_point_A, p_index_A, -normal, cinfo.userdata);159} else {160cinfo.result_callback(p_point_A, p_index_A, p_point_B, cinfo.node_index, normal, cinfo.userdata);161}162}163164struct _SoftBodyQueryInfo {165GodotSoftBody3D *soft_body = nullptr;166const GodotShape3D *shape_A = nullptr;167const GodotShape3D *shape_B = nullptr;168Transform3D transform_A;169Transform3D node_transform;170_SoftBodyContactCollisionInfo contact_info;171#ifdef DEBUG_ENABLED172int node_query_count = 0;173int convex_query_count = 0;174#endif175};176177bool GodotCollisionSolver3D::soft_body_query_callback(uint32_t p_node_index, void *p_userdata) {178_SoftBodyQueryInfo &query_cinfo = *(static_cast<_SoftBodyQueryInfo *>(p_userdata));179180Vector3 node_position = query_cinfo.soft_body->get_node_position(p_node_index);181182Transform3D transform_B;183transform_B.origin = query_cinfo.node_transform.xform(node_position);184185query_cinfo.contact_info.node_index = p_node_index;186bool collided = solve_static(query_cinfo.shape_A, query_cinfo.transform_A, query_cinfo.shape_B, transform_B, soft_body_contact_callback, &query_cinfo.contact_info);187188#ifdef DEBUG_ENABLED189++query_cinfo.node_query_count;190#endif191192// Stop at first collision if contacts are not needed.193return (collided && !query_cinfo.contact_info.result_callback);194}195196bool GodotCollisionSolver3D::soft_body_concave_callback(void *p_userdata, GodotShape3D *p_convex) {197_SoftBodyQueryInfo &query_cinfo = *(static_cast<_SoftBodyQueryInfo *>(p_userdata));198199query_cinfo.shape_A = p_convex;200201// Calculate AABB for internal soft body query (in world space).202AABB shape_aabb;203for (int i = 0; i < 3; i++) {204Vector3 axis;205axis[i] = 1.0;206207real_t smin, smax;208p_convex->project_range(axis, query_cinfo.transform_A, smin, smax);209210shape_aabb.position[i] = smin;211shape_aabb.size[i] = smax - smin;212}213214shape_aabb.grow_by(query_cinfo.soft_body->get_collision_margin());215216query_cinfo.soft_body->query_aabb(shape_aabb, soft_body_query_callback, &query_cinfo);217218bool collided = (query_cinfo.contact_info.contact_count > 0);219220#ifdef DEBUG_ENABLED221++query_cinfo.convex_query_count;222#endif223224// Stop at first collision if contacts are not needed.225return (collided && !query_cinfo.contact_info.result_callback);226}227228bool GodotCollisionSolver3D::solve_soft_body(const GodotShape3D *p_shape_A, const Transform3D &p_transform_A, const GodotShape3D *p_shape_B, const Transform3D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result) {229const GodotSoftBodyShape3D *soft_body_shape_B = static_cast<const GodotSoftBodyShape3D *>(p_shape_B);230231GodotSoftBody3D *soft_body = soft_body_shape_B->get_soft_body();232const Transform3D &world_to_local = soft_body->get_inv_transform();233234const real_t collision_margin = soft_body->get_collision_margin();235236GodotSphereShape3D sphere_shape;237sphere_shape.set_data(collision_margin);238239_SoftBodyQueryInfo query_cinfo;240query_cinfo.contact_info.result_callback = p_result_callback;241query_cinfo.contact_info.userdata = p_userdata;242query_cinfo.contact_info.swap_result = p_swap_result;243query_cinfo.soft_body = soft_body;244query_cinfo.node_transform = p_transform_B * world_to_local;245query_cinfo.shape_A = p_shape_A;246query_cinfo.transform_A = p_transform_A;247query_cinfo.shape_B = &sphere_shape;248249if (p_shape_A->is_concave()) {250// In case of concave shape, query convex shapes first.251const GodotConcaveShape3D *concave_shape_A = static_cast<const GodotConcaveShape3D *>(p_shape_A);252253AABB soft_body_aabb = soft_body->get_bounds();254soft_body_aabb.grow_by(collision_margin);255256// Calculate AABB for internal concave shape query (in local space).257AABB local_aabb;258for (int i = 0; i < 3; i++) {259Vector3 axis(p_transform_A.basis.get_column(i));260real_t axis_scale = 1.0 / axis.length();261262real_t smin = soft_body_aabb.position[i];263real_t smax = smin + soft_body_aabb.size[i];264265smin *= axis_scale;266smax *= axis_scale;267268local_aabb.position[i] = smin;269local_aabb.size[i] = smax - smin;270}271272concave_shape_A->cull(local_aabb, soft_body_concave_callback, &query_cinfo, true);273} else {274AABB shape_aabb = p_transform_A.xform(p_shape_A->get_aabb());275shape_aabb.grow_by(collision_margin);276277soft_body->query_aabb(shape_aabb, soft_body_query_callback, &query_cinfo);278}279280return (query_cinfo.contact_info.contact_count > 0);281}282283struct _ConcaveCollisionInfo {284const Transform3D *transform_A = nullptr;285const GodotShape3D *shape_A = nullptr;286const Transform3D *transform_B = nullptr;287GodotCollisionSolver3D::CallbackResult result_callback = nullptr;288void *userdata = nullptr;289bool swap_result = false;290bool collided = false;291int aabb_tests = 0;292int collisions = 0;293bool tested = false;294real_t margin_A = 0.0f;295real_t margin_B = 0.0f;296Vector3 close_A;297Vector3 close_B;298};299300bool GodotCollisionSolver3D::concave_callback(void *p_userdata, GodotShape3D *p_convex) {301_ConcaveCollisionInfo &cinfo = *(static_cast<_ConcaveCollisionInfo *>(p_userdata));302cinfo.aabb_tests++;303304bool collided = collision_solver(cinfo.shape_A, *cinfo.transform_A, p_convex, *cinfo.transform_B, cinfo.result_callback, cinfo.userdata, cinfo.swap_result, nullptr, cinfo.margin_A, cinfo.margin_B);305if (!collided) {306return false;307}308309cinfo.collided = true;310cinfo.collisions++;311312// Stop at first collision if contacts are not needed.313return !cinfo.result_callback;314}315316bool GodotCollisionSolver3D::solve_concave(const GodotShape3D *p_shape_A, const Transform3D &p_transform_A, const GodotShape3D *p_shape_B, const Transform3D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result, real_t p_margin_A, real_t p_margin_B) {317const GodotConcaveShape3D *concave_B = static_cast<const GodotConcaveShape3D *>(p_shape_B);318319_ConcaveCollisionInfo cinfo;320cinfo.transform_A = &p_transform_A;321cinfo.shape_A = p_shape_A;322cinfo.transform_B = &p_transform_B;323cinfo.result_callback = p_result_callback;324cinfo.userdata = p_userdata;325cinfo.swap_result = p_swap_result;326cinfo.collided = false;327cinfo.collisions = 0;328cinfo.margin_A = p_margin_A;329cinfo.margin_B = p_margin_B;330331cinfo.aabb_tests = 0;332333Transform3D rel_transform = p_transform_A;334rel_transform.origin -= p_transform_B.origin;335336//quickly compute a local AABB337338AABB local_aabb;339for (int i = 0; i < 3; i++) {340Vector3 axis(p_transform_B.basis.get_column(i));341real_t axis_scale = 1.0 / axis.length();342axis *= axis_scale;343344real_t smin = 0.0, smax = 0.0;345p_shape_A->project_range(axis, rel_transform, smin, smax);346smin -= p_margin_A;347smax += p_margin_A;348smin *= axis_scale;349smax *= axis_scale;350351local_aabb.position[i] = smin;352local_aabb.size[i] = smax - smin;353}354355concave_B->cull(local_aabb, concave_callback, &cinfo, false);356357return cinfo.collided;358}359360bool GodotCollisionSolver3D::solve_static(const GodotShape3D *p_shape_A, const Transform3D &p_transform_A, const GodotShape3D *p_shape_B, const Transform3D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, Vector3 *r_sep_axis, real_t p_margin_A, real_t p_margin_B) {361PhysicsServer3D::ShapeType type_A = p_shape_A->get_type();362PhysicsServer3D::ShapeType type_B = p_shape_B->get_type();363bool concave_A = p_shape_A->is_concave();364bool concave_B = p_shape_B->is_concave();365366bool swap = false;367368if (type_A > type_B) {369SWAP(type_A, type_B);370SWAP(concave_A, concave_B);371swap = true;372}373374if (type_A == PhysicsServer3D::SHAPE_WORLD_BOUNDARY) {375if (type_B == PhysicsServer3D::SHAPE_WORLD_BOUNDARY) {376WARN_PRINT_ONCE("Collisions between world boundaries are not supported.");377return false;378}379if (type_B == PhysicsServer3D::SHAPE_SEPARATION_RAY) {380WARN_PRINT_ONCE("Collisions between world boundaries and rays are not supported.");381return false;382}383if (type_B == PhysicsServer3D::SHAPE_SOFT_BODY) {384WARN_PRINT_ONCE("Collisions between world boundaries and soft bodies are not supported.");385return false;386}387388if (swap) {389return solve_static_world_boundary(p_shape_B, p_transform_B, p_shape_A, p_transform_A, p_result_callback, p_userdata, true, p_margin_A);390} else {391return solve_static_world_boundary(p_shape_A, p_transform_A, p_shape_B, p_transform_B, p_result_callback, p_userdata, false, p_margin_B);392}393394} else if (type_A == PhysicsServer3D::SHAPE_SEPARATION_RAY) {395if (type_B == PhysicsServer3D::SHAPE_SEPARATION_RAY) {396WARN_PRINT_ONCE("Collisions between rays are not supported.");397return false;398}399400if (swap) {401return solve_separation_ray(p_shape_B, p_transform_B, p_shape_A, p_transform_A, p_result_callback, p_userdata, true, p_margin_B);402} else {403return solve_separation_ray(p_shape_A, p_transform_A, p_shape_B, p_transform_B, p_result_callback, p_userdata, false, p_margin_A);404}405406} else if (type_B == PhysicsServer3D::SHAPE_SOFT_BODY) {407if (type_A == PhysicsServer3D::SHAPE_SOFT_BODY) {408WARN_PRINT_ONCE("Collisions between soft bodies are not supported.");409return false;410}411412if (swap) {413return solve_soft_body(p_shape_B, p_transform_B, p_shape_A, p_transform_A, p_result_callback, p_userdata, true);414} else {415return solve_soft_body(p_shape_A, p_transform_A, p_shape_B, p_transform_B, p_result_callback, p_userdata, false);416}417418} else if (concave_B) {419if (concave_A) {420WARN_PRINT_ONCE("Collisions between two concave shapes are not supported.");421return false;422}423424if (!swap) {425return solve_concave(p_shape_A, p_transform_A, p_shape_B, p_transform_B, p_result_callback, p_userdata, false, p_margin_A, p_margin_B);426} else {427return solve_concave(p_shape_B, p_transform_B, p_shape_A, p_transform_A, p_result_callback, p_userdata, true, p_margin_A, p_margin_B);428}429430} else {431return collision_solver(p_shape_A, p_transform_A, p_shape_B, p_transform_B, p_result_callback, p_userdata, false, r_sep_axis, p_margin_A, p_margin_B);432}433}434435bool GodotCollisionSolver3D::concave_distance_callback(void *p_userdata, GodotShape3D *p_convex) {436_ConcaveCollisionInfo &cinfo = *(static_cast<_ConcaveCollisionInfo *>(p_userdata));437cinfo.aabb_tests++;438439Vector3 close_A, close_B;440cinfo.collided = !gjk_epa_calculate_distance(cinfo.shape_A, *cinfo.transform_A, p_convex, *cinfo.transform_B, close_A, close_B);441442if (cinfo.collided) {443// No need to process any more result.444return true;445}446447if (!cinfo.tested || close_A.distance_squared_to(close_B) < cinfo.close_A.distance_squared_to(cinfo.close_B)) {448cinfo.close_A = close_A;449cinfo.close_B = close_B;450cinfo.tested = true;451}452453cinfo.collisions++;454return false;455}456457bool GodotCollisionSolver3D::solve_distance_world_boundary(const GodotShape3D *p_shape_A, const Transform3D &p_transform_A, const GodotShape3D *p_shape_B, const Transform3D &p_transform_B, Vector3 &r_point_A, Vector3 &r_point_B) {458const GodotWorldBoundaryShape3D *world_boundary = static_cast<const GodotWorldBoundaryShape3D *>(p_shape_A);459if (p_shape_B->get_type() == PhysicsServer3D::SHAPE_WORLD_BOUNDARY) {460return false;461}462Plane p = p_transform_A.xform(world_boundary->get_plane());463464static const int max_supports = 16;465Vector3 supports[max_supports];466int support_count;467GodotShape3D::FeatureType support_type;468Vector3 support_direction = p_transform_B.basis.xform_inv(-p.normal).normalized();469470p_shape_B->get_supports(support_direction, max_supports, supports, support_count, support_type);471472if (support_count == 0) { // This is a poor man's way to detect shapes that don't implement get_supports, such as GodotMotionShape3D.473Vector3 support_B = p_transform_B.xform(p_shape_B->get_support(support_direction));474r_point_A = p.project(support_B);475r_point_B = support_B;476bool collided = p.distance_to(support_B) <= 0;477return collided;478}479480if (support_type == GodotShape3D::FEATURE_CIRCLE) {481ERR_FAIL_COND_V(support_count != 3, false);482483Vector3 circle_pos = supports[0];484Vector3 circle_axis_1 = supports[1] - circle_pos;485Vector3 circle_axis_2 = supports[2] - circle_pos;486487// Use 3 equidistant points on the circle.488for (int i = 0; i < 3; ++i) {489Vector3 vertex_pos = circle_pos;490vertex_pos += circle_axis_1 * Math::cos(2.0 * Math::PI * i / 3.0);491vertex_pos += circle_axis_2 * Math::sin(2.0 * Math::PI * i / 3.0);492supports[i] = vertex_pos;493}494}495496bool collided = false;497Vector3 closest;498real_t closest_d = 0;499500for (int i = 0; i < support_count; i++) {501supports[i] = p_transform_B.xform(supports[i]);502real_t d = p.distance_to(supports[i]);503if (i == 0 || d < closest_d) {504closest = supports[i];505closest_d = d;506if (d <= 0) {507collided = true;508}509}510}511512r_point_A = p.project(closest);513r_point_B = closest;514515return collided;516}517518bool GodotCollisionSolver3D::solve_distance(const GodotShape3D *p_shape_A, const Transform3D &p_transform_A, const GodotShape3D *p_shape_B, const Transform3D &p_transform_B, Vector3 &r_point_A, Vector3 &r_point_B, const AABB &p_concave_hint, Vector3 *r_sep_axis) {519if (p_shape_B->get_type() == PhysicsServer3D::SHAPE_WORLD_BOUNDARY) {520Vector3 a, b;521bool col = solve_distance_world_boundary(p_shape_B, p_transform_B, p_shape_A, p_transform_A, a, b);522r_point_A = b;523r_point_B = a;524return !col;525526} else if (p_shape_B->is_concave()) {527if (p_shape_A->is_concave()) {528return false;529}530531const GodotConcaveShape3D *concave_B = static_cast<const GodotConcaveShape3D *>(p_shape_B);532533_ConcaveCollisionInfo cinfo;534cinfo.transform_A = &p_transform_A;535cinfo.shape_A = p_shape_A;536cinfo.transform_B = &p_transform_B;537cinfo.result_callback = nullptr;538cinfo.userdata = nullptr;539cinfo.swap_result = false;540cinfo.collided = false;541cinfo.collisions = 0;542cinfo.aabb_tests = 0;543cinfo.tested = false;544545Transform3D rel_transform = p_transform_A;546rel_transform.origin -= p_transform_B.origin;547548//quickly compute a local AABB549550bool use_cc_hint = p_concave_hint != AABB();551AABB cc_hint_aabb;552if (use_cc_hint) {553cc_hint_aabb = p_concave_hint;554cc_hint_aabb.position -= p_transform_B.origin;555}556557AABB local_aabb;558for (int i = 0; i < 3; i++) {559Vector3 axis(p_transform_B.basis.get_column(i));560real_t axis_scale = ((real_t)1.0) / axis.length();561axis *= axis_scale;562563real_t smin, smax;564565if (use_cc_hint) {566cc_hint_aabb.project_range_in_plane(Plane(axis), smin, smax);567} else {568p_shape_A->project_range(axis, rel_transform, smin, smax);569}570571smin *= axis_scale;572smax *= axis_scale;573574local_aabb.position[i] = smin;575local_aabb.size[i] = smax - smin;576}577578concave_B->cull(local_aabb, concave_distance_callback, &cinfo, false);579if (!cinfo.collided) {580r_point_A = cinfo.close_A;581r_point_B = cinfo.close_B;582}583584return !cinfo.collided;585} else {586return gjk_epa_calculate_distance(p_shape_A, p_transform_A, p_shape_B, p_transform_B, r_point_A, r_point_B); //should pass sepaxis..587}588}589590591