Path: blob/master/modules/godot_physics_2d/godot_shape_2d.h
10277 views
/**************************************************************************/1/* godot_shape_2d.h */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#pragma once3132#include "servers/physics_server_2d.h"3334class GodotShape2D;3536class GodotShapeOwner2D {37public:38virtual void _shape_changed() = 0;39virtual void remove_shape(GodotShape2D *p_shape) = 0;4041virtual ~GodotShapeOwner2D() {}42};4344class GodotShape2D {45RID self;46Rect2 aabb;47bool configured = false;48real_t custom_bias = 0.0;4950HashMap<GodotShapeOwner2D *, int> owners;5152protected:53const double segment_is_valid_support_threshold = 0.99998;54const double segment_is_valid_support_threshold_lower =55Math::sqrt(1.0 - segment_is_valid_support_threshold * segment_is_valid_support_threshold);5657void configure(const Rect2 &p_aabb);5859public:60_FORCE_INLINE_ void set_self(const RID &p_self) { self = p_self; }61_FORCE_INLINE_ RID get_self() const { return self; }6263virtual PhysicsServer2D::ShapeType get_type() const = 0;6465_FORCE_INLINE_ Rect2 get_aabb() const { return aabb; }66_FORCE_INLINE_ bool is_configured() const { return configured; }6768virtual bool allows_one_way_collision() const { return true; }6970virtual bool is_concave() const { return false; }7172virtual bool contains_point(const Vector2 &p_point) const = 0;7374virtual void project_rangev(const Vector2 &p_normal, const Transform2D &p_transform, real_t &r_min, real_t &r_max) const = 0;75virtual void project_range_castv(const Vector2 &p_cast, const Vector2 &p_normal, const Transform2D &p_transform, real_t &r_min, real_t &r_max) const = 0;76virtual Vector2 get_support(const Vector2 &p_normal) const;77virtual void get_supports(const Vector2 &p_normal, Vector2 *r_supports, int &r_amount) const = 0;7879virtual bool intersect_segment(const Vector2 &p_begin, const Vector2 &p_end, Vector2 &r_point, Vector2 &r_normal) const = 0;80virtual real_t get_moment_of_inertia(real_t p_mass, const Size2 &p_scale) const = 0;81virtual void set_data(const Variant &p_data) = 0;82virtual Variant get_data() const = 0;8384_FORCE_INLINE_ void set_custom_bias(real_t p_bias) { custom_bias = p_bias; }85_FORCE_INLINE_ real_t get_custom_bias() const { return custom_bias; }8687void add_owner(GodotShapeOwner2D *p_owner);88void remove_owner(GodotShapeOwner2D *p_owner);89bool is_owner(GodotShapeOwner2D *p_owner) const;90const HashMap<GodotShapeOwner2D *, int> &get_owners() const;9192_FORCE_INLINE_ void get_supports_transformed_cast(const Vector2 &p_cast, const Vector2 &p_normal, const Transform2D &p_xform, Vector2 *r_supports, int &r_amount) const {93get_supports(p_xform.basis_xform_inv(p_normal).normalized(), r_supports, r_amount);94for (int i = 0; i < r_amount; i++) {95r_supports[i] = p_xform.xform(r_supports[i]);96}9798if (r_amount == 1) {99if (Math::abs(p_normal.dot(p_cast.normalized())) < segment_is_valid_support_threshold_lower) {100//make line because they are parallel101r_amount = 2;102r_supports[1] = r_supports[0] + p_cast;103} else if (p_cast.dot(p_normal) > 0) {104//normal points towards cast, add cast105r_supports[0] += p_cast;106}107108} else {109if (Math::abs(p_normal.dot(p_cast.normalized())) < segment_is_valid_support_threshold_lower) {110//optimize line and make it larger because they are parallel111if ((r_supports[1] - r_supports[0]).dot(p_cast) > 0) {112//larger towards 1113r_supports[1] += p_cast;114} else {115//larger towards 0116r_supports[0] += p_cast;117}118} else if (p_cast.dot(p_normal) > 0) {119//normal points towards cast, add cast120r_supports[0] += p_cast;121r_supports[1] += p_cast;122}123}124}125GodotShape2D() {}126virtual ~GodotShape2D();127};128129//let the optimizer do the magic130#define DEFAULT_PROJECT_RANGE_CAST \131virtual void project_range_castv(const Vector2 &p_cast, const Vector2 &p_normal, const Transform2D &p_transform, real_t &r_min, real_t &r_max) const override { \132project_range_cast(p_cast, p_normal, p_transform, r_min, r_max); \133} \134_FORCE_INLINE_ void project_range_cast(const Vector2 &p_cast, const Vector2 &p_normal, const Transform2D &p_transform, real_t &r_min, real_t &r_max) const { \135real_t mina, maxa; \136real_t minb, maxb; \137Transform2D ofsb = p_transform; \138ofsb.columns[2] += p_cast; \139project_range(p_normal, p_transform, mina, maxa); \140project_range(p_normal, ofsb, minb, maxb); \141r_min = MIN(mina, minb); \142r_max = MAX(maxa, maxb); \143}144145class GodotWorldBoundaryShape2D : public GodotShape2D {146Vector2 normal;147real_t d = 0.0;148149public:150_FORCE_INLINE_ Vector2 get_normal() const { return normal; }151_FORCE_INLINE_ real_t get_d() const { return d; }152153virtual PhysicsServer2D::ShapeType get_type() const override { return PhysicsServer2D::SHAPE_WORLD_BOUNDARY; }154155virtual void project_rangev(const Vector2 &p_normal, const Transform2D &p_transform, real_t &r_min, real_t &r_max) const override { project_range(p_normal, p_transform, r_min, r_max); }156virtual void get_supports(const Vector2 &p_normal, Vector2 *r_supports, int &r_amount) const override;157158virtual bool contains_point(const Vector2 &p_point) const override;159virtual bool intersect_segment(const Vector2 &p_begin, const Vector2 &p_end, Vector2 &r_point, Vector2 &r_normal) const override;160virtual real_t get_moment_of_inertia(real_t p_mass, const Size2 &p_scale) const override;161162virtual void set_data(const Variant &p_data) override;163virtual Variant get_data() const override;164165_FORCE_INLINE_ void project_range(const Vector2 &p_normal, const Transform2D &p_transform, real_t &r_min, real_t &r_max) const {166//real large167r_min = -1e10;168r_max = 1e10;169}170171virtual void project_range_castv(const Vector2 &p_cast, const Vector2 &p_normal, const Transform2D &p_transform, real_t &r_min, real_t &r_max) const override {172project_range_cast(p_cast, p_normal, p_transform, r_min, r_max);173}174175_FORCE_INLINE_ void project_range_cast(const Vector2 &p_cast, const Vector2 &p_normal, const Transform2D &p_transform, real_t &r_min, real_t &r_max) const {176//real large177r_min = -1e10;178r_max = 1e10;179}180};181182class GodotSeparationRayShape2D : public GodotShape2D {183real_t length = 0.0;184bool slide_on_slope = false;185186public:187_FORCE_INLINE_ real_t get_length() const { return length; }188_FORCE_INLINE_ bool get_slide_on_slope() const { return slide_on_slope; }189190virtual PhysicsServer2D::ShapeType get_type() const override { return PhysicsServer2D::SHAPE_SEPARATION_RAY; }191192virtual bool allows_one_way_collision() const override { return false; }193194virtual void project_rangev(const Vector2 &p_normal, const Transform2D &p_transform, real_t &r_min, real_t &r_max) const override { project_range(p_normal, p_transform, r_min, r_max); }195virtual void get_supports(const Vector2 &p_normal, Vector2 *r_supports, int &r_amount) const override;196197virtual bool contains_point(const Vector2 &p_point) const override;198virtual bool intersect_segment(const Vector2 &p_begin, const Vector2 &p_end, Vector2 &r_point, Vector2 &r_normal) const override;199virtual real_t get_moment_of_inertia(real_t p_mass, const Size2 &p_scale) const override;200201virtual void set_data(const Variant &p_data) override;202virtual Variant get_data() const override;203204_FORCE_INLINE_ void project_range(const Vector2 &p_normal, const Transform2D &p_transform, real_t &r_min, real_t &r_max) const {205//real large206r_max = p_normal.dot(p_transform.get_origin());207r_min = p_normal.dot(p_transform.xform(Vector2(0, length)));208if (r_max < r_min) {209SWAP(r_max, r_min);210}211}212213DEFAULT_PROJECT_RANGE_CAST214215_FORCE_INLINE_ GodotSeparationRayShape2D() {}216_FORCE_INLINE_ GodotSeparationRayShape2D(real_t p_length) { length = p_length; }217};218219class GodotSegmentShape2D : public GodotShape2D {220Vector2 a;221Vector2 b;222Vector2 n;223224public:225_FORCE_INLINE_ const Vector2 &get_a() const { return a; }226_FORCE_INLINE_ const Vector2 &get_b() const { return b; }227_FORCE_INLINE_ const Vector2 &get_normal() const { return n; }228229virtual PhysicsServer2D::ShapeType get_type() const override { return PhysicsServer2D::SHAPE_SEGMENT; }230231_FORCE_INLINE_ Vector2 get_xformed_normal(const Transform2D &p_xform) const {232return (p_xform.xform(b) - p_xform.xform(a)).normalized().orthogonal();233}234virtual void project_rangev(const Vector2 &p_normal, const Transform2D &p_transform, real_t &r_min, real_t &r_max) const override { project_range(p_normal, p_transform, r_min, r_max); }235virtual void get_supports(const Vector2 &p_normal, Vector2 *r_supports, int &r_amount) const override;236237virtual bool contains_point(const Vector2 &p_point) const override;238virtual bool intersect_segment(const Vector2 &p_begin, const Vector2 &p_end, Vector2 &r_point, Vector2 &r_normal) const override;239virtual real_t get_moment_of_inertia(real_t p_mass, const Size2 &p_scale) const override;240241virtual void set_data(const Variant &p_data) override;242virtual Variant get_data() const override;243244_FORCE_INLINE_ void project_range(const Vector2 &p_normal, const Transform2D &p_transform, real_t &r_min, real_t &r_max) const {245//real large246r_max = p_normal.dot(p_transform.xform(a));247r_min = p_normal.dot(p_transform.xform(b));248if (r_max < r_min) {249SWAP(r_max, r_min);250}251}252253DEFAULT_PROJECT_RANGE_CAST254255_FORCE_INLINE_ GodotSegmentShape2D() {}256_FORCE_INLINE_ GodotSegmentShape2D(const Vector2 &p_a, const Vector2 &p_b, const Vector2 &p_n) {257a = p_a;258b = p_b;259n = p_n;260}261};262263class GodotCircleShape2D : public GodotShape2D {264real_t radius;265266public:267_FORCE_INLINE_ const real_t &get_radius() const { return radius; }268269virtual PhysicsServer2D::ShapeType get_type() const override { return PhysicsServer2D::SHAPE_CIRCLE; }270271virtual void project_rangev(const Vector2 &p_normal, const Transform2D &p_transform, real_t &r_min, real_t &r_max) const override { project_range(p_normal, p_transform, r_min, r_max); }272virtual void get_supports(const Vector2 &p_normal, Vector2 *r_supports, int &r_amount) const override;273274virtual bool contains_point(const Vector2 &p_point) const override;275virtual bool intersect_segment(const Vector2 &p_begin, const Vector2 &p_end, Vector2 &r_point, Vector2 &r_normal) const override;276virtual real_t get_moment_of_inertia(real_t p_mass, const Size2 &p_scale) const override;277278virtual void set_data(const Variant &p_data) override;279virtual Variant get_data() const override;280281_FORCE_INLINE_ void project_range(const Vector2 &p_normal, const Transform2D &p_transform, real_t &r_min, real_t &r_max) const {282//real large283real_t d = p_normal.dot(p_transform.get_origin());284285// figure out scale at point286Vector2 local_normal = p_transform.basis_xform_inv(p_normal);287real_t scale = local_normal.length();288289r_min = d - (radius)*scale;290r_max = d + (radius)*scale;291}292293DEFAULT_PROJECT_RANGE_CAST294};295296class GodotRectangleShape2D : public GodotShape2D {297Vector2 half_extents;298299public:300_FORCE_INLINE_ const Vector2 &get_half_extents() const { return half_extents; }301302virtual PhysicsServer2D::ShapeType get_type() const override { return PhysicsServer2D::SHAPE_RECTANGLE; }303304virtual void project_rangev(const Vector2 &p_normal, const Transform2D &p_transform, real_t &r_min, real_t &r_max) const override { project_range(p_normal, p_transform, r_min, r_max); }305virtual void get_supports(const Vector2 &p_normal, Vector2 *r_supports, int &r_amount) const override;306307virtual bool contains_point(const Vector2 &p_point) const override;308virtual bool intersect_segment(const Vector2 &p_begin, const Vector2 &p_end, Vector2 &r_point, Vector2 &r_normal) const override;309virtual real_t get_moment_of_inertia(real_t p_mass, const Size2 &p_scale) const override;310311virtual void set_data(const Variant &p_data) override;312virtual Variant get_data() const override;313314_FORCE_INLINE_ void project_range(const Vector2 &p_normal, const Transform2D &p_transform, real_t &r_min, real_t &r_max) const {315// no matter the angle, the box is mirrored anyway316r_max = -1e20;317r_min = 1e20;318for (int i = 0; i < 4; i++) {319real_t d = p_normal.dot(p_transform.xform(Vector2(((i & 1) * 2 - 1) * half_extents.x, ((i >> 1) * 2 - 1) * half_extents.y)));320321if (d > r_max) {322r_max = d;323}324if (d < r_min) {325r_min = d;326}327}328}329330_FORCE_INLINE_ Vector2 get_circle_axis(const Transform2D &p_xform, const Transform2D &p_xform_inv, const Vector2 &p_circle) const {331Vector2 local_v = p_xform_inv.xform(p_circle);332333Vector2 he(334(local_v.x < 0) ? -half_extents.x : half_extents.x,335(local_v.y < 0) ? -half_extents.y : half_extents.y);336337return (p_xform.xform(he) - p_circle).normalized();338}339340_FORCE_INLINE_ Vector2 get_box_axis(const Transform2D &p_xform, const Transform2D &p_xform_inv, const GodotRectangleShape2D *p_B, const Transform2D &p_B_xform, const Transform2D &p_B_xform_inv) const {341Vector2 a, b;342343{344Vector2 local_v = p_xform_inv.xform(p_B_xform.get_origin());345346Vector2 he(347(local_v.x < 0) ? -half_extents.x : half_extents.x,348(local_v.y < 0) ? -half_extents.y : half_extents.y);349350a = p_xform.xform(he);351}352{353Vector2 local_v = p_B_xform_inv.xform(p_xform.get_origin());354355Vector2 he(356(local_v.x < 0) ? -p_B->half_extents.x : p_B->half_extents.x,357(local_v.y < 0) ? -p_B->half_extents.y : p_B->half_extents.y);358359b = p_B_xform.xform(he);360}361362return (a - b).normalized();363}364365DEFAULT_PROJECT_RANGE_CAST366};367368class GodotCapsuleShape2D : public GodotShape2D {369real_t radius = 0.0;370real_t height = 0.0;371372public:373_FORCE_INLINE_ const real_t &get_radius() const { return radius; }374_FORCE_INLINE_ const real_t &get_height() const { return height; }375376virtual PhysicsServer2D::ShapeType get_type() const override { return PhysicsServer2D::SHAPE_CAPSULE; }377378virtual void project_rangev(const Vector2 &p_normal, const Transform2D &p_transform, real_t &r_min, real_t &r_max) const override { project_range(p_normal, p_transform, r_min, r_max); }379virtual void get_supports(const Vector2 &p_normal, Vector2 *r_supports, int &r_amount) const override;380381virtual bool contains_point(const Vector2 &p_point) const override;382virtual bool intersect_segment(const Vector2 &p_begin, const Vector2 &p_end, Vector2 &r_point, Vector2 &r_normal) const override;383virtual real_t get_moment_of_inertia(real_t p_mass, const Size2 &p_scale) const override;384385virtual void set_data(const Variant &p_data) override;386virtual Variant get_data() const override;387388_FORCE_INLINE_ void project_range(const Vector2 &p_normal, const Transform2D &p_transform, real_t &r_min, real_t &r_max) const {389// no matter the angle, the box is mirrored anyway390Vector2 n = p_transform.basis_xform_inv(p_normal).normalized();391real_t h = height * 0.5 - radius;392393n *= radius;394n.y += (n.y > 0) ? h : -h;395396r_max = p_normal.dot(p_transform.xform(n));397r_min = p_normal.dot(p_transform.xform(-n));398399if (r_max < r_min) {400SWAP(r_max, r_min);401}402403//ERR_FAIL_COND( r_max < r_min );404}405406DEFAULT_PROJECT_RANGE_CAST407};408409class GodotConvexPolygonShape2D : public GodotShape2D {410struct Point {411Vector2 pos;412Vector2 normal; //normal to next segment413};414415Point *points = nullptr;416int point_count = 0;417418public:419_FORCE_INLINE_ int get_point_count() const { return point_count; }420_FORCE_INLINE_ const Vector2 &get_point(int p_idx) const { return points[p_idx].pos; }421_FORCE_INLINE_ const Vector2 &get_segment_normal(int p_idx) const { return points[p_idx].normal; }422_FORCE_INLINE_ Vector2 get_xformed_segment_normal(const Transform2D &p_xform, int p_idx) const {423Vector2 a = points[p_idx].pos;424p_idx++;425Vector2 b = points[p_idx == point_count ? 0 : p_idx].pos;426return (p_xform.xform(b) - p_xform.xform(a)).normalized().orthogonal();427}428429virtual PhysicsServer2D::ShapeType get_type() const override { return PhysicsServer2D::SHAPE_CONVEX_POLYGON; }430431virtual void project_rangev(const Vector2 &p_normal, const Transform2D &p_transform, real_t &r_min, real_t &r_max) const override { project_range(p_normal, p_transform, r_min, r_max); }432virtual void get_supports(const Vector2 &p_normal, Vector2 *r_supports, int &r_amount) const override;433434virtual bool contains_point(const Vector2 &p_point) const override;435virtual bool intersect_segment(const Vector2 &p_begin, const Vector2 &p_end, Vector2 &r_point, Vector2 &r_normal) const override;436virtual real_t get_moment_of_inertia(real_t p_mass, const Size2 &p_scale) const override;437438virtual void set_data(const Variant &p_data) override;439virtual Variant get_data() const override;440441_FORCE_INLINE_ void project_range(const Vector2 &p_normal, const Transform2D &p_transform, real_t &r_min, real_t &r_max) const {442if (!points || point_count <= 0) {443r_min = r_max = 0;444return;445}446447r_min = r_max = p_normal.dot(p_transform.xform(points[0].pos));448for (int i = 1; i < point_count; i++) {449real_t d = p_normal.dot(p_transform.xform(points[i].pos));450if (d > r_max) {451r_max = d;452}453if (d < r_min) {454r_min = d;455}456}457}458459DEFAULT_PROJECT_RANGE_CAST460461GodotConvexPolygonShape2D() {}462~GodotConvexPolygonShape2D();463};464465class GodotConcaveShape2D : public GodotShape2D {466public:467virtual bool is_concave() const override { return true; }468469// Returns true to stop the query.470typedef bool (*QueryCallback)(void *p_userdata, GodotShape2D *p_convex);471472virtual void cull(const Rect2 &p_local_aabb, QueryCallback p_callback, void *p_userdata) const = 0;473};474475class GodotConcavePolygonShape2D : public GodotConcaveShape2D {476struct Segment {477int points[2] = {};478};479480Vector<Segment> segments;481Vector<Point2> points;482483struct BVH {484Rect2 aabb;485int left = 0, right = 0;486};487488Vector<BVH> bvh;489int bvh_depth = 0;490491struct BVH_CompareX {492_FORCE_INLINE_ bool operator()(const BVH &a, const BVH &b) const {493return (a.aabb.position.x + a.aabb.size.x * 0.5) < (b.aabb.position.x + b.aabb.size.x * 0.5);494}495};496497struct BVH_CompareY {498_FORCE_INLINE_ bool operator()(const BVH &a, const BVH &b) const {499return (a.aabb.position.y + a.aabb.size.y * 0.5) < (b.aabb.position.y + b.aabb.size.y * 0.5);500}501};502503int _generate_bvh(BVH *p_bvh, int p_len, int p_depth);504505public:506virtual PhysicsServer2D::ShapeType get_type() const override { return PhysicsServer2D::SHAPE_CONCAVE_POLYGON; }507508virtual void project_rangev(const Vector2 &p_normal, const Transform2D &p_transform, real_t &r_min, real_t &r_max) const override {509r_min = 0;510r_max = 0;511ERR_FAIL_MSG("Unsupported call to project_rangev in GodotConcavePolygonShape2D");512}513514void project_range(const Vector2 &p_normal, const Transform2D &p_transform, real_t &r_min, real_t &r_max) const {515r_min = 0;516r_max = 0;517ERR_FAIL_MSG("Unsupported call to project_range in GodotConcavePolygonShape2D");518}519520virtual void get_supports(const Vector2 &p_normal, Vector2 *r_supports, int &r_amount) const override;521522virtual bool contains_point(const Vector2 &p_point) const override;523virtual bool intersect_segment(const Vector2 &p_begin, const Vector2 &p_end, Vector2 &r_point, Vector2 &r_normal) const override;524525virtual real_t get_moment_of_inertia(real_t p_mass, const Size2 &p_scale) const override { return 0; }526527virtual void set_data(const Variant &p_data) override;528virtual Variant get_data() const override;529530virtual void cull(const Rect2 &p_local_aabb, QueryCallback p_callback, void *p_userdata) const override;531532DEFAULT_PROJECT_RANGE_CAST533};534535#undef DEFAULT_PROJECT_RANGE_CAST536537538