You're breathtaking!
This commit is contained in:
183
Source/Engine/Physics/Colliders/BoxCollider.cpp
Normal file
183
Source/Engine/Physics/Colliders/BoxCollider.cpp
Normal file
@@ -0,0 +1,183 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "BoxCollider.h"
|
||||
#include "Engine/Serialization/Serialization.h"
|
||||
#include "Engine/Physics/Utilities.h"
|
||||
#include <ThirdParty/PhysX/PxShape.h>
|
||||
#if USE_EDITOR
|
||||
#include "Engine/Level/Scene/SceneRendering.h"
|
||||
#endif
|
||||
|
||||
BoxCollider::BoxCollider(const SpawnParams& params)
|
||||
: Collider(params)
|
||||
, _size(100.0f)
|
||||
{
|
||||
}
|
||||
|
||||
void BoxCollider::SetSize(const Vector3& value)
|
||||
{
|
||||
if (Vector3::NearEqual(value, _size))
|
||||
return;
|
||||
|
||||
_size = value;
|
||||
|
||||
UpdateGeometry();
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
#include "Engine/Debug/DebugDraw.h"
|
||||
|
||||
void BoxCollider::DrawPhysicsDebug(RenderView& view)
|
||||
{
|
||||
const Color color = Color::GreenYellow;
|
||||
DEBUG_DRAW_WIRE_BOX(_bounds, color * 0.8f, 0, true);
|
||||
}
|
||||
|
||||
void BoxCollider::OnDebugDraw()
|
||||
{
|
||||
if (GetIsTrigger())
|
||||
{
|
||||
const Color color = Color::GreenYellow;
|
||||
DEBUG_DRAW_WIRE_BOX(_bounds, color, 0, true);
|
||||
}
|
||||
|
||||
// Base
|
||||
Collider::OnDebugDraw();
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
OrientedBoundingBox GetWriteBox(const Vector3& min, const Vector3& max, const float margin)
|
||||
{
|
||||
OrientedBoundingBox box;
|
||||
const Vector3 vec = max - min;
|
||||
const Vector3 dir = Vector3::Normalize(vec);
|
||||
Quaternion orientation;
|
||||
if (Vector3::Dot(dir, Vector3::Up) >= 0.999f)
|
||||
Quaternion::RotationAxis(Vector3::Left, PI_HALF, orientation);
|
||||
else
|
||||
Quaternion::LookRotation(dir, Vector3::Cross(Vector3::Cross(dir, Vector3::Up), dir), orientation);
|
||||
const Vector3 up = orientation * Vector3::Up;
|
||||
Matrix::CreateWorld(min + vec * 0.5f, dir, up, box.Transformation);
|
||||
Matrix inv;
|
||||
Matrix::Invert(box.Transformation, inv);
|
||||
Vector3 vecLocal;
|
||||
Vector3::TransformNormal(vec * 0.5f, inv, vecLocal);
|
||||
box.Extents.X = margin;
|
||||
box.Extents.Y = margin;
|
||||
box.Extents.Z = vecLocal.Z;
|
||||
return box;
|
||||
}
|
||||
}
|
||||
|
||||
void BoxCollider::OnDebugDrawSelected()
|
||||
{
|
||||
const Color color = Color::GreenYellow;
|
||||
DEBUG_DRAW_WIRE_BOX(_bounds, color * 0.3f, 0, false);
|
||||
|
||||
Vector3 corners[8];
|
||||
_bounds.GetCorners(corners);
|
||||
const float margin = 1.0f;
|
||||
const Color wiresColor = color.AlphaMultiplied(0.6f);
|
||||
DEBUG_DRAW_BOX(GetWriteBox(corners[0], corners[1], margin), wiresColor, 0, true);
|
||||
DEBUG_DRAW_BOX(GetWriteBox(corners[0], corners[3], margin), wiresColor, 0, true);
|
||||
DEBUG_DRAW_BOX(GetWriteBox(corners[0], corners[4], margin), wiresColor, 0, true);
|
||||
DEBUG_DRAW_BOX(GetWriteBox(corners[1], corners[2], margin), wiresColor, 0, true);
|
||||
DEBUG_DRAW_BOX(GetWriteBox(corners[1], corners[5], margin), wiresColor, 0, true);
|
||||
DEBUG_DRAW_BOX(GetWriteBox(corners[2], corners[3], margin), wiresColor, 0, true);
|
||||
DEBUG_DRAW_BOX(GetWriteBox(corners[2], corners[6], margin), wiresColor, 0, true);
|
||||
DEBUG_DRAW_BOX(GetWriteBox(corners[3], corners[7], margin), wiresColor, 0, true);
|
||||
DEBUG_DRAW_BOX(GetWriteBox(corners[4], corners[5], margin), wiresColor, 0, true);
|
||||
DEBUG_DRAW_BOX(GetWriteBox(corners[4], corners[7], margin), wiresColor, 0, true);
|
||||
DEBUG_DRAW_BOX(GetWriteBox(corners[5], corners[6], margin), wiresColor, 0, true);
|
||||
DEBUG_DRAW_BOX(GetWriteBox(corners[6], corners[7], margin), wiresColor, 0, true);
|
||||
|
||||
// Base
|
||||
Collider::OnDebugDrawSelected();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
bool BoxCollider::IntersectsItself(const Ray& ray, float& distance, Vector3& normal)
|
||||
{
|
||||
return _bounds.Intersects(ray, distance, normal);
|
||||
}
|
||||
|
||||
void BoxCollider::Serialize(SerializeStream& stream, const void* otherObj)
|
||||
{
|
||||
// Base
|
||||
Collider::Serialize(stream, otherObj);
|
||||
|
||||
SERIALIZE_GET_OTHER_OBJ(BoxCollider);
|
||||
|
||||
SERIALIZE_MEMBER(Size, _size);
|
||||
}
|
||||
|
||||
void BoxCollider::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
|
||||
{
|
||||
// Base
|
||||
Collider::Deserialize(stream, modifier);
|
||||
|
||||
DESERIALIZE_MEMBER(Size, _size);
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
void BoxCollider::OnEnable()
|
||||
{
|
||||
GetSceneRendering()->AddPhysicsDebug<BoxCollider, &BoxCollider::DrawPhysicsDebug>(this);
|
||||
|
||||
// Base
|
||||
Collider::OnEnable();
|
||||
}
|
||||
|
||||
void BoxCollider::OnDisable()
|
||||
{
|
||||
GetSceneRendering()->RemovePhysicsDebug<BoxCollider, &BoxCollider::DrawPhysicsDebug>(this);
|
||||
|
||||
// Base
|
||||
Collider::OnDisable();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void BoxCollider::UpdateBounds()
|
||||
{
|
||||
// Cache bounds
|
||||
OrientedBoundingBox::CreateCentered(_center, _size, _bounds);
|
||||
_bounds.Transform(_transform.GetWorld());
|
||||
_bounds.GetBoundingBox(_box);
|
||||
BoundingSphere::FromBox(_box, _sphere);
|
||||
}
|
||||
|
||||
void BoxCollider::CreateShape()
|
||||
{
|
||||
// Setup shape geometry
|
||||
_cachedScale = GetScale();
|
||||
Vector3 size = _size * _cachedScale;
|
||||
size.Absolute();
|
||||
const float minSize = 0.001f;
|
||||
const PxBoxGeometry geometry(Math::Max(size.X * 0.5f, minSize), Math::Max(size.Y * 0.5f, minSize), Math::Max(size.Z * 0.5f, minSize));
|
||||
|
||||
// Setup shape
|
||||
CreateShapeBase(geometry);
|
||||
}
|
||||
|
||||
void BoxCollider::UpdateGeometry()
|
||||
{
|
||||
// Check if has no shape created
|
||||
if (_shape == nullptr)
|
||||
return;
|
||||
|
||||
// Setup shape geometry
|
||||
_cachedScale = GetScale();
|
||||
Vector3 size = _size * _cachedScale;
|
||||
size.Absolute();
|
||||
const float minSize = 0.001f;
|
||||
const PxBoxGeometry geometry(Math::Max(size.X * 0.5f, minSize), Math::Max(size.Y * 0.5f, minSize), Math::Max(size.Z * 0.5f, minSize));
|
||||
|
||||
// Setup shape
|
||||
_shape->setGeometry(geometry);
|
||||
}
|
||||
77
Source/Engine/Physics/Colliders/BoxCollider.h
Normal file
77
Source/Engine/Physics/Colliders/BoxCollider.h
Normal file
@@ -0,0 +1,77 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Collider.h"
|
||||
#include "Engine/Core/Math/OrientedBoundingBox.h"
|
||||
|
||||
/// <summary>
|
||||
/// A box-shaped primitive collider.
|
||||
/// </summary>
|
||||
/// <seealso cref="Collider" />
|
||||
API_CLASS() class FLAXENGINE_API BoxCollider : public Collider
|
||||
{
|
||||
DECLARE_SCENE_OBJECT(BoxCollider);
|
||||
private:
|
||||
|
||||
Vector3 _size;
|
||||
OrientedBoundingBox _bounds;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Gets the size of the box, measured in the object's local space.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The box size will be scaled by the actor's world scale.
|
||||
/// </remarks>
|
||||
API_PROPERTY(Attributes="EditorOrder(100), DefaultValue(typeof(Vector3), \"100,100,100\"), EditorDisplay(\"Collider\")")
|
||||
FORCE_INLINE Vector3 GetSize() const
|
||||
{
|
||||
return _size;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the size of the box, measured in the object's local space.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The box size will be scaled by the actor's world scale.
|
||||
/// </remarks>
|
||||
API_PROPERTY() void SetSize(const Vector3& value);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the volume bounding box (oriented).
|
||||
/// </summary>
|
||||
API_PROPERTY() FORCE_INLINE OrientedBoundingBox GetOrientedBox() const
|
||||
{
|
||||
return _bounds;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
#if USE_EDITOR
|
||||
void DrawPhysicsDebug(RenderView& view);
|
||||
#endif
|
||||
|
||||
public:
|
||||
|
||||
// [Collider]
|
||||
#if USE_EDITOR
|
||||
void OnDebugDraw() override;
|
||||
void OnDebugDrawSelected() override;
|
||||
#endif
|
||||
bool IntersectsItself(const Ray& ray, float& distance, Vector3& normal) override;
|
||||
void Serialize(SerializeStream& stream, const void* otherObj) override;
|
||||
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
|
||||
|
||||
protected:
|
||||
|
||||
// [Collider]
|
||||
#if USE_EDITOR
|
||||
void OnEnable() override;
|
||||
void OnDisable() override;
|
||||
#endif
|
||||
void UpdateBounds() override;
|
||||
void CreateShape() override;
|
||||
void UpdateGeometry() override;
|
||||
};
|
||||
156
Source/Engine/Physics/Colliders/CapsuleCollider.cpp
Normal file
156
Source/Engine/Physics/Colliders/CapsuleCollider.cpp
Normal file
@@ -0,0 +1,156 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "CapsuleCollider.h"
|
||||
#include "Engine/Serialization/Serialization.h"
|
||||
#include "Engine/Physics/Utilities.h"
|
||||
#include <ThirdParty/PhysX/PxShape.h>
|
||||
#if USE_EDITOR
|
||||
#include "Engine/Level/Scene/SceneRendering.h"
|
||||
#endif
|
||||
|
||||
CapsuleCollider::CapsuleCollider(const SpawnParams& params)
|
||||
: Collider(params)
|
||||
, _radius(20.0f)
|
||||
, _height(100.0f)
|
||||
{
|
||||
}
|
||||
|
||||
void CapsuleCollider::SetRadius(const float value)
|
||||
{
|
||||
if (Math::NearEqual(value, _radius))
|
||||
return;
|
||||
|
||||
_radius = value;
|
||||
|
||||
UpdateGeometry();
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
void CapsuleCollider::SetHeight(const float value)
|
||||
{
|
||||
if (Math::NearEqual(value, _height))
|
||||
return;
|
||||
|
||||
_height = value;
|
||||
|
||||
UpdateGeometry();
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
#include "Engine/Debug/DebugDraw.h"
|
||||
|
||||
void CapsuleCollider::DrawPhysicsDebug(RenderView& view)
|
||||
{
|
||||
Quaternion rot;
|
||||
Quaternion::Multiply(_transform.Orientation, Quaternion::Euler(0, 90, 0), rot);
|
||||
const float scaling = _cachedScale.GetAbsolute().MaxValue();
|
||||
const float minSize = 0.001f;
|
||||
const float radius = Math::Max(Math::Abs(_radius) * scaling, minSize);
|
||||
const float height = Math::Max(Math::Abs(_height) * scaling, minSize);
|
||||
DEBUG_DRAW_WIRE_TUBE(_transform.LocalToWorld(_center), rot, radius, height, Color::GreenYellow * 0.8f, 0, true);
|
||||
}
|
||||
|
||||
void CapsuleCollider::OnDebugDrawSelected()
|
||||
{
|
||||
Quaternion rot;
|
||||
Quaternion::Multiply(_transform.Orientation, Quaternion::Euler(0, 90, 0), rot);
|
||||
const float scaling = _cachedScale.GetAbsolute().MaxValue();
|
||||
const float minSize = 0.001f;
|
||||
const float radius = Math::Max(Math::Abs(_radius) * scaling, minSize);
|
||||
const float height = Math::Max(Math::Abs(_height) * scaling, minSize);
|
||||
DEBUG_DRAW_WIRE_TUBE(_transform.LocalToWorld(_center), rot, radius, height, Color::GreenYellow, 0, false);
|
||||
|
||||
// Base
|
||||
Collider::OnDebugDrawSelected();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
bool CapsuleCollider::IntersectsItself(const Ray& ray, float& distance, Vector3& normal)
|
||||
{
|
||||
return _orientedBox.Intersects(ray, distance, normal);
|
||||
}
|
||||
|
||||
void CapsuleCollider::Serialize(SerializeStream& stream, const void* otherObj)
|
||||
{
|
||||
// Base
|
||||
Collider::Serialize(stream, otherObj);
|
||||
|
||||
SERIALIZE_GET_OTHER_OBJ(CapsuleCollider);
|
||||
|
||||
SERIALIZE_MEMBER(Radius, _radius);
|
||||
SERIALIZE_MEMBER(Height, _height);
|
||||
}
|
||||
|
||||
void CapsuleCollider::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
|
||||
{
|
||||
// Base
|
||||
Collider::Deserialize(stream, modifier);
|
||||
|
||||
DESERIALIZE_MEMBER(Radius, _radius);
|
||||
DESERIALIZE_MEMBER(Height, _height);
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
void CapsuleCollider::OnEnable()
|
||||
{
|
||||
GetSceneRendering()->AddPhysicsDebug<CapsuleCollider, &CapsuleCollider::DrawPhysicsDebug>(this);
|
||||
|
||||
// Base
|
||||
Collider::OnEnable();
|
||||
}
|
||||
|
||||
void CapsuleCollider::OnDisable()
|
||||
{
|
||||
GetSceneRendering()->RemovePhysicsDebug<CapsuleCollider, &CapsuleCollider::DrawPhysicsDebug>(this);
|
||||
|
||||
// Base
|
||||
Collider::OnDisable();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void CapsuleCollider::UpdateBounds()
|
||||
{
|
||||
// Cache bounds
|
||||
const float radiusTwice = _radius * 2.0f;
|
||||
OrientedBoundingBox::CreateCentered(_center, Vector3(_height + radiusTwice, radiusTwice, radiusTwice), _orientedBox);
|
||||
_orientedBox.Transform(_transform.GetWorld());
|
||||
_orientedBox.GetBoundingBox(_box);
|
||||
BoundingSphere::FromBox(_box, _sphere);
|
||||
}
|
||||
|
||||
void CapsuleCollider::CreateShape()
|
||||
{
|
||||
// Setup shape geometry
|
||||
_cachedScale = GetScale();
|
||||
const float scaling = _cachedScale.GetAbsolute().MaxValue();
|
||||
const float minSize = 0.001f;
|
||||
const float radius = Math::Max(Math::Abs(_radius) * scaling, minSize);
|
||||
const float height = Math::Max(Math::Abs(_height) * scaling, minSize);
|
||||
const PxCapsuleGeometry geometry(radius, height * 0.5f);
|
||||
|
||||
// Setup shape
|
||||
CreateShapeBase(geometry);
|
||||
}
|
||||
|
||||
void CapsuleCollider::UpdateGeometry()
|
||||
{
|
||||
// Check if has no shape created
|
||||
if (_shape == nullptr)
|
||||
return;
|
||||
|
||||
// Setup shape geometry
|
||||
_cachedScale = GetScale();
|
||||
const float scaling = _cachedScale.GetAbsolute().MaxValue();
|
||||
const float minSize = 0.001f;
|
||||
const float radius = Math::Max(Math::Abs(_radius) * scaling, minSize);
|
||||
const float height = Math::Max(Math::Abs(_height) * scaling, minSize);
|
||||
const PxCapsuleGeometry geometry(radius, height * 0.5f);
|
||||
|
||||
// Setup shape
|
||||
_shape->setGeometry(geometry);
|
||||
}
|
||||
92
Source/Engine/Physics/Colliders/CapsuleCollider.h
Normal file
92
Source/Engine/Physics/Colliders/CapsuleCollider.h
Normal file
@@ -0,0 +1,92 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Collider.h"
|
||||
#include "Engine/Core/Math/OrientedBoundingBox.h"
|
||||
|
||||
/// <summary>
|
||||
/// A capsule-shaped primitive collider.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Capsules are cylinders with a half-sphere at each end centered at the origin and extending along the X axis, and two hemispherical ends.
|
||||
/// </remarks>
|
||||
/// <seealso cref="Collider" />
|
||||
API_CLASS() class FLAXENGINE_API CapsuleCollider : public Collider
|
||||
{
|
||||
DECLARE_SCENE_OBJECT(CapsuleCollider);
|
||||
private:
|
||||
|
||||
float _radius;
|
||||
float _height;
|
||||
OrientedBoundingBox _orientedBox;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Gets the radius of the sphere, measured in the object's local space.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The sphere radius will be scaled by the actor's world scale.
|
||||
/// </remarks>
|
||||
API_PROPERTY(Attributes="EditorOrder(100), DefaultValue(20.0f), EditorDisplay(\"Collider\")")
|
||||
FORCE_INLINE float GetRadius() const
|
||||
{
|
||||
return _radius;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the radius of the sphere, measured in the object's local space.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The sphere radius will be scaled by the actor's world scale.
|
||||
/// </remarks>
|
||||
API_PROPERTY() void SetRadius(float value);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the height of the capsule, measured in the object's local space.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The capsule height will be scaled by the actor's world scale.
|
||||
/// </remarks>
|
||||
API_PROPERTY(Attributes="EditorOrder(110), DefaultValue(100.0f), EditorDisplay(\"Collider\")")
|
||||
FORCE_INLINE float GetHeight() const
|
||||
{
|
||||
return _height;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the height of the capsule, measured in the object's local space.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The capsule height will be scaled by the actor's world scale.
|
||||
/// </remarks>
|
||||
API_PROPERTY() void SetHeight(float value);
|
||||
|
||||
private:
|
||||
|
||||
#if USE_EDITOR
|
||||
void DrawPhysicsDebug(RenderView& view);
|
||||
#endif
|
||||
|
||||
public:
|
||||
|
||||
// [Collider]
|
||||
#if USE_EDITOR
|
||||
void OnDebugDrawSelected() override;
|
||||
#endif
|
||||
bool IntersectsItself(const Ray& ray, float& distance, Vector3& normal) override;
|
||||
void Serialize(SerializeStream& stream, const void* otherObj) override;
|
||||
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
|
||||
|
||||
protected:
|
||||
|
||||
// [Collider]
|
||||
#if USE_EDITOR
|
||||
void OnEnable() override;
|
||||
void OnDisable() override;
|
||||
#endif
|
||||
void UpdateBounds() override;
|
||||
void CreateShape() override;
|
||||
void UpdateGeometry() override;
|
||||
};
|
||||
391
Source/Engine/Physics/Colliders/CharacterController.cpp
Normal file
391
Source/Engine/Physics/Colliders/CharacterController.cpp
Normal file
@@ -0,0 +1,391 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "CharacterController.h"
|
||||
#include "Engine/Physics/Utilities.h"
|
||||
#include "Engine/Physics/Colliders/Collider.h"
|
||||
#include "Engine/Physics/Physics.h"
|
||||
#include "Engine/Serialization/Serialization.h"
|
||||
#include "Engine/Engine/Time.h"
|
||||
#include "Engine/Physics/PhysicalMaterial.h"
|
||||
#if USE_EDITOR
|
||||
#include "Engine/Level/Scene/SceneRendering.h"
|
||||
#endif
|
||||
#include <ThirdParty/PhysX/PxRigidActor.h>
|
||||
#include <ThirdParty/PhysX/PxRigidDynamic.h>
|
||||
#include <ThirdParty/PhysX/PxPhysics.h>
|
||||
#include <ThirdParty/PhysX/characterkinematic/PxController.h>
|
||||
#include <ThirdParty/PhysX/characterkinematic/PxControllerManager.h>
|
||||
#include <ThirdParty/PhysX/characterkinematic//PxCapsuleController.h>
|
||||
|
||||
CharacterController::CharacterController(const SpawnParams& params)
|
||||
: Collider(params)
|
||||
, _controller(nullptr)
|
||||
, _stepOffset(30.0f)
|
||||
, _slopeLimit(45.0f)
|
||||
, _radius(50.0f)
|
||||
, _height(150.0f)
|
||||
, _minMoveDistance(0.0f)
|
||||
, _isUpdatingTransform(false)
|
||||
, _nonWalkableMode(CharacterController::NonWalkableModes::PreventClimbing)
|
||||
, _lastFlags(CollisionFlags::None)
|
||||
{
|
||||
}
|
||||
|
||||
void CharacterController::SetRadius(const float value)
|
||||
{
|
||||
if (Math::NearEqual(value, _radius))
|
||||
return;
|
||||
|
||||
_radius = value;
|
||||
|
||||
UpdateSize();
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
void CharacterController::SetHeight(const float value)
|
||||
{
|
||||
if (Math::NearEqual(value, _height))
|
||||
return;
|
||||
|
||||
_height = value;
|
||||
|
||||
UpdateSize();
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
void CharacterController::SetSlopeLimit(float value)
|
||||
{
|
||||
value = Math::Clamp(value, 0.0f, 90.0f);
|
||||
if (Math::NearEqual(value, _slopeLimit))
|
||||
return;
|
||||
|
||||
_slopeLimit = value;
|
||||
|
||||
if (_controller)
|
||||
_controller->setSlopeLimit(cosf(value * DegreesToRadians));
|
||||
}
|
||||
|
||||
void CharacterController::SetNonWalkableMode(NonWalkableModes value)
|
||||
{
|
||||
_nonWalkableMode = value;
|
||||
|
||||
if (_controller)
|
||||
_controller->setNonWalkableMode(static_cast<PxControllerNonWalkableMode::Enum>(value));
|
||||
}
|
||||
|
||||
void CharacterController::SetStepOffset(float value)
|
||||
{
|
||||
if (Math::NearEqual(value, _stepOffset))
|
||||
return;
|
||||
|
||||
_stepOffset = value;
|
||||
|
||||
if (_controller)
|
||||
_controller->setStepOffset(value);
|
||||
}
|
||||
|
||||
void CharacterController::SetMinMoveDistance(float value)
|
||||
{
|
||||
_minMoveDistance = Math::Max(value, 0.0f);
|
||||
}
|
||||
|
||||
Vector3 CharacterController::GetVelocity() const
|
||||
{
|
||||
return _controller ? P2C(_controller->getActor()->getLinearVelocity()) : Vector3::Zero;
|
||||
}
|
||||
|
||||
CharacterController::CollisionFlags CharacterController::SimpleMove(const Vector3& speed)
|
||||
{
|
||||
const float deltaTime = Time::GetCurrentSafe()->DeltaTime.GetTotalSeconds();
|
||||
Vector3 displacement = speed;
|
||||
displacement += Physics::GetGravity() * deltaTime;
|
||||
displacement *= deltaTime;
|
||||
|
||||
return Move(displacement);
|
||||
}
|
||||
|
||||
CharacterController::CollisionFlags CharacterController::Move(const Vector3& displacement)
|
||||
{
|
||||
CollisionFlags result = CollisionFlags::None;
|
||||
|
||||
if (_controller)
|
||||
{
|
||||
const float deltaTime = Time::GetCurrentSafe()->DeltaTime.GetTotalSeconds();
|
||||
PxControllerFilters filters;
|
||||
filters.mFilterData = &_filterData;
|
||||
filters.mFilterCallback = Physics::GetCharacterQueryFilterCallback();
|
||||
filters.mFilterFlags = PxQueryFlag::eDYNAMIC | PxQueryFlag::eSTATIC | PxQueryFlag::ePREFILTER;
|
||||
|
||||
result = (CollisionFlags)(byte)_controller->move(C2P(displacement), _minMoveDistance, deltaTime, filters);
|
||||
_lastFlags = result;
|
||||
|
||||
SetPosition(P2C(_controller->getPosition()));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
PxRigidDynamic* CharacterController::GetPhysXRigidActor() const
|
||||
{
|
||||
return _controller ? _controller->getActor() : nullptr;
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
#include "Engine/Debug/DebugDraw.h"
|
||||
|
||||
void CharacterController::DrawPhysicsDebug(RenderView& view)
|
||||
{
|
||||
const float scaling = _cachedScale.GetAbsolute().MaxValue();
|
||||
const float minSize = 0.001f;
|
||||
const float radius = Math::Max(Math::Abs(_radius) * scaling, minSize);
|
||||
const float height = Math::Max(Math::Abs(_height) * scaling, minSize);
|
||||
DEBUG_DRAW_WIRE_TUBE(_transform.LocalToWorld(_center), Quaternion::Euler(90, 0, 0), radius, height, Color::GreenYellow * 0.8f, 0, true);
|
||||
}
|
||||
|
||||
void CharacterController::OnDebugDrawSelected()
|
||||
{
|
||||
const float scaling = _cachedScale.GetAbsolute().MaxValue();
|
||||
const float minSize = 0.001f;
|
||||
const float radius = Math::Max(Math::Abs(_radius) * scaling, minSize);
|
||||
const float height = Math::Max(Math::Abs(_height) * scaling, minSize);
|
||||
DEBUG_DRAW_WIRE_TUBE(_transform.LocalToWorld(_center), Quaternion::Euler(90, 0, 0), radius, height, Color::GreenYellow, 0, false);
|
||||
|
||||
// Base
|
||||
Collider::OnDebugDrawSelected();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void CharacterController::CreateActor()
|
||||
{
|
||||
ASSERT(_controller == nullptr && _shape == nullptr);
|
||||
|
||||
PxCapsuleControllerDesc desc;
|
||||
desc.userData = this;
|
||||
desc.contactOffset = Math::Max(_contactOffset, ZeroTolerance);
|
||||
desc.position = PxExtendedVec3(_transform.Translation.X, _transform.Translation.Y, _transform.Translation.Z);
|
||||
desc.slopeLimit = Math::Cos(_slopeLimit * DegreesToRadians);
|
||||
desc.nonWalkableMode = static_cast<PxControllerNonWalkableMode::Enum>(_nonWalkableMode);
|
||||
desc.climbingMode = PxCapsuleClimbingMode::eEASY;
|
||||
desc.stepOffset = _stepOffset;
|
||||
if (Material && !Material->WaitForLoaded())
|
||||
desc.material = ((PhysicalMaterial*)Material->Instance)->GetPhysXMaterial();
|
||||
else
|
||||
desc.material = Physics::GetDefaultMaterial();
|
||||
_cachedScale = GetScale();
|
||||
const float scaling = _cachedScale.GetAbsolute().MaxValue();
|
||||
const float minSize = 0.001f;
|
||||
desc.height = Math::Max(Math::Abs(_height) * scaling, minSize);
|
||||
desc.radius = Math::Max(Math::Abs(_radius) * scaling, minSize);
|
||||
|
||||
// Create controller
|
||||
_controller = (PxCapsuleController*)Physics::GetControllerManager()->createController(desc);
|
||||
ASSERT(_controller);
|
||||
const auto actor = _controller->getActor();
|
||||
ASSERT(actor && actor->getNbShapes() == 1);
|
||||
actor->getShapes(&_shape, 1);
|
||||
actor->userData = this;
|
||||
_shape->userData = this;
|
||||
|
||||
// Apply additional shape properties
|
||||
_shape->setLocalPose(PxTransform(C2P(_center)));
|
||||
UpdateLayerBits();
|
||||
|
||||
// Update cached data
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
void CharacterController::UpdateSize() const
|
||||
{
|
||||
if (_controller)
|
||||
{
|
||||
const float scaling = _cachedScale.GetAbsolute().MaxValue();
|
||||
const float minSize = 0.001f;
|
||||
const float radius = Math::Max(Math::Abs(_radius) * scaling, minSize);
|
||||
const float height = Math::Max(Math::Abs(_height) * scaling, minSize);
|
||||
|
||||
_controller->setRadius(radius);
|
||||
_controller->resize(height);
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterController::CreateShape()
|
||||
{
|
||||
// Not used
|
||||
}
|
||||
|
||||
void CharacterController::UpdateBounds()
|
||||
{
|
||||
const auto actor = GetPhysXRigidActor();
|
||||
const float boundsScale = 1.03f;
|
||||
if (actor)
|
||||
_box = P2C(actor->getWorldBounds(boundsScale));
|
||||
else
|
||||
_box = BoundingBox(_transform.Translation, _transform.Translation);
|
||||
BoundingSphere::FromBox(_box, _sphere);
|
||||
}
|
||||
|
||||
void CharacterController::AddMovement(const Vector3& translation, const Quaternion& rotation)
|
||||
{
|
||||
Move(translation);
|
||||
|
||||
if (!rotation.IsIdentity())
|
||||
{
|
||||
SetOrientation(GetOrientation() * rotation);
|
||||
}
|
||||
}
|
||||
|
||||
bool CharacterController::CanAttach(RigidBody* rigidBody) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
RigidBody* CharacterController::GetAttachedRigidBody() const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void CharacterController::OnActiveTransformChanged(const PxTransform& transform)
|
||||
{
|
||||
// Change actor transform (but with locking)
|
||||
ASSERT(!_isUpdatingTransform);
|
||||
_isUpdatingTransform = true;
|
||||
Transform t = _transform;
|
||||
t.Translation = P2C(transform.p);
|
||||
SetTransform(t);
|
||||
_isUpdatingTransform = false;
|
||||
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
PxRigidActor* CharacterController::GetRigidActor()
|
||||
{
|
||||
return _shape ? _shape->getActor() : nullptr;
|
||||
}
|
||||
|
||||
void CharacterController::UpdateGeometry()
|
||||
{
|
||||
// Check if has no character created
|
||||
if (_shape == nullptr)
|
||||
return;
|
||||
|
||||
// Setup shape geometry
|
||||
_cachedScale = GetScale();
|
||||
UpdateSize();
|
||||
}
|
||||
|
||||
void CharacterController::UpdateLayerBits()
|
||||
{
|
||||
// Base
|
||||
Collider::UpdateLayerBits();
|
||||
|
||||
// Cache filter data
|
||||
_filterData = _shape->getSimulationFilterData();
|
||||
}
|
||||
|
||||
void CharacterController::BeginPlay(SceneBeginData* data)
|
||||
{
|
||||
CreateActor();
|
||||
|
||||
// Skip collider base
|
||||
Actor::BeginPlay(data);
|
||||
}
|
||||
|
||||
void CharacterController::EndPlay()
|
||||
{
|
||||
// Skip collider base
|
||||
Actor::EndPlay();
|
||||
|
||||
// Remove controller
|
||||
if (_controller)
|
||||
{
|
||||
_shape->userData = nullptr;
|
||||
_controller->getActor()->userData = nullptr;
|
||||
_controller->release();
|
||||
_controller = nullptr;
|
||||
}
|
||||
_shape = nullptr;
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
void CharacterController::OnEnable()
|
||||
{
|
||||
GetSceneRendering()->AddPhysicsDebug<CharacterController, &CharacterController::DrawPhysicsDebug>(this);
|
||||
|
||||
// Base
|
||||
Collider::OnEnable();
|
||||
}
|
||||
|
||||
void CharacterController::OnDisable()
|
||||
{
|
||||
GetSceneRendering()->RemovePhysicsDebug<CharacterController, &CharacterController::DrawPhysicsDebug>(this);
|
||||
|
||||
// Base
|
||||
Collider::OnDisable();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void CharacterController::OnActiveInTreeChanged()
|
||||
{
|
||||
// Skip collider base
|
||||
Actor::OnActiveInTreeChanged();
|
||||
|
||||
// Clear velocities and the forces on disabled
|
||||
if (!IsActiveInHierarchy() && _controller)
|
||||
{
|
||||
// TODO: sleep actor? clear forces?
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterController::OnParentChanged()
|
||||
{
|
||||
// Skip collider base
|
||||
Actor::OnParentChanged();
|
||||
}
|
||||
|
||||
void CharacterController::OnTransformChanged()
|
||||
{
|
||||
// Skip collider base
|
||||
Actor::OnTransformChanged();
|
||||
|
||||
// Update physics
|
||||
if (!_isUpdatingTransform && _controller)
|
||||
{
|
||||
const PxExtendedVec3 pos(_transform.Translation.X, _transform.Translation.Y, _transform.Translation.Z);
|
||||
_controller->setPosition(pos);
|
||||
|
||||
UpdateScale();
|
||||
UpdateBounds();
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterController::Serialize(SerializeStream& stream, const void* otherObj)
|
||||
{
|
||||
// Base
|
||||
Collider::Serialize(stream, otherObj);
|
||||
|
||||
SERIALIZE_GET_OTHER_OBJ(CharacterController);
|
||||
|
||||
SERIALIZE_MEMBER(StepOffset, _stepOffset);
|
||||
SERIALIZE_MEMBER(SlopeLimit, _slopeLimit);
|
||||
SERIALIZE_MEMBER(NonWalkableMode, _nonWalkableMode);
|
||||
SERIALIZE_MEMBER(Radius, _radius);
|
||||
SERIALIZE_MEMBER(Height, _height);
|
||||
SERIALIZE_MEMBER(MinMoveDistance, _minMoveDistance);
|
||||
}
|
||||
|
||||
void CharacterController::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
|
||||
{
|
||||
// Base
|
||||
Collider::Deserialize(stream, modifier);
|
||||
|
||||
DESERIALIZE_MEMBER(StepOffset, _stepOffset);
|
||||
DESERIALIZE_MEMBER(SlopeLimit, _slopeLimit);
|
||||
DESERIALIZE_MEMBER(NonWalkableMode, _nonWalkableMode);
|
||||
DESERIALIZE_MEMBER(Radius, _radius);
|
||||
DESERIALIZE_MEMBER(Height, _height);
|
||||
DESERIALIZE_MEMBER(MinMoveDistance, _minMoveDistance);
|
||||
}
|
||||
254
Source/Engine/Physics/Colliders/CharacterController.h
Normal file
254
Source/Engine/Physics/Colliders/CharacterController.h
Normal file
@@ -0,0 +1,254 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Collider.h"
|
||||
#include "Engine/Physics/Actors/IPhysicsActor.h"
|
||||
#include <PxFiltering.h>
|
||||
|
||||
/// <summary>
|
||||
/// Physical objects that allows to easily do player movement constrained by collisions without having to deal with a rigidbody.
|
||||
/// </summary>
|
||||
/// <seealso cref="Collider" />
|
||||
API_CLASS() class FLAXENGINE_API CharacterController : public Collider, public IPhysicsActor
|
||||
{
|
||||
DECLARE_SCENE_OBJECT(CharacterController);
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Specifies which sides a character is colliding with.
|
||||
/// </summary>
|
||||
API_ENUM(Attributes="Flags") enum class CollisionFlags
|
||||
{
|
||||
/// <summary>
|
||||
/// The character is not colliding.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The character is colliding to the sides.
|
||||
/// </summary>
|
||||
Sides = 1 << 0,
|
||||
|
||||
/// <summary>
|
||||
/// The character has collision above.
|
||||
/// </summary>
|
||||
Above = 1 << 1,
|
||||
|
||||
/// <summary>
|
||||
/// The character has collision below.
|
||||
/// </summary>
|
||||
Below = 1 << 2,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Specifies how a character controller interacts with non-walkable parts.
|
||||
/// </summary>
|
||||
API_ENUM() enum class NonWalkableModes
|
||||
{
|
||||
/// <summary>
|
||||
/// Stops character from climbing up non-walkable slopes, but doesn't move it otherwise.
|
||||
/// </summary>
|
||||
PreventClimbing,
|
||||
|
||||
/// <summary>
|
||||
/// Stops character from climbing up non-walkable slopes, and forces it to slide down those slopes.
|
||||
/// </summary>
|
||||
PreventClimbingAndForceSliding,
|
||||
};
|
||||
|
||||
private:
|
||||
|
||||
PxCapsuleController* _controller;
|
||||
float _stepOffset;
|
||||
float _slopeLimit;
|
||||
float _radius;
|
||||
float _height;
|
||||
float _minMoveDistance;
|
||||
bool _isUpdatingTransform;
|
||||
NonWalkableModes _nonWalkableMode;
|
||||
CollisionFlags _lastFlags;
|
||||
PxFilterData _filterData;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Gets the radius of the sphere, measured in the object's local space. The sphere radius will be scaled by the actor's world scale.
|
||||
/// </summary>
|
||||
API_PROPERTY(Attributes="EditorOrder(100), DefaultValue(50.0f), EditorDisplay(\"Collider\")")
|
||||
FORCE_INLINE float GetRadius() const
|
||||
{
|
||||
return _radius;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the radius of the sphere, measured in the object's local space. The sphere radius will be scaled by the actor's world scale.
|
||||
/// </summary>
|
||||
API_PROPERTY() void SetRadius(float value);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the height of the capsule, measured in the object's local space. The capsule height will be scaled by the actor's world scale.
|
||||
/// </summary>
|
||||
API_PROPERTY(Attributes="EditorOrder(110), DefaultValue(150.0f), EditorDisplay(\"Collider\")")
|
||||
FORCE_INLINE float GetHeight() const
|
||||
{
|
||||
return _height;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the height of the capsule, measured in the object's local space. The capsule height will be scaled by the actor's world scale.
|
||||
/// </summary>
|
||||
API_PROPERTY() void SetHeight(float value);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the slope limit (in degrees). Limits the collider to only climb slopes that are less steep (in degrees) than the indicated value.
|
||||
/// </summary>
|
||||
API_PROPERTY(Attributes="EditorOrder(210), DefaultValue(45.0f), Limit(0, 100), EditorDisplay(\"Character Controller\")")
|
||||
FORCE_INLINE float GetSlopeLimit() const
|
||||
{
|
||||
return _slopeLimit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the slope limit (in degrees). Limits the collider to only climb slopes that are less steep (in degrees) than the indicated value.
|
||||
/// </summary>
|
||||
API_PROPERTY() void SetSlopeLimit(float value);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the non-walkable mode for the character controller.
|
||||
/// </summary>
|
||||
API_PROPERTY(Attributes="EditorOrder(215), DefaultValue(NonWalkableModes.PreventClimbing), EditorDisplay(\"Character Controller\")")
|
||||
FORCE_INLINE NonWalkableModes GetNonWalkableMode() const
|
||||
{
|
||||
return _nonWalkableMode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the non-walkable mode for the character controller.
|
||||
/// </summary>
|
||||
API_PROPERTY() void SetNonWalkableMode(NonWalkableModes value);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the step height. The character will step up a stair only if it is closer to the ground than the indicated value. This should not be greater than the Character Controller’s height or it will generate an error.
|
||||
/// </summary>
|
||||
API_PROPERTY(Attributes="EditorOrder(220), DefaultValue(30.0f), Limit(0), EditorDisplay(\"Character Controller\")")
|
||||
FORCE_INLINE float GetStepOffset() const
|
||||
{
|
||||
return _stepOffset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the step height. The character will step up a stair only if it is closer to the ground than the indicated value. This should not be greater than the Character Controller’s height or it will generate an error.
|
||||
/// </summary>
|
||||
API_PROPERTY() void SetStepOffset(float value);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the minimum move distance of the character controller. The minimum travelled distance to consider. If travelled distance is smaller, the character doesn't move. This is used to stop the recursive motion algorithm when remaining distance to travel is small.
|
||||
/// </summary>
|
||||
API_PROPERTY(Attributes="EditorOrder(230), DefaultValue(0.0f), Limit(0, 1000), EditorDisplay(\"Character Controller\")")
|
||||
FORCE_INLINE float GetMinMoveDistance() const
|
||||
{
|
||||
return _minMoveDistance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the minimum move distance of the character controller.The minimum travelled distance to consider. If travelled distance is smaller, the character doesn't move. This is used to stop the recursive motion algorithm when remaining distance to travel is small.
|
||||
/// </summary>
|
||||
API_PROPERTY() void SetMinMoveDistance(float value);
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Gets the linear velocity of the Character Controller. This allows tracking how fast the character is actually moving, for instance when it is stuck at a wall this value will be the near zero vector.
|
||||
/// </summary>
|
||||
API_PROPERTY() Vector3 GetVelocity() const;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this character was grounded during last move call grounded.
|
||||
/// </summary>
|
||||
API_PROPERTY() FORCE_INLINE bool IsGrounded() const
|
||||
{
|
||||
return (static_cast<int>(_lastFlags) & static_cast<int>(CollisionFlags::Below)) != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current collision flags. Tells which parts of the character capsule collided with the environment during the last move call. It can be used to trigger various character animations.
|
||||
/// </summary>
|
||||
API_PROPERTY() FORCE_INLINE CollisionFlags GetFlags() const
|
||||
{
|
||||
return _lastFlags;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Moves the character with the given speed. Gravity is automatically applied. It will slide along colliders. Result collision flags is the summary of collisions that occurred during the Move.
|
||||
/// </summary>
|
||||
/// <param name="speed">The movement speed (in units/s).</param>
|
||||
/// <returns>The collision flags. It can be used to trigger various character animations.</returns>
|
||||
API_FUNCTION() CollisionFlags SimpleMove(const Vector3& speed);
|
||||
|
||||
/// <summary>
|
||||
/// Moves the character using a 'collide-and-slide' algorithm. Attempts to move the controller by the given displacement vector, the motion will only be constrained by collisions. It will slide along colliders. Result collision flags is the summary of collisions that occurred during the Move. This function does not apply any gravity.
|
||||
/// </summary>
|
||||
/// <param name="displacement">The displacement vector (in world units).</param>
|
||||
/// <returns>The collision flags. It can be used to trigger various character animations.</returns>
|
||||
API_FUNCTION() CollisionFlags Move(const Vector3& displacement);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the native PhysX rigid actor object.
|
||||
/// </summary>
|
||||
/// <returns>The PhysX dynamic rigid actor.</returns>
|
||||
PxRigidDynamic* GetPhysXRigidActor() const;
|
||||
|
||||
protected:
|
||||
|
||||
/// <summary>
|
||||
/// Creates the physics actor.
|
||||
/// </summary>
|
||||
void CreateActor();
|
||||
|
||||
/// <summary>
|
||||
/// Updates the character height and radius.
|
||||
/// </summary>
|
||||
void UpdateSize() const;
|
||||
|
||||
private:
|
||||
|
||||
#if USE_EDITOR
|
||||
void DrawPhysicsDebug(RenderView& view);
|
||||
#endif
|
||||
|
||||
public:
|
||||
|
||||
// [Collider]
|
||||
#if USE_EDITOR
|
||||
void OnDebugDrawSelected() override;
|
||||
#endif
|
||||
void Serialize(SerializeStream& stream, const void* otherObj) override;
|
||||
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
|
||||
void CreateShape() override;
|
||||
void UpdateBounds() override;
|
||||
void AddMovement(const Vector3& translation, const Quaternion& rotation) override;
|
||||
bool CanAttach(RigidBody* rigidBody) const override;
|
||||
RigidBody* GetAttachedRigidBody() const override;
|
||||
|
||||
// [IPhysicsActor]
|
||||
void OnActiveTransformChanged(const PxTransform& transform) override;
|
||||
PxRigidActor* GetRigidActor() override;
|
||||
|
||||
protected:
|
||||
|
||||
// [PhysicsActor]
|
||||
void UpdateGeometry() override;
|
||||
void UpdateLayerBits() override;
|
||||
void BeginPlay(SceneBeginData* data) override;
|
||||
void EndPlay() override;
|
||||
#if USE_EDITOR
|
||||
void OnEnable() override;
|
||||
void OnDisable() override;
|
||||
#endif
|
||||
void OnActiveInTreeChanged() override;
|
||||
void OnParentChanged() override;
|
||||
void OnTransformChanged() override;
|
||||
};
|
||||
456
Source/Engine/Physics/Colliders/Collider.cpp
Normal file
456
Source/Engine/Physics/Colliders/Collider.cpp
Normal file
@@ -0,0 +1,456 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "Collider.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Serialization/Serialization.h"
|
||||
#include "Engine/Physics/Utilities.h"
|
||||
#include "Engine/Physics/PhysicsSettings.h"
|
||||
#include "Engine/Physics/Physics.h"
|
||||
#include "Engine/Physics/PhysicalMaterial.h"
|
||||
#include "Engine/Physics/Actors/RigidBody.h"
|
||||
#include <ThirdParty/PhysX/geometry/PxGeometryQuery.h>
|
||||
#include <ThirdParty/PhysX/PxShape.h>
|
||||
#include <ThirdParty/PhysX/PxPhysics.h>
|
||||
#include <ThirdParty/PhysX/PxFiltering.h>
|
||||
#include <ThirdParty/PhysX/PxRigidDynamic.h>
|
||||
#include <ThirdParty/PhysX/PxRigidStatic.h>
|
||||
#include <ThirdParty/PhysX/PxScene.h>
|
||||
|
||||
Collider::Collider(const SpawnParams& params)
|
||||
: PhysicsColliderActor(params)
|
||||
, _center(Vector3::Zero)
|
||||
, _isTrigger(false)
|
||||
, _shape(nullptr)
|
||||
, _staticActor(nullptr)
|
||||
, _cachedScale(1.0f)
|
||||
, _contactOffset(10.0f)
|
||||
{
|
||||
Material.Changed.Bind<Collider, &Collider::OnMaterialChanged>(this);
|
||||
}
|
||||
|
||||
void Collider::SetIsTrigger(bool value)
|
||||
{
|
||||
if (value == _isTrigger || !CanBeTrigger())
|
||||
return;
|
||||
|
||||
_isTrigger = value;
|
||||
|
||||
if (_shape)
|
||||
{
|
||||
const bool isTrigger = _isTrigger && CanBeTrigger();
|
||||
const PxShapeFlags shapeFlags = GetShapeFlags(isTrigger, IsActiveInHierarchy());
|
||||
_shape->setFlags(shapeFlags);
|
||||
}
|
||||
}
|
||||
|
||||
void Collider::SetCenter(const Vector3& value)
|
||||
{
|
||||
if (Vector3::NearEqual(value, _center))
|
||||
return;
|
||||
|
||||
_center = value;
|
||||
|
||||
if (_staticActor)
|
||||
{
|
||||
_shape->setLocalPose(PxTransform(C2P(_center)));
|
||||
}
|
||||
else if (const RigidBody* rigidBody = GetAttachedRigidBody())
|
||||
{
|
||||
_shape->setLocalPose(PxTransform(C2P((_localTransform.Translation + _localTransform.Orientation * _center) * rigidBody->GetScale()), C2P(_localTransform.Orientation)));
|
||||
}
|
||||
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
void Collider::SetContactOffset(float value)
|
||||
{
|
||||
value = Math::Clamp(value, 0.0f, 100.0f);
|
||||
if (Math::NearEqual(value, _contactOffset))
|
||||
return;
|
||||
|
||||
_contactOffset = value;
|
||||
|
||||
if (_shape)
|
||||
{
|
||||
_shape->setContactOffset(Math::Max(_shape->getRestOffset() + ZeroTolerance, _contactOffset));
|
||||
}
|
||||
}
|
||||
|
||||
bool Collider::RayCast(const Vector3& origin, const Vector3& direction, float& resultHitDistance, float maxDistance) const
|
||||
{
|
||||
resultHitDistance = MAX_float;
|
||||
if (_shape == nullptr)
|
||||
return false;
|
||||
|
||||
// Prepare data
|
||||
const PxTransform trans(C2P(_transform.Translation), C2P(_transform.Orientation));
|
||||
const PxHitFlags hitFlags = PxHitFlag::ePOSITION | PxHitFlag::eNORMAL | PxHitFlag::eFACE_INDEX | PxHitFlag::eUV;
|
||||
|
||||
// Perform raycast test
|
||||
PxRaycastHit hit;
|
||||
if (PxGeometryQuery::raycast(C2P(origin), C2P(direction), _shape->getGeometry().any(), trans, maxDistance, hitFlags, 1, &hit) != 0)
|
||||
{
|
||||
resultHitDistance = hit.distance;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Collider::RayCast(const Vector3& origin, const Vector3& direction, RayCastHit& hitInfo, float maxDistance) const
|
||||
{
|
||||
if (_shape == nullptr)
|
||||
return false;
|
||||
|
||||
// Prepare data
|
||||
const PxTransform trans(C2P(_transform.Translation), C2P(_transform.Orientation));
|
||||
const PxHitFlags hitFlags = PxHitFlag::ePOSITION | PxHitFlag::eNORMAL | PxHitFlag::eUV;
|
||||
PxRaycastHit hit;
|
||||
|
||||
// Perform raycast test
|
||||
if (PxGeometryQuery::raycast(C2P(origin), C2P(direction), _shape->getGeometry().any(), trans, maxDistance, hitFlags, 1, &hit) == 0)
|
||||
return false;
|
||||
|
||||
// Gather results
|
||||
hitInfo.Gather(hit);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Collider::ClosestPoint(const Vector3& position, Vector3& result) const
|
||||
{
|
||||
if (_shape == nullptr)
|
||||
{
|
||||
result = Vector3::Maximum;
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare data
|
||||
const PxTransform trans(C2P(_transform.Translation), C2P(_transform.Orientation));
|
||||
PxVec3 closestPoint;
|
||||
|
||||
// Compute distance between a point and a geometry object
|
||||
const float distanceSqr = PxGeometryQuery::pointDistance(C2P(position), _shape->getGeometry().any(), trans, &closestPoint);
|
||||
if (distanceSqr > 0.0f)
|
||||
{
|
||||
// Use calculated point
|
||||
result = P2C(closestPoint);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback to the input location
|
||||
result = position;
|
||||
}
|
||||
}
|
||||
|
||||
bool Collider::ContainsPoint(const Vector3& point) const
|
||||
{
|
||||
if (_shape)
|
||||
{
|
||||
const PxTransform trans(C2P(_transform.Translation), C2P(_transform.Orientation));
|
||||
const float distanceSqr = PxGeometryQuery::pointDistance(C2P(point), _shape->getGeometry().any(), trans);
|
||||
return distanceSqr <= 0.0f;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Collider::ComputePenetration(const Collider* colliderA, const Collider* colliderB, Vector3& direction, float& distance)
|
||||
{
|
||||
direction = Vector3::Zero;
|
||||
distance = 0.0f;
|
||||
|
||||
CHECK_RETURN(colliderA && colliderB, false);
|
||||
const PxShape* shapeA = colliderA->GetPxShape();
|
||||
const PxShape* shapeB = colliderB->GetPxShape();
|
||||
if (!shapeA || !shapeB)
|
||||
return false;
|
||||
|
||||
const PxTransform poseA(C2P(colliderA->GetPosition()), C2P(colliderA->GetOrientation()));
|
||||
const PxTransform poseB(C2P(colliderB->GetPosition()), C2P(colliderB->GetOrientation()));
|
||||
|
||||
return PxGeometryQuery::computePenetration(
|
||||
C2P(direction),
|
||||
distance,
|
||||
shapeA->getGeometry().any(),
|
||||
poseA,
|
||||
shapeB->getGeometry().any(),
|
||||
poseB
|
||||
);
|
||||
}
|
||||
|
||||
bool Collider::IsAttached() const
|
||||
{
|
||||
return _shape && _shape->getActor() != nullptr;
|
||||
}
|
||||
|
||||
RigidBody* Collider::GetAttachedRigidBody() const
|
||||
{
|
||||
if (_shape && _staticActor == nullptr)
|
||||
{
|
||||
auto actor = _shape->getActor();
|
||||
if (actor && actor->is<PxRigidDynamic>())
|
||||
return static_cast<RigidBody*>(actor->userData);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Collider::Attach(RigidBody* rigidBody)
|
||||
{
|
||||
ASSERT(CanAttach(rigidBody));
|
||||
|
||||
// Remove static body if used
|
||||
if (_staticActor)
|
||||
RemoveStaticActor();
|
||||
|
||||
// Create shape if missing
|
||||
if (_shape == nullptr)
|
||||
CreateShape();
|
||||
|
||||
// Attach
|
||||
rigidBody->GetPhysXRigidActor()->attachShape(*_shape);
|
||||
_shape->setLocalPose(PxTransform(C2P((_localTransform.Translation + _localTransform.Orientation * _center) * rigidBody->GetScale()), C2P(_localTransform.Orientation)));
|
||||
if (rigidBody->IsDuringPlay())
|
||||
rigidBody->UpdateBounds();
|
||||
}
|
||||
|
||||
void Collider::UpdateScale()
|
||||
{
|
||||
const Vector3 scale = GetScale();
|
||||
if (Vector3::NearEqual(_cachedScale, scale))
|
||||
return;
|
||||
|
||||
// Recreate shape geometry
|
||||
UpdateGeometry();
|
||||
}
|
||||
|
||||
void Collider::UpdateLayerBits()
|
||||
{
|
||||
ASSERT(_shape);
|
||||
|
||||
PxFilterData filterData;
|
||||
|
||||
// Own layer ID
|
||||
filterData.word0 = GetLayerMask();
|
||||
|
||||
// Own layer mask
|
||||
filterData.word1 = PhysicsSettings::Instance()->LayerMasks[GetLayer()];
|
||||
|
||||
_shape->setSimulationFilterData(filterData);
|
||||
_shape->setQueryFilterData(filterData);
|
||||
}
|
||||
|
||||
void Collider::CreateShapeBase(const PxGeometry& geometry)
|
||||
{
|
||||
ASSERT(_shape == nullptr);
|
||||
|
||||
// Prepare
|
||||
const bool isTrigger = _isTrigger && CanBeTrigger();
|
||||
const PxShapeFlags shapeFlags = GetShapeFlags(isTrigger, IsActiveInHierarchy());
|
||||
PxMaterial* material = Physics::GetDefaultMaterial();
|
||||
if (Material && !Material->WaitForLoaded())
|
||||
{
|
||||
material = ((PhysicalMaterial*)Material->Instance)->GetPhysXMaterial();
|
||||
}
|
||||
|
||||
// Create shape
|
||||
_shape = CPhysX->createShape(geometry, *material, true, shapeFlags);
|
||||
ASSERT(_shape);
|
||||
_shape->userData = this;
|
||||
|
||||
// Setup properties
|
||||
_shape->setContactOffset(Math::Max(_shape->getRestOffset() + ZeroTolerance, _contactOffset));
|
||||
UpdateLayerBits();
|
||||
}
|
||||
|
||||
void Collider::CreateStaticActor()
|
||||
{
|
||||
ASSERT(_staticActor == nullptr);
|
||||
|
||||
const PxTransform trans(C2P(_transform.Translation), C2P(_transform.Orientation));
|
||||
_staticActor = CPhysX->createRigidStatic(trans);
|
||||
ASSERT(_staticActor);
|
||||
_staticActor->userData = this;
|
||||
|
||||
// Reset local pos of the shape and link it to the actor
|
||||
_shape->setLocalPose(PxTransform(C2P(_center)));
|
||||
_staticActor->attachShape(*_shape);
|
||||
|
||||
Physics::AddActor(_staticActor);
|
||||
}
|
||||
|
||||
void Collider::RemoveStaticActor()
|
||||
{
|
||||
ASSERT(_staticActor != nullptr);
|
||||
|
||||
Physics::RemoveActor(_staticActor);
|
||||
_staticActor = nullptr;
|
||||
}
|
||||
|
||||
void Collider::OnMaterialChanged()
|
||||
{
|
||||
// Update the shape material
|
||||
if (_shape)
|
||||
{
|
||||
PxMaterial* material = Physics::GetDefaultMaterial();
|
||||
if (Material)
|
||||
{
|
||||
if (!Material->WaitForLoaded())
|
||||
{
|
||||
material = ((PhysicalMaterial*)Material->Instance)->GetPhysXMaterial();
|
||||
}
|
||||
}
|
||||
_shape->setMaterials(&material, 1);
|
||||
}
|
||||
}
|
||||
|
||||
void Collider::Serialize(SerializeStream& stream, const void* otherObj)
|
||||
{
|
||||
// Base
|
||||
PhysicsColliderActor::Serialize(stream, otherObj);
|
||||
|
||||
SERIALIZE_GET_OTHER_OBJ(Collider);
|
||||
|
||||
SERIALIZE_MEMBER(IsTrigger, _isTrigger);
|
||||
SERIALIZE_MEMBER(Center, _center);
|
||||
SERIALIZE_MEMBER(ContactOffset, _contactOffset);
|
||||
SERIALIZE(Material);
|
||||
}
|
||||
|
||||
void Collider::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
|
||||
{
|
||||
// Base
|
||||
PhysicsColliderActor::Deserialize(stream, modifier);
|
||||
|
||||
DESERIALIZE_MEMBER(IsTrigger, _isTrigger);
|
||||
DESERIALIZE_MEMBER(Center, _center);
|
||||
DESERIALIZE_MEMBER(ContactOffset, _contactOffset);
|
||||
DESERIALIZE(Material);
|
||||
}
|
||||
|
||||
void Collider::BeginPlay(SceneBeginData* data)
|
||||
{
|
||||
// Check if has no shape created (it means no rigidbody requested it but also collider may be spawned at runtime)
|
||||
if (_shape == nullptr)
|
||||
{
|
||||
CreateShape();
|
||||
|
||||
// Check if parent is a rigidbody
|
||||
const auto rigidBody = dynamic_cast<RigidBody*>(GetParent());
|
||||
if (rigidBody && CanAttach(rigidBody))
|
||||
{
|
||||
// Attach to the rigidbody
|
||||
Attach(rigidBody);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Be a static collider
|
||||
CreateStaticActor();
|
||||
}
|
||||
}
|
||||
|
||||
// Base
|
||||
PhysicsColliderActor::BeginPlay(data);
|
||||
}
|
||||
|
||||
void Collider::EndPlay()
|
||||
{
|
||||
if (_shape)
|
||||
{
|
||||
// Detach from the actor
|
||||
auto actor = _shape->getActor();
|
||||
if (actor)
|
||||
actor->detachShape(*_shape);
|
||||
|
||||
// Check if was using a static actor and cleanup it
|
||||
if (_staticActor)
|
||||
{
|
||||
RemoveStaticActor();
|
||||
}
|
||||
|
||||
// Release shape
|
||||
Physics::RemoveCollider(this);
|
||||
_shape->release();
|
||||
_shape = nullptr;
|
||||
}
|
||||
|
||||
// Base
|
||||
PhysicsColliderActor::EndPlay();
|
||||
}
|
||||
|
||||
void Collider::OnActiveInTreeChanged()
|
||||
{
|
||||
// Base
|
||||
PhysicsColliderActor::OnActiveInTreeChanged();
|
||||
|
||||
if (_shape)
|
||||
{
|
||||
const bool isTrigger = _isTrigger && CanBeTrigger();
|
||||
const PxShapeFlags shapeFlags = GetShapeFlags(isTrigger, IsActiveInHierarchy());
|
||||
_shape->setFlags(shapeFlags);
|
||||
|
||||
auto rigidBody = GetAttachedRigidBody();
|
||||
if (rigidBody)
|
||||
{
|
||||
rigidBody->UpdateMass();
|
||||
|
||||
// TODO: maybe wake up only if one ore more shapes attached is active?
|
||||
//if (rigidBody->GetStartAwake())
|
||||
// rigidBody->WakeUp();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Collider::OnParentChanged()
|
||||
{
|
||||
// Base
|
||||
PhysicsColliderActor::OnParentChanged();
|
||||
|
||||
// Check reparenting collider case
|
||||
if (_shape)
|
||||
{
|
||||
// Detach from the actor
|
||||
auto actor = _shape->getActor();
|
||||
if (actor)
|
||||
actor->detachShape(*_shape);
|
||||
|
||||
// Check if the new parent is a rigidbody
|
||||
const auto rigidBody = dynamic_cast<RigidBody*>(GetParent());
|
||||
if (rigidBody && CanAttach(rigidBody))
|
||||
{
|
||||
// Attach to the rigidbody (will remove static actor if it's n use)
|
||||
Attach(rigidBody);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use static actor (if not created yet)
|
||||
if (_staticActor == nullptr)
|
||||
CreateStaticActor();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Collider::OnTransformChanged()
|
||||
{
|
||||
// Base
|
||||
PhysicsColliderActor::OnTransformChanged();
|
||||
|
||||
if (_staticActor)
|
||||
{
|
||||
_staticActor->setGlobalPose(PxTransform(C2P(_transform.Translation), C2P(_transform.Orientation)));
|
||||
}
|
||||
else if (const RigidBody* rigidBody = GetAttachedRigidBody())
|
||||
{
|
||||
_shape->setLocalPose(PxTransform(C2P((_localTransform.Translation + _localTransform.Orientation * _center) * rigidBody->GetScale()), C2P(_localTransform.Orientation)));
|
||||
}
|
||||
|
||||
UpdateScale();
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
void Collider::OnLayerChanged()
|
||||
{
|
||||
// Base
|
||||
PhysicsColliderActor::OnLayerChanged();
|
||||
|
||||
if (_shape)
|
||||
UpdateLayerBits();
|
||||
}
|
||||
246
Source/Engine/Physics/Colliders/Collider.h
Normal file
246
Source/Engine/Physics/Colliders/Collider.h
Normal file
@@ -0,0 +1,246 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Physics/Types.h"
|
||||
#include "Engine/Content/JsonAsset.h"
|
||||
#include "Engine/Content/AssetReference.h"
|
||||
#include "Engine/Physics/Actors/PhysicsColliderActor.h"
|
||||
|
||||
struct RayCastHit;
|
||||
class RigidBody;
|
||||
|
||||
/// <summary>
|
||||
/// A base class for all colliders.
|
||||
/// </summary>
|
||||
/// <seealso cref="Actor" />
|
||||
/// <seealso cref="PhysicsColliderActor" />
|
||||
API_CLASS(Abstract) class FLAXENGINE_API Collider : public PhysicsColliderActor
|
||||
{
|
||||
DECLARE_SCENE_OBJECT_ABSTRACT(Collider);
|
||||
protected:
|
||||
|
||||
Vector3 _center;
|
||||
bool _isTrigger;
|
||||
PxShape* _shape;
|
||||
PxRigidStatic* _staticActor;
|
||||
Vector3 _cachedScale;
|
||||
float _contactOffset;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collider shape PhysX object.
|
||||
/// </summary>
|
||||
/// <returns>The PhysX Shape object or null.</returns>
|
||||
FORCE_INLINE PxShape* GetPxShape() const
|
||||
{
|
||||
return _shape;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the 'IsTrigger' flag.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A trigger doesn't register a collision with an incoming Rigidbody. Instead, it sends OnTriggerEnter, OnTriggerExit and OnTriggerStay message when a rigidbody enters or exits the trigger volume.
|
||||
/// </remarks>
|
||||
API_PROPERTY(Attributes="EditorOrder(0), DefaultValue(false), EditorDisplay(\"Collider\")")
|
||||
FORCE_INLINE bool GetIsTrigger() const
|
||||
{
|
||||
return _isTrigger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the `IsTrigger` flag. A trigger doesn't register a collision with an incoming Rigidbody. Instead, it sends OnTriggerEnter, OnTriggerExit and OnTriggerStay message when a rigidbody enters or exits the trigger volume.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A trigger doesn't register a collision with an incoming Rigidbody. Instead, it sends OnTriggerEnter, OnTriggerExit and OnTriggerStay message when a rigidbody enters or exits the trigger volume.
|
||||
/// </remarks>
|
||||
API_PROPERTY() void SetIsTrigger(bool value);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the center of the collider, measured in the object's local space.
|
||||
/// </summary>
|
||||
API_PROPERTY(Attributes="EditorOrder(10), DefaultValue(typeof(Vector3), \"0,0,0\"), EditorDisplay(\"Collider\")")
|
||||
FORCE_INLINE Vector3 GetCenter() const
|
||||
{
|
||||
return _center;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the center of the collider, measured in the object's local space.
|
||||
/// </summary>
|
||||
API_PROPERTY() void SetCenter(const Vector3& value);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the contact offset.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Colliders whose distance is less than the sum of their ContactOffset values will generate contacts. The contact offset must be positive. Contact offset allows the collision detection system to predictively enforce the contact constraint even when the objects are slightly separated.
|
||||
/// </remarks>
|
||||
API_PROPERTY(Attributes="EditorOrder(1), DefaultValue(10.0f), Limit(0, 100), EditorDisplay(\"Collider\")")
|
||||
FORCE_INLINE float GetContactOffset() const
|
||||
{
|
||||
return _contactOffset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the contact offset.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Colliders whose distance is less than the sum of their ContactOffset values will generate contacts. The contact offset must be positive. Contact offset allows the collision detection system to predictively enforce the contact constraint even when the objects are slightly separated.
|
||||
/// </remarks>
|
||||
API_PROPERTY() void SetContactOffset(float value);
|
||||
|
||||
/// <summary>
|
||||
/// The physical material used to define the collider physical properties.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(2), DefaultValue(null), AssetReference(typeof(PhysicalMaterial), true), EditorDisplay(\"Collider\")")
|
||||
AssetReference<JsonAsset> Material;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Performs a raycast against this collider shape.
|
||||
/// </summary>
|
||||
/// <param name="origin">The origin of the ray.</param>
|
||||
/// <param name="direction">The normalized direction of the ray.</param>
|
||||
/// <param name="resultHitDistance">The raycast result hit position distance from the ray origin. Valid only if raycast hits anything.</param>
|
||||
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
|
||||
/// <returns>True if ray hits an object, otherwise false.</returns>
|
||||
API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) float& resultHitDistance, float maxDistance = MAX_float) const;
|
||||
|
||||
/// <summary>
|
||||
/// Performs a raycast against this collider, returns results in a RaycastHit structure.
|
||||
/// </summary>
|
||||
/// <param name="origin">The origin of the ray.</param>
|
||||
/// <param name="direction">The normalized direction of the ray.</param>
|
||||
/// <param name="hitInfo">The result hit information. Valid only when method returns true.</param>
|
||||
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
|
||||
/// <returns>True if ray hits an object, otherwise false.</returns>
|
||||
API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) RayCastHit& hitInfo, float maxDistance = MAX_float) const;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a point on the collider that is closest to a given location. Can be used to find a hit location or position to apply explosion force or any other special effects.
|
||||
/// </summary>
|
||||
/// <param name="position">The position to find the closest point to it.</param>
|
||||
/// <param name="result">The result point on the collider that is closest to the specified location.</param>
|
||||
API_FUNCTION() void ClosestPoint(const Vector3& position, API_PARAM(Out) Vector3& result) const;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a point is inside the collider.
|
||||
/// </summary>
|
||||
/// <param name="point">The point to check if is contained by the collider shape (in world-space).</param>
|
||||
/// <returns>True if collider shape contains a given point, otherwise false.</returns>
|
||||
API_FUNCTION() bool ContainsPoint(const Vector3& point) const;
|
||||
|
||||
/// <summary>
|
||||
/// Computes minimum translational distance between two geometry objects.
|
||||
/// Translating the first collider by direction * distance will separate the colliders apart if the function returned true. Otherwise, direction and distance are not defined.
|
||||
/// The one of the colliders has to be BoxCollider, SphereCollider CapsuleCollider or a convex MeshCollider. The other one can be any type.
|
||||
/// If objects do not overlap, the function can not compute the distance and returns false.
|
||||
/// </summary>
|
||||
/// <param name="colliderA">The first collider.</param>
|
||||
/// <param name="colliderB">The second collider.</param>
|
||||
/// <param name="direction">The computed direction along which the translation required to separate the colliders apart is minimal. Valid only if function returns true.</param>
|
||||
/// <param name="distance">The penetration distance along direction that is required to separate the colliders apart. Valid only if function returns true.</param>
|
||||
/// <returns>True if the distance has successfully been computed, i.e. if objects do overlap, otherwise false.</returns>
|
||||
API_FUNCTION() static bool ComputePenetration(const Collider* colliderA, const Collider* colliderB, API_PARAM(Out) Vector3& direction, API_PARAM(Out) float& distance);
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this collider is attached to the body.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> if this instance is attached; otherwise, <c>false</c>.</returns>
|
||||
API_PROPERTY() bool IsAttached() const;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this collider can be attached the specified rigid body.
|
||||
/// </summary>
|
||||
/// <param name="rigidBody">The rigid body.</param>
|
||||
/// <returns><c>true</c> if this collider can be attached the specified rigid body; otherwise, <c>false</c>.</returns>
|
||||
virtual bool CanAttach(RigidBody* rigidBody) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this collider can be a trigger shape.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> if this collider can be trigger; otherwise, <c>false</c>.</returns>
|
||||
virtual bool CanBeTrigger() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attaches collider to the the specified rigid body.
|
||||
/// </summary>
|
||||
/// <param name="rigidBody">The rigid body.</param>
|
||||
void Attach(RigidBody* rigidBody);
|
||||
|
||||
protected:
|
||||
|
||||
/// <summary>
|
||||
/// Updates the shape scale (may be modified when actor transformation changes).
|
||||
/// </summary>
|
||||
void UpdateScale();
|
||||
|
||||
/// <summary>
|
||||
/// Updates the shape actor collisions/queries layer mask bits.
|
||||
/// </summary>
|
||||
virtual void UpdateLayerBits();
|
||||
|
||||
/// <summary>
|
||||
/// Updates the bounding box of the shape.
|
||||
/// </summary>
|
||||
virtual void UpdateBounds() = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Creates the collider shape.
|
||||
/// </summary>
|
||||
virtual void CreateShape() = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Creates the collider shape from the given geometry.
|
||||
/// </summary>
|
||||
/// <param name="geometry">The geometry.</param>
|
||||
void CreateShapeBase(const PxGeometry& geometry);
|
||||
|
||||
/// <summary>
|
||||
/// Updates the shape geometry.
|
||||
/// </summary>
|
||||
virtual void UpdateGeometry() = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Creates the static actor.
|
||||
/// </summary>
|
||||
void CreateStaticActor();
|
||||
|
||||
/// <summary>
|
||||
/// Removes the static actor.
|
||||
/// </summary>
|
||||
void RemoveStaticActor();
|
||||
|
||||
private:
|
||||
|
||||
void OnMaterialChanged();
|
||||
|
||||
public:
|
||||
|
||||
// [PhysicsColliderActor]
|
||||
void Serialize(SerializeStream& stream, const void* otherObj) override;
|
||||
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
|
||||
RigidBody* GetAttachedRigidBody() const override;
|
||||
|
||||
protected:
|
||||
|
||||
// [PhysicsColliderActor]
|
||||
void BeginPlay(SceneBeginData* data) override;
|
||||
void EndPlay() override;
|
||||
void OnActiveInTreeChanged() override;
|
||||
void OnParentChanged() override;
|
||||
void OnTransformChanged() override;
|
||||
void OnLayerChanged() override;
|
||||
};
|
||||
264
Source/Engine/Physics/Colliders/MeshCollider.cpp
Normal file
264
Source/Engine/Physics/Colliders/MeshCollider.cpp
Normal file
@@ -0,0 +1,264 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "MeshCollider.h"
|
||||
#include "Engine/Core/Math/Matrix.h"
|
||||
#include "Engine/Serialization/Serialization.h"
|
||||
#include "Engine/Physics/Utilities.h"
|
||||
#include "Engine/Physics/Physics.h"
|
||||
#include <ThirdParty/PhysX/PxShape.h>
|
||||
#include <ThirdParty/PhysX/PxRigidActor.h>
|
||||
#if USE_EDITOR
|
||||
#include "Engine/Level/Scene/SceneRendering.h"
|
||||
#endif
|
||||
|
||||
MeshCollider::MeshCollider(const SpawnParams& params)
|
||||
: Collider(params)
|
||||
{
|
||||
CollisionData.Changed.Bind<MeshCollider, &MeshCollider::OnCollisionDataChanged>(this);
|
||||
CollisionData.Loaded.Bind<MeshCollider, &MeshCollider::OnCollisionDataLoaded>(this);
|
||||
}
|
||||
|
||||
void MeshCollider::OnCollisionDataChanged()
|
||||
{
|
||||
// This should not be called during physics simulation, if it happened use write lock on physx scene
|
||||
ASSERT(!Physics::IsDuringSimulation());
|
||||
|
||||
if (CollisionData)
|
||||
{
|
||||
// Ensure that collision asset is loaded (otherwise objects might fall though collider that is not yet loaded on play begin)
|
||||
CollisionData->WaitForLoaded();
|
||||
}
|
||||
|
||||
UpdateGeometry();
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
void MeshCollider::OnCollisionDataLoaded()
|
||||
{
|
||||
UpdateGeometry();
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
bool MeshCollider::CanAttach(RigidBody* rigidBody) const
|
||||
{
|
||||
CollisionDataType type = CollisionDataType::None;
|
||||
if (CollisionData && CollisionData->IsLoaded())
|
||||
type = CollisionData->GetOptions().Type;
|
||||
return type != CollisionDataType::TriangleMesh;
|
||||
}
|
||||
|
||||
bool MeshCollider::CanBeTrigger() const
|
||||
{
|
||||
CollisionDataType type = CollisionDataType::None;
|
||||
if (CollisionData && CollisionData->IsLoaded())
|
||||
type = CollisionData->GetOptions().Type;
|
||||
return type != CollisionDataType::TriangleMesh;
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
#include "Engine/Debug/DebugDraw.h"
|
||||
|
||||
void MeshCollider::DrawPhysicsDebug(RenderView& view)
|
||||
{
|
||||
if (CollisionData && CollisionData->IsLoaded())
|
||||
{
|
||||
const auto& debugLines = CollisionData->GetDebugLines();
|
||||
DEBUG_DRAW_LINES(Span<Vector3>(debugLines.Get(), debugLines.Count()), _transform.GetWorld(), Color::GreenYellow * 0.8f, 0, true);
|
||||
}
|
||||
}
|
||||
|
||||
void MeshCollider::OnDebugDrawSelected()
|
||||
{
|
||||
if (CollisionData && CollisionData->IsLoaded())
|
||||
{
|
||||
const auto& debugLines = CollisionData->GetDebugLines();
|
||||
DEBUG_DRAW_LINES(Span<Vector3>(debugLines.Get(), debugLines.Count()), _transform.GetWorld(), Color::GreenYellow, 0, false);
|
||||
}
|
||||
|
||||
// Base
|
||||
Collider::OnDebugDrawSelected();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
bool MeshCollider::IntersectsItself(const Ray& ray, float& distance, Vector3& normal)
|
||||
{
|
||||
// Use detailed hit
|
||||
if (_shape)
|
||||
{
|
||||
RayCastHit hitInfo;
|
||||
if (!RayCast(ray.Position, ray.Direction, hitInfo))
|
||||
return false;
|
||||
distance = hitInfo.Distance;
|
||||
normal = hitInfo.Normal;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fallback to AABB
|
||||
return _box.Intersects(ray, distance, normal);
|
||||
}
|
||||
|
||||
void MeshCollider::Serialize(SerializeStream& stream, const void* otherObj)
|
||||
{
|
||||
// Base
|
||||
Collider::Serialize(stream, otherObj);
|
||||
|
||||
SERIALIZE_GET_OTHER_OBJ(MeshCollider);
|
||||
|
||||
SERIALIZE(CollisionData);
|
||||
}
|
||||
|
||||
void MeshCollider::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
|
||||
{
|
||||
// Base
|
||||
Collider::Deserialize(stream, modifier);
|
||||
|
||||
DESERIALIZE(CollisionData);
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
void MeshCollider::OnEnable()
|
||||
{
|
||||
GetSceneRendering()->AddPhysicsDebug<MeshCollider, &MeshCollider::DrawPhysicsDebug>(this);
|
||||
|
||||
// Base
|
||||
Collider::OnEnable();
|
||||
}
|
||||
|
||||
void MeshCollider::OnDisable()
|
||||
{
|
||||
GetSceneRendering()->RemovePhysicsDebug<MeshCollider, &MeshCollider::DrawPhysicsDebug>(this);
|
||||
|
||||
// Base
|
||||
Collider::OnDisable();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void MeshCollider::UpdateBounds()
|
||||
{
|
||||
// Cache bounds
|
||||
BoundingBox box;
|
||||
if (CollisionData && CollisionData->IsLoaded())
|
||||
box = CollisionData->GetOptions().Box;
|
||||
else
|
||||
box = BoundingBox::Zero;
|
||||
BoundingBox::Transform(box, _transform.GetWorld(), _box);
|
||||
BoundingSphere::FromBox(_box, _sphere);
|
||||
}
|
||||
|
||||
void MeshCollider::CreateShape()
|
||||
{
|
||||
// Prepare scale
|
||||
Vector3 scale = GetScale();
|
||||
_cachedScale = scale;
|
||||
scale.Absolute();
|
||||
const float minSize = 0.001f;
|
||||
scale = Vector3::Max(scale, minSize);
|
||||
|
||||
// Setup shape (based on type)
|
||||
CollisionDataType type = CollisionDataType::None;
|
||||
if (CollisionData && CollisionData->IsLoaded())
|
||||
type = CollisionData->GetOptions().Type;
|
||||
if (type == CollisionDataType::ConvexMesh)
|
||||
{
|
||||
// Convex mesh
|
||||
PxConvexMeshGeometry geometry;
|
||||
geometry.scale.scale = C2P(scale);
|
||||
geometry.convexMesh = CollisionData->GetConvex();
|
||||
CreateShapeBase(geometry);
|
||||
}
|
||||
else if (type == CollisionDataType::TriangleMesh)
|
||||
{
|
||||
// Triangle mesh
|
||||
PxTriangleMeshGeometry geometry;
|
||||
geometry.scale.scale = C2P(scale);
|
||||
geometry.triangleMesh = CollisionData->GetTriangle();
|
||||
CreateShapeBase(geometry);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Dummy geometry
|
||||
const PxSphereGeometry geometry(0.01f);
|
||||
CreateShapeBase(geometry);
|
||||
}
|
||||
}
|
||||
|
||||
void MeshCollider::UpdateGeometry()
|
||||
{
|
||||
// Check if has no shape created
|
||||
if (_shape == nullptr)
|
||||
return;
|
||||
|
||||
// Recreate shape if geometry has different type
|
||||
CollisionDataType type = CollisionDataType::None;
|
||||
if (CollisionData && CollisionData->IsLoaded())
|
||||
type = CollisionData->GetOptions().Type;
|
||||
if ((type == CollisionDataType::ConvexMesh && _shape->getGeometryType() != PxGeometryType::eCONVEXMESH)
|
||||
|| (type == CollisionDataType::TriangleMesh && _shape->getGeometryType() != PxGeometryType::eTRIANGLEMESH)
|
||||
|| (type == CollisionDataType::None && _shape->getGeometryType() != PxGeometryType::eSPHERE)
|
||||
)
|
||||
{
|
||||
// Detach from the actor
|
||||
auto actor = _shape->getActor();
|
||||
if (actor)
|
||||
actor->detachShape(*_shape);
|
||||
|
||||
// Release shape
|
||||
Physics::RemoveCollider(this);
|
||||
_shape->release();
|
||||
_shape = nullptr;
|
||||
|
||||
// Recreate shape
|
||||
CreateShape();
|
||||
|
||||
// Reattach again (only if can, see CanAttach function)
|
||||
if (actor)
|
||||
{
|
||||
if (_staticActor != nullptr || type != CollisionDataType::TriangleMesh)
|
||||
{
|
||||
actor->attachShape(*_shape);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Be static triangle mesh
|
||||
CreateStaticActor();
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare scale
|
||||
Vector3 scale = GetScale();
|
||||
_cachedScale = scale;
|
||||
scale.Absolute();
|
||||
const float minSize = 0.001f;
|
||||
scale = Vector3::Max(scale, minSize);
|
||||
|
||||
// Setup shape (based on type)
|
||||
if (type == CollisionDataType::ConvexMesh)
|
||||
{
|
||||
// Convex mesh
|
||||
PxConvexMeshGeometry geometry;
|
||||
geometry.scale.scale = C2P(scale);
|
||||
geometry.convexMesh = CollisionData->GetConvex();
|
||||
_shape->setGeometry(geometry);
|
||||
}
|
||||
else if (type == CollisionDataType::TriangleMesh)
|
||||
{
|
||||
// Triangle mesh
|
||||
PxTriangleMeshGeometry geometry;
|
||||
geometry.scale.scale = C2P(scale);
|
||||
geometry.triangleMesh = CollisionData->GetTriangle();
|
||||
_shape->setGeometry(geometry);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Dummy geometry
|
||||
const PxSphereGeometry geometry(0.01f);
|
||||
_shape->setGeometry(geometry);
|
||||
}
|
||||
}
|
||||
54
Source/Engine/Physics/Colliders/MeshCollider.h
Normal file
54
Source/Engine/Physics/Colliders/MeshCollider.h
Normal file
@@ -0,0 +1,54 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Collider.h"
|
||||
#include "Engine/Content/AssetReference.h"
|
||||
#include "Engine/Physics/CollisionData.h"
|
||||
|
||||
/// <summary>
|
||||
/// A collider represented by an arbitrary mesh.
|
||||
/// </summary>
|
||||
/// <seealso cref="Collider" />
|
||||
API_CLASS() class FLAXENGINE_API MeshCollider : public Collider
|
||||
{
|
||||
DECLARE_SCENE_OBJECT(MeshCollider);
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Linked collision data asset that contains convex mesh or triangle mesh used to represent a mesh collider shape.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(100), DefaultValue(null), EditorDisplay(\"Collider\")")
|
||||
AssetReference<CollisionData> CollisionData;
|
||||
|
||||
private:
|
||||
|
||||
void OnCollisionDataChanged();
|
||||
void OnCollisionDataLoaded();
|
||||
#if USE_EDITOR
|
||||
void DrawPhysicsDebug(RenderView& view);
|
||||
#endif
|
||||
|
||||
public:
|
||||
|
||||
// [Collider]
|
||||
bool CanAttach(RigidBody* rigidBody) const override;
|
||||
bool CanBeTrigger() const override;
|
||||
#if USE_EDITOR
|
||||
void OnDebugDrawSelected() override;
|
||||
#endif
|
||||
bool IntersectsItself(const Ray& ray, float& distance, Vector3& normal) override;
|
||||
void Serialize(SerializeStream& stream, const void* otherObj) override;
|
||||
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
|
||||
|
||||
protected:
|
||||
|
||||
// [Collider]
|
||||
#if USE_EDITOR
|
||||
void OnEnable() override;
|
||||
void OnDisable() override;
|
||||
#endif
|
||||
void UpdateBounds() override;
|
||||
void CreateShape() override;
|
||||
void UpdateGeometry() override;
|
||||
};
|
||||
126
Source/Engine/Physics/Colliders/SphereCollider.cpp
Normal file
126
Source/Engine/Physics/Colliders/SphereCollider.cpp
Normal file
@@ -0,0 +1,126 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "SphereCollider.h"
|
||||
#include "Engine/Serialization/Serialization.h"
|
||||
#include "Engine/Physics/Utilities.h"
|
||||
#include <PxShape.h>
|
||||
#if USE_EDITOR
|
||||
#include "Engine/Level/Scene/SceneRendering.h"
|
||||
#endif
|
||||
|
||||
SphereCollider::SphereCollider(const SpawnParams& params)
|
||||
: Collider(params)
|
||||
, _radius(50.0f)
|
||||
{
|
||||
}
|
||||
|
||||
void SphereCollider::SetRadius(const float value)
|
||||
{
|
||||
if (Math::NearEqual(value, _radius))
|
||||
return;
|
||||
|
||||
_radius = value;
|
||||
|
||||
UpdateGeometry();
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
#include "Engine/Debug/DebugDraw.h"
|
||||
|
||||
void SphereCollider::DrawPhysicsDebug(RenderView& view)
|
||||
{
|
||||
DEBUG_DRAW_WIRE_SPHERE(_sphere, Color::GreenYellow * 0.8f, 0, true);
|
||||
}
|
||||
|
||||
void SphereCollider::OnDebugDrawSelected()
|
||||
{
|
||||
DEBUG_DRAW_WIRE_SPHERE(_sphere, Color::GreenYellow, 0, false);
|
||||
|
||||
// Base
|
||||
Collider::OnDebugDrawSelected();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
bool SphereCollider::IntersectsItself(const Ray& ray, float& distance, Vector3& normal)
|
||||
{
|
||||
return _sphere.Intersects(ray, distance, normal);
|
||||
}
|
||||
|
||||
void SphereCollider::Serialize(SerializeStream& stream, const void* otherObj)
|
||||
{
|
||||
// Base
|
||||
Collider::Serialize(stream, otherObj);
|
||||
|
||||
SERIALIZE_GET_OTHER_OBJ(SphereCollider);
|
||||
|
||||
SERIALIZE_MEMBER(Radius, _radius);
|
||||
}
|
||||
|
||||
void SphereCollider::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
|
||||
{
|
||||
// Base
|
||||
Collider::Deserialize(stream, modifier);
|
||||
|
||||
DESERIALIZE_MEMBER(Radius, _radius);
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
void SphereCollider::OnEnable()
|
||||
{
|
||||
GetSceneRendering()->AddPhysicsDebug<SphereCollider, &SphereCollider::DrawPhysicsDebug>(this);
|
||||
|
||||
// Base
|
||||
Collider::OnEnable();
|
||||
}
|
||||
|
||||
void SphereCollider::OnDisable()
|
||||
{
|
||||
GetSceneRendering()->RemovePhysicsDebug<SphereCollider, &SphereCollider::DrawPhysicsDebug>(this);
|
||||
|
||||
// Base
|
||||
Collider::OnDisable();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void SphereCollider::UpdateBounds()
|
||||
{
|
||||
// Cache bounds
|
||||
_sphere.Center = _transform.LocalToWorld(_center);
|
||||
_sphere.Radius = _radius * _transform.Scale.MaxValue();
|
||||
_sphere.GetBoundingBox(_box);
|
||||
}
|
||||
|
||||
void SphereCollider::CreateShape()
|
||||
{
|
||||
// Setup shape geometry
|
||||
_cachedScale = GetScale();
|
||||
const float scaling = _cachedScale.GetAbsolute().MaxValue();
|
||||
const float radius = Math::Abs(_radius) * scaling;
|
||||
const float minSize = 0.001f;
|
||||
const PxSphereGeometry geometry(Math::Max(radius, minSize));
|
||||
|
||||
// Setup shape
|
||||
CreateShapeBase(geometry);
|
||||
}
|
||||
|
||||
void SphereCollider::UpdateGeometry()
|
||||
{
|
||||
// Check if has no shape created
|
||||
if (_shape == nullptr)
|
||||
return;
|
||||
|
||||
// Setup shape geometry
|
||||
_cachedScale = GetScale();
|
||||
const float scaling = _cachedScale.GetAbsolute().MaxValue();
|
||||
const float radius = Math::Abs(_radius) * scaling;
|
||||
const float minSize = 0.001f;
|
||||
const PxSphereGeometry geometry(Math::Max(radius, minSize));
|
||||
|
||||
// Setup shape
|
||||
_shape->setGeometry(geometry);
|
||||
}
|
||||
66
Source/Engine/Physics/Colliders/SphereCollider.h
Normal file
66
Source/Engine/Physics/Colliders/SphereCollider.h
Normal file
@@ -0,0 +1,66 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Collider.h"
|
||||
|
||||
/// <summary>
|
||||
/// A sphere-shaped primitive collider.
|
||||
/// </summary>
|
||||
/// <seealso cref="Collider" />
|
||||
API_CLASS() class FLAXENGINE_API SphereCollider : public Collider
|
||||
{
|
||||
DECLARE_SCENE_OBJECT(SphereCollider);
|
||||
private:
|
||||
|
||||
float _radius;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Gets the radius of the sphere, measured in the object's local space.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The sphere radius will be scaled by the actor's world scale.
|
||||
/// </remarks>
|
||||
API_PROPERTY(Attributes="EditorOrder(100), DefaultValue(50.0f), EditorDisplay(\"Collider\")")
|
||||
FORCE_INLINE float GetRadius() const
|
||||
{
|
||||
return _radius;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the radius of the sphere, measured in the object's local space.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The sphere radius will be scaled by the actor's world scale.
|
||||
/// </remarks>
|
||||
API_PROPERTY() void SetRadius(float value);
|
||||
|
||||
private:
|
||||
|
||||
#if USE_EDITOR
|
||||
void DrawPhysicsDebug(RenderView& view);
|
||||
#endif
|
||||
|
||||
public:
|
||||
|
||||
// [Collider]
|
||||
#if USE_EDITOR
|
||||
void OnDebugDrawSelected() override;
|
||||
#endif
|
||||
bool IntersectsItself(const Ray& ray, float& distance, Vector3& normal) override;
|
||||
void Serialize(SerializeStream& stream, const void* otherObj) override;
|
||||
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
|
||||
|
||||
protected:
|
||||
|
||||
// [Collider]
|
||||
#if USE_EDITOR
|
||||
void OnEnable() override;
|
||||
void OnDisable() override;
|
||||
#endif
|
||||
void UpdateBounds() override;
|
||||
void CreateShape() override;
|
||||
void UpdateGeometry() override;
|
||||
};
|
||||
Reference in New Issue
Block a user