You're breathtaking!

This commit is contained in:
Wojtek Figat
2020-12-07 23:40:54 +01:00
commit 6fb9eee74c
5143 changed files with 1153594 additions and 0 deletions

View 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);
}

View 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;
};

View 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);
}

View 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;
};

View 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);
}

View 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 Controllers 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 Controllers 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;
};

View 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();
}

View 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;
};

View 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);
}
}

View 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;
};

View 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);
}

View 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;
};