From 09e2b737f0087f04a55ad1d20435caa607b94fe7 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 20 Aug 2021 10:51:27 +0200 Subject: [PATCH 01/53] Fix rare crashes due to selection not being empty on play mode start --- Source/Editor/States/PlayingState.cs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/Source/Editor/States/PlayingState.cs b/Source/Editor/States/PlayingState.cs index 6258975ae..c9100dcf9 100644 --- a/Source/Editor/States/PlayingState.cs +++ b/Source/Editor/States/PlayingState.cs @@ -92,9 +92,13 @@ namespace FlaxEditor.States { Profiler.BeginEvent("PlayingState.CacheSelection"); _selectedObjects.Clear(); - for (int i = 0; i < Editor.SceneEditing.Selection.Count; i++) + var selection = Editor.SceneEditing.Selection; + if (selection.Count != 0) { - _selectedObjects.Add(Editor.SceneEditing.Selection[i].ID); + for (int i = 0; i < selection.Count; i++) + _selectedObjects.Add(selection[i].ID); + selection.Clear(); + Editor.SceneEditing.OnSelectionChanged(); } Profiler.EndEvent(); } @@ -102,15 +106,16 @@ namespace FlaxEditor.States private void RestoreSelection() { Profiler.BeginEvent("PlayingState.RestoreSelection"); - var count = Editor.SceneEditing.Selection.Count; - Editor.SceneEditing.Selection.Clear(); + var selection = Editor.SceneEditing.Selection; + var count = selection.Count; + selection.Clear(); for (int i = 0; i < _selectedObjects.Count; i++) { var node = SceneGraphFactory.FindNode(_selectedObjects[i]); if (node != null) - Editor.SceneEditing.Selection.Add(node); + selection.Add(node); } - if (Editor.SceneEditing.Selection.Count != count) + if (selection.Count != count) Editor.SceneEditing.OnSelectionChanged(); Profiler.EndEvent(); } From aa224f6296144b49b9a077b768b2f8fd4b5d034e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 20 Aug 2021 15:30:01 +0200 Subject: [PATCH 02/53] Fix WheeledVehicle driving and suspension raycasts --- .../Engine/Physics/Actors/WheeledVehicle.cpp | 33 +++++++++++--- Source/Engine/Physics/Actors/WheeledVehicle.h | 44 +++++++++++-------- Source/Engine/Physics/Physics.cpp | 32 +++++++------- 3 files changed, 66 insertions(+), 43 deletions(-) diff --git a/Source/Engine/Physics/Actors/WheeledVehicle.cpp b/Source/Engine/Physics/Actors/WheeledVehicle.cpp index 971f882cd..7d86b87bd 100644 --- a/Source/Engine/Physics/Actors/WheeledVehicle.cpp +++ b/Source/Engine/Physics/Actors/WheeledVehicle.cpp @@ -291,7 +291,8 @@ void WheeledVehicle::Setup() offsets[i] = C2P(wheel.Collider->GetLocalPosition()); } PxF32 sprungMasses[PX_MAX_NB_WHEELS]; - PxVehicleComputeSprungMasses(wheels.Count(), offsets, centerOfMassOffset.p, _actor->getMass(), 1, sprungMasses); + const float mass = _actor->getMass(); + PxVehicleComputeSprungMasses(wheels.Count(), offsets, centerOfMassOffset.p, mass, 1, sprungMasses); PxVehicleWheelsSimData* wheelsSimData = PxVehicleWheelsSimData::allocate(wheels.Count()); for (int32 i = 0; i < wheels.Count(); i++) { @@ -300,7 +301,6 @@ void WheeledVehicle::Setup() auto& data = _wheelsData[i]; data.Collider = wheel.Collider; data.LocalOrientation = wheel.Collider->GetLocalOrientation(); - data.ChildrenPoses.Resize(0); PxVehicleSuspensionData suspensionData; const float suspensionFrequency = 7.0f; @@ -324,7 +324,7 @@ void WheeledVehicle::Setup() wheelData.mMaxHandBrakeTorque = M2ToCm2(wheel.MaxHandBrakeTorque); PxVec3 centreOffset = centerOfMassOffset.transformInv(offsets[i]); - PxVec3 forceAppPointOffset(centreOffset.z, centreOffset.y + wheel.SuspensionForceOffset, centreOffset.z); + PxVec3 forceAppPointOffset(centreOffset.z, wheel.SuspensionForceOffset, centreOffset.z); wheelsSimData->setTireData(i, tire); wheelsSimData->setWheelData(i, wheelData); @@ -333,6 +333,8 @@ void WheeledVehicle::Setup() wheelsSimData->setWheelCentreOffset(i, centreOffset); wheelsSimData->setSuspForceAppPointOffset(i, forceAppPointOffset); wheelsSimData->setTireForceAppPointOffset(i, forceAppPointOffset); + wheelsSimData->setSubStepCount(4.0f * 100.0f, 3, 1); + wheelsSimData->setMinLongSlipDenominator(4.0f * 100.0f); PxShape* wheelShape = wheel.Collider->GetPxShape(); if (wheel.Collider->IsActiveInHierarchy()) @@ -345,6 +347,9 @@ void WheeledVehicle::Setup() wheelShape->setQueryFilterData(filter); wheelShape->setSimulationFilterData(filter); wheelsSimData->setSceneQueryFilterData(i, filter); + + // Remove wheels from the simulation (suspension force hold the vehicle) + wheelShape->setFlag(PxShapeFlag::eSIMULATION_SHAPE, false); } else { @@ -520,12 +525,16 @@ void WheeledVehicle::DrawPhysicsDebug(RenderView& view) auto& wheel = _wheels[wheelIndex]; if (wheel.Collider && wheel.Collider->GetParent() == this && !wheel.Collider->GetIsTrigger()) { - const Vector3 basePos = wheel.Collider->GetPosition(); - const Vector3 currentPos = basePos + Vector3(0, data.State.SuspensionOffset, 0); + const Vector3 currentPos = wheel.Collider->GetPosition(); + const Vector3 basePos = currentPos - Vector3(0, data.State.SuspensionOffset, 0); DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(basePos, wheel.Radius * 0.07f), Color::Blue * 0.3f, 0, true); DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(currentPos, wheel.Radius * 0.08f), Color::Blue * 0.8f, 0, true); DEBUG_DRAW_LINE(basePos, currentPos, Color::Blue, 0, true); DEBUG_DRAW_WIRE_CYLINDER(currentPos, wheel.Collider->GetOrientation(), wheel.Radius, wheel.Width, Color::Red * 0.8f, 0, true); + if (!data.State.IsInAir) + { + DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(data.State.TireContactPoint, 5.0f), Color::Green, 0, true); + } } } } @@ -546,12 +555,22 @@ void WheeledVehicle::OnDebugDrawSelected() auto& wheel = _wheels[wheelIndex]; if (wheel.Collider && wheel.Collider->GetParent() == this && !wheel.Collider->GetIsTrigger()) { - const Vector3 basePos = wheel.Collider->GetPosition(); - const Vector3 currentPos = basePos + Vector3(0, data.State.SuspensionOffset, 0); + const Vector3 currentPos = wheel.Collider->GetPosition(); + const Vector3 basePos = currentPos - Vector3(0, data.State.SuspensionOffset, 0); DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(basePos, wheel.Radius * 0.07f), Color::Blue * 0.3f, 0, false); DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(currentPos, wheel.Radius * 0.08f), Color::Blue * 0.8f, 0, false); + DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(P2C(_actor->getGlobalPose().transform(wheel.Collider->GetPxShape()->getLocalPose()).p), wheel.Radius * 0.11f), Color::OrangeRed * 0.8f, 0, false); DEBUG_DRAW_LINE(basePos, currentPos, Color::Blue, 0, false); DEBUG_DRAW_WIRE_CYLINDER(currentPos, wheel.Collider->GetOrientation(), wheel.Radius, wheel.Width, Color::Red * 0.4f, 0, false); + if (!data.State.SuspensionTraceStart.IsZero()) + { + DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(data.State.SuspensionTraceStart, 5.0f), Color::AliceBlue, 0, false); + DEBUG_DRAW_LINE(data.State.SuspensionTraceStart, data.State.SuspensionTraceEnd, data.State.IsInAir ? Color::Red : Color::Green, 0, false); + } + if (!data.State.IsInAir) + { + DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(data.State.TireContactPoint, 5.0f), Color::Green, 0, false); + } } } diff --git a/Source/Engine/Physics/Actors/WheeledVehicle.h b/Source/Engine/Physics/Actors/WheeledVehicle.h index bbccfd76b..b7ce2000b 100644 --- a/Source/Engine/Physics/Actors/WheeledVehicle.h +++ b/Source/Engine/Physics/Actors/WheeledVehicle.h @@ -172,67 +172,67 @@ public: /// /// Wheel placement type. /// - API_FIELD() WheelTypes Type = WheelTypes::FrontLeft; + API_FIELD(Attributes="EditorOrder(0)") WheelTypes Type = WheelTypes::FrontLeft; /// /// Combined mass of the wheel and the tire in kg. Typically, a wheel has mass between 20Kg and 80Kg but can be lower and higher depending on the vehicle. /// - API_FIELD() float Mass = 20.0f; + API_FIELD(Attributes="EditorOrder(1)") float Mass = 20.0f; /// /// Distance in metres between the center of the wheel and the outside rim of the tire. It is important that the value of the radius closely matches the radius of the render mesh of the wheel. Any mismatch will result in the wheels either hovering above the ground or intersecting the ground. /// - API_FIELD() float Radius = 50.0f; + API_FIELD(Attributes="EditorOrder(2)") float Radius = 50.0f; /// /// Full width of the wheel in metres. This parameter has no bearing on the handling but is a very useful parameter to have when trying to render debug data relating to the wheel/tire/suspension. /// - API_FIELD() float Width = 20.0f; + API_FIELD(Attributes="EditorOrder(3)") float Width = 20.0f; /// /// Max steer angle that can be achieved by the wheel (in degrees). /// - API_FIELD(Attributes="Limit(0)") float MaxSteerAngle = 0.0f; + API_FIELD(Attributes="Limit(0), EditorDisplay(\"Steering\"), EditorOrder(10)") float MaxSteerAngle = 0.0f; /// /// Damping rate applied to wheel. Specified in kilograms metres-squared per second (kg m^2 s^-1). /// - API_FIELD(Attributes="Limit(0)") float DampingRate = 0.25f; + API_FIELD(Attributes="Limit(0), EditorDisplay(\"Steering\"), EditorOrder(11)") float DampingRate = 0.25f; /// /// Max brake torque that can be applied to wheel. Specified in kilograms metres-squared per second-squared (kg m^2 s^-2) /// - API_FIELD(Attributes="Limit(0)") float MaxBrakeTorque = 1500.0f; + API_FIELD(Attributes="Limit(0), EditorDisplay(\"Steering\"), EditorOrder(12)") float MaxBrakeTorque = 1500.0f; /// /// Max handbrake torque that can be applied to wheel. Specified in kilograms metres-squared per second-squared (kg m^2 s^-2) /// - API_FIELD(Attributes="Limit(0)") float MaxHandBrakeTorque = 2000.0f; + API_FIELD(Attributes="Limit(0), EditorDisplay(\"Steering\"), EditorOrder(13)") float MaxHandBrakeTorque = 2000.0f; /// /// Collider that represents the wheel shape and it's placement. Has to be attached as a child to the vehicle. Triangle mesh collider is not supported (use convex mesh or basic shapes). /// - API_FIELD() ScriptingObjectReference Collider; + API_FIELD(Attributes="EditorOrder(4)") ScriptingObjectReference Collider; /// /// Spring damper rate of suspension unit. /// - API_FIELD(Attributes="Limit(0)") float SuspensionDampingRate = 1.0f; + API_FIELD(Attributes="Limit(0), EditorDisplay(\"Suspension\"), EditorOrder(20)") float SuspensionDampingRate = 1.0f; /// /// The maximum offset for the suspension that wheel can go above resting location. /// - API_FIELD(Attributes="Limit(0)") float SuspensionMaxRaise = 10.0f; + API_FIELD(Attributes="Limit(0), EditorDisplay(\"Suspension\"), EditorOrder(21)") float SuspensionMaxRaise = 10.0f; /// /// The maximum offset for the suspension that wheel can go below resting location. /// - API_FIELD(Attributes="Limit(0)") float SuspensionMaxDrop = 10.0f; + API_FIELD(Attributes="Limit(0), EditorDisplay(\"Suspension\"), EditorOrder(22)") float SuspensionMaxDrop = 10.0f; /// /// The vertical offset from where suspension forces are applied. /// - API_FIELD() float SuspensionForceOffset = 0.0f; + API_FIELD(Attributes="EditorDisplay(\"Suspension\"), EditorOrder(23)") float SuspensionForceOffset = 0.0f; }; /// @@ -281,6 +281,18 @@ public: /// The compression of the suspension spring. Offsets the wheel location. /// API_FIELD() float SuspensionOffset = 0.0f; + +#if USE_EDITOR + /// + /// The start location of the suspension raycast start (Editor only for debugging). + /// + API_FIELD() Vector3 SuspensionTraceStart = Vector3::Zero; + + /// + /// The start location of the suspension raycast end (Editor only for debugging). + /// + API_FIELD() Vector3 SuspensionTraceEnd = Vector3::Zero; +#endif }; private: @@ -290,12 +302,6 @@ private: Collider* Collider; Quaternion LocalOrientation; WheelState State; - struct ChildPose - { - Actor* Child; - Vector3 Pose; - }; - Array> ChildrenPoses; }; void* _drive = nullptr; diff --git a/Source/Engine/Physics/Physics.cpp b/Source/Engine/Physics/Physics.cpp index fb34eff1e..684f852ef 100644 --- a/Source/Engine/Physics/Physics.cpp +++ b/Source/Engine/Physics/Physics.cpp @@ -789,8 +789,8 @@ void Physics::CollectResults() { if (WheelRaycastBatchQuery) WheelRaycastBatchQuery->release(); - WheelQueryResults.Resize(wheelsCount); - WheelQueryResults.Resize(WheelQueryResults.Capacity()); + WheelQueryResults.Resize(wheelsCount, false); + WheelHitResults.Resize(wheelsCount, false); PxBatchQueryDesc desc(wheelsCount, 0, 0); desc.queryMemory.userRaycastResultBuffer = WheelQueryResults.Get(); desc.queryMemory.userRaycastTouchBuffer = WheelHitResults.Get(); @@ -857,23 +857,21 @@ void Physics::CollectResults() state.SteerAngle = RadiansToDegrees * perWheel.steerAngle; state.RotationAngle = -RadiansToDegrees * drive->mWheelsDynData.getWheelRotationAngle(j); state.SuspensionOffset = perWheel.suspJounce; +#if USE_EDITOR + state.SuspensionTraceStart = P2C(perWheel.suspLineStart); + state.SuspensionTraceEnd = P2C(perWheel.suspLineStart + perWheel.suspLineDir * perWheel.suspLineLength); +#endif - // Rotate wheel - wheelData.Collider->SetLocalOrientation(Quaternion::Euler(0, state.SteerAngle, state.RotationAngle) * wheelData.LocalOrientation); + if (!wheelData.Collider) + continue; + auto shape = wheelData.Collider->GetPxShape(); - // Apply suspension offset (cannot move collider because it breaks driving so move it's children but preserve the initial pose) - for (auto child : wheelData.Collider->Children) - { - int32 poseIndex = 0; - for (; poseIndex < wheelData.ChildrenPoses.Count(); poseIndex++) - { - if (wheelData.ChildrenPoses[poseIndex].Child == child) - break; - } - if (poseIndex == wheelData.ChildrenPoses.Count()) - wheelData.ChildrenPoses.Add({ child, child->GetLocalPosition() }); - child->SetPosition(wheelData.Collider->GetTransform().LocalToWorld(wheelData.ChildrenPoses[poseIndex].Pose) + Vector3(0, perWheel.suspJounce, 0)); - } + // Update wheel collider transformation + auto localPose = shape->getLocalPose(); + Transform t = wheelData.Collider->GetLocalTransform(); + t.Orientation = Quaternion::Euler(0, state.SteerAngle, state.RotationAngle) * wheelData.LocalOrientation; + t.Translation = P2C(localPose.p) / wheelVehicle->GetScale() - t.Orientation * wheelData.Collider->GetCenter(); + wheelData.Collider->SetLocalTransform(t); } } } From 17f128afeb1e6be981787eea5d43caa07a073947 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 20 Aug 2021 16:23:45 +0200 Subject: [PATCH 03/53] Add digital steering option for a vehicle --- .../Engine/Physics/Actors/WheeledVehicle.cpp | 2 + Source/Engine/Physics/Actors/WheeledVehicle.h | 6 ++ Source/Engine/Physics/Physics.cpp | 87 +++++++++++++++---- 3 files changed, 76 insertions(+), 19 deletions(-) diff --git a/Source/Engine/Physics/Actors/WheeledVehicle.cpp b/Source/Engine/Physics/Actors/WheeledVehicle.cpp index 7d86b87bd..3d56be785 100644 --- a/Source/Engine/Physics/Actors/WheeledVehicle.cpp +++ b/Source/Engine/Physics/Actors/WheeledVehicle.cpp @@ -591,6 +591,7 @@ void WheeledVehicle::Serialize(SerializeStream& stream, const void* otherObj) SERIALIZE_MEMBER(DriveType, _driveType); SERIALIZE_MEMBER(Wheels, _wheels); SERIALIZE(UseReverseAsBrake); + SERIALIZE(UseAnalogSteering); SERIALIZE_MEMBER(Engine, _engine); SERIALIZE_MEMBER(Differential, _differential); SERIALIZE_MEMBER(Gearbox, _gearbox); @@ -603,6 +604,7 @@ void WheeledVehicle::Deserialize(DeserializeStream& stream, ISerializeModifier* DESERIALIZE_MEMBER(DriveType, _driveType); DESERIALIZE_MEMBER(Wheels, _wheels); DESERIALIZE(UseReverseAsBrake); + DESERIALIZE(UseAnalogSteering); DESERIALIZE_MEMBER(Engine, _engine); DESERIALIZE_MEMBER(Differential, _differential); DESERIALIZE_MEMBER(Gearbox, _gearbox); diff --git a/Source/Engine/Physics/Actors/WheeledVehicle.h b/Source/Engine/Physics/Actors/WheeledVehicle.h index b7ce2000b..59d1a54d2 100644 --- a/Source/Engine/Physics/Actors/WheeledVehicle.h +++ b/Source/Engine/Physics/Actors/WheeledVehicle.h @@ -321,6 +321,12 @@ public: API_FIELD(Attributes="EditorOrder(0), EditorDisplay(\"Vehicle\")") bool UseReverseAsBrake = true; + /// + /// If checked, the vehicle driving and steering inputs will be used as analog values (from gamepad), otherwise will be used as digital input (from keyboard). + /// + API_FIELD(Attributes="EditorOrder(1), EditorDisplay(\"Vehicle\")") + bool UseAnalogSteering = false; + /// /// Gets the vehicle driving model type. /// diff --git a/Source/Engine/Physics/Physics.cpp b/Source/Engine/Physics/Physics.cpp index 684f852ef..a0b4d67dd 100644 --- a/Source/Engine/Physics/Physics.cpp +++ b/Source/Engine/Physics/Physics.cpp @@ -744,6 +744,23 @@ void Physics::CollectResults() 5.0f, // fall rate eANALOG_INPUT_STEER_RIGHT } }; + PxVehicleKeySmoothingData keySmoothing = + { + { + 3.0f, // rise rate eANALOG_INPUT_ACCEL + 3.0f, // rise rate eANALOG_INPUT_BRAKE + 10.0f, // rise rate eANALOG_INPUT_HANDBRAKE + 2.5f, // rise rate eANALOG_INPUT_STEER_LEFT + 2.5f, // rise rate eANALOG_INPUT_STEER_RIGHT + }, + { + 5.0f, // fall rate eANALOG_INPUT__ACCEL + 5.0f, // fall rate eANALOG_INPUT__BRAKE + 10.0f, // fall rate eANALOG_INPUT__HANDBRAKE + 5.0f, // fall rate eANALOG_INPUT_STEER_LEFT + 5.0f // fall rate eANALOG_INPUT_STEER_RIGHT + } + }; // Reference: PhysX SDK docs // TODO: expose steer vs forward curve into per-vehicle (up to 8 points, values clamped into 0/1 range) static constexpr PxF32 steerVsForwardSpeedData[] = @@ -759,28 +776,60 @@ void Physics::CollectResults() }; const PxFixedSizeLookupTable<8> steerVsForwardSpeed(steerVsForwardSpeedData, 4); // @formatter:on - switch (wheelVehicle->_driveTypeCurrent) + if (wheelVehicle->UseAnalogSteering) { - case WheeledVehicle::DriveTypes::Drive4W: - { - PxVehicleDrive4WRawInputData rawInputData; - rawInputData.setAnalogAccel(throttle); - rawInputData.setAnalogBrake(brake); - rawInputData.setAnalogSteer(wheelVehicle->_steering); - rawInputData.setAnalogHandbrake(wheelVehicle->_handBrake); - PxVehicleDrive4WSmoothAnalogRawInputsAndSetAnalogInputs(padSmoothing, steerVsForwardSpeed, rawInputData, LastDeltaTime, false, *(PxVehicleDrive4W*)drive); - break; + switch (wheelVehicle->_driveTypeCurrent) + { + case WheeledVehicle::DriveTypes::Drive4W: + { + PxVehicleDrive4WRawInputData rawInputData; + rawInputData.setAnalogAccel(throttle); + rawInputData.setAnalogBrake(brake); + rawInputData.setAnalogSteer(wheelVehicle->_steering); + rawInputData.setAnalogHandbrake(wheelVehicle->_handBrake); + PxVehicleDrive4WSmoothAnalogRawInputsAndSetAnalogInputs(padSmoothing, steerVsForwardSpeed, rawInputData, LastDeltaTime, false, *(PxVehicleDrive4W*)drive); + break; + } + case WheeledVehicle::DriveTypes::DriveNW: + { + PxVehicleDriveNWRawInputData rawInputData; + rawInputData.setAnalogAccel(throttle); + rawInputData.setAnalogBrake(brake); + rawInputData.setAnalogSteer(wheelVehicle->_steering); + rawInputData.setAnalogHandbrake(wheelVehicle->_handBrake); + PxVehicleDriveNWSmoothAnalogRawInputsAndSetAnalogInputs(padSmoothing, steerVsForwardSpeed, rawInputData, LastDeltaTime, false, *(PxVehicleDriveNW*)drive); + break; + } + } } - case WheeledVehicle::DriveTypes::DriveNW: + else { - PxVehicleDriveNWRawInputData rawInputData; - rawInputData.setAnalogAccel(throttle); - rawInputData.setAnalogBrake(brake); - rawInputData.setAnalogSteer(wheelVehicle->_steering); - rawInputData.setAnalogHandbrake(wheelVehicle->_handBrake); - PxVehicleDriveNWSmoothAnalogRawInputsAndSetAnalogInputs(padSmoothing, steerVsForwardSpeed, rawInputData, LastDeltaTime, false, *(PxVehicleDriveNW*)drive); - break; - } + const float deadZone = 0.1f; + switch (wheelVehicle->_driveTypeCurrent) + { + case WheeledVehicle::DriveTypes::Drive4W: + { + PxVehicleDrive4WRawInputData rawInputData; + rawInputData.setDigitalAccel(throttle > deadZone); + rawInputData.setDigitalBrake(brake > deadZone); + rawInputData.setDigitalSteerLeft(wheelVehicle->_steering < -deadZone); + rawInputData.setDigitalSteerRight(wheelVehicle->_steering > deadZone); + rawInputData.setDigitalHandbrake(wheelVehicle->_handBrake > deadZone); + PxVehicleDrive4WSmoothDigitalRawInputsAndSetAnalogInputs(keySmoothing, steerVsForwardSpeed, rawInputData, LastDeltaTime, false, *(PxVehicleDrive4W*)drive); + break; + } + case WheeledVehicle::DriveTypes::DriveNW: + { + PxVehicleDriveNWRawInputData rawInputData; + rawInputData.setDigitalAccel(throttle > deadZone); + rawInputData.setDigitalBrake(brake > deadZone); + rawInputData.setDigitalSteerLeft(wheelVehicle->_steering < -deadZone); + rawInputData.setDigitalSteerRight(wheelVehicle->_steering > deadZone); + rawInputData.setDigitalHandbrake(wheelVehicle->_handBrake > deadZone); + PxVehicleDriveNWSmoothDigitalRawInputsAndSetAnalogInputs(keySmoothing, steerVsForwardSpeed, rawInputData, LastDeltaTime, false, *(PxVehicleDriveNW*)drive); + break; + } + } } } From 078f56febed1334615236046cb9790d14b1d16ec Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 20 Aug 2021 16:32:47 +0200 Subject: [PATCH 04/53] Add vehicle telemetry logging debug option --- Source/Engine/Physics/Physics.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Source/Engine/Physics/Physics.cpp b/Source/Engine/Physics/Physics.cpp index a0b4d67dd..aca6f6242 100644 --- a/Source/Engine/Physics/Physics.cpp +++ b/Source/Engine/Physics/Physics.cpp @@ -31,6 +31,12 @@ // Temporary memory size used by the PhysX during the simulation. Must be multiply of 4kB and 16bit aligned. #define SCRATCH_BLOCK_SIZE (1024 * 128) +#define PHYSX_VEHICLE_DEBUG_TELEMETRY 0 + +#if PHYSX_VEHICLE_DEBUG_TELEMETRY +#include "Engine/Core/Utilities.h" +#endif + class PhysXAllocator : public PxAllocatorCallback { public: @@ -890,12 +896,18 @@ void Physics::CollectResults() auto drive = WheelVehiclesCache[ii]; auto& perVehicle = WheelVehiclesResultsPerVehicle[ii]; ii++; +#if PHYSX_VEHICLE_DEBUG_TELEMETRY + LOG(Info, "Vehicle[{}] Gear={}, RPM={}", ii, wheelVehicle->GetCurrentGear(), (int32)wheelVehicle->GetEngineRotationSpeed()); +#endif // Update wheels for (int32 j = 0; j < wheelVehicle->_wheelsData.Count(); j++) { auto& wheelData = wheelVehicle->_wheelsData[j]; auto& perWheel = perVehicle.wheelQueryResults[j]; +#if PHYSX_VEHICLE_DEBUG_TELEMETRY + LOG(Info, "Vehicle[{}] Wheel[{}] longitudinalSlip={}, lateralSlip={}, suspSpringForce={}", ii, j, Utilities::RoundTo2DecimalPlaces(perWheel.longitudinalSlip), Utilities::RoundTo2DecimalPlaces(perWheel.lateralSlip), (int32)perWheel.suspSpringForce); +#endif auto& state = wheelData.State; state.IsInAir = perWheel.isInAir; From aaca4ecc698a788bcd0fecfb8cecc209a15e4e70 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 20 Aug 2021 16:33:11 +0200 Subject: [PATCH 05/53] Add wheeled vehicle tire options --- Source/Engine/Physics/Actors/WheeledVehicle.cpp | 3 +++ Source/Engine/Physics/Actors/WheeledVehicle.h | 15 +++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/Source/Engine/Physics/Actors/WheeledVehicle.cpp b/Source/Engine/Physics/Actors/WheeledVehicle.cpp index 3d56be785..324d51c43 100644 --- a/Source/Engine/Physics/Actors/WheeledVehicle.cpp +++ b/Source/Engine/Physics/Actors/WheeledVehicle.cpp @@ -312,6 +312,9 @@ void WheeledVehicle::Setup() PxVehicleTireData tire; tire.mType = 0; + tire.mLatStiffX = wheel.TireLateralMax; + tire.mLatStiffY = wheel.TireLateralStiffness; + tire.mLongitudinalStiffnessPerUnitGravity = wheel.TireLongitudinalStiffness; PxVehicleWheelData wheelData; wheelData.mMass = wheel.Mass; diff --git a/Source/Engine/Physics/Actors/WheeledVehicle.h b/Source/Engine/Physics/Actors/WheeledVehicle.h index 59d1a54d2..dfdb1da50 100644 --- a/Source/Engine/Physics/Actors/WheeledVehicle.h +++ b/Source/Engine/Physics/Actors/WheeledVehicle.h @@ -233,6 +233,21 @@ public: /// The vertical offset from where suspension forces are applied. /// API_FIELD(Attributes="EditorDisplay(\"Suspension\"), EditorOrder(23)") float SuspensionForceOffset = 0.0f; + + /// + /// The tire lateral stiffness to have given lateral slip. + /// + API_FIELD(Attributes="EditorDisplay(\"Tire\"), EditorOrder(30)") float TireLateralStiffness = 17.0f; + + /// + /// The maximum tire load (normalized) at which tire cannot provide more lateral stiffness (no matter how much extra load is applied to it). + /// + API_FIELD(Attributes="EditorDisplay(\"Tire\"), EditorOrder(31)") float TireLateralMax = 2.0f; + + /// + /// The tire longitudinal stiffness to have given longitudinal slip. + /// + API_FIELD(Attributes="EditorDisplay(\"Tire\"), EditorOrder(32)") float TireLongitudinalStiffness = 1000.0f; }; /// From a59aec842563267be7a19558929d27b9d5883ced Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 20 Aug 2021 16:33:31 +0200 Subject: [PATCH 06/53] Fix suspension force offset location --- Source/Engine/Physics/Actors/WheeledVehicle.cpp | 2 +- Source/Engine/Physics/Actors/WheeledVehicle.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Physics/Actors/WheeledVehicle.cpp b/Source/Engine/Physics/Actors/WheeledVehicle.cpp index 324d51c43..7c6106158 100644 --- a/Source/Engine/Physics/Actors/WheeledVehicle.cpp +++ b/Source/Engine/Physics/Actors/WheeledVehicle.cpp @@ -327,7 +327,7 @@ void WheeledVehicle::Setup() wheelData.mMaxHandBrakeTorque = M2ToCm2(wheel.MaxHandBrakeTorque); PxVec3 centreOffset = centerOfMassOffset.transformInv(offsets[i]); - PxVec3 forceAppPointOffset(centreOffset.z, wheel.SuspensionForceOffset, centreOffset.z); + PxVec3 forceAppPointOffset(centreOffset.x, wheel.SuspensionForceOffset, centreOffset.z); wheelsSimData->setTireData(i, tire); wheelsSimData->setWheelData(i, wheelData); diff --git a/Source/Engine/Physics/Actors/WheeledVehicle.h b/Source/Engine/Physics/Actors/WheeledVehicle.h index dfdb1da50..bbc52af5b 100644 --- a/Source/Engine/Physics/Actors/WheeledVehicle.h +++ b/Source/Engine/Physics/Actors/WheeledVehicle.h @@ -230,7 +230,7 @@ public: API_FIELD(Attributes="Limit(0), EditorDisplay(\"Suspension\"), EditorOrder(22)") float SuspensionMaxDrop = 10.0f; /// - /// The vertical offset from where suspension forces are applied. + /// The vertical offset from where suspension forces are applied (relative to the vehicle center of mass). The suspension force is applies on the vertical axis going though the wheel center. /// API_FIELD(Attributes="EditorDisplay(\"Suspension\"), EditorOrder(23)") float SuspensionForceOffset = 0.0f; From 700b561ad1f56d2e29b699aafc7a18e86b16d6ee Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 19 Aug 2021 23:36:07 +0200 Subject: [PATCH 07/53] Improve rigidbody warning message --- Source/Engine/Physics/Colliders/MeshCollider.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Physics/Colliders/MeshCollider.cpp b/Source/Engine/Physics/Colliders/MeshCollider.cpp index 9e49e82b7..aa0c1c554 100644 --- a/Source/Engine/Physics/Colliders/MeshCollider.cpp +++ b/Source/Engine/Physics/Colliders/MeshCollider.cpp @@ -43,10 +43,10 @@ bool MeshCollider::CanAttach(RigidBody* rigidBody) const CollisionDataType type = CollisionDataType::None; if (CollisionData && CollisionData->IsLoaded()) type = CollisionData->GetOptions().Type; -#if USE_EDITOR +#if USE_EDITOR || !BUILD_RELEASE if (type == CollisionDataType::TriangleMesh) { - LOG(Warning, "Cannot attach {0} using Triangle Mesh collider {1} to RigidBody (not supported)", GetNamePath(), CollisionData->ToString()); + LOG(Warning, "Cannot attach '{0}' using Triangle Mesh collider '{1}' to Rigid Body (not supported)", GetNamePath(), CollisionData->ToString()); } #endif return type != CollisionDataType::TriangleMesh; From 51016949b811fcb196823e4c43c208bab661358a Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 9 Aug 2021 11:59:08 +0200 Subject: [PATCH 08/53] Fix actor renaming on paste to keep previous name if can --- Source/Editor/Undo/Actions/PasteActorsAction.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Source/Editor/Undo/Actions/PasteActorsAction.cs b/Source/Editor/Undo/Actions/PasteActorsAction.cs index c77feb71d..94e8990b2 100644 --- a/Source/Editor/Undo/Actions/PasteActorsAction.cs +++ b/Source/Editor/Undo/Actions/PasteActorsAction.cs @@ -141,15 +141,20 @@ namespace FlaxEditor.Actions for (int i = 0; i < nodeParents.Count; i++) { var node = nodeParents[i]; - var parent = node.Actor?.Parent; + var actor = node.Actor; + var parent = actor?.Parent; if (parent != null) { // Fix name collisions - string name = node.Name; - Actor[] children = parent.Children; - if (children.Any(x => x.Name == name)) + var name = actor.Name; + for (int j = 0; j < parent.ChildrenCount; j++) { - node.Actor.Name = StringUtils.IncrementNameNumber(name, x => children.All(y => y.Name != x)); + var child = parent.Children[j]; + if (child != actor && child.Name == actor.Name) + { + var children = parent.Children; + actor.Name = StringUtils.IncrementNameNumber(name, x => children.All(y => y.Name != x)); + } } } From 783372c7873ebc16716ca5a821124506c7eec2fe Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 9 Aug 2021 12:12:32 +0200 Subject: [PATCH 09/53] Fix pasting actors if cannot spawn a object (eg. type missing) to still paste valid objects --- Source/Engine/Level/Actor.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp index 63fc12b20..076ac4608 100644 --- a/Source/Engine/Level/Actor.cpp +++ b/Source/Engine/Level/Actor.cpp @@ -1611,21 +1611,22 @@ bool Actor::FromBytes(const Span& data, Array& output, ISerializeM // Create object auto obj = SceneObjectsFactory::Spawn(document, modifier); + sceneObjects->At(i) = obj; if (obj == nullptr) { LOG(Warning, "Cannot create object."); - return true; + continue; } obj->RegisterObject(); // Add to results - sceneObjects->At(i) = obj; Actor* actor = dynamic_cast(obj); if (actor) { output.Add(actor); } } + // TODO: optimize this to call json parsing only once per-object instead of twice (spawn + load) stream.SetPosition(startPos); for (int32 i = 0; i < objectsCount; i++) { @@ -1639,7 +1640,7 @@ bool Actor::FromBytes(const Span& data, Array& output, ISerializeM int32 orderInParent; stream.ReadInt32(&orderInParent); - // Load JSON + // Load JSON rapidjson_flax::Document document; { PROFILE_CPU_NAMED("Json.Parse"); @@ -1653,7 +1654,10 @@ bool Actor::FromBytes(const Span& data, Array& output, ISerializeM // Deserialize object auto obj = sceneObjects->At(i); - SceneObjectsFactory::Deserialize(obj, document, modifier); + if (obj) + SceneObjectsFactory::Deserialize(obj, document, modifier); + else + SceneObjectsFactory::HandleObjectDeserializationError(document); } Scripting::ObjectsLookupIdMapping.Set(nullptr); From d194a06e59aa93d3f8b0fb09cd7d407010a8c901 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 9 Aug 2021 14:35:22 +0200 Subject: [PATCH 10/53] Add options for vehicle wheel suspension configuration and state --- .../Engine/Physics/Actors/WheeledVehicle.cpp | 7 ++--- Source/Engine/Physics/Actors/WheeledVehicle.h | 30 +++++++++++++++++++ Source/Engine/Physics/Physics.cpp | 8 +++-- 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/Source/Engine/Physics/Actors/WheeledVehicle.cpp b/Source/Engine/Physics/Actors/WheeledVehicle.cpp index 0fe53f570..388aa1f21 100644 --- a/Source/Engine/Physics/Actors/WheeledVehicle.cpp +++ b/Source/Engine/Physics/Actors/WheeledVehicle.cpp @@ -303,12 +303,11 @@ void WheeledVehicle::Setup() PxVehicleSuspensionData suspensionData; const float suspensionFrequency = 7.0f; - const float suspensionDampingRatio = 1.0f; - suspensionData.mMaxCompression = 10.0f; - suspensionData.mMaxDroop = 10.0f; + suspensionData.mMaxCompression = wheel.SuspensionMaxRaise; + suspensionData.mMaxDroop = wheel.SuspensionMaxDrop; suspensionData.mSprungMass = sprungMasses[i]; suspensionData.mSpringStrength = Math::Square(suspensionFrequency) * suspensionData.mSprungMass; - suspensionData.mSpringDamperRate = suspensionDampingRatio * 2.0f * Math::Sqrt(suspensionData.mSpringStrength * suspensionData.mSprungMass); + suspensionData.mSpringDamperRate = wheel.SuspensionDampingRate * 2.0f * Math::Sqrt(suspensionData.mSpringStrength * suspensionData.mSprungMass); PxVehicleTireData tire; tire.mType = 0; diff --git a/Source/Engine/Physics/Actors/WheeledVehicle.h b/Source/Engine/Physics/Actors/WheeledVehicle.h index 1941038bf..a6a1708ad 100644 --- a/Source/Engine/Physics/Actors/WheeledVehicle.h +++ b/Source/Engine/Physics/Actors/WheeledVehicle.h @@ -213,6 +213,21 @@ public: /// Collider that represents the wheel shape and it's placement. Has to be attached as a child to the vehicle. Triangle mesh collider is not supported (use convex mesh or basic shapes). /// API_FIELD() ScriptingObjectReference Collider; + + /// + /// Spring damper rate of suspension unit. + /// + API_FIELD(Attributes="Limit(0)") float SuspensionDampingRate = 1.0f; + + /// + /// The maximum offset for the suspension that wheel can go above resting location. + /// + API_FIELD(Attributes="Limit(0)") float SuspensionMaxRaise = 10.0f; + + /// + /// The maximum offset for the suspension that wheel can go below resting location. + /// + API_FIELD(Attributes="Limit(0)") float SuspensionMaxDrop = 10.0f; }; /// @@ -246,6 +261,21 @@ public: /// The friction experienced by the tire for the combination of tire type and surface type after accounting. /// API_FIELD() float TireFriction = 0.0f; + + /// + /// The steer angle (in degrees) of the wheel about the "up" vector accounting for input steer and toe and, if applicable, Ackermann steer correction. + /// + API_FIELD() float SteerAngle = 0.0f; + + /// + /// The rotation angle (in degrees) about the rolling axis for the specified wheel. + /// + API_FIELD() float RotationAngle = 0.0f; + + /// + /// The compression of the suspension spring. Offsets the wheel location. + /// + API_FIELD() float SuspensionOffset = 0.0f; }; private: diff --git a/Source/Engine/Physics/Physics.cpp b/Source/Engine/Physics/Physics.cpp index bdee2fdc2..8066f404d 100644 --- a/Source/Engine/Physics/Physics.cpp +++ b/Source/Engine/Physics/Physics.cpp @@ -845,10 +845,12 @@ void Physics::CollectResults() state.TireContactPoint = P2C(perWheel.tireContactPoint); state.TireContactNormal = P2C(perWheel.tireContactNormal); state.TireFriction = perWheel.tireFriction; + state.SteerAngle = RadiansToDegrees * perWheel.steerAngle; + state.RotationAngle = -RadiansToDegrees * drive->mWheelsDynData.getWheelRotationAngle(j); + const float suspensionOffsetDelta = perWheel.suspJounce - state.SuspensionOffset; + state.SuspensionOffset = perWheel.suspJounce; - const float wheelRotationAngle = -RadiansToDegrees * drive->mWheelsDynData.getWheelRotationAngle(j); - const float wheelSteerAngle = RadiansToDegrees * perWheel.steerAngle; - wheelData.Collider->SetLocalOrientation(Quaternion::Euler(0, wheelSteerAngle, wheelRotationAngle) * wheelData.LocalOrientation); + wheelData.Collider->SetLocalOrientation(Quaternion::Euler(0, state.SteerAngle, state.RotationAngle) * wheelData.LocalOrientation); } } } From 8c97a645e99fe683489af9595eb857d495fb348b Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 9 Aug 2021 15:22:10 +0200 Subject: [PATCH 11/53] Fix car wheel location by applying compression of the suspension spring --- .../Engine/Physics/Actors/WheeledVehicle.cpp | 37 +++++++++++++++++-- Source/Engine/Physics/Actors/WheeledVehicle.h | 6 +++ Source/Engine/Physics/Physics.cpp | 16 +++++++- 3 files changed, 54 insertions(+), 5 deletions(-) diff --git a/Source/Engine/Physics/Actors/WheeledVehicle.cpp b/Source/Engine/Physics/Actors/WheeledVehicle.cpp index 388aa1f21..2c21e8880 100644 --- a/Source/Engine/Physics/Actors/WheeledVehicle.cpp +++ b/Source/Engine/Physics/Actors/WheeledVehicle.cpp @@ -300,6 +300,7 @@ void WheeledVehicle::Setup() auto& data = _wheelsData[i]; data.Collider = wheel.Collider; data.LocalOrientation = wheel.Collider->GetLocalOrientation(); + data.ChildrenPoses.Resize(0); PxVehicleSuspensionData suspensionData; const float suspensionFrequency = 7.0f; @@ -507,11 +508,25 @@ void WheeledVehicle::Setup() void WheeledVehicle::DrawPhysicsDebug(RenderView& view) { // Wheels shapes - for (auto& wheel : _wheels) + for (auto& data : _wheelsData) { + int32 wheelIndex = 0; + for (; wheelIndex < _wheels.Count(); wheelIndex++) + { + if (_wheels[wheelIndex].Collider == data.Collider) + break; + } + if (wheelIndex == _wheels.Count()) + break; + auto& wheel = _wheels[wheelIndex]; if (wheel.Collider && wheel.Collider->GetParent() == this && !wheel.Collider->GetIsTrigger()) { - DEBUG_DRAW_WIRE_CYLINDER(wheel.Collider->GetPosition(), wheel.Collider->GetOrientation(), wheel.Radius, wheel.Width, Color::Red * 0.8f, 0, true); + const Vector3 basePos = wheel.Collider->GetPosition(); + const Vector3 currentPos = basePos + Vector3(0, data.State.SuspensionOffset, 0); + DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(basePos, wheel.Radius * 0.07f), Color::Blue * 0.3f, 0, true); + DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(currentPos, wheel.Radius * 0.08f), Color::Blue * 0.8f, 0, true); + DEBUG_DRAW_LINE(basePos, currentPos, Color::Blue, 0, true); + DEBUG_DRAW_WIRE_CYLINDER(currentPos, wheel.Collider->GetOrientation(), wheel.Radius, wheel.Width, Color::Red * 0.8f, 0, true); } } } @@ -519,11 +534,25 @@ void WheeledVehicle::DrawPhysicsDebug(RenderView& view) void WheeledVehicle::OnDebugDrawSelected() { // Wheels shapes - for (auto& wheel : _wheels) + for (auto& data : _wheelsData) { + int32 wheelIndex = 0; + for (; wheelIndex < _wheels.Count(); wheelIndex++) + { + if (_wheels[wheelIndex].Collider == data.Collider) + break; + } + if (wheelIndex == _wheels.Count()) + break; + auto& wheel = _wheels[wheelIndex]; if (wheel.Collider && wheel.Collider->GetParent() == this && !wheel.Collider->GetIsTrigger()) { - DEBUG_DRAW_WIRE_CYLINDER(wheel.Collider->GetPosition(), wheel.Collider->GetOrientation(), wheel.Radius, wheel.Width, Color::Red * 0.4f, 0, false); + const Vector3 basePos = wheel.Collider->GetPosition(); + const Vector3 currentPos = basePos + Vector3(0, data.State.SuspensionOffset, 0); + DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(basePos, wheel.Radius * 0.07f), Color::Blue * 0.3f, 0, false); + DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(currentPos, wheel.Radius * 0.08f), Color::Blue * 0.8f, 0, false); + DEBUG_DRAW_LINE(basePos, currentPos, Color::Blue, 0, false); + DEBUG_DRAW_WIRE_CYLINDER(currentPos, wheel.Collider->GetOrientation(), wheel.Radius, wheel.Width, Color::Red * 0.4f, 0, false); } } diff --git a/Source/Engine/Physics/Actors/WheeledVehicle.h b/Source/Engine/Physics/Actors/WheeledVehicle.h index a6a1708ad..12b5c864c 100644 --- a/Source/Engine/Physics/Actors/WheeledVehicle.h +++ b/Source/Engine/Physics/Actors/WheeledVehicle.h @@ -285,6 +285,12 @@ private: Collider* Collider; Quaternion LocalOrientation; WheelState State; + struct ChildPose + { + Actor* Child; + Vector3 Pose; + }; + Array> ChildrenPoses; }; void* _drive = nullptr; diff --git a/Source/Engine/Physics/Physics.cpp b/Source/Engine/Physics/Physics.cpp index 8066f404d..07b9de909 100644 --- a/Source/Engine/Physics/Physics.cpp +++ b/Source/Engine/Physics/Physics.cpp @@ -847,10 +847,24 @@ void Physics::CollectResults() state.TireFriction = perWheel.tireFriction; state.SteerAngle = RadiansToDegrees * perWheel.steerAngle; state.RotationAngle = -RadiansToDegrees * drive->mWheelsDynData.getWheelRotationAngle(j); - const float suspensionOffsetDelta = perWheel.suspJounce - state.SuspensionOffset; state.SuspensionOffset = perWheel.suspJounce; + // Rotate wheel wheelData.Collider->SetLocalOrientation(Quaternion::Euler(0, state.SteerAngle, state.RotationAngle) * wheelData.LocalOrientation); + + // Apply suspension offset (cannot move collider because it breaks driving so move it's children but preserve the initial pose) + for (auto child : wheelData.Collider->Children) + { + int32 poseIndex = 0; + for (; poseIndex < wheelData.ChildrenPoses.Count(); poseIndex++) + { + if (wheelData.ChildrenPoses[poseIndex].Child == child) + break; + } + if (poseIndex == wheelData.ChildrenPoses.Count()) + wheelData.ChildrenPoses.Add({ child, child->GetLocalPosition() }); + child->SetPosition(wheelData.Collider->GetTransform().LocalToWorld(wheelData.ChildrenPoses[poseIndex].Pose) + Vector3(0, perWheel.suspJounce, 0)); + } } } } From 9e7ce69e9ca0819790802fbd0f7f85addcaec8fe Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 9 Aug 2021 15:24:39 +0200 Subject: [PATCH 12/53] Add `Engine.HasGameViewportFocus` to C# API --- Source/Engine/Engine/Engine.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Source/Engine/Engine/Engine.h b/Source/Engine/Engine/Engine.h index 51860e5a2..403c7d575 100644 --- a/Source/Engine/Engine/Engine.h +++ b/Source/Engine/Engine/Engine.h @@ -156,8 +156,7 @@ public: /// /// Checks whenever the game viewport is focused by the user (eg. can receive input). /// - /// True if game viewport is focused, otherwise false. - static bool HasGameViewportFocus(); + API_PROPERTY() static bool HasGameViewportFocus(); private: From 2b7e6944e80d7762bfc65be4bd1bf9b30cc30799 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 10 Aug 2021 13:29:12 +0200 Subject: [PATCH 13/53] Add timeline view panning with right mouse button --- .../Editor/GUI/Timeline/GUI/BackgroundArea.cs | 95 +++++++++++++++++++ Source/Editor/GUI/Timeline/Timeline.cs | 71 +++++++------- 2 files changed, 132 insertions(+), 34 deletions(-) create mode 100644 Source/Editor/GUI/Timeline/GUI/BackgroundArea.cs diff --git a/Source/Editor/GUI/Timeline/GUI/BackgroundArea.cs b/Source/Editor/GUI/Timeline/GUI/BackgroundArea.cs new file mode 100644 index 000000000..a62fb5023 --- /dev/null +++ b/Source/Editor/GUI/Timeline/GUI/BackgroundArea.cs @@ -0,0 +1,95 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using FlaxEngine; +using FlaxEngine.GUI; + +namespace FlaxEditor.GUI.Timeline.GUI +{ + /// + /// The timeline background area control. + /// + class BackgroundArea : Panel + { + private Timeline _timeline; + internal bool _rightMouseButtonDown; + private Vector2 _rightMouseButtonLastPos; + private float _rightMouseButtonMovement; + + public BackgroundArea(Timeline timeline) + : base(ScrollBars.Both) + { + _timeline = timeline; + } + + /// + public override bool OnMouseDown(Vector2 location, MouseButton button) + { + if (base.OnMouseDown(location, button)) + return true; + + if (button == MouseButton.Right) + { + _rightMouseButtonDown = true; + _rightMouseButtonLastPos = location; + _rightMouseButtonMovement = 0; + Focus(); + StartMouseCapture(); + return true; + } + + return false; + } + + /// + public override void OnMouseMove(Vector2 location) + { + // Panning timeline view with a right-mouse button + if (_rightMouseButtonDown) + { + var movePos = location + ViewOffset; + var delta = _rightMouseButtonLastPos - movePos; + _rightMouseButtonLastPos = movePos; + _rightMouseButtonMovement += delta.Length; + + var hScroll = HScrollBar.Visible && HScrollBar.Enabled; + var vScroll = VScrollBar.Visible && VScrollBar.Enabled; + if (vScroll && hScroll) + Cursor = CursorType.SizeAll; + else if (vScroll) + Cursor = CursorType.SizeNS; + else if (hScroll) + Cursor = CursorType.SizeWE; + + bool wasLocked = IsLayoutLocked; + IsLayoutLocked = true; + if (hScroll) + HScrollBar.TargetValue += delta.X; + if (vScroll) + VScrollBar.TargetValue += delta.Y; + IsLayoutLocked = wasLocked; + PerformLayout(); + return; + } + + base.OnMouseMove(location); + } + + /// + public override bool OnMouseUp(Vector2 location, MouseButton button) + { + if (button == MouseButton.Right && _rightMouseButtonDown) + { + EndMouseCapture(); + _rightMouseButtonDown = false; + Cursor = CursorType.Default; + if (_rightMouseButtonMovement < 1.0f) + { + _timeline.ShowContextMenu(PointToParent(_timeline, location)); + } + return true; + } + + return base.OnMouseUp(location, button); + } + } +} diff --git a/Source/Editor/GUI/Timeline/Timeline.cs b/Source/Editor/GUI/Timeline/Timeline.cs index 3e3b303f4..5f7dbb912 100644 --- a/Source/Editor/GUI/Timeline/Timeline.cs +++ b/Source/Editor/GUI/Timeline/Timeline.cs @@ -288,7 +288,7 @@ namespace FlaxEditor.GUI.Timeline private TimeIntervalsHeader _timeIntervalsHeader; private ContainerControl _backgroundScroll; private Background _background; - private Panel _backgroundArea; + private BackgroundArea _backgroundArea; private TimelineEdge _leftEdge; private TimelineEdge _rightEdge; private Button _addTrackButton; @@ -305,6 +305,7 @@ namespace FlaxEditor.GUI.Timeline private PositionHandle _positionHandle; private bool _isRightMouseButtonDown; private Vector2 _rightMouseButtonDownPos; + private Vector2 _rightMouseButtonMovePos; private float _zoom = 1.0f; private bool _isMovingPositionHandle; private bool _canPlayPauseStop = true; @@ -912,7 +913,7 @@ namespace FlaxEditor.GUI.Timeline Offsets = new Margin(0, 0, 0, HeaderTopAreaHeight), Parent = _splitter.Panel2 }; - _backgroundArea = new Panel(ScrollBars.Both) + _backgroundArea = new BackgroundArea(this) { AutoFocus = false, ClipChildren = false, @@ -1954,6 +1955,38 @@ namespace FlaxEditor.GUI.Timeline } } + internal void ShowContextMenu(Vector2 location) + { + if (!ContainsFocus) + Focus(); + + var controlUnderMouse = GetChildAtRecursive(location); + var mediaUnderMouse = controlUnderMouse; + while (mediaUnderMouse != null && !(mediaUnderMouse is Media)) + { + mediaUnderMouse = mediaUnderMouse.Parent; + } + + var menu = new ContextMenu.ContextMenu(); + if (mediaUnderMouse is Media media) + { + media.OnTimelineShowContextMenu(menu, controlUnderMouse); + if (media.PropertiesEditObject != null) + { + menu.AddButton("Edit media", () => ShowEditPopup(media.PropertiesEditObject, ref location, media.Track)); + } + } + if (PropertiesEditObject != null) + { + menu.AddButton("Edit timeline", () => ShowEditPopup(PropertiesEditObject, ref location, this)); + } + menu.AddSeparator(); + menu.AddButton("Reset zoom", () => Zoom = 1.0f); + menu.AddButton("Show whole timeline", ShowWholeTimeline); + OnShowContextMenu(menu); + menu.Show(this, location); + } + /// protected override void PerformLayoutBeforeChildren() { @@ -1986,8 +2019,8 @@ namespace FlaxEditor.GUI.Timeline { _isRightMouseButtonDown = true; _rightMouseButtonDownPos = location; + _rightMouseButtonMovePos = location; Focus(); - return true; } @@ -2006,38 +2039,8 @@ namespace FlaxEditor.GUI.Timeline if (button == MouseButton.Right && _isRightMouseButtonDown) { _isRightMouseButtonDown = false; - if (Vector2.Distance(ref location, ref _rightMouseButtonDownPos) < 4.0f) - { - if (!ContainsFocus) - Focus(); - - var controlUnderMouse = GetChildAtRecursive(location); - var mediaUnderMouse = controlUnderMouse; - while (mediaUnderMouse != null && !(mediaUnderMouse is Media)) - { - mediaUnderMouse = mediaUnderMouse.Parent; - } - - var menu = new ContextMenu.ContextMenu(); - if (mediaUnderMouse is Media media) - { - media.OnTimelineShowContextMenu(menu, controlUnderMouse); - if (media.PropertiesEditObject != null) - { - menu.AddButton("Edit media", () => ShowEditPopup(media.PropertiesEditObject, ref location, media.Track)); - } - } - if (PropertiesEditObject != null) - { - menu.AddButton("Edit timeline", () => ShowEditPopup(PropertiesEditObject, ref location, this)); - } - menu.AddSeparator(); - menu.AddButton("Reset zoom", () => Zoom = 1.0f); - menu.AddButton("Show whole timeline", ShowWholeTimeline); - OnShowContextMenu(menu); - menu.Show(this, location); - } + ShowContextMenu(location); } return base.OnMouseUp(location, button); From 99b0cf71a82e7fe7af55e766981c8fb2eb9e8d96 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 10 Aug 2021 13:29:42 +0200 Subject: [PATCH 14/53] Add better timeline UI positioning for improved viewport navigation --- Source/Editor/GUI/Timeline/GUI/PositionHandle.cs | 5 +++-- Source/Editor/GUI/Timeline/GUI/TimelineEdge.cs | 3 ++- Source/Editor/GUI/Timeline/Timeline.cs | 16 ++++++++-------- Source/Engine/UI/GUI/Panels/ScrollBar.cs | 1 + 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/Source/Editor/GUI/Timeline/GUI/PositionHandle.cs b/Source/Editor/GUI/Timeline/GUI/PositionHandle.cs index 0e9cddc46..6cc2f12c4 100644 --- a/Source/Editor/GUI/Timeline/GUI/PositionHandle.cs +++ b/Source/Editor/GUI/Timeline/GUI/PositionHandle.cs @@ -27,7 +27,8 @@ namespace FlaxEditor.GUI.Timeline.GUI { var style = Style.Current; var icon = Editor.Instance.Icons.VisjectArrowClosed32; - var timeAxisHeaderOffset = -_timeline.MediaBackground.ViewOffset.Y; + var timeAxisOverlap = Timeline.HeaderTopAreaHeight * 0.5f; + var timeAxisHeaderOffset = -_timeline.MediaBackground.ViewOffset.Y - timeAxisOverlap; Matrix3x3.RotationZ(Mathf.PiOverTwo, out var m1); var m2 = Matrix3x3.Translation2D(0, timeAxisHeaderOffset); @@ -36,7 +37,7 @@ namespace FlaxEditor.GUI.Timeline.GUI Render2D.DrawSprite(icon, new Rectangle(new Vector2(4, -Width), Size), _timeline.IsMovingPositionHandle ? style.ProgressNormal : style.Foreground); Render2D.PopTransform(); - Render2D.FillRectangle(new Rectangle(Width * 0.5f, Height + timeAxisHeaderOffset, 1, _timeline.MediaPanel.Height - timeAxisHeaderOffset), _timeline.IsMovingPositionHandle ? style.ProgressNormal : style.Foreground.RGBMultiplied(0.8f)); + Render2D.FillRectangle(new Rectangle(Width * 0.5f, Height + timeAxisHeaderOffset, 1, _timeline.MediaPanel.Height - timeAxisHeaderOffset - timeAxisOverlap), _timeline.IsMovingPositionHandle ? style.ProgressNormal : style.Foreground.RGBMultiplied(0.8f)); base.Draw(); } diff --git a/Source/Editor/GUI/Timeline/GUI/TimelineEdge.cs b/Source/Editor/GUI/Timeline/GUI/TimelineEdge.cs index 0a7699cc5..83cb9760e 100644 --- a/Source/Editor/GUI/Timeline/GUI/TimelineEdge.cs +++ b/Source/Editor/GUI/Timeline/GUI/TimelineEdge.cs @@ -37,7 +37,8 @@ namespace FlaxEditor.GUI.Timeline.GUI public override void Draw() { var style = Style.Current; - var timeAxisHeaderOffset = -_timeline.MediaBackground.ViewOffset.Y; + var timeAxisOverlap = Timeline.HeaderTopAreaHeight * 0.5f; + var timeAxisHeaderOffset = -_timeline.MediaBackground.ViewOffset.Y - timeAxisOverlap; var moveColor = style.ProgressNormal; var thickness = 2.0f; diff --git a/Source/Editor/GUI/Timeline/Timeline.cs b/Source/Editor/GUI/Timeline/Timeline.cs index 5f7dbb912..6f7564f7a 100644 --- a/Source/Editor/GUI/Timeline/Timeline.cs +++ b/Source/Editor/GUI/Timeline/Timeline.cs @@ -959,12 +959,12 @@ namespace FlaxEditor.GUI.Timeline private void UpdatePositionHandle() { var handleWidth = 12.0f; - _positionHandle.Bounds = new Rectangle( - StartOffset * 2.0f - handleWidth * 0.5f + _currentFrame / _framesPerSecond * UnitsPerSecond * Zoom, - HeaderTopAreaHeight * -0.5f, - handleWidth, - HeaderTopAreaHeight * 0.5f - ); + var bounds = new Rectangle(); + bounds.Location.X = StartOffset * 2.0f - handleWidth * 0.5f + _currentFrame / _framesPerSecond * UnitsPerSecond * Zoom; + bounds.Location.Y = 0; + bounds.Size.X = handleWidth; + bounds.Size.Y = HeaderTopAreaHeight * 0.5f; + _positionHandle.Bounds = bounds; } private void OnFpsPopupShowing(ComboBox comboBox) @@ -1936,8 +1936,8 @@ namespace FlaxEditor.GUI.Timeline _background.Bounds = new Rectangle(StartOffset, 0, Duration * UnitsPerSecond * Zoom, height); var edgeWidth = 6.0f; - _leftEdge.Bounds = new Rectangle(_background.Left - edgeWidth * 0.5f + StartOffset, HeaderTopAreaHeight * -0.5f, edgeWidth, height + HeaderTopAreaHeight * 0.5f); - _rightEdge.Bounds = new Rectangle(_background.Right - edgeWidth * 0.5f + StartOffset, HeaderTopAreaHeight * -0.5f, edgeWidth, height + HeaderTopAreaHeight * 0.5f); + _leftEdge.Bounds = new Rectangle(_background.Left - edgeWidth * 0.5f + StartOffset, 0, edgeWidth, height); + _rightEdge.Bounds = new Rectangle(_background.Right - edgeWidth * 0.5f + StartOffset, 0, edgeWidth, height); _backgroundScroll.Bounds = new Rectangle(0, 0, _background.Width + 5 * StartOffset, height); } diff --git a/Source/Engine/UI/GUI/Panels/ScrollBar.cs b/Source/Engine/UI/GUI/Panels/ScrollBar.cs index bea692e19..0bae50591 100644 --- a/Source/Engine/UI/GUI/Panels/ScrollBar.cs +++ b/Source/Engine/UI/GUI/Panels/ScrollBar.cs @@ -140,6 +140,7 @@ namespace FlaxEngine.GUI { _targetValue = value; _value = value; + SetUpdate(ref _update, null); OnValueChanged(); } } From e2881044722b8f9351eb6c4882f0e526ea4602a0 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 10 Aug 2021 13:54:22 +0200 Subject: [PATCH 15/53] Add helper tooltips for Scene Animation actions UI --- .../CustomEditors/Editors/FlaxObjectRefEditor.cs | 9 +-------- .../Editor/CustomEditors/Editors/TypeEditor.cs | 9 +-------- Source/Editor/GUI/Timeline/Tracks/ActorTrack.cs | 2 +- Source/Editor/GUI/Timeline/Tracks/ObjectTrack.cs | 8 +++++--- Source/Editor/Surface/Archetypes/Packing.cs | 5 +---- Source/Editor/Surface/SurfaceUtils.cs | 16 ++++++++++++++++ Source/Editor/Utilities/Utils.cs | 12 ++++++++++++ Source/Editor/Windows/ToolboxWindow.cs | 9 +-------- 8 files changed, 38 insertions(+), 32 deletions(-) diff --git a/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs b/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs index 7412ca4dc..bad441d20 100644 --- a/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs +++ b/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs @@ -96,14 +96,7 @@ namespace FlaxEditor.CustomEditors.Editors // Update tooltip if (_value is SceneObject sceneObject) { - var str = sceneObject is Actor actor ? actor.Name : type.Name; - var o = sceneObject.Parent; - while (o) - { - str = o.Name + " -> " + str; - o = o.Parent; - } - TooltipText = str; + TooltipText = Utilities.Utils.GetTooltip(sceneObject); } else { diff --git a/Source/Editor/CustomEditors/Editors/TypeEditor.cs b/Source/Editor/CustomEditors/Editors/TypeEditor.cs index 95b9d0f9d..8d32d6c3e 100644 --- a/Source/Editor/CustomEditors/Editors/TypeEditor.cs +++ b/Source/Editor/CustomEditors/Editors/TypeEditor.cs @@ -76,14 +76,7 @@ namespace FlaxEditor.CustomEditors.Editors if (_value) { _valueName = _value.Name; - TooltipText = _value.TypeName; - - var attributes = _value.GetAttributes(false); - var tooltipAttribute = (TooltipAttribute)attributes.FirstOrDefault(x => x is TooltipAttribute); - if (tooltipAttribute != null) - { - TooltipText += "\n" + tooltipAttribute.Text; - } + TooltipText = Surface.SurfaceUtils.GetVisualScriptTypeDescription(_value); } else { diff --git a/Source/Editor/GUI/Timeline/Tracks/ActorTrack.cs b/Source/Editor/GUI/Timeline/Tracks/ActorTrack.cs index 22dae35e0..8a4e4b4d2 100644 --- a/Source/Editor/GUI/Timeline/Tracks/ActorTrack.cs +++ b/Source/Editor/GUI/Timeline/Tracks/ActorTrack.cs @@ -103,7 +103,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks { if (selection.Count == 1 && selection[0] is ActorNode actorNode && actorNode.Actor && IsActorValid(actorNode.Actor)) { - menu.AddButton("Select " + actorNode.Actor, OnClickedSelectActor); + menu.AddButton("Select " + actorNode.Actor, OnClickedSelectActor).TooltipText = Utilities.Utils.GetTooltip(actorNode.Actor); } else { diff --git a/Source/Editor/GUI/Timeline/Tracks/ObjectTrack.cs b/Source/Editor/GUI/Timeline/Tracks/ObjectTrack.cs index 2ed5070c5..e8ef8794b 100644 --- a/Source/Editor/GUI/Timeline/Tracks/ObjectTrack.cs +++ b/Source/Editor/GUI/Timeline/Tracks/ObjectTrack.cs @@ -7,6 +7,7 @@ using System.Reflection; using System.Text; using FlaxEditor.CustomEditors; using FlaxEditor.GUI.ContextMenu; +using FlaxEditor.Scripting; using FlaxEngine; using FlaxEngine.GUI; @@ -197,8 +198,8 @@ namespace FlaxEditor.GUI.Timeline.Tracks AddMemberTag tag; tag.Member = m; tag.Archetype = EventTrack.GetArchetype(); - - menu.AddButton(sb.ToString(), OnAddMemberTrack).Tag = tag; + var tooltip = Surface.SurfaceUtils.GetVisualScriptMemberInfoDescription(new ScriptMemberInfo(m)); + menu.AddButton(sb.ToString(), OnAddMemberTrack).LinkTooltip(tooltip).Tag = tag; count++; } @@ -295,7 +296,8 @@ namespace FlaxEditor.GUI.Timeline.Tracks AddMemberTag tag; tag.Member = m; tag.Archetype = archetype; - menu.AddButton(name + " " + m.Name, OnAddMemberTrack).Tag = tag; + var tooltip = Surface.SurfaceUtils.GetVisualScriptMemberInfoDescription(new ScriptMemberInfo(m)); + menu.AddButton(name + " " + m.Name, OnAddMemberTrack).LinkTooltip(tooltip).Tag = tag; count++; } diff --git a/Source/Editor/Surface/Archetypes/Packing.cs b/Source/Editor/Surface/Archetypes/Packing.cs index 83d321577..c3866b101 100644 --- a/Source/Editor/Surface/Archetypes/Packing.cs +++ b/Source/Editor/Surface/Archetypes/Packing.cs @@ -122,10 +122,7 @@ namespace FlaxEditor.Surface.Archetypes { GetBox(0).CurrentType = type; Title = (_isUnpacking ? "Unpack " : "Pack ") + type.Name; - var attributes = type.GetAttributes(false); - var tooltipAttribute = (TooltipAttribute)attributes.FirstOrDefault(x => x is TooltipAttribute); - if (tooltipAttribute != null) - TooltipText += "\n" + tooltipAttribute.Text; + TooltipText = SurfaceUtils.GetVisualScriptTypeDescription(type); } else { diff --git a/Source/Editor/Surface/SurfaceUtils.cs b/Source/Editor/Surface/SurfaceUtils.cs index 74f1d6130..c86a5ae14 100644 --- a/Source/Editor/Surface/SurfaceUtils.cs +++ b/Source/Editor/Surface/SurfaceUtils.cs @@ -427,6 +427,22 @@ namespace FlaxEditor.Surface return !TypeUtils.IsDelegate(managedType); } + internal static string GetVisualScriptTypeDescription(ScriptType type) + { + var sb = new StringBuilder(); + if (type.IsStatic) + sb.Append("static "); + else if (type.IsAbstract) + sb.Append("abstract "); + sb.Append(type.TypeName); + + var attributes = type.GetAttributes(false); + var tooltipAttribute = (TooltipAttribute)attributes.FirstOrDefault(x => x is TooltipAttribute); + if (tooltipAttribute != null) + sb.Append("\n").Append(tooltipAttribute.Text); + return sb.ToString(); + } + internal static string GetVisualScriptMemberInfoDescription(ScriptMemberInfo member) { var name = member.Name; diff --git a/Source/Editor/Utilities/Utils.cs b/Source/Editor/Utilities/Utils.cs index 721b5a0bc..5364cf3b8 100644 --- a/Source/Editor/Utilities/Utils.cs +++ b/Source/Editor/Utilities/Utils.cs @@ -76,6 +76,18 @@ namespace FlaxEditor.Utilities return string.Format("{0:0.##} {1}", bytes, MemorySizePostfixes[order]); } + internal static string GetTooltip(SceneObject obj) + { + var str = obj is Actor actor ? actor.Name : TypeUtils.GetObjectType(obj).Name; + var o = obj.Parent; + while (o) + { + str = o.Name + " -> " + str; + o = o.Parent; + } + return str; + } + /// /// The colors for the keyframes used by the curve editor. /// diff --git a/Source/Editor/Windows/ToolboxWindow.cs b/Source/Editor/Windows/ToolboxWindow.cs index 33827ea93..15d4a0948 100644 --- a/Source/Editor/Windows/ToolboxWindow.cs +++ b/Source/Editor/Windows/ToolboxWindow.cs @@ -204,14 +204,7 @@ namespace FlaxEditor.Windows continue; var item = _groupSearch.AddChild(CreateActorItem(CustomEditors.CustomEditorsUtil.GetPropertyNameUI(text), actorType)); - item.TooltipText = actorType.TypeName; - var attributes = actorType.GetAttributes(false); - var tooltipAttribute = (TooltipAttribute)attributes.FirstOrDefault(x => x is TooltipAttribute); - if (tooltipAttribute != null) - { - item.TooltipText += '\n'; - item.TooltipText += tooltipAttribute.Text; - } + item.TooltipText = Surface.SurfaceUtils.GetVisualScriptTypeDescription(actorType); var highlights = new List(ranges.Length); var style = Style.Current; From 670e48ec1c345686204771396017a29ee8333b71 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 10 Aug 2021 16:32:00 +0200 Subject: [PATCH 16/53] Add option to Scene context menu to unload it --- Source/Editor/SceneGraph/Actors/SceneNode.cs | 29 ++++++++++++++----- .../Windows/SceneTreeWindow.ContextMenu.cs | 11 ++++--- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/Source/Editor/SceneGraph/Actors/SceneNode.cs b/Source/Editor/SceneGraph/Actors/SceneNode.cs index 35c71bf02..2846715fc 100644 --- a/Source/Editor/SceneGraph/Actors/SceneNode.cs +++ b/Source/Editor/SceneGraph/Actors/SceneNode.cs @@ -1,5 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. +using System.Net; +using FlaxEditor.GUI.ContextMenu; using FlaxEditor.SceneGraph.GUI; using FlaxEngine; @@ -17,9 +19,6 @@ namespace FlaxEditor.SceneGraph.Actors /// /// Gets or sets a value indicating whether this scene is edited. /// - /// - /// true if this scene is edited; otherwise, false. - /// public bool IsEdited { get => _isEdited; @@ -28,7 +27,6 @@ namespace FlaxEditor.SceneGraph.Actors if (_isEdited != value) { _isEdited = value; - _treeNode.UpdateText(); } } @@ -37,9 +35,6 @@ namespace FlaxEditor.SceneGraph.Actors /// /// Gets the scene. /// - /// - /// The scene. - /// public Scene Scene => _actor as Scene; /// @@ -68,5 +63,25 @@ namespace FlaxEditor.SceneGraph.Actors /// public override SceneNode ParentScene => this; + + /// + public override void OnContextMenu(ContextMenu contextMenu) + { + contextMenu.AddSeparator(); + contextMenu.AddButton("Save scene", OnSave).LinkTooltip("Saves this scene.").Enabled = IsEdited && !Editor.IsPlayMode; + contextMenu.AddButton("Unload scene", OnUnload).LinkTooltip("Unloads this scene.").Enabled = Editor.Instance.StateMachine.CurrentState.CanChangeScene; + + base.OnContextMenu(contextMenu); + } + + private void OnSave() + { + Editor.Instance.Scene.SaveScene(this); + } + + private void OnUnload() + { + Editor.Instance.Scene.CloseScene(Scene); + } } } diff --git a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs index 50187ed3f..fe88a13a1 100644 --- a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs +++ b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs @@ -104,12 +104,11 @@ namespace FlaxEditor.Windows Editor.Windows.ContentWin.CurrentViewFolder.CanHaveAssets; bool hasPrefabLink = canEditScene && isSingleActorSelected && (Editor.SceneEditing.Selection[0] as ActorNode).HasPrefabLink; - - b = contextMenu.AddButton("Select Prefab", Editor.Prefabs.SelectPrefab); - b.Enabled = hasPrefabLink; - - b = contextMenu.AddButton("Break Prefab Link", Editor.Prefabs.BreakLinks); - b.Enabled = hasPrefabLink; + if (hasPrefabLink) + { + contextMenu.AddButton("Select Prefab", Editor.Prefabs.SelectPrefab); + contextMenu.AddButton("Break Prefab Link", Editor.Prefabs.BreakLinks); + } // Spawning actors options From 14068307d1760c144a845f251eb68047a91fee3b Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 10 Aug 2021 16:32:24 +0200 Subject: [PATCH 17/53] Add support for changing scenes via Editor during play mode --- Source/Editor/Modules/SceneModule.cs | 23 +++++++++++++++++++++++ Source/Editor/States/PlayingState.cs | 3 +++ 2 files changed, 26 insertions(+) diff --git a/Source/Editor/Modules/SceneModule.cs b/Source/Editor/Modules/SceneModule.cs index 33d41ddd6..3e6662f6c 100644 --- a/Source/Editor/Modules/SceneModule.cs +++ b/Source/Editor/Modules/SceneModule.cs @@ -251,6 +251,15 @@ namespace FlaxEditor.Modules if (!Editor.StateMachine.CurrentState.CanChangeScene) return; + // In play-mode Editor mocks the level streaming script + if (Editor.IsPlayMode) + { + if (!additive) + Level.UnloadAllScenesAsync(); + Level.LoadSceneAsync(sceneId); + return; + } + if (!additive) { // Ensure to save all pending changes @@ -272,6 +281,13 @@ namespace FlaxEditor.Modules if (!Editor.StateMachine.CurrentState.CanChangeScene) return; + // In play-mode Editor mocks the level streaming script + if (Editor.IsPlayMode) + { + Level.UnloadSceneAsync(scene); + return; + } + // Ensure to save all pending changes if (CheckSaveBeforeClose()) return; @@ -289,6 +305,13 @@ namespace FlaxEditor.Modules if (!Editor.StateMachine.CurrentState.CanChangeScene) return; + // In play-mode Editor mocks the level streaming script + if (Editor.IsPlayMode) + { + Level.UnloadAllScenesAsync(); + return; + } + // Ensure to save all pending changes if (CheckSaveBeforeClose()) return; diff --git a/Source/Editor/States/PlayingState.cs b/Source/Editor/States/PlayingState.cs index 171022745..6258975ae 100644 --- a/Source/Editor/States/PlayingState.cs +++ b/Source/Editor/States/PlayingState.cs @@ -28,6 +28,9 @@ namespace FlaxEditor.States /// public override bool CanEditScene => true; + /// + public override bool CanChangeScene => true; + /// public override bool CanUseUndoRedo => false; From 9c1fa7c0ce96f553e3c260f4e9c050eef3e4eee0 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 10 Aug 2021 17:27:44 +0200 Subject: [PATCH 18/53] Add Zoom value box to timeline view context menu --- Source/Editor/GUI/Timeline/Timeline.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Source/Editor/GUI/Timeline/Timeline.cs b/Source/Editor/GUI/Timeline/Timeline.cs index 6f7564f7a..258dbbe81 100644 --- a/Source/Editor/GUI/Timeline/Timeline.cs +++ b/Source/Editor/GUI/Timeline/Timeline.cs @@ -580,7 +580,7 @@ namespace FlaxEditor.GUI.Timeline get => _zoom; set { - value = Mathf.Clamp(value, 0.0001f, 1000.0f); + value = Mathf.Clamp(value, 0.00001f, 1000.0f); if (Mathf.NearEqual(_zoom, value)) return; @@ -1040,6 +1040,13 @@ namespace FlaxEditor.GUI.Timeline menu.AddButton("Show preview values", () => ShowPreviewValues = !ShowPreviewValues).Checked = ShowPreviewValues; + { + var zoom = menu.AddButton("Zoom"); + var zoomValue = new FloatValueBox(Zoom, 140, 2, 50.0f, 0.00001f, 1000.0f, 0.001f); + zoomValue.Parent = zoom; + zoomValue.ValueChanged += () => Zoom = zoomValue.Value; + } + OnShowViewContextMenu(menu); menu.Show(_viewButton.Parent, _viewButton.BottomLeft); From 1943c64a336d6bc0574afb0db020a1595308b8f4 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 10 Aug 2021 17:35:27 +0200 Subject: [PATCH 19/53] Fix timeline background stops drawing to be ore readable #519 --- Source/Editor/GUI/Timeline/GUI/Background.cs | 40 ++++++++------------ 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/Source/Editor/GUI/Timeline/GUI/Background.cs b/Source/Editor/GUI/Timeline/GUI/Background.cs index 6734b3ec9..8269fc29e 100644 --- a/Source/Editor/GUI/Timeline/GUI/Background.cs +++ b/Source/Editor/GUI/Timeline/GUI/Background.cs @@ -74,23 +74,21 @@ namespace FlaxEditor.GUI.Timeline.GUI } // Setup time axis ticks - int minDistanceBetweenTicks = 4000; - int maxDistanceBetweenTicks = 6000; + var minDistanceBetweenTicks = 50.0f; + var maxDistanceBetweenTicks = 100.0f; var zoom = Timeline.UnitsPerSecond * _timeline.Zoom; var left = Vector2.Min(leftSideMin, rightSideMax).X; var right = Vector2.Max(leftSideMin, rightSideMax).X; - var pixelRange = (right - left) * zoom; var leftFrame = Mathf.Floor((left - Timeline.StartOffset) / zoom) * _timeline.FramesPerSecond; var rightFrame = Mathf.Ceil((right - Timeline.StartOffset) / zoom) * _timeline.FramesPerSecond; var min = leftFrame; var max = rightFrame; - var range = max - min; int smallestTick = 0; int biggestTick = _tickSteps.Length - 1; for (int i = _tickSteps.Length - 1; i >= 0; i--) { // Calculate how far apart these modulo tick steps are spaced - float tickSpacing = _tickSteps[i] * pixelRange / range; + float tickSpacing = _tickSteps[i] * _timeline.Zoom; // Calculate the strength of the tick markers based on the spacing _tickStrengths[i] = Mathf.Saturate((tickSpacing - minDistanceBetweenTicks) / (maxDistanceBetweenTicks - minDistanceBetweenTicks)); @@ -117,14 +115,16 @@ namespace FlaxEditor.GUI.Timeline.GUI // Draw all ticks int l = Mathf.Clamp(smallestTick + level, 0, _tickSteps.Length - 1); - int startTick = Mathf.FloorToInt(min / _tickSteps[l]); - int endTick = Mathf.CeilToInt(max / _tickSteps[l]); + var lStep = _tickSteps[l]; + var lNextStep = _tickSteps[l + 1]; + int startTick = Mathf.FloorToInt(min / lStep); + int endTick = Mathf.CeilToInt(max / lStep); Color lineColor = style.ForegroundDisabled.RGBMultiplied(0.7f).AlphaMultiplied(strength); for (int i = startTick; i <= endTick; i++) { - if (l < biggestTick && (i % Mathf.RoundToInt(_tickSteps[l + 1] / _tickSteps[l]) == 0)) + if (l < biggestTick && (i % Mathf.RoundToInt(lNextStep / lStep) == 0)) continue; - var tick = i * _tickSteps[l]; + var tick = i * lStep; var time = tick / _timeline.FramesPerSecond; var x = time * zoom + Timeline.StartOffset; @@ -163,15 +163,17 @@ namespace FlaxEditor.GUI.Timeline.GUI // Draw all ticks int l = Mathf.Clamp(smallestTick + level, 0, _tickSteps.Length - 1); - int startTick = Mathf.FloorToInt(min / _tickSteps[l]); - int endTick = Mathf.CeilToInt(max / _tickSteps[l]); + var lStep = _tickSteps[l]; + var lNextStep = _tickSteps[l + 1]; + int startTick = Mathf.FloorToInt(min / lStep); + int endTick = Mathf.CeilToInt(max / lStep); Color lineColor = style.Foreground.RGBMultiplied(0.8f).AlphaMultiplied(strength); Color labelColor = style.ForegroundDisabled.AlphaMultiplied(strength); for (int i = startTick; i <= endTick; i++) { - if (l < biggestTick && (i % Mathf.RoundToInt(_tickSteps[l + 1] / _tickSteps[l]) == 0)) + if (l < biggestTick && (i % Mathf.RoundToInt(lNextStep / lStep) == 0)) continue; - var tick = i * _tickSteps[l]; + var tick = i * lStep; var time = tick / _timeline.FramesPerSecond; var x = time * zoom + Timeline.StartOffset; @@ -195,17 +197,7 @@ namespace FlaxEditor.GUI.Timeline.GUI default: throw new ArgumentOutOfRangeException(); } var labelRect = new Rectangle(x + 2, -verticalLinesHeaderExtend + timeAxisHeaderOffset, 50, verticalLinesHeaderExtend); - Render2D.DrawText( - style.FontSmall, - labelText, - labelRect, - labelColor, - TextAlignment.Near, - TextAlignment.Center, - TextWrapping.NoWrap, - 1.0f, - 0.8f - ); + Render2D.DrawText(style.FontSmall, labelText, labelRect, labelColor, TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, 0.8f); } } } From d85c65bfc5ce703f2a0c8415b8c2a7cbc073db56 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 11 Aug 2021 19:28:53 +0200 Subject: [PATCH 20/53] Add check to prevent double-free from native event in C# --- .../Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index 0f0a5c726..05fdff485 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -665,7 +665,11 @@ namespace Flax.Build.Bindings indent += " "; var eventInstance = eventInfo.IsStatic ? string.Empty : "__unmanagedPtr, "; contents.Append(indent).Append($"add {{ Internal_{eventInfo.Name} += value; if (Internal_{eventInfo.Name}_Count++ == 0) Internal_{eventInfo.Name}_Bind({eventInstance}true); }}").AppendLine(); - contents.Append(indent).Append($"remove {{ Internal_{eventInfo.Name} -= value; if (--Internal_{eventInfo.Name}_Count == 0) Internal_{eventInfo.Name}_Bind({eventInstance}false); }}").AppendLine(); + contents.Append(indent).Append("remove { ").AppendLine(); + contents.Append("#if FLAX_EDITOR || BUILD_DEBUG").AppendLine(); + contents.Append(indent).Append($"if (Internal_{eventInfo.Name} != null) {{ bool invalid = true; foreach (Delegate e in Internal_{eventInfo.Name}.GetInvocationList()) {{ if (e == (Delegate)value) {{ invalid = false; break; }} }} if (invalid) throw new Exception(\"Cannot unregister from event if not registered before.\"); }}").AppendLine(); + contents.Append("#endif").AppendLine(); + contents.Append(indent).Append($"Internal_{eventInfo.Name} -= value; if (--Internal_{eventInfo.Name}_Count == 0) Internal_{eventInfo.Name}_Bind({eventInstance}false); }}").AppendLine(); indent = indent.Substring(0, indent.Length - 4); contents.Append(indent).Append('}').AppendLine(); From 26d37103ba3413de8cad3c71c1ff4050929ca9f1 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 11 Aug 2021 14:58:56 +0200 Subject: [PATCH 21/53] Fix some problems with LocalizedString serialization in C# --- Source/Engine/Serialization/JsonConverters.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Serialization/JsonConverters.cs b/Source/Engine/Serialization/JsonConverters.cs index 3130b597f..3da6342e2 100644 --- a/Source/Engine/Serialization/JsonConverters.cs +++ b/Source/Engine/Serialization/JsonConverters.cs @@ -244,16 +244,21 @@ namespace FlaxEngine.Json /// public override void WriteJson(JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer) { +#if FLAX_EDITOR + bool writeTypename = (serializer.TypeNameHandling & TypeNameHandling.Objects) == TypeNameHandling.Objects; +#else + bool writeTypename = false; +#endif var str = (LocalizedString)value; - if (string.IsNullOrEmpty(str.Id)) + if (string.IsNullOrEmpty(str.Id) && !writeTypename) { - writer.WriteValue(str.Value); + writer.WriteValue(str.Value ?? string.Empty); } else { writer.WriteStartObject(); #if FLAX_EDITOR - if ((serializer.TypeNameHandling & TypeNameHandling.Objects) == TypeNameHandling.Objects) + if (writeTypename) { writer.WritePropertyName("$type"); writer.WriteValue("FlaxEngine.LocalizedString, FlaxEngine.CSharp"); From f6eef82864f3ad984c7538779d4bf2be9ecad733 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 12 Aug 2021 11:48:08 +0200 Subject: [PATCH 22/53] Fix LocalizedString undo bug --- Source/Engine/Serialization/JsonConverters.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Source/Engine/Serialization/JsonConverters.cs b/Source/Engine/Serialization/JsonConverters.cs index 3da6342e2..b0c43f9a5 100644 --- a/Source/Engine/Serialization/JsonConverters.cs +++ b/Source/Engine/Serialization/JsonConverters.cs @@ -212,6 +212,7 @@ namespace FlaxEngine.Json break; } case JsonToken.Comment: break; + case JsonToken.String: break; default: return value; } } @@ -302,10 +303,15 @@ namespace FlaxEngine.Json break; } case JsonToken.Comment: break; + case JsonToken.String: break; default: return str; } } } + else + return null; + if (existingValue == null && str.Id == null && str.Value == null) + return null; return str; } From 718ae78036a95021f8008fd405dedbb00cfa854c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 12 Aug 2021 11:47:38 +0200 Subject: [PATCH 23/53] Fix UI with list of null items when layout gets rebuilt at item level --- Source/Editor/CustomEditors/CustomEditor.cs | 16 +++++++--------- .../CustomEditors/Editors/CollectionEditor.cs | 12 ++++++++++-- .../CustomEditors/Editors/DictionaryEditor.cs | 9 ++++++++- .../CustomEditors/Editors/GenericEditor.cs | 19 ++++++++++++------- .../CustomEditors/LayoutElementsContainer.cs | 7 +++++++ .../CustomEditors/Values/ValueContainer.cs | 16 ++++++++++++++++ 6 files changed, 60 insertions(+), 19 deletions(-) diff --git a/Source/Editor/CustomEditors/CustomEditor.cs b/Source/Editor/CustomEditors/CustomEditor.cs index 07cc1c08d..e681127fd 100644 --- a/Source/Editor/CustomEditors/CustomEditor.cs +++ b/Source/Editor/CustomEditors/CustomEditor.cs @@ -101,11 +101,6 @@ namespace FlaxEditor.CustomEditors /// protected bool IsSetBlocked => _isSetBlocked; - /// - /// Gets a value indicating whether this editor needs value propagation up (value synchronization when one of the child editors changes value, used by the struct types). - /// - protected virtual bool NeedsValuePropagationUp => Values.HasValueType; - /// /// The linked label used to show this custom editor. Can be null if not used (eg. editor is inlined or is using a very customized UI layout). /// @@ -281,7 +276,7 @@ namespace FlaxEditor.CustomEditors // Propagate values up (eg. when member of structure gets modified, also structure should be updated as a part of the other object) var obj = _parent; - while (obj._parent != null && !(obj._parent is SyncPointEditor)) // && obj.NeedsValuePropagationUp) + while (obj._parent != null && !(obj._parent is SyncPointEditor)) { obj.Values.Set(obj._parent.Values, obj.Values); obj = obj._parent; @@ -301,12 +296,15 @@ namespace FlaxEditor.CustomEditors _isSetBlocked = false; // Update children + if (_skipChildrenRefresh) + { + _skipChildrenRefresh = false; + return; + } try { - var childrenCount = _skipChildrenRefresh ? 0 : _children.Count; - for (int i = 0; i < childrenCount; i++) + for (int i = 0; i < _children.Count; i++) _children[i].RefreshInternal(); - _skipChildrenRefresh = false; } catch (TargetException ex) { diff --git a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs index 6c3dda04e..9f660c328 100644 --- a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs +++ b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs @@ -154,6 +154,10 @@ namespace FlaxEditor.CustomEditors.Editors var panel = layout.VerticalPanel(); panel.Panel.BackgroundColor = _background; var elementType = ElementType; + + // Use separate layout cells for each collection items to improve layout updates for them in separation + var useSharedLayout = elementType.IsPrimitive || elementType.IsEnum; + if (_canReorderItems) { for (int i = 0; i < size; i++) @@ -178,7 +182,9 @@ namespace FlaxEditor.CustomEditors.Editors } var overrideEditor = overrideEditorType != null ? (CustomEditor)Activator.CreateInstance(overrideEditorType) : null; - panel.Object(new CollectionItemLabel(this, i), new ListValueContainer(elementType, i, Values), overrideEditor); + var property = panel.AddPropertyItem(new CollectionItemLabel(this, i)); + var itemLayout = useSharedLayout ? (LayoutElementsContainer)property : property.VerticalPanel(); + itemLayout.Object(new ListValueContainer(elementType, i, Values), overrideEditor); } } else @@ -194,7 +200,9 @@ namespace FlaxEditor.CustomEditors.Editors } var overrideEditor = overrideEditorType != null ? (CustomEditor)Activator.CreateInstance(overrideEditorType) : null; - panel.Object("Element " + i, new ListValueContainer(elementType, i, Values), overrideEditor); + var property = panel.AddPropertyItem("Element " + i); + var itemLayout = useSharedLayout ? (LayoutElementsContainer)property : property.VerticalPanel(); + itemLayout.Object(new ListValueContainer(elementType, i, Values), overrideEditor); } } } diff --git a/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs b/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs index 51309a2fa..53e539234 100644 --- a/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs +++ b/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs @@ -216,6 +216,11 @@ namespace FlaxEditor.CustomEditors.Editors panel.Panel.BackgroundColor = _background; var keysEnumerable = ((IDictionary)Values[0]).Keys.OfType(); var keys = keysEnumerable as object[] ?? keysEnumerable.ToArray(); + var valuesType = new ScriptType(valueType); + + // Use separate layout cells for each collection items to improve layout updates for them in separation + var useSharedLayout = valueType.IsPrimitive || valueType.IsEnum; + for (int i = 0; i < size; i++) { if (i != 0 && spacing > 0f) @@ -239,7 +244,9 @@ namespace FlaxEditor.CustomEditors.Editors var key = keys.ElementAt(i); var overrideEditor = overrideEditorType != null ? (CustomEditor)Activator.CreateInstance(overrideEditorType) : null; - panel.Object(new DictionaryItemLabel(this, key), new DictionaryValueContainer(new ScriptType(valueType), key, Values), overrideEditor); + var property = panel.AddPropertyItem(new DictionaryItemLabel(this, key)); + var itemLayout = useSharedLayout ? (LayoutElementsContainer)property : property.VerticalPanel(); + itemLayout.Object(new DictionaryValueContainer(valuesType, key, Values), overrideEditor); } } _elementsCount = size; diff --git a/Source/Editor/CustomEditors/Editors/GenericEditor.cs b/Source/Editor/CustomEditors/Editors/GenericEditor.cs index fc1b3b047..8510fa9ff 100644 --- a/Source/Editor/CustomEditors/Editors/GenericEditor.cs +++ b/Source/Editor/CustomEditors/Editors/GenericEditor.cs @@ -228,6 +228,7 @@ namespace FlaxEditor.CustomEditors.Editors } private VisibleIfCache[] _visibleIfCaches; + private bool _isNull; /// /// Gets the items for the type @@ -264,7 +265,7 @@ namespace FlaxEditor.CustomEditors.Editors // Skip properties without getter or setter if (!p.HasGet || (!p.HasSet && !showInEditor)) continue; - + // Skip hidden fields, handle special attributes if ((!p.IsPublic && !showInEditor) || attributes.Any(x => x is HideInEditorAttribute)) continue; @@ -421,7 +422,9 @@ namespace FlaxEditor.CustomEditors.Editors /// public override void Initialize(LayoutElementsContainer layout) { + var values = Values; _visibleIfCaches = null; + _isNull = values != null && values.IsNull; // Collect items to edit List items; @@ -446,12 +449,7 @@ namespace FlaxEditor.CustomEditors.Editors Parent = layout.ContainerControl, Location = new Vector2(layout.ContainerControl.Width - ButtonSize - 4, (layout.ContainerControl.Height - ButtonSize) * 0.5f), }; - button.Clicked += () => - { - var newType = Values.Type; - SetValue(newType.CreateInstance()); - RebuildLayoutOnRefresh(); - }; + button.Clicked += () => SetValue(Values.Type.CreateInstance()); } layout.Label(""); @@ -558,6 +556,13 @@ namespace FlaxEditor.CustomEditors.Editors /// public override void Refresh() { + // Automatic refresh when value nullability changed + if (_isNull != Values.IsNull) + { + RebuildLayout(); + return; + } + if (_visibleIfCaches != null) { try diff --git a/Source/Editor/CustomEditors/LayoutElementsContainer.cs b/Source/Editor/CustomEditors/LayoutElementsContainer.cs index d7df2fbb1..b63d9f6b9 100644 --- a/Source/Editor/CustomEditors/LayoutElementsContainer.cs +++ b/Source/Editor/CustomEditors/LayoutElementsContainer.cs @@ -30,6 +30,11 @@ namespace FlaxEditor.CustomEditors /// public readonly List Children = new List(); + /// + /// The child custom editors. + /// + public readonly List Editors = new List(); + /// /// Gets the control represented by this element. /// @@ -722,6 +727,7 @@ namespace FlaxEditor.CustomEditors var customEditor = CustomEditor.CurrentCustomEditor; Assert.IsNotNull(customEditor); customEditor.OnChildCreated(editor); + Editors.Add(editor); } /// @@ -730,6 +736,7 @@ namespace FlaxEditor.CustomEditors public virtual void ClearLayout() { Children.Clear(); + Editors.Clear(); } /// diff --git a/Source/Editor/CustomEditors/Values/ValueContainer.cs b/Source/Editor/CustomEditors/Values/ValueContainer.cs index 9bdbc4bcf..1252c9179 100644 --- a/Source/Editor/CustomEditors/Values/ValueContainer.cs +++ b/Source/Editor/CustomEditors/Values/ValueContainer.cs @@ -101,6 +101,22 @@ namespace FlaxEditor.CustomEditors } } + /// + /// Gets a value indicating whether all values in the collection are null. Returns true if collection is empty. + /// + public bool IsNull + { + get + { + for (int i = 0; i < Count; i++) + { + if (this[i] != null) + return false; + } + return true; + } + } + /// /// Gets a value indicating whether this any value in the collection is of value type (eg. a structure, not a class type). Returns false if collection is empty. /// From 6f5605b7ca0945a24fdd8f1325ab787c9242c5ea Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 12 Aug 2021 12:51:20 +0200 Subject: [PATCH 24/53] Fix window focus issue when removing actors --- Source/Editor/Modules/SceneEditingModule.cs | 4 ++++ Source/Editor/Windows/PropertiesWindow.cs | 1 + 2 files changed, 5 insertions(+) diff --git a/Source/Editor/Modules/SceneEditingModule.cs b/Source/Editor/Modules/SceneEditingModule.cs index 6b2a7090d..255247671 100644 --- a/Source/Editor/Modules/SceneEditingModule.cs +++ b/Source/Editor/Modules/SceneEditingModule.cs @@ -383,6 +383,7 @@ namespace FlaxEditor.Modules var objects = Selection.Where(x => x.CanDelete).ToList().BuildAllNodes().Where(x => x.CanDelete).ToList(); if (objects.Count == 0) return; + var isSceneTreeFocus = Editor.Windows.SceneWin.ContainsFocus; SelectionDeleteBegin?.Invoke(); @@ -404,6 +405,9 @@ namespace FlaxEditor.Modules SelectionDeleteEnd?.Invoke(); OnDirty(objects); + + if (isSceneTreeFocus) + Editor.Windows.SceneWin.Focus(); } /// diff --git a/Source/Editor/Windows/PropertiesWindow.cs b/Source/Editor/Windows/PropertiesWindow.cs index 70f58dcc6..f96385536 100644 --- a/Source/Editor/Windows/PropertiesWindow.cs +++ b/Source/Editor/Windows/PropertiesWindow.cs @@ -29,6 +29,7 @@ namespace FlaxEditor.Windows : base(editor, true, ScrollBars.Vertical) { Title = "Properties"; + AutoFocus = true; Presenter = new CustomEditorPresenter(editor.Undo); Presenter.Panel.Parent = this; From d77ad33583f231d9a6db6d8aa1d008038d17b35f Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 12 Aug 2021 14:04:52 +0200 Subject: [PATCH 25/53] Increase fog properties limits --- Source/Engine/Level/Actors/ExponentialHeightFog.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Level/Actors/ExponentialHeightFog.h b/Source/Engine/Level/Actors/ExponentialHeightFog.h index f46970b0f..ffbcef3a6 100644 --- a/Source/Engine/Level/Actors/ExponentialHeightFog.h +++ b/Source/Engine/Level/Actors/ExponentialHeightFog.h @@ -25,13 +25,13 @@ public: /// /// The fog density factor. /// - API_FIELD(Attributes="EditorOrder(10), DefaultValue(0.02f), Limit(0.000001f, 0.8f, 0.001f), EditorDisplay(\"Exponential Height Fog\")") + API_FIELD(Attributes="EditorOrder(10), DefaultValue(0.02f), Limit(0.0000001f, 100.0f, 0.001f), EditorDisplay(\"Exponential Height Fog\")") float FogDensity = 0.02f; /// /// The fog height density factor that controls how the density increases as height decreases. The smaller values produce more visible transition larger. /// - API_FIELD(Attributes="EditorOrder(20), DefaultValue(0.2f), Limit(0.001f, 2.0f, 0.001f), EditorDisplay(\"Exponential Height Fog\")") + API_FIELD(Attributes="EditorOrder(20), DefaultValue(0.2f), Limit(0.0001f, 10.0f, 0.001f), EditorDisplay(\"Exponential Height Fog\")") float FogHeightFalloff = 0.2f; /// From 7656cf541de11a00dc2d6433666a30777d2b66be Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 12 Aug 2021 13:59:30 +0200 Subject: [PATCH 26/53] Fix snap to the ground to use scene graph query instead of physics only raycast --- Source/Editor/Gizmo/GizmoBase.cs | 7 +++ Source/Editor/Gizmo/IGizmoOwner.cs | 5 ++ Source/Editor/Gizmo/TransformGizmo.cs | 6 +++ Source/Editor/Gizmo/TransformGizmoBase.cs | 51 +++++++++++++++---- .../Editor/Tools/Foliage/EditFoliageGizmo.cs | 22 ++++++++ Source/Editor/Viewport/EditorGizmoViewport.cs | 7 ++- .../Viewport/MainEditorGizmoViewport.cs | 2 +- .../Editor/Viewport/PrefabWindowViewport.cs | 3 ++ 8 files changed, 92 insertions(+), 11 deletions(-) diff --git a/Source/Editor/Gizmo/GizmoBase.cs b/Source/Editor/Gizmo/GizmoBase.cs index a44c677dd..5dfa742bd 100644 --- a/Source/Editor/Gizmo/GizmoBase.cs +++ b/Source/Editor/Gizmo/GizmoBase.cs @@ -80,6 +80,13 @@ namespace FlaxEditor.Gizmo { } + /// + /// Performs scene objects snapping to the ground. + /// + public virtual void SnapToGround() + { + } + /// /// Draws the gizmo. /// diff --git a/Source/Editor/Gizmo/IGizmoOwner.cs b/Source/Editor/Gizmo/IGizmoOwner.cs index d3dc6cbc0..54983b3b8 100644 --- a/Source/Editor/Gizmo/IGizmoOwner.cs +++ b/Source/Editor/Gizmo/IGizmoOwner.cs @@ -89,5 +89,10 @@ namespace FlaxEditor.Gizmo /// Gets a object used by the gizmo owner. /// Undo Undo { get; } + + /// + /// Gets the root tree node for the scene graph. + /// + SceneGraph.RootNode SceneGraphRoot { get; } } } diff --git a/Source/Editor/Gizmo/TransformGizmo.cs b/Source/Editor/Gizmo/TransformGizmo.cs index f490de430..6d7a2ccd3 100644 --- a/Source/Editor/Gizmo/TransformGizmo.cs +++ b/Source/Editor/Gizmo/TransformGizmo.cs @@ -218,6 +218,12 @@ namespace FlaxEditor.Gizmo } } + /// + protected override bool IsSelected(SceneGraphNode obj) + { + return _selection.Contains(obj); + } + /// protected override void OnApplyTransformation(ref Vector3 translationDelta, ref Quaternion rotationDelta, ref Vector3 scaleDelta) { diff --git a/Source/Editor/Gizmo/TransformGizmoBase.cs b/Source/Editor/Gizmo/TransformGizmoBase.cs index 768fa0617..60fc7b816 100644 --- a/Source/Editor/Gizmo/TransformGizmoBase.cs +++ b/Source/Editor/Gizmo/TransformGizmoBase.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using FlaxEditor.SceneGraph; using FlaxEngine; namespace FlaxEditor.Gizmo @@ -398,15 +399,7 @@ namespace FlaxEditor.Gizmo // Snap to ground if (_activeAxis == Axis.None && SelectionCount != 0 && Owner.SnapToGround) { - if (Physics.RayCast(Position, Vector3.Down, out var hit, float.MaxValue, uint.MaxValue, false)) - { - StartTransforming(); - var translationDelta = hit.Point - Position; - var rotationDelta = Quaternion.Identity; - var scaleDelta = Vector3.Zero; - OnApplyTransformation(ref translationDelta, ref rotationDelta, ref scaleDelta); - EndTransforming(); - } + SnapToGround(); } // Only when is active else if (_isActive) @@ -517,6 +510,39 @@ namespace FlaxEditor.Gizmo UpdateMatrices(); } + /// + public override void SnapToGround() + { + if (Owner.SceneGraphRoot == null) + return; + var ray = new Ray(Position, Vector3.Down); + while (true) + { + var view = new Ray(Owner.ViewPosition, Owner.ViewDirection); + var rayCastFlags = SceneGraphNode.RayCastData.FlagTypes.SkipEditorPrimitives; + var hit = Owner.SceneGraphRoot.RayCast(ref ray, ref view, out var distance, out _, rayCastFlags); + if (hit != null) + { + // Skip snapping selection to itself + if (IsSelected(hit)) + { + GetSelectedObjectsBounds(out var selectionBounds, out _); + ray.Position = ray.GetPoint(selectionBounds.Size.Y * 0.5f); + continue; + } + + // Snap + StartTransforming(); + var translationDelta = ray.GetPoint(distance) - Position; + var rotationDelta = Quaternion.Identity; + var scaleDelta = Vector3.Zero; + OnApplyTransformation(ref translationDelta, ref rotationDelta, ref scaleDelta); + EndTransforming(); + } + break; + } + } + /// /// Gets a value indicating whether this tool can transform objects. /// @@ -545,6 +571,13 @@ namespace FlaxEditor.Gizmo /// True if editing the selected objects transformations marks the navigation system area dirty (for auto-rebuild), otherwise skip update. protected abstract void GetSelectedObjectsBounds(out BoundingBox bounds, out bool navigationDirty); + /// + /// Checks if the specified object is selected. + /// + /// The object to check. + /// True if it's selected, otherwise false. + protected abstract bool IsSelected(SceneGraphNode obj); + /// /// Called when user starts transforming selected objects. /// diff --git a/Source/Editor/Tools/Foliage/EditFoliageGizmo.cs b/Source/Editor/Tools/Foliage/EditFoliageGizmo.cs index 7443b52ad..5401000c9 100644 --- a/Source/Editor/Tools/Foliage/EditFoliageGizmo.cs +++ b/Source/Editor/Tools/Foliage/EditFoliageGizmo.cs @@ -2,6 +2,7 @@ using System; using FlaxEditor.Gizmo; +using FlaxEditor.SceneGraph; using FlaxEditor.Tools.Foliage.Undo; using FlaxEngine; @@ -87,6 +88,12 @@ namespace FlaxEditor.Tools.Foliage navigationDirty = false; } + /// + protected override bool IsSelected(SceneGraphNode obj) + { + return false; + } + /// protected override void OnStartTransforming() { @@ -231,6 +238,21 @@ namespace FlaxEditor.Tools.Foliage Owner.Undo?.AddAction(action); } + /// + public override void SnapToGround() + { + if (Physics.RayCast(Position, Vector3.Down, out var hit, float.MaxValue, uint.MaxValue, false)) + { + // Snap + StartTransforming(); + var translationDelta = hit.Point - Position; + var rotationDelta = Quaternion.Identity; + var scaleDelta = Vector3.Zero; + OnApplyTransformation(ref translationDelta, ref rotationDelta, ref scaleDelta); + EndTransforming(); + } + } + /// public override void OnActivated() { diff --git a/Source/Editor/Viewport/EditorGizmoViewport.cs b/Source/Editor/Viewport/EditorGizmoViewport.cs index 5c9fcf034..0e81292a3 100644 --- a/Source/Editor/Viewport/EditorGizmoViewport.cs +++ b/Source/Editor/Viewport/EditorGizmoViewport.cs @@ -21,10 +21,12 @@ namespace FlaxEditor.Viewport /// /// The task. /// The undo. - public EditorGizmoViewport(SceneRenderTask task, Undo undo) + /// The scene graph root. + public EditorGizmoViewport(SceneRenderTask task, Undo undo, SceneGraph.RootNode sceneGraphRoot) : base(task, new FPSCamera(), true) { Undo = undo; + SceneGraphRoot = sceneGraphRoot; SetUpdate(ref _update, OnUpdate); } @@ -73,6 +75,9 @@ namespace FlaxEditor.Viewport /// public Undo Undo { get; } + /// + public SceneGraph.RootNode SceneGraphRoot { get; } + /// protected override bool IsControllingMouse => Gizmos.Active?.IsControllingMouse ?? false; diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs index 0c759c950..e29b97dc7 100644 --- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs +++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs @@ -184,7 +184,7 @@ namespace FlaxEditor.Viewport /// /// Editor instance. public MainEditorGizmoViewport(Editor editor) - : base(Object.New(), editor.Undo) + : base(Object.New(), editor.Undo, editor.Scene.Root) { _editor = editor; _dragAssets = new DragAssets(ValidateDragItem); diff --git a/Source/Editor/Viewport/PrefabWindowViewport.cs b/Source/Editor/Viewport/PrefabWindowViewport.cs index 4e71677b2..9b2c9b189 100644 --- a/Source/Editor/Viewport/PrefabWindowViewport.cs +++ b/Source/Editor/Viewport/PrefabWindowViewport.cs @@ -336,6 +336,9 @@ namespace FlaxEditor.Viewport /// public Undo Undo { get; } + /// + public RootNode SceneGraphRoot => _window.Graph.Root; + /// public EditorViewport Viewport => this; From 0bbb72e24dab5a0e89e9d7f69bd7c1defdcdcd52 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 13 Aug 2021 13:13:12 +0200 Subject: [PATCH 27/53] Fix some memory leaks --- Source/Engine/Input/Input.cpp | 22 ++++++++++++++++++++++ Source/Engine/Threading/JobSystem.cpp | 10 +++++++--- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Input/Input.cpp b/Source/Engine/Input/Input.cpp index 44131504d..788ea6f2e 100644 --- a/Source/Engine/Input/Input.cpp +++ b/Source/Engine/Input/Input.cpp @@ -73,6 +73,7 @@ public: } void Update() override; + void Dispose() override; }; InputService InputServiceInstance; @@ -914,3 +915,24 @@ void InputService::Update() } } } + +void InputService::Dispose() +{ + // Dispose input devices + if (Input::Mouse) + { + Input::Mouse->DeleteObject(); + Input::Mouse = nullptr; + } + if (Input::Keyboard) + { + Input::Keyboard->DeleteObject(); + Input::Keyboard = nullptr; + } + for (int32 i = 0; i < Input::Gamepads.Count(); i++) + Input::Gamepads[i]->DeleteObject(); + Input::Gamepads.Clear(); + for (int32 i = 0; i < Input::CustomDevices.Count(); i++) + Input::CustomDevices[i]->DeleteObject(); + Input::CustomDevices.Clear(); +} diff --git a/Source/Engine/Threading/JobSystem.cpp b/Source/Engine/Threading/JobSystem.cpp index 7b6651952..56d23901c 100644 --- a/Source/Engine/Threading/JobSystem.cpp +++ b/Source/Engine/Threading/JobSystem.cpp @@ -134,9 +134,13 @@ void JobSystemService::Dispose() for (int32 i = 0; i < ThreadsCount; i++) { - if (Threads[i] && Threads[i]->IsRunning()) - Threads[i]->Kill(true); - Threads[i] = nullptr; + if (Threads[i]) + { + if (Threads[i]->IsRunning()) + Threads[i]->Kill(true); + Delete(Threads[i]); + Threads[i] = nullptr; + } } } From 9deb021a5ebb6066271e1634e87ce88c2904e9a7 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 13 Aug 2021 13:44:29 +0200 Subject: [PATCH 28/53] Refactor Tracy source locations to be static #597 --- Source/Engine/Content/Assets/VisualScript.cpp | 7 ++- Source/Engine/Content/Assets/VisualScript.h | 4 ++ Source/Engine/Profiler/ProfilerCPU.h | 26 +++++------ Source/Engine/Profiler/ProfilerSrcLoc.h | 18 ++++++++ .../Scripting/ManagedCLR/MMethod.Mono.cpp | 9 +++- Source/Engine/Scripting/ManagedCLR/MMethod.h | 4 ++ .../Engine/Scripting/Scripting.Internal.cpp | 45 ++++++++++++++++++- .../ThirdParty/tracy/client/TracyScoped.hpp | 17 +++++++ .../ThirdParty/tracy/common/TracySystem.hpp | 1 + .../Bindings/BindingsGenerator.Cpp.cs | 4 +- 10 files changed, 115 insertions(+), 20 deletions(-) create mode 100644 Source/Engine/Profiler/ProfilerSrcLoc.h diff --git a/Source/Engine/Content/Assets/VisualScript.cpp b/Source/Engine/Content/Assets/VisualScript.cpp index 184360f1f..fe3279856 100644 --- a/Source/Engine/Content/Assets/VisualScript.cpp +++ b/Source/Engine/Content/Assets/VisualScript.cpp @@ -1222,6 +1222,11 @@ Asset::LoadResult VisualScript::load() method.ProfilerName.Get()[assetName.Length()] = ':'; method.ProfilerName.Get()[assetName.Length() + 1] = ':'; Platform::MemoryCopy(method.ProfilerName.Get() + assetName.Length() + 2, method.Name.Get(), method.Name.Length()); + method.ProfilerData.name = method.ProfilerName.Get(); + method.ProfilerData.function = method.Name.Get(); + method.ProfilerData.file = nullptr; + method.ProfilerData.line = 0; + method.ProfilerData.color = 0; } #endif @@ -2139,7 +2144,7 @@ VisualScriptingBinaryModule* VisualScripting::GetBinaryModule() Variant VisualScripting::Invoke(VisualScript::Method* method, ScriptingObject* instance, Span parameters) { CHECK_RETURN(method && method->Script->IsLoaded(), Variant::Zero); - PROFILE_CPU_NAMED(*method->ProfilerName); + PROFILE_CPU_SRC_LOC(method->ProfilerData); // Add to the calling stack ScopeContext scope; diff --git a/Source/Engine/Content/Assets/VisualScript.h b/Source/Engine/Content/Assets/VisualScript.h index db6b5ebd3..3ea98e9a3 100644 --- a/Source/Engine/Content/Assets/VisualScript.h +++ b/Source/Engine/Content/Assets/VisualScript.h @@ -5,6 +5,9 @@ #include "../BinaryAsset.h" #include "Engine/Scripting/BinaryModule.h" #include "Engine/Visject/VisjectGraph.h" +#if COMPILE_WITH_PROFILER +#include "Engine/Profiler/ProfilerSrcLoc.h" +#endif #define VISUAL_SCRIPT_GRAPH_MAX_CALL_STACK 250 #define VISUAL_SCRIPT_DEBUGGING USE_EDITOR @@ -118,6 +121,7 @@ public: Array> ParamNames; #if COMPILE_WITH_PROFILER StringAnsi ProfilerName; + SourceLocationData ProfilerData; #endif }; diff --git a/Source/Engine/Profiler/ProfilerCPU.h b/Source/Engine/Profiler/ProfilerCPU.h index 2a48e6c15..7ffdc85eb 100644 --- a/Source/Engine/Profiler/ProfilerCPU.h +++ b/Source/Engine/Profiler/ProfilerCPU.h @@ -386,33 +386,31 @@ struct TIsPODType enum { Value = true }; }; +#include "ProfilerSrcLoc.h" + // Shortcut macros for profiling a single code block execution on CPU // Use ZoneTransient for Tracy for code that can be hot-reloaded (eg. in Editor) or if name can be a variable +#define PROFILE_CPU_USE_TRANSIENT_DATA 0 -#define PROFILE_CPU_NAMED(name) ZoneTransientN(___tracy_scoped_zone, name, true); ScopeProfileBlockCPU ProfileBlockCPU(name) - -#if defined(_MSC_VER) - -#if USE_EDITOR -#define PROFILE_CPU() ZoneTransient(___tracy_scoped_zone, true); ScopeProfileBlockCPU ProfileBlockCPU(TEXT(__FUNCTION__)) -#else -#define PROFILE_CPU() ZoneNamed(___tracy_scoped_zone, true); ScopeProfileBlockCPU ProfileBlockCPU(TEXT(__FUNCTION__)) -#endif - -#else - -#if USE_EDITOR +#if PROFILE_CPU_USE_TRANSIENT_DATA #define PROFILE_CPU() ZoneTransient(___tracy_scoped_zone, true); ScopeProfileBlockCPU ProfileBlockCPU(__FUNCTION__) +#define PROFILE_CPU_NAMED(name) ZoneTransientN(___tracy_scoped_zone, name, true); ScopeProfileBlockCPU ProfileBlockCPU(name) #else #define PROFILE_CPU() ZoneNamed(___tracy_scoped_zone, true); ScopeProfileBlockCPU ProfileBlockCPU(__FUNCTION__) +#define PROFILE_CPU_NAMED(name) ZoneNamedN(___tracy_scoped_zone, name, true); ScopeProfileBlockCPU ProfileBlockCPU(name) #endif +#ifdef TRACY_ENABLE +#define PROFILE_CPU_SRC_LOC(srcLoc) tracy::ScopedZone ___tracy_scoped_zone( (tracy::SourceLocationData*)&(srcLoc) ); ScopeProfileBlockCPU ProfileBlockCPU((srcLoc).name) +#else +#define PROFILE_CPU_SRC_LOC(srcLoc) ScopeProfileBlockCPU ProfileBlockCPU((srcLoc).name) #endif #else // Empty macros for disabled profiler -#define PROFILE_CPU_NAMED(name) #define PROFILE_CPU() +#define PROFILE_CPU_NAMED(name) +#define PROFILE_CPU_SRC_LOC(srcLoc) #endif diff --git a/Source/Engine/Profiler/ProfilerSrcLoc.h b/Source/Engine/Profiler/ProfilerSrcLoc.h new file mode 100644 index 000000000..6ee116668 --- /dev/null +++ b/Source/Engine/Profiler/ProfilerSrcLoc.h @@ -0,0 +1,18 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#if COMPILE_WITH_PROFILER + +#include "Engine/Core/Types/BaseTypes.h" + +struct FLAXENGINE_API SourceLocationData +{ + const char* name; + const char* function; + const char* file; + uint32 line; + uint32 color; +}; + +#endif diff --git a/Source/Engine/Scripting/ManagedCLR/MMethod.Mono.cpp b/Source/Engine/Scripting/ManagedCLR/MMethod.Mono.cpp index 3fb8a16a4..6abff601d 100644 --- a/Source/Engine/Scripting/ManagedCLR/MMethod.Mono.cpp +++ b/Source/Engine/Scripting/ManagedCLR/MMethod.Mono.cpp @@ -60,18 +60,23 @@ MMethod::MMethod(MonoMethod* monoMethod, const char* name, MClass* parentClass) ProfilerName.Get()[className.Length()] = ':'; ProfilerName.Get()[className.Length() + 1] = ':'; Platform::MemoryCopy(ProfilerName.Get() + className.Length() + 2, _name.Get(), _name.Length()); + ProfilerData.name = ProfilerName.Get(); + ProfilerData.function = _name.Get(); + ProfilerData.file = nullptr; + ProfilerData.line = 0; + ProfilerData.color = 0; #endif } MonoObject* MMethod::Invoke(void* instance, void** params, MonoObject** exception) const { - PROFILE_CPU_NAMED(*ProfilerName); + PROFILE_CPU_SRC_LOC(ProfilerData); return mono_runtime_invoke(_monoMethod, instance, params, exception); } MonoObject* MMethod::InvokeVirtual(MonoObject* instance, void** params, MonoObject** exception) const { - PROFILE_CPU_NAMED(*ProfilerName); + PROFILE_CPU_SRC_LOC(ProfilerData); MonoMethod* virtualMethod = mono_object_get_virtual_method(instance, _monoMethod); return mono_runtime_invoke(virtualMethod, instance, params, exception); } diff --git a/Source/Engine/Scripting/ManagedCLR/MMethod.h b/Source/Engine/Scripting/ManagedCLR/MMethod.h index 60701a9b3..46320013f 100644 --- a/Source/Engine/Scripting/ManagedCLR/MMethod.h +++ b/Source/Engine/Scripting/ManagedCLR/MMethod.h @@ -3,6 +3,9 @@ #pragma once #include "Engine/Core/Collections/Array.h" +#if COMPILE_WITH_PROFILER +#include "Engine/Profiler/ProfilerSrcLoc.h" +#endif #include "MTypes.h" /// @@ -42,6 +45,7 @@ public: #if COMPILE_WITH_PROFILER MString ProfilerName; + SourceLocationData ProfilerData; #endif #if USE_MONO diff --git a/Source/Engine/Scripting/Scripting.Internal.cpp b/Source/Engine/Scripting/Scripting.Internal.cpp index b437c442e..1c07d3cf4 100644 --- a/Source/Engine/Scripting/Scripting.Internal.cpp +++ b/Source/Engine/Scripting/Scripting.Internal.cpp @@ -9,13 +9,29 @@ #include "Engine/Scripting/ManagedCLR/MUtils.h" #include "Engine/Core/ObjectsRemovalService.h" #include "Engine/Profiler/Profiler.h" +#if TRACY_ENABLE && !PROFILE_CPU_USE_TRANSIENT_DATA +#include "Engine/Core/Collections/ChunkedArray.h" +#endif +#include "Engine/Core/Types/Pair.h" #include "Engine/Threading/Threading.h" #include namespace ProfilerInternal { #if COMPILE_WITH_PROFILER - Array ManagedEventsGPU; + Array> ManagedEventsGPU; +#if TRACY_ENABLE && !PROFILE_CPU_USE_TRANSIENT_DATA + CriticalSection ManagedSourceLocationsLocker; + + struct Location + { + String Name; + StringAnsi NameAnsi; + tracy::SourceLocationData SrcLocation; + }; + + ChunkedArray ManagedSourceLocations; +#endif #endif void BeginEvent(MonoString* nameObj) @@ -24,7 +40,34 @@ namespace ProfilerInternal const StringView name((const Char*)mono_string_chars(nameObj), mono_string_length(nameObj)); ProfilerCPU::BeginEvent(*name); #if TRACY_ENABLE +#if PROFILE_CPU_USE_TRANSIENT_DATA tracy::ScopedZone::Begin(__LINE__, __FILE__, strlen( __FILE__ ), __FUNCTION__, strlen( __FUNCTION__ ), name.Get(), name.Length() ); +#else + ScopeLock lock(ManagedSourceLocationsLocker); + tracy::SourceLocationData* srcLoc = nullptr; + for (auto& e = ManagedSourceLocations.Begin(); e.IsNotEnd(); ++e) + { + if (name == e->Name) + { + srcLoc = &e->SrcLocation; + break; + } + } + if (!srcLoc) + { + auto& e = ManagedSourceLocations.AddOne(); + e.Name = name; + e.NameAnsi = name.Get(); + srcLoc = &e.SrcLocation; + srcLoc->name = e.NameAnsi.Get(); + srcLoc->function = nullptr; + srcLoc->file = nullptr; + srcLoc->line = 0; + srcLoc->color = 0; + } + //static constexpr tracy::SourceLocationData tracySrcLoc{ nullptr, __FUNCTION__, __FILE__, (uint32_t)__LINE__, 0 }; + tracy::ScopedZone::Begin(srcLoc); +#endif #endif #endif } diff --git a/Source/ThirdParty/tracy/client/TracyScoped.hpp b/Source/ThirdParty/tracy/client/TracyScoped.hpp index 5de03105c..b18f02388 100644 --- a/Source/ThirdParty/tracy/client/TracyScoped.hpp +++ b/Source/ThirdParty/tracy/client/TracyScoped.hpp @@ -12,8 +12,22 @@ namespace tracy { +void ScopedZone::Begin(const SourceLocationData* srcloc) +{ +#ifdef TRACY_ON_DEMAND + if (!GetProfiler().IsConnected()) return; +#endif + TracyLfqPrepare( QueueType::ZoneBegin ); + MemWrite( &item->zoneBegin.time, Profiler::GetTime() ); + MemWrite( &item->zoneBegin.srcloc, (uint64_t)srcloc ); + TracyLfqCommit; +} + void ScopedZone::Begin(uint32_t line, const char* source, size_t sourceSz, const char* function, size_t functionSz, const Char* name, size_t nameSz) { +#ifdef TRACY_ON_DEMAND + if (!GetProfiler().IsConnected()) return; +#endif TracyLfqPrepare( QueueType::ZoneBeginAllocSrcLoc ); const auto srcloc = Profiler::AllocSourceLocation( line, source, sourceSz, function, functionSz, name, nameSz ); MemWrite( &item->zoneBegin.time, Profiler::GetTime() ); @@ -23,6 +37,9 @@ void ScopedZone::Begin(uint32_t line, const char* source, size_t sourceSz, const void ScopedZone::End() { +#ifdef TRACY_ON_DEMAND + if (!GetProfiler().IsConnected()) return; +#endif TracyLfqPrepare( QueueType::ZoneEnd ); MemWrite( &item->zoneEnd.time, Profiler::GetTime() ); TracyLfqCommit; diff --git a/Source/ThirdParty/tracy/common/TracySystem.hpp b/Source/ThirdParty/tracy/common/TracySystem.hpp index b2d0e8dda..8a699886b 100644 --- a/Source/ThirdParty/tracy/common/TracySystem.hpp +++ b/Source/ThirdParty/tracy/common/TracySystem.hpp @@ -46,6 +46,7 @@ struct TRACY_API SourceLocationData class TRACY_API ScopedZone { public: + static void Begin( const SourceLocationData* srcloc ); static void Begin( uint32_t line, const char* source, size_t sourceSz, const char* function, size_t functionSz, const Char* name, size_t nameSz ); static void End(); diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index e61a2cf0a..07d9b490a 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -1028,7 +1028,7 @@ namespace Flax.Build.Bindings contents.AppendLine(" auto scriptVTable = (MMethod**)managedTypePtr->Script.ScriptVTable;"); contents.AppendLine($" ASSERT(scriptVTable && scriptVTable[{scriptVTableIndex}]);"); contents.AppendLine($" auto method = scriptVTable[{scriptVTableIndex}];"); - contents.AppendLine(" PROFILE_CPU_NAMED(*method->ProfilerName);"); + contents.AppendLine($" PROFILE_CPU_NAMED(\"{classInfo.FullNameManaged}::{functionInfo.Name}\");"); contents.AppendLine(" MonoObject* exception = nullptr;"); contents.AppendLine(" auto prevWrapperCallInstance = WrapperCallInstance;"); @@ -1290,7 +1290,7 @@ namespace Flax.Build.Bindings contents.Append(" if (!mmethod)").AppendLine(); contents.AppendFormat(" mmethod = {1}::TypeInitializer.GetType().ManagedClass->GetMethod(\"Internal_{0}_Invoke\", {2});", eventInfo.Name, classTypeNameNative, paramsCount).AppendLine(); contents.Append(" CHECK(mmethod);").AppendLine(); - contents.Append(" PROFILE_CPU_NAMED(*mmethod->ProfilerName);").AppendLine(); + contents.Append($" PROFILE_CPU_NAMED(\"{classInfo.FullNameManaged}::On{eventInfo.Name}\");").AppendLine(); contents.Append(" MonoObject* exception = nullptr;").AppendLine(); if (paramsCount == 0) contents.AppendLine(" void** params = nullptr;"); From a2782515a204da96041f686842cff3242a4c941d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 13 Aug 2021 15:57:10 +0200 Subject: [PATCH 29/53] Fix compilation --- Source/Engine/Scripting/Scripting.Internal.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Scripting/Scripting.Internal.cpp b/Source/Engine/Scripting/Scripting.Internal.cpp index 1c07d3cf4..1f26fc8b5 100644 --- a/Source/Engine/Scripting/Scripting.Internal.cpp +++ b/Source/Engine/Scripting/Scripting.Internal.cpp @@ -45,7 +45,7 @@ namespace ProfilerInternal #else ScopeLock lock(ManagedSourceLocationsLocker); tracy::SourceLocationData* srcLoc = nullptr; - for (auto& e = ManagedSourceLocations.Begin(); e.IsNotEnd(); ++e) + for (auto e = ManagedSourceLocations.Begin(); e.IsNotEnd(); ++e) { if (name == e->Name) { From 7a1817b0994e5b591567af7ea2cf6b78e78597a8 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 16 Aug 2021 09:48:31 +0200 Subject: [PATCH 30/53] Fix Custom Editor UI regression 3cf3f58db17352fa4118b7e617f50787c4d5045e --- Source/Editor/CustomEditors/Editors/GenericEditor.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Source/Editor/CustomEditors/Editors/GenericEditor.cs b/Source/Editor/CustomEditors/Editors/GenericEditor.cs index 8510fa9ff..6a20b6a28 100644 --- a/Source/Editor/CustomEditors/Editors/GenericEditor.cs +++ b/Source/Editor/CustomEditors/Editors/GenericEditor.cs @@ -419,12 +419,18 @@ namespace FlaxEditor.CustomEditors.Editors } } + /// + internal override void Initialize(CustomEditorPresenter presenter, LayoutElementsContainer layout, ValueContainer values) + { + _isNull = values != null && values.IsNull; + + base.Initialize(presenter, layout, values); + } + /// public override void Initialize(LayoutElementsContainer layout) { - var values = Values; _visibleIfCaches = null; - _isNull = values != null && values.IsNull; // Collect items to edit List items; From ec18b01ef78e6a6577fca422584dd91291431c44 Mon Sep 17 00:00:00 2001 From: Damian Korczowski Date: Mon, 16 Aug 2021 19:54:20 +0200 Subject: [PATCH 31/53] Fix ENet driver crash --- Source/Engine/Networking/Drivers/ENetDriver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Networking/Drivers/ENetDriver.cpp b/Source/Engine/Networking/Drivers/ENetDriver.cpp index 8d5b4b7f6..32ddfde01 100644 --- a/Source/Engine/Networking/Drivers/ENetDriver.cpp +++ b/Source/Engine/Networking/Drivers/ENetDriver.cpp @@ -153,7 +153,7 @@ void ENetDriver::Disconnect(const NetworkConnection& connection) void* peer = nullptr; if(_peerMap.TryGet(connectionId, peer)) { - enet_peer_disconnect_now((ENetPeer*)_peer, 0); + enet_peer_disconnect_now((ENetPeer*)peer, 0); _peerMap.Remove(connectionId); } else From 07d06c74b52e0eedef96299aba07c846a8b0e716 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 16 Aug 2021 21:55:48 +0200 Subject: [PATCH 32/53] Fix context menu keyboard arrows navigation to skip disabled items --- Source/Editor/GUI/ContextMenu/ContextMenu.cs | 18 +++++++++++++++--- .../GUI/ContextMenu/ContextMenuButton.cs | 4 ++-- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/Source/Editor/GUI/ContextMenu/ContextMenu.cs b/Source/Editor/GUI/ContextMenu/ContextMenu.cs index 966f3502b..bf32fc802 100644 --- a/Source/Editor/GUI/ContextMenu/ContextMenu.cs +++ b/Source/Editor/GUI/ContextMenu/ContextMenu.cs @@ -456,18 +456,30 @@ namespace FlaxEditor.GUI.ContextMenu if (base.OnKeyDown(key)) return true; - // Se;ect the first item - if (key == KeyboardKeys.ArrowDown) + switch (key) { + case KeyboardKeys.ArrowDown: for (int i = 0; i < _panel.Children.Count; i++) { - if (_panel.Children[i] is ContextMenuButton item && item.Visible) + if (_panel.Children[i] is ContextMenuButton item && item.Visible && item.Enabled) { item.Focus(); _panel.ScrollViewTo(item); return true; } } + break; + case KeyboardKeys.ArrowUp: + for (int i = _panel.Children.Count - 1; i >= 0; i--) + { + if (_panel.Children[i] is ContextMenuButton item && item.Visible && item.Enabled) + { + item.Focus(); + _panel.ScrollViewTo(item); + return true; + } + } + break; } return false; diff --git a/Source/Editor/GUI/ContextMenu/ContextMenuButton.cs b/Source/Editor/GUI/ContextMenu/ContextMenuButton.cs index c4bdbce68..dc48cae73 100644 --- a/Source/Editor/GUI/ContextMenu/ContextMenuButton.cs +++ b/Source/Editor/GUI/ContextMenu/ContextMenuButton.cs @@ -183,7 +183,7 @@ namespace FlaxEditor.GUI.ContextMenu case KeyboardKeys.ArrowUp: for (int i = IndexInParent - 1; i >= 0; i--) { - if (ParentContextMenu.ItemsContainer.Children[i] is ContextMenuButton item && item.Visible) + if (ParentContextMenu.ItemsContainer.Children[i] is ContextMenuButton item && item.Visible && item.Enabled) { item.Focus(); ParentContextMenu.ItemsContainer.ScrollViewTo(item); @@ -194,7 +194,7 @@ namespace FlaxEditor.GUI.ContextMenu case KeyboardKeys.ArrowDown: for (int i = IndexInParent + 1; i < ParentContextMenu.ItemsContainer.Children.Count; i++) { - if (ParentContextMenu.ItemsContainer.Children[i] is ContextMenuButton item && item.Visible) + if (ParentContextMenu.ItemsContainer.Children[i] is ContextMenuButton item && item.Visible && item.Enabled) { item.Focus(); ParentContextMenu.ItemsContainer.ScrollViewTo(item); From 890ea384700ae4744c34b6c65f911263a45414bb Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 16 Aug 2021 22:49:51 +0200 Subject: [PATCH 33/53] Add `-new` command line arg to create a new project --- Source/Editor/Editor.cpp | 99 +++++++++++++++++++++++ Source/Editor/Editor.cs | 31 ++++++- Source/Editor/Managed/ManagedEditor.cpp | 6 +- Source/Engine/Core/Config/GameSettings.cs | 2 +- Source/Engine/Engine/CommandLine.cpp | 1 + Source/Engine/Engine/CommandLine.h | 5 ++ 6 files changed, 140 insertions(+), 4 deletions(-) diff --git a/Source/Editor/Editor.cpp b/Source/Editor/Editor.cpp index f33d27dbe..ce42154da 100644 --- a/Source/Editor/Editor.cpp +++ b/Source/Editor/Editor.cpp @@ -388,6 +388,105 @@ int32 Editor::LoadProduct() projectPath.Clear(); } + // Create new project option + if (CommandLine::Options.NewProject) + { + if (projectPath.IsEmpty()) + projectPath = Platform::GetWorkingDirectory(); + else if (!FileSystem::DirectoryExists(projectPath)) + FileSystem::CreateDirectory(projectPath); + FileSystem::NormalizePath(projectPath); + + String folderName = StringUtils::GetFileName(projectPath); + String tmpName; + for (int32 i = 0; i < folderName.Length(); i++) + { + Char c = folderName[i]; + if (StringUtils::IsAlnum(c) && c != ' ' && c != '.') + tmpName += c; + } + + // Create project file + ProjectInfo newProject; + newProject.Name = MoveTemp(tmpName); + newProject.ProjectPath = projectPath / newProject.Name + TEXT(".flaxproj"); + newProject.ProjectFolderPath = projectPath; + newProject.Version = Version(1, 0); + newProject.Company = TEXT("My Company"); + newProject.MinEngineVersion = FLAXENGINE_VERSION; + newProject.GameTarget = TEXT("GameTarget"); + newProject.EditorTarget = TEXT("GameEditorTarget"); + auto& flaxRef = newProject.References.AddOne(); + flaxRef.Name = TEXT("$(EnginePath)/Flax.flaxproj"); + flaxRef.Project = nullptr; + if (newProject.SaveProject()) + return 10; + + // Generate source files + if (FileSystem::CreateDirectory(projectPath / TEXT("Content"))) + return 11; + if (FileSystem::CreateDirectory(projectPath / TEXT("Source/Game"))) + return 11; + bool failed = File::WriteAllText(projectPath / TEXT("Source/GameTarget.Build.cs"),TEXT( + "using Flax.Build;\n" + "\n" + "public class GameTarget : GameProjectTarget\n" + "{\n" + " /// \n" + " public override void Init()\n" + " {\n" + " base.Init();\n" + "\n" + " // Reference the modules for game\n" + " Modules.Add(\"Game\");\n" + " }\n" + "}\n"), Encoding::Unicode); + failed |= File::WriteAllText(projectPath / TEXT("Source/GameEditorTarget.Build.cs"),TEXT( + "using Flax.Build;\n" + "\n" + "public class GameEditorTarget : GameProjectEditorTarget\n" + "{\n" + " /// \n" + " public override void Init()\n" + " {\n" + " base.Init();\n" + "\n" + " // Reference the modules for editor\n" + " Modules.Add(\"Game\");\n" + " }\n" + "}\n"), Encoding::Unicode); + failed |= File::WriteAllText(projectPath / TEXT("Source/Game/Game.Build.cs"),TEXT( + "using Flax.Build;\n" + "using Flax.Build.NativeCpp;\n" + "\n" + "public class Game : GameModule\n" + "{\n" + " /// \n" + " public override void Init()\n" + " {\n" + " base.Init();\n" + "\n" + " // C#-only scripting\n" + " BuildNativeCode = false;\n" + " }\n" + "\n" + " /// \n" + " public override void Setup(BuildOptions options)\n" + " {\n" + " base.Setup(options);\n" + "\n" + " options.ScriptingAPI.IgnoreMissingDocumentationWarnings = true;\n" + "\n" + " // Here you can modify the build options for your game module\n" + " // To reference another module use: options.PublicDependencies.Add(\"Audio\");\n" + " // To add C++ define use: options.PublicDefinitions.Add(\"COMPILE_WITH_FLAX\");\n" + " // To learn more see scripting documentation.\n" + " }\n" + "}\n"), Encoding::Unicode); + if (failed) + return 12; + } + // Missing project case if (projectPath.IsEmpty()) { diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs index 19d699726..4831b9958 100644 --- a/Source/Editor/Editor.cs +++ b/Source/Editor/Editor.cs @@ -272,8 +272,10 @@ namespace FlaxEditor module.OnEndInit(); } - internal void Init(bool isHeadless, bool skipCompile, Guid startupScene) + internal void Init(bool isHeadless, bool skipCompile, bool newProject, Guid startupScene) { + if (newProject) + InitProject(); EnsureState(); _isHeadlessMode = isHeadless; _startupSceneCmdLine = startupScene; @@ -473,6 +475,33 @@ namespace FlaxEditor } } + private void InitProject() + { + // Initialize empty project with default game configuration + Log("Initialize new project"); + var project = GameProject; + var gameSettings = new GameSettings + { + ProductName = project.Name, + CompanyName = project.Company, + }; + GameSettings.Save(gameSettings); + GameSettings.Save(new TimeSettings()); + GameSettings.Save(new AudioSettings()); + GameSettings.Save(new PhysicsSettings()); + GameSettings.Save(new LayersAndTagsSettings()); + GameSettings.Save(new InputSettings()); + GameSettings.Save(new GraphicsSettings()); + GameSettings.Save(new NavigationSettings()); + GameSettings.Save(new LocalizationSettings()); + GameSettings.Save(new BuildSettings()); + GameSettings.Save(new StreamingSettings()); + GameSettings.Save(new WindowsPlatformSettings()); + GameSettings.Save(new LinuxPlatformSettings()); + GameSettings.Save(new AndroidPlatformSettings()); + GameSettings.Save(new UWPPlatformSettings()); + } + internal void OnPlayBeginning() { for (int i = 0; i < _modules.Count; i++) diff --git a/Source/Editor/Managed/ManagedEditor.cpp b/Source/Editor/Managed/ManagedEditor.cpp index 6a83ceeb5..089faeb7f 100644 --- a/Source/Editor/Managed/ManagedEditor.cpp +++ b/Source/Editor/Managed/ManagedEditor.cpp @@ -175,7 +175,7 @@ ManagedEditor::~ManagedEditor() void ManagedEditor::Init() { // Note: editor modules should perform quite fast init, any longer things should be done in async during 'editor splash screen time - void* args[3]; + void* args[4]; MClass* mclass = GetClass(); if (mclass == nullptr) { @@ -194,14 +194,16 @@ void ManagedEditor::Init() MonoObject* exception = nullptr; bool isHeadless = CommandLine::Options.Headless.IsTrue(); bool skipCompile = CommandLine::Options.SkipCompile.IsTrue(); + bool newProject = CommandLine::Options.NewProject.IsTrue(); args[0] = &isHeadless; args[1] = &skipCompile; + args[2] = &newProject; Guid sceneId; if (!CommandLine::Options.Play.HasValue() || (CommandLine::Options.Play.HasValue() && Guid::Parse(CommandLine::Options.Play.GetValue(), sceneId))) { sceneId = Guid::Empty; } - args[2] = &sceneId; + args[3] = &sceneId; initMethod->Invoke(instance, args, &exception); if (exception) { diff --git a/Source/Engine/Core/Config/GameSettings.cs b/Source/Engine/Core/Config/GameSettings.cs index b9ba4e110..d5dfd3726 100644 --- a/Source/Engine/Core/Config/GameSettings.cs +++ b/Source/Engine/Core/Config/GameSettings.cs @@ -296,7 +296,7 @@ namespace FlaxEditor.Content.Settings } // Create new settings asset and link it to the game settings - var path = StringUtils.CombinePaths(Globals.ProjectContentFolder, CustomEditors.CustomEditorsUtil.GetPropertyNameUI(typeof(T).Name) + ".json"); + var path = StringUtils.CombinePaths(Globals.ProjectContentFolder, "Settings", CustomEditors.CustomEditorsUtil.GetPropertyNameUI(typeof(T).Name) + ".json"); if (Editor.SaveJsonAsset(path, obj)) return true; asset = FlaxEngine.Content.LoadAsync(path); diff --git a/Source/Engine/Engine/CommandLine.cpp b/Source/Engine/Engine/CommandLine.cpp index 9f5078c64..e7fe07fb5 100644 --- a/Source/Engine/Engine/CommandLine.cpp +++ b/Source/Engine/Engine/CommandLine.cpp @@ -150,6 +150,7 @@ bool CommandLine::Parse(const Char* cmdLine) PARSE_BOOL_SWITCH("-clearcache ", ClearCache); PARSE_BOOL_SWITCH("-clearcooker ", ClearCookerCache); PARSE_ARG_SWITCH("-project ", Project); + PARSE_BOOL_SWITCH("-new ", NewProject); PARSE_BOOL_SWITCH("-genprojectfiles ", GenProjectFiles); PARSE_ARG_SWITCH("-build ", Build); PARSE_BOOL_SWITCH("-skipcompile ", SkipCompile); diff --git a/Source/Engine/Engine/CommandLine.h b/Source/Engine/Engine/CommandLine.h index d9d3547d3..41f985dee 100644 --- a/Source/Engine/Engine/CommandLine.h +++ b/Source/Engine/Engine/CommandLine.h @@ -134,6 +134,11 @@ public: /// String Project; + /// + /// -new (generates the project files inside the specified project folder or uses current workspace folder) + /// + Nullable NewProject; + /// /// -genprojectfiles (generates the scripts project files) /// From 8b8a83eb208efa81b6f4d06d77cf936090a503fc Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 18 Aug 2021 22:55:31 +0200 Subject: [PATCH 34/53] Fix materials parameters display issues after editing --- Source/Editor/Surface/VisjectSurfaceWindow.cs | 19 +++++++++++++------ .../Windows/Assets/AnimationGraphWindow.cs | 16 +++++++++------- .../Windows/Assets/MaterialInstanceWindow.cs | 12 +++--------- .../Editor/Windows/Assets/MaterialWindow.cs | 15 +++++++++------ .../Windows/Assets/ParticleEmitterWindow.cs | 12 +++++++++--- 5 files changed, 43 insertions(+), 31 deletions(-) diff --git a/Source/Editor/Surface/VisjectSurfaceWindow.cs b/Source/Editor/Surface/VisjectSurfaceWindow.cs index 72d02f597..37092026c 100644 --- a/Source/Editor/Surface/VisjectSurfaceWindow.cs +++ b/Source/Editor/Surface/VisjectSurfaceWindow.cs @@ -604,7 +604,7 @@ namespace FlaxEditor.Surface Parent = this }; - Presenter = new CustomEditorPresenter(undo); + Presenter = new CustomEditorPresenter(undo, "Loading..."); Presenter.Panel.Parent = scrollPanel; } @@ -744,7 +744,7 @@ namespace FlaxEditor.Surface } else { - _propertiesEditor = new CustomEditorPresenter(_undo); + _propertiesEditor = new CustomEditorPresenter(_undo, "Loading..."); _propertiesEditor.Panel.Parent = _split2.Panel2; } _propertiesEditor.Modified += OnPropertyEdited; @@ -896,6 +896,16 @@ namespace FlaxEditor.Surface Close(); } + /// + /// Called when surface gets loaded and user can edit it. + /// + protected virtual void OnSurfaceEditingStart() + { + _undo.Clear(); + _surface.Enabled = true; + _propertiesEditor.BuildLayout(); + } + /// /// Loads the surface from the asset. Called during when asset is loaded and surface is missing. /// @@ -939,10 +949,7 @@ namespace FlaxEditor.Surface return; } - // Setup - _undo.Clear(); - _surface.Enabled = true; - _propertiesEditor.BuildLayout(); + OnSurfaceEditingStart(); ClearEditedFlag(); if (_showWholeGraphOnLoad) { diff --git a/Source/Editor/Windows/Assets/AnimationGraphWindow.cs b/Source/Editor/Windows/Assets/AnimationGraphWindow.cs index f4a2ef8b9..444baaaee 100644 --- a/Source/Editor/Windows/Assets/AnimationGraphWindow.cs +++ b/Source/Editor/Windows/Assets/AnimationGraphWindow.cs @@ -186,7 +186,6 @@ namespace FlaxEditor.Windows.Assets // Asset properties proxy _properties = new PropertiesProxy(); - _propertiesEditor.Select(_properties); // Preview properties editor _previewTab = new Tab("Preview"); @@ -322,22 +321,17 @@ namespace FlaxEditor.Windows.Assets { if (value == null) { - // Error Editor.LogError("Failed to save animation graph surface"); return; } - - // Save data to the temporary asset if (_asset.SaveSurface(value)) { - // Error _surface.MarkAsEdited(); Editor.LogError("Failed to save animation graph surface data"); return; } _asset.Reload(); - - // Reset any root motion + _asset.WaitForLoaded(); _preview.PreviewActor.ResetLocalTransform(); _previewTab.Presenter.BuildLayoutOnUpdate(); } @@ -376,6 +370,14 @@ namespace FlaxEditor.Windows.Assets return false; } + /// + protected override void OnSurfaceEditingStart() + { + _propertiesEditor.Select(_properties); + + base.OnSurfaceEditingStart(); + } + /// protected override void PerformLayoutBeforeChildren() { diff --git a/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs b/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs index c5becfe7d..e9862beab 100644 --- a/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs +++ b/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs @@ -229,7 +229,7 @@ namespace FlaxEditor.Windows.Assets layout.Label("No parameters"); return; } - if (!materialInstance.IsLoaded || materialInstance.BaseMaterial && !materialInstance.BaseMaterial.IsLoaded) + if (!materialInstance.IsLoaded || (materialInstance.BaseMaterial && !materialInstance.BaseMaterial.IsLoaded)) { layout.Label("Loading..."); return; @@ -352,10 +352,9 @@ namespace FlaxEditor.Windows.Assets }; // Material properties editor - _editor = new CustomEditorPresenter(_undo); + _editor = new CustomEditorPresenter(_undo, "Loading..."); _editor.Panel.Parent = _split.Panel2; _properties = new PropertiesProxy(); - _editor.Select(_properties); _editor.Modified += OnMaterialPropertyEdited; // Setup input actions @@ -479,16 +478,11 @@ namespace FlaxEditor.Windows.Assets /// public override void Update(float deltaTime) { - // Check if need to load if (_isWaitingForLoad && _asset.IsLoaded && (_asset.BaseMaterial == null || _asset.BaseMaterial.IsLoaded)) { - // Clear flag _isWaitingForLoad = false; - - // Init material properties and parameters proxy _properties.OnLoad(this); - - // Setup + _editor.Select(_properties); ClearEditedFlag(); _undo.Clear(); _editor.BuildLayout(); diff --git a/Source/Editor/Windows/Assets/MaterialWindow.cs b/Source/Editor/Windows/Assets/MaterialWindow.cs index 37dcc40ce..1b28512be 100644 --- a/Source/Editor/Windows/Assets/MaterialWindow.cs +++ b/Source/Editor/Windows/Assets/MaterialWindow.cs @@ -222,7 +222,6 @@ namespace FlaxEditor.Windows.Assets // Asset properties proxy _properties = new PropertiesProxy(); - _propertiesEditor.Select(_properties); // Surface _surface = new MaterialSurface(this, Save, _undo) @@ -324,17 +323,14 @@ namespace FlaxEditor.Windows.Assets get => _asset.LoadSurface(true); set { - // Create material info FillMaterialInfo(out var info); - - // Save data to the temporary material if (_asset.SaveSurface(value, info)) { - // Error _surface.MarkAsEdited(); Editor.LogError("Failed to save material surface data"); } _asset.Reload(); + _asset.WaitForLoaded(); } } @@ -347,7 +343,6 @@ namespace FlaxEditor.Windows.Assets // Load surface graph if (_surface.Load()) { - // Error Editor.LogError("Failed to load material surface."); return true; } @@ -362,6 +357,14 @@ namespace FlaxEditor.Windows.Assets return false; } + /// + protected override void OnSurfaceEditingStart() + { + _propertiesEditor.Select(_properties); + + base.OnSurfaceEditingStart(); + } + /// protected override bool CanEditSurfaceOnAssetLoadError => true; diff --git a/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs b/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs index 1968c5735..423c512f3 100644 --- a/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs +++ b/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs @@ -120,7 +120,6 @@ namespace FlaxEditor.Windows.Assets // Asset properties proxy _properties = new PropertiesProxy(); - _propertiesEditor.Select(_properties); // Preview properties editor _previewTab = new Tab("Preview"); @@ -217,14 +216,13 @@ namespace FlaxEditor.Windows.Assets get => _asset.LoadSurface(true); set { - // Save data to the temporary asset if (_asset.SaveSurface(value)) { - // Error _surface.MarkAsEdited(); Editor.LogError("Failed to save Particle Emitter surface data"); } _asset.Reload(); + _asset.WaitForLoaded(); _preview.PreviewActor.ResetSimulation(); _previewTab.Presenter.BuildLayoutOnUpdate(); } @@ -255,6 +253,14 @@ namespace FlaxEditor.Windows.Assets return false; } + /// + protected override void OnSurfaceEditingStart() + { + _propertiesEditor.Select(_properties); + + base.OnSurfaceEditingStart(); + } + /// protected override bool SaveToOriginal() { From ed8a10f2761ac19c5120cffd124c14ea8b4a0f62 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 16 Aug 2021 13:15:17 +0200 Subject: [PATCH 35/53] Add option to select scene asset from Scene tree window --- Source/Editor/SceneGraph/Actors/SceneNode.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Source/Editor/SceneGraph/Actors/SceneNode.cs b/Source/Editor/SceneGraph/Actors/SceneNode.cs index 2846715fc..894934038 100644 --- a/Source/Editor/SceneGraph/Actors/SceneNode.cs +++ b/Source/Editor/SceneGraph/Actors/SceneNode.cs @@ -1,6 +1,6 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. -using System.Net; +using System.IO; using FlaxEditor.GUI.ContextMenu; using FlaxEditor.SceneGraph.GUI; using FlaxEngine; @@ -68,12 +68,20 @@ namespace FlaxEditor.SceneGraph.Actors public override void OnContextMenu(ContextMenu contextMenu) { contextMenu.AddSeparator(); + var path = Scene.Path; + if (!string.IsNullOrEmpty(path) && File.Exists(path)) + contextMenu.AddButton("Select in Content", OnSelect).LinkTooltip("Finds and selects the scene asset int Content window."); contextMenu.AddButton("Save scene", OnSave).LinkTooltip("Saves this scene.").Enabled = IsEdited && !Editor.IsPlayMode; contextMenu.AddButton("Unload scene", OnUnload).LinkTooltip("Unloads this scene.").Enabled = Editor.Instance.StateMachine.CurrentState.CanChangeScene; base.OnContextMenu(contextMenu); } + private void OnSelect() + { + Editor.Instance.Windows.ContentWin.Select(Editor.Instance.ContentDatabase.Find(Scene.Path)); + } + private void OnSave() { Editor.Instance.Scene.SaveScene(this); From ac21c4d1e7a28d2e3e31c021f4f76ab1cb72889a Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 16 Aug 2021 15:30:38 +0200 Subject: [PATCH 36/53] Adjust scene node context menu --- Source/Editor/SceneGraph/Actors/SceneNode.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Source/Editor/SceneGraph/Actors/SceneNode.cs b/Source/Editor/SceneGraph/Actors/SceneNode.cs index 894934038..63c36eddf 100644 --- a/Source/Editor/SceneGraph/Actors/SceneNode.cs +++ b/Source/Editor/SceneGraph/Actors/SceneNode.cs @@ -70,7 +70,11 @@ namespace FlaxEditor.SceneGraph.Actors contextMenu.AddSeparator(); var path = Scene.Path; if (!string.IsNullOrEmpty(path) && File.Exists(path)) - contextMenu.AddButton("Select in Content", OnSelect).LinkTooltip("Finds and selects the scene asset int Content window."); + { + var b = contextMenu.AddButton("Show in content window", OnSelect); + b.Icon = Editor.Instance.Icons.Search12; + b.TooltipText = "Finds and selects the scene asset int Content window."; + } contextMenu.AddButton("Save scene", OnSave).LinkTooltip("Saves this scene.").Enabled = IsEdited && !Editor.IsPlayMode; contextMenu.AddButton("Unload scene", OnUnload).LinkTooltip("Unloads this scene.").Enabled = Editor.Instance.StateMachine.CurrentState.CanChangeScene; From a7880d73f3e2b4264175ec54082c4d9d40557209 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 19 Aug 2021 13:05:37 +0200 Subject: [PATCH 37/53] Fix --- Source/Editor/Surface/SurfaceUtils.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Editor/Surface/SurfaceUtils.cs b/Source/Editor/Surface/SurfaceUtils.cs index c86a5ae14..75b649bf0 100644 --- a/Source/Editor/Surface/SurfaceUtils.cs +++ b/Source/Editor/Surface/SurfaceUtils.cs @@ -92,6 +92,7 @@ namespace FlaxEditor.Surface case MaterialParameterType.GPUTexture: return typeof(GPUTexture); case MaterialParameterType.Matrix: return typeof(Matrix); case MaterialParameterType.ChannelMask: return typeof(ChannelMask); + case MaterialParameterType.TextureGroupSampler: return typeof(int); default: throw new ArgumentOutOfRangeException(nameof(type), type, null); } } From d816af41b41d4a53165829e32e66af7dcc4ba997 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 19 Aug 2021 15:00:28 +0200 Subject: [PATCH 38/53] Fix invalid navmesh build for triggers --- Source/Engine/Navigation/NavMeshBuilder.cpp | 23 ++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Navigation/NavMeshBuilder.cpp b/Source/Engine/Navigation/NavMeshBuilder.cpp index 8148b0b37..2e6b6ac86 100644 --- a/Source/Engine/Navigation/NavMeshBuilder.cpp +++ b/Source/Engine/Navigation/NavMeshBuilder.cpp @@ -47,6 +47,11 @@ int32 BoxTrianglesIndicesCache[] = }; #define NAV_MESH_TILE_MAX_EXTENT 100000000 +#define NAV_MESH_BUILD_DEBUG_DRAW_GEOMETRY 0 + +#if NAV_MESH_BUILD_DEBUG_DRAW_GEOMETRY +#include "Engine/Debug/DebugDraw.h" +#endif struct OffMeshLink { @@ -108,6 +113,9 @@ struct NavigationSceneRasterization auto v0 = vb[ib[i0++]]; auto v1 = vb[ib[i0++]]; auto v2 = vb[ib[i0++]]; +#if NAV_MESH_BUILD_DEBUG_DRAW_GEOMETRY + DEBUG_DRAW_TRIANGLE(v0, v1, v2, Color::Orange.AlphaMultiplied(0.3f), 1.0f, true); +#endif auto n = Vector3::Cross(v0 - v1, v0 - v2); n.Normalize(); @@ -124,6 +132,9 @@ struct NavigationSceneRasterization auto v0 = Vector3::Transform(vb[ib[i0++]], worldToNavMesh); auto v1 = Vector3::Transform(vb[ib[i0++]], worldToNavMesh); auto v2 = Vector3::Transform(vb[ib[i0++]], worldToNavMesh); +#if NAV_MESH_BUILD_DEBUG_DRAW_GEOMETRY + DEBUG_DRAW_TRIANGLE(v0, v1, v2, Color::Orange.AlphaMultiplied(0.3f), 1.0f, true); +#endif auto n = Vector3::Cross(v0 - v1, v0 - v2); n.Normalize(); @@ -223,6 +234,8 @@ struct NavigationSceneRasterization // Extract data from the actor if (const auto* boxCollider = dynamic_cast(actor)) { + if (boxCollider->GetIsTrigger()) + return true; PROFILE_CPU_NAMED("BoxCollider"); const OrientedBoundingBox box = boxCollider->GetOrientedBox(); @@ -232,6 +245,8 @@ struct NavigationSceneRasterization } else if (const auto* sphereCollider = dynamic_cast(actor)) { + if (sphereCollider->GetIsTrigger()) + return true; PROFILE_CPU_NAMED("SphereCollider"); const BoundingSphere sphere = sphereCollider->GetSphere(); @@ -241,6 +256,8 @@ struct NavigationSceneRasterization } else if (const auto* capsuleCollider = dynamic_cast(actor)) { + if (capsuleCollider->GetIsTrigger()) + return true; PROFILE_CPU_NAMED("CapsuleCollider"); const BoundingBox box = capsuleCollider->GetBox(); @@ -250,6 +267,8 @@ struct NavigationSceneRasterization } else if (const auto* meshCollider = dynamic_cast(actor)) { + if (meshCollider->GetIsTrigger()) + return true; PROFILE_CPU_NAMED("MeshCollider"); auto collisionData = meshCollider->CollisionData.Get(); @@ -260,13 +279,15 @@ struct NavigationSceneRasterization Matrix meshColliderToWorld; meshCollider->GetLocalToWorldMatrix(meshColliderToWorld); - for(auto& v : vb) + for (auto& v : vb) Vector3::Transform(v, meshColliderToWorld, v); e.RasterizeTriangles(); } else if (const auto* splineCollider = dynamic_cast(actor)) { + if (splineCollider->GetIsTrigger()) + return true; PROFILE_CPU_NAMED("SplineCollider"); auto collisionData = splineCollider->CollisionData.Get(); From 949bc67354c8a580d2d0df7320fe2b0332e6fcf0 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 19 Aug 2021 15:22:45 +0200 Subject: [PATCH 39/53] Fix decal material normal vector blending --- Content/Editor/MaterialTemplates/Decal.shader | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Content/Editor/MaterialTemplates/Decal.shader b/Content/Editor/MaterialTemplates/Decal.shader index 452937955..a304b8c6e 100644 --- a/Content/Editor/MaterialTemplates/Decal.shader +++ b/Content/Editor/MaterialTemplates/Decal.shader @@ -194,16 +194,16 @@ void PS_Decal( Out2 = float4(material.Emissive, material.Opacity); #if USE_NORMAL // GBuffer1 - Out3 = float4(material.WorldNormal * 0.5f + 0.5f, 1); + Out3 = float4(material.WorldNormal * 0.5f + 0.5f, material.Opacity); #endif #elif USE_NORMAL // GBuffer1 - Out2 = float4(material.WorldNormal * 0.5f + 0.5f, 1); + Out2 = float4(material.WorldNormal * 0.5f + 0.5f, material.Opacity); #endif #elif DECAL_BLEND_MODE == DECAL_BLEND_MODE_STAIN Out0 = float4(material.Color, material.Opacity); #elif DECAL_BLEND_MODE == DECAL_BLEND_MODE_NORMAL - Out0 = float4(material.WorldNormal * 0.5f + 0.5f, 1); + Out0 = float4(material.WorldNormal * 0.5f + 0.5f, material.Opacity); #elif DECAL_BLEND_MODE == DECAL_BLEND_MODE_EMISSIVE Out0 = float4(material.Emissive * material.Opacity, material.Opacity); #else From 1a54ea82b5feac5c529fce4a7384e584763ed787 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 19 Aug 2021 15:46:36 +0200 Subject: [PATCH 40/53] Fix invalid rigidbody center of mass location --- Source/Engine/Physics/Actors/RigidBody.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Source/Engine/Physics/Actors/RigidBody.cpp b/Source/Engine/Physics/Actors/RigidBody.cpp index 1f213f8f3..1b4a3ec93 100644 --- a/Source/Engine/Physics/Actors/RigidBody.cpp +++ b/Source/Engine/Physics/Actors/RigidBody.cpp @@ -276,8 +276,7 @@ void RigidBody::UpdateMass() float raiseMassToPower = 0.75f; // TODO: link physical material or expose density parameter - PxVec3 centerOfMassOffset = C2P(_centerOfMassOffset); - PxRigidBodyExt::updateMassAndInertia(*_actor, densityKGPerCubicUU, ¢erOfMassOffset); + PxRigidBodyExt::updateMassAndInertia(*_actor, densityKGPerCubicUU); // Grab old mass so we can apply new mass while maintaining inertia tensor const float oldMass = _actor->getMass(); From 15f60b33b1ddc07a6492a6c5e0dc34f14e4202e7 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 19 Aug 2021 16:26:49 +0200 Subject: [PATCH 41/53] Optimize ticking disabled vehicles --- Source/Engine/Physics/Physics.cpp | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/Source/Engine/Physics/Physics.cpp b/Source/Engine/Physics/Physics.cpp index 07b9de909..fb34eff1e 100644 --- a/Source/Engine/Physics/Physics.cpp +++ b/Source/Engine/Physics/Physics.cpp @@ -661,6 +661,8 @@ void Physics::CollectResults() int32 wheelsCount = 0; for (auto wheelVehicle : WheelVehicles) { + if (!wheelVehicle->IsActiveInHierarchy()) + continue; auto drive = (PxVehicleWheels*)wheelVehicle->_drive; ASSERT(drive); WheelVehiclesCache.Add(drive); @@ -810,13 +812,17 @@ void Physics::CollectResults() } // Setup cache for wheel states - WheelVehiclesResultsPerVehicle.Resize(WheelVehicles.Count(), false); + WheelVehiclesResultsPerVehicle.Resize(WheelVehiclesCache.Count(), false); WheelVehiclesResultsPerWheel.Resize(wheelsCount, false); wheelsCount = 0; - for (int32 i = 0; i < WheelVehicles.Count(); i++) + for (int32 i = 0, ii = 0; i < WheelVehicles.Count(); i++) { - auto drive = (PxVehicleWheels*)WheelVehicles[i]->_drive; - auto& perVehicle = WheelVehiclesResultsPerVehicle[i]; + auto wheelVehicle = WheelVehicles[i]; + if (!wheelVehicle->IsActiveInHierarchy()) + continue; + auto drive = (PxVehicleWheels*)WheelVehicles[ii]->_drive; + auto& perVehicle = WheelVehiclesResultsPerVehicle[ii]; + ii++; perVehicle.nbWheelQueryResults = drive->mWheelsSimData.getNbWheels(); perVehicle.wheelQueryResults = WheelVehiclesResultsPerWheel.Get() + wheelsCount; wheelsCount += perVehicle.nbWheelQueryResults; @@ -827,11 +833,14 @@ void Physics::CollectResults() PxVehicleUpdates(LastDeltaTime, PhysicsScene->getGravity(), *WheelTireFrictions, WheelVehiclesCache.Count(), WheelVehiclesCache.Get(), WheelVehiclesResultsPerVehicle.Get()); // Synchronize state - for (int32 i = 0; i < WheelVehicles.Count(); i++) + for (int32 i = 0, ii = 0; i < WheelVehicles.Count(); i++) { auto wheelVehicle = WheelVehicles[i]; - auto drive = WheelVehiclesCache[i]; - auto& perVehicle = WheelVehiclesResultsPerVehicle[i]; + if (!wheelVehicle->IsActiveInHierarchy()) + continue; + auto drive = WheelVehiclesCache[ii]; + auto& perVehicle = WheelVehiclesResultsPerVehicle[ii]; + ii++; // Update wheels for (int32 j = 0; j < wheelVehicle->_wheelsData.Count(); j++) From b29d1f9902525b9df50e48a57609ed874493c289 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 20 Aug 2021 10:51:27 +0200 Subject: [PATCH 42/53] Fix rare crashes due to selection not being empty on play mode start --- Source/Editor/States/PlayingState.cs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/Source/Editor/States/PlayingState.cs b/Source/Editor/States/PlayingState.cs index 6258975ae..c9100dcf9 100644 --- a/Source/Editor/States/PlayingState.cs +++ b/Source/Editor/States/PlayingState.cs @@ -92,9 +92,13 @@ namespace FlaxEditor.States { Profiler.BeginEvent("PlayingState.CacheSelection"); _selectedObjects.Clear(); - for (int i = 0; i < Editor.SceneEditing.Selection.Count; i++) + var selection = Editor.SceneEditing.Selection; + if (selection.Count != 0) { - _selectedObjects.Add(Editor.SceneEditing.Selection[i].ID); + for (int i = 0; i < selection.Count; i++) + _selectedObjects.Add(selection[i].ID); + selection.Clear(); + Editor.SceneEditing.OnSelectionChanged(); } Profiler.EndEvent(); } @@ -102,15 +106,16 @@ namespace FlaxEditor.States private void RestoreSelection() { Profiler.BeginEvent("PlayingState.RestoreSelection"); - var count = Editor.SceneEditing.Selection.Count; - Editor.SceneEditing.Selection.Clear(); + var selection = Editor.SceneEditing.Selection; + var count = selection.Count; + selection.Clear(); for (int i = 0; i < _selectedObjects.Count; i++) { var node = SceneGraphFactory.FindNode(_selectedObjects[i]); if (node != null) - Editor.SceneEditing.Selection.Add(node); + selection.Add(node); } - if (Editor.SceneEditing.Selection.Count != count) + if (selection.Count != count) Editor.SceneEditing.OnSelectionChanged(); Profiler.EndEvent(); } From 330c46b94928da8eb3ce4943dc3d044378e06702 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 12 Aug 2021 18:21:46 +0200 Subject: [PATCH 43/53] Add `SuspensionForceOffset` to vehicle wheel config --- Source/Engine/Physics/Actors/WheeledVehicle.cpp | 5 ++--- Source/Engine/Physics/Actors/WheeledVehicle.h | 5 +++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Physics/Actors/WheeledVehicle.cpp b/Source/Engine/Physics/Actors/WheeledVehicle.cpp index 2c21e8880..971f882cd 100644 --- a/Source/Engine/Physics/Actors/WheeledVehicle.cpp +++ b/Source/Engine/Physics/Actors/WheeledVehicle.cpp @@ -324,13 +324,12 @@ void WheeledVehicle::Setup() wheelData.mMaxHandBrakeTorque = M2ToCm2(wheel.MaxHandBrakeTorque); PxVec3 centreOffset = centerOfMassOffset.transformInv(offsets[i]); - float suspensionForceOffset = 0.0f; - PxVec3 forceAppPointOffset(centreOffset.x, centreOffset.y, centreOffset.z + suspensionForceOffset); + PxVec3 forceAppPointOffset(centreOffset.z, centreOffset.y + wheel.SuspensionForceOffset, centreOffset.z); wheelsSimData->setTireData(i, tire); wheelsSimData->setWheelData(i, wheelData); wheelsSimData->setSuspensionData(i, suspensionData); - wheelsSimData->setSuspTravelDirection(i, PxVec3(0, -1, 0)); + wheelsSimData->setSuspTravelDirection(i, centerOfMassOffset.rotate(PxVec3(0.0f, -1.0f, 0.0f))); wheelsSimData->setWheelCentreOffset(i, centreOffset); wheelsSimData->setSuspForceAppPointOffset(i, forceAppPointOffset); wheelsSimData->setTireForceAppPointOffset(i, forceAppPointOffset); diff --git a/Source/Engine/Physics/Actors/WheeledVehicle.h b/Source/Engine/Physics/Actors/WheeledVehicle.h index 12b5c864c..bbccfd76b 100644 --- a/Source/Engine/Physics/Actors/WheeledVehicle.h +++ b/Source/Engine/Physics/Actors/WheeledVehicle.h @@ -228,6 +228,11 @@ public: /// The maximum offset for the suspension that wheel can go below resting location. /// API_FIELD(Attributes="Limit(0)") float SuspensionMaxDrop = 10.0f; + + /// + /// The vertical offset from where suspension forces are applied. + /// + API_FIELD() float SuspensionForceOffset = 0.0f; }; /// From 4b506560e996b4d7a6094a17422a08e72affa57f Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 20 Aug 2021 15:30:01 +0200 Subject: [PATCH 44/53] Fix WheeledVehicle driving and suspension raycasts --- .../Engine/Physics/Actors/WheeledVehicle.cpp | 33 +++++++++++--- Source/Engine/Physics/Actors/WheeledVehicle.h | 44 +++++++++++-------- Source/Engine/Physics/Physics.cpp | 32 +++++++------- 3 files changed, 66 insertions(+), 43 deletions(-) diff --git a/Source/Engine/Physics/Actors/WheeledVehicle.cpp b/Source/Engine/Physics/Actors/WheeledVehicle.cpp index 971f882cd..7d86b87bd 100644 --- a/Source/Engine/Physics/Actors/WheeledVehicle.cpp +++ b/Source/Engine/Physics/Actors/WheeledVehicle.cpp @@ -291,7 +291,8 @@ void WheeledVehicle::Setup() offsets[i] = C2P(wheel.Collider->GetLocalPosition()); } PxF32 sprungMasses[PX_MAX_NB_WHEELS]; - PxVehicleComputeSprungMasses(wheels.Count(), offsets, centerOfMassOffset.p, _actor->getMass(), 1, sprungMasses); + const float mass = _actor->getMass(); + PxVehicleComputeSprungMasses(wheels.Count(), offsets, centerOfMassOffset.p, mass, 1, sprungMasses); PxVehicleWheelsSimData* wheelsSimData = PxVehicleWheelsSimData::allocate(wheels.Count()); for (int32 i = 0; i < wheels.Count(); i++) { @@ -300,7 +301,6 @@ void WheeledVehicle::Setup() auto& data = _wheelsData[i]; data.Collider = wheel.Collider; data.LocalOrientation = wheel.Collider->GetLocalOrientation(); - data.ChildrenPoses.Resize(0); PxVehicleSuspensionData suspensionData; const float suspensionFrequency = 7.0f; @@ -324,7 +324,7 @@ void WheeledVehicle::Setup() wheelData.mMaxHandBrakeTorque = M2ToCm2(wheel.MaxHandBrakeTorque); PxVec3 centreOffset = centerOfMassOffset.transformInv(offsets[i]); - PxVec3 forceAppPointOffset(centreOffset.z, centreOffset.y + wheel.SuspensionForceOffset, centreOffset.z); + PxVec3 forceAppPointOffset(centreOffset.z, wheel.SuspensionForceOffset, centreOffset.z); wheelsSimData->setTireData(i, tire); wheelsSimData->setWheelData(i, wheelData); @@ -333,6 +333,8 @@ void WheeledVehicle::Setup() wheelsSimData->setWheelCentreOffset(i, centreOffset); wheelsSimData->setSuspForceAppPointOffset(i, forceAppPointOffset); wheelsSimData->setTireForceAppPointOffset(i, forceAppPointOffset); + wheelsSimData->setSubStepCount(4.0f * 100.0f, 3, 1); + wheelsSimData->setMinLongSlipDenominator(4.0f * 100.0f); PxShape* wheelShape = wheel.Collider->GetPxShape(); if (wheel.Collider->IsActiveInHierarchy()) @@ -345,6 +347,9 @@ void WheeledVehicle::Setup() wheelShape->setQueryFilterData(filter); wheelShape->setSimulationFilterData(filter); wheelsSimData->setSceneQueryFilterData(i, filter); + + // Remove wheels from the simulation (suspension force hold the vehicle) + wheelShape->setFlag(PxShapeFlag::eSIMULATION_SHAPE, false); } else { @@ -520,12 +525,16 @@ void WheeledVehicle::DrawPhysicsDebug(RenderView& view) auto& wheel = _wheels[wheelIndex]; if (wheel.Collider && wheel.Collider->GetParent() == this && !wheel.Collider->GetIsTrigger()) { - const Vector3 basePos = wheel.Collider->GetPosition(); - const Vector3 currentPos = basePos + Vector3(0, data.State.SuspensionOffset, 0); + const Vector3 currentPos = wheel.Collider->GetPosition(); + const Vector3 basePos = currentPos - Vector3(0, data.State.SuspensionOffset, 0); DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(basePos, wheel.Radius * 0.07f), Color::Blue * 0.3f, 0, true); DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(currentPos, wheel.Radius * 0.08f), Color::Blue * 0.8f, 0, true); DEBUG_DRAW_LINE(basePos, currentPos, Color::Blue, 0, true); DEBUG_DRAW_WIRE_CYLINDER(currentPos, wheel.Collider->GetOrientation(), wheel.Radius, wheel.Width, Color::Red * 0.8f, 0, true); + if (!data.State.IsInAir) + { + DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(data.State.TireContactPoint, 5.0f), Color::Green, 0, true); + } } } } @@ -546,12 +555,22 @@ void WheeledVehicle::OnDebugDrawSelected() auto& wheel = _wheels[wheelIndex]; if (wheel.Collider && wheel.Collider->GetParent() == this && !wheel.Collider->GetIsTrigger()) { - const Vector3 basePos = wheel.Collider->GetPosition(); - const Vector3 currentPos = basePos + Vector3(0, data.State.SuspensionOffset, 0); + const Vector3 currentPos = wheel.Collider->GetPosition(); + const Vector3 basePos = currentPos - Vector3(0, data.State.SuspensionOffset, 0); DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(basePos, wheel.Radius * 0.07f), Color::Blue * 0.3f, 0, false); DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(currentPos, wheel.Radius * 0.08f), Color::Blue * 0.8f, 0, false); + DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(P2C(_actor->getGlobalPose().transform(wheel.Collider->GetPxShape()->getLocalPose()).p), wheel.Radius * 0.11f), Color::OrangeRed * 0.8f, 0, false); DEBUG_DRAW_LINE(basePos, currentPos, Color::Blue, 0, false); DEBUG_DRAW_WIRE_CYLINDER(currentPos, wheel.Collider->GetOrientation(), wheel.Radius, wheel.Width, Color::Red * 0.4f, 0, false); + if (!data.State.SuspensionTraceStart.IsZero()) + { + DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(data.State.SuspensionTraceStart, 5.0f), Color::AliceBlue, 0, false); + DEBUG_DRAW_LINE(data.State.SuspensionTraceStart, data.State.SuspensionTraceEnd, data.State.IsInAir ? Color::Red : Color::Green, 0, false); + } + if (!data.State.IsInAir) + { + DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(data.State.TireContactPoint, 5.0f), Color::Green, 0, false); + } } } diff --git a/Source/Engine/Physics/Actors/WheeledVehicle.h b/Source/Engine/Physics/Actors/WheeledVehicle.h index bbccfd76b..b7ce2000b 100644 --- a/Source/Engine/Physics/Actors/WheeledVehicle.h +++ b/Source/Engine/Physics/Actors/WheeledVehicle.h @@ -172,67 +172,67 @@ public: /// /// Wheel placement type. /// - API_FIELD() WheelTypes Type = WheelTypes::FrontLeft; + API_FIELD(Attributes="EditorOrder(0)") WheelTypes Type = WheelTypes::FrontLeft; /// /// Combined mass of the wheel and the tire in kg. Typically, a wheel has mass between 20Kg and 80Kg but can be lower and higher depending on the vehicle. /// - API_FIELD() float Mass = 20.0f; + API_FIELD(Attributes="EditorOrder(1)") float Mass = 20.0f; /// /// Distance in metres between the center of the wheel and the outside rim of the tire. It is important that the value of the radius closely matches the radius of the render mesh of the wheel. Any mismatch will result in the wheels either hovering above the ground or intersecting the ground. /// - API_FIELD() float Radius = 50.0f; + API_FIELD(Attributes="EditorOrder(2)") float Radius = 50.0f; /// /// Full width of the wheel in metres. This parameter has no bearing on the handling but is a very useful parameter to have when trying to render debug data relating to the wheel/tire/suspension. /// - API_FIELD() float Width = 20.0f; + API_FIELD(Attributes="EditorOrder(3)") float Width = 20.0f; /// /// Max steer angle that can be achieved by the wheel (in degrees). /// - API_FIELD(Attributes="Limit(0)") float MaxSteerAngle = 0.0f; + API_FIELD(Attributes="Limit(0), EditorDisplay(\"Steering\"), EditorOrder(10)") float MaxSteerAngle = 0.0f; /// /// Damping rate applied to wheel. Specified in kilograms metres-squared per second (kg m^2 s^-1). /// - API_FIELD(Attributes="Limit(0)") float DampingRate = 0.25f; + API_FIELD(Attributes="Limit(0), EditorDisplay(\"Steering\"), EditorOrder(11)") float DampingRate = 0.25f; /// /// Max brake torque that can be applied to wheel. Specified in kilograms metres-squared per second-squared (kg m^2 s^-2) /// - API_FIELD(Attributes="Limit(0)") float MaxBrakeTorque = 1500.0f; + API_FIELD(Attributes="Limit(0), EditorDisplay(\"Steering\"), EditorOrder(12)") float MaxBrakeTorque = 1500.0f; /// /// Max handbrake torque that can be applied to wheel. Specified in kilograms metres-squared per second-squared (kg m^2 s^-2) /// - API_FIELD(Attributes="Limit(0)") float MaxHandBrakeTorque = 2000.0f; + API_FIELD(Attributes="Limit(0), EditorDisplay(\"Steering\"), EditorOrder(13)") float MaxHandBrakeTorque = 2000.0f; /// /// Collider that represents the wheel shape and it's placement. Has to be attached as a child to the vehicle. Triangle mesh collider is not supported (use convex mesh or basic shapes). /// - API_FIELD() ScriptingObjectReference Collider; + API_FIELD(Attributes="EditorOrder(4)") ScriptingObjectReference Collider; /// /// Spring damper rate of suspension unit. /// - API_FIELD(Attributes="Limit(0)") float SuspensionDampingRate = 1.0f; + API_FIELD(Attributes="Limit(0), EditorDisplay(\"Suspension\"), EditorOrder(20)") float SuspensionDampingRate = 1.0f; /// /// The maximum offset for the suspension that wheel can go above resting location. /// - API_FIELD(Attributes="Limit(0)") float SuspensionMaxRaise = 10.0f; + API_FIELD(Attributes="Limit(0), EditorDisplay(\"Suspension\"), EditorOrder(21)") float SuspensionMaxRaise = 10.0f; /// /// The maximum offset for the suspension that wheel can go below resting location. /// - API_FIELD(Attributes="Limit(0)") float SuspensionMaxDrop = 10.0f; + API_FIELD(Attributes="Limit(0), EditorDisplay(\"Suspension\"), EditorOrder(22)") float SuspensionMaxDrop = 10.0f; /// /// The vertical offset from where suspension forces are applied. /// - API_FIELD() float SuspensionForceOffset = 0.0f; + API_FIELD(Attributes="EditorDisplay(\"Suspension\"), EditorOrder(23)") float SuspensionForceOffset = 0.0f; }; /// @@ -281,6 +281,18 @@ public: /// The compression of the suspension spring. Offsets the wheel location. /// API_FIELD() float SuspensionOffset = 0.0f; + +#if USE_EDITOR + /// + /// The start location of the suspension raycast start (Editor only for debugging). + /// + API_FIELD() Vector3 SuspensionTraceStart = Vector3::Zero; + + /// + /// The start location of the suspension raycast end (Editor only for debugging). + /// + API_FIELD() Vector3 SuspensionTraceEnd = Vector3::Zero; +#endif }; private: @@ -290,12 +302,6 @@ private: Collider* Collider; Quaternion LocalOrientation; WheelState State; - struct ChildPose - { - Actor* Child; - Vector3 Pose; - }; - Array> ChildrenPoses; }; void* _drive = nullptr; diff --git a/Source/Engine/Physics/Physics.cpp b/Source/Engine/Physics/Physics.cpp index fb34eff1e..684f852ef 100644 --- a/Source/Engine/Physics/Physics.cpp +++ b/Source/Engine/Physics/Physics.cpp @@ -789,8 +789,8 @@ void Physics::CollectResults() { if (WheelRaycastBatchQuery) WheelRaycastBatchQuery->release(); - WheelQueryResults.Resize(wheelsCount); - WheelQueryResults.Resize(WheelQueryResults.Capacity()); + WheelQueryResults.Resize(wheelsCount, false); + WheelHitResults.Resize(wheelsCount, false); PxBatchQueryDesc desc(wheelsCount, 0, 0); desc.queryMemory.userRaycastResultBuffer = WheelQueryResults.Get(); desc.queryMemory.userRaycastTouchBuffer = WheelHitResults.Get(); @@ -857,23 +857,21 @@ void Physics::CollectResults() state.SteerAngle = RadiansToDegrees * perWheel.steerAngle; state.RotationAngle = -RadiansToDegrees * drive->mWheelsDynData.getWheelRotationAngle(j); state.SuspensionOffset = perWheel.suspJounce; +#if USE_EDITOR + state.SuspensionTraceStart = P2C(perWheel.suspLineStart); + state.SuspensionTraceEnd = P2C(perWheel.suspLineStart + perWheel.suspLineDir * perWheel.suspLineLength); +#endif - // Rotate wheel - wheelData.Collider->SetLocalOrientation(Quaternion::Euler(0, state.SteerAngle, state.RotationAngle) * wheelData.LocalOrientation); + if (!wheelData.Collider) + continue; + auto shape = wheelData.Collider->GetPxShape(); - // Apply suspension offset (cannot move collider because it breaks driving so move it's children but preserve the initial pose) - for (auto child : wheelData.Collider->Children) - { - int32 poseIndex = 0; - for (; poseIndex < wheelData.ChildrenPoses.Count(); poseIndex++) - { - if (wheelData.ChildrenPoses[poseIndex].Child == child) - break; - } - if (poseIndex == wheelData.ChildrenPoses.Count()) - wheelData.ChildrenPoses.Add({ child, child->GetLocalPosition() }); - child->SetPosition(wheelData.Collider->GetTransform().LocalToWorld(wheelData.ChildrenPoses[poseIndex].Pose) + Vector3(0, perWheel.suspJounce, 0)); - } + // Update wheel collider transformation + auto localPose = shape->getLocalPose(); + Transform t = wheelData.Collider->GetLocalTransform(); + t.Orientation = Quaternion::Euler(0, state.SteerAngle, state.RotationAngle) * wheelData.LocalOrientation; + t.Translation = P2C(localPose.p) / wheelVehicle->GetScale() - t.Orientation * wheelData.Collider->GetCenter(); + wheelData.Collider->SetLocalTransform(t); } } } From 5e3fe56aa7dab69fd80cf80e36ad5f0049f32fd8 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 20 Aug 2021 16:23:45 +0200 Subject: [PATCH 45/53] Add digital steering option for a vehicle --- .../Engine/Physics/Actors/WheeledVehicle.cpp | 2 + Source/Engine/Physics/Actors/WheeledVehicle.h | 6 ++ Source/Engine/Physics/Physics.cpp | 87 +++++++++++++++---- 3 files changed, 76 insertions(+), 19 deletions(-) diff --git a/Source/Engine/Physics/Actors/WheeledVehicle.cpp b/Source/Engine/Physics/Actors/WheeledVehicle.cpp index 7d86b87bd..3d56be785 100644 --- a/Source/Engine/Physics/Actors/WheeledVehicle.cpp +++ b/Source/Engine/Physics/Actors/WheeledVehicle.cpp @@ -591,6 +591,7 @@ void WheeledVehicle::Serialize(SerializeStream& stream, const void* otherObj) SERIALIZE_MEMBER(DriveType, _driveType); SERIALIZE_MEMBER(Wheels, _wheels); SERIALIZE(UseReverseAsBrake); + SERIALIZE(UseAnalogSteering); SERIALIZE_MEMBER(Engine, _engine); SERIALIZE_MEMBER(Differential, _differential); SERIALIZE_MEMBER(Gearbox, _gearbox); @@ -603,6 +604,7 @@ void WheeledVehicle::Deserialize(DeserializeStream& stream, ISerializeModifier* DESERIALIZE_MEMBER(DriveType, _driveType); DESERIALIZE_MEMBER(Wheels, _wheels); DESERIALIZE(UseReverseAsBrake); + DESERIALIZE(UseAnalogSteering); DESERIALIZE_MEMBER(Engine, _engine); DESERIALIZE_MEMBER(Differential, _differential); DESERIALIZE_MEMBER(Gearbox, _gearbox); diff --git a/Source/Engine/Physics/Actors/WheeledVehicle.h b/Source/Engine/Physics/Actors/WheeledVehicle.h index b7ce2000b..59d1a54d2 100644 --- a/Source/Engine/Physics/Actors/WheeledVehicle.h +++ b/Source/Engine/Physics/Actors/WheeledVehicle.h @@ -321,6 +321,12 @@ public: API_FIELD(Attributes="EditorOrder(0), EditorDisplay(\"Vehicle\")") bool UseReverseAsBrake = true; + /// + /// If checked, the vehicle driving and steering inputs will be used as analog values (from gamepad), otherwise will be used as digital input (from keyboard). + /// + API_FIELD(Attributes="EditorOrder(1), EditorDisplay(\"Vehicle\")") + bool UseAnalogSteering = false; + /// /// Gets the vehicle driving model type. /// diff --git a/Source/Engine/Physics/Physics.cpp b/Source/Engine/Physics/Physics.cpp index 684f852ef..a0b4d67dd 100644 --- a/Source/Engine/Physics/Physics.cpp +++ b/Source/Engine/Physics/Physics.cpp @@ -744,6 +744,23 @@ void Physics::CollectResults() 5.0f, // fall rate eANALOG_INPUT_STEER_RIGHT } }; + PxVehicleKeySmoothingData keySmoothing = + { + { + 3.0f, // rise rate eANALOG_INPUT_ACCEL + 3.0f, // rise rate eANALOG_INPUT_BRAKE + 10.0f, // rise rate eANALOG_INPUT_HANDBRAKE + 2.5f, // rise rate eANALOG_INPUT_STEER_LEFT + 2.5f, // rise rate eANALOG_INPUT_STEER_RIGHT + }, + { + 5.0f, // fall rate eANALOG_INPUT__ACCEL + 5.0f, // fall rate eANALOG_INPUT__BRAKE + 10.0f, // fall rate eANALOG_INPUT__HANDBRAKE + 5.0f, // fall rate eANALOG_INPUT_STEER_LEFT + 5.0f // fall rate eANALOG_INPUT_STEER_RIGHT + } + }; // Reference: PhysX SDK docs // TODO: expose steer vs forward curve into per-vehicle (up to 8 points, values clamped into 0/1 range) static constexpr PxF32 steerVsForwardSpeedData[] = @@ -759,28 +776,60 @@ void Physics::CollectResults() }; const PxFixedSizeLookupTable<8> steerVsForwardSpeed(steerVsForwardSpeedData, 4); // @formatter:on - switch (wheelVehicle->_driveTypeCurrent) + if (wheelVehicle->UseAnalogSteering) { - case WheeledVehicle::DriveTypes::Drive4W: - { - PxVehicleDrive4WRawInputData rawInputData; - rawInputData.setAnalogAccel(throttle); - rawInputData.setAnalogBrake(brake); - rawInputData.setAnalogSteer(wheelVehicle->_steering); - rawInputData.setAnalogHandbrake(wheelVehicle->_handBrake); - PxVehicleDrive4WSmoothAnalogRawInputsAndSetAnalogInputs(padSmoothing, steerVsForwardSpeed, rawInputData, LastDeltaTime, false, *(PxVehicleDrive4W*)drive); - break; + switch (wheelVehicle->_driveTypeCurrent) + { + case WheeledVehicle::DriveTypes::Drive4W: + { + PxVehicleDrive4WRawInputData rawInputData; + rawInputData.setAnalogAccel(throttle); + rawInputData.setAnalogBrake(brake); + rawInputData.setAnalogSteer(wheelVehicle->_steering); + rawInputData.setAnalogHandbrake(wheelVehicle->_handBrake); + PxVehicleDrive4WSmoothAnalogRawInputsAndSetAnalogInputs(padSmoothing, steerVsForwardSpeed, rawInputData, LastDeltaTime, false, *(PxVehicleDrive4W*)drive); + break; + } + case WheeledVehicle::DriveTypes::DriveNW: + { + PxVehicleDriveNWRawInputData rawInputData; + rawInputData.setAnalogAccel(throttle); + rawInputData.setAnalogBrake(brake); + rawInputData.setAnalogSteer(wheelVehicle->_steering); + rawInputData.setAnalogHandbrake(wheelVehicle->_handBrake); + PxVehicleDriveNWSmoothAnalogRawInputsAndSetAnalogInputs(padSmoothing, steerVsForwardSpeed, rawInputData, LastDeltaTime, false, *(PxVehicleDriveNW*)drive); + break; + } + } } - case WheeledVehicle::DriveTypes::DriveNW: + else { - PxVehicleDriveNWRawInputData rawInputData; - rawInputData.setAnalogAccel(throttle); - rawInputData.setAnalogBrake(brake); - rawInputData.setAnalogSteer(wheelVehicle->_steering); - rawInputData.setAnalogHandbrake(wheelVehicle->_handBrake); - PxVehicleDriveNWSmoothAnalogRawInputsAndSetAnalogInputs(padSmoothing, steerVsForwardSpeed, rawInputData, LastDeltaTime, false, *(PxVehicleDriveNW*)drive); - break; - } + const float deadZone = 0.1f; + switch (wheelVehicle->_driveTypeCurrent) + { + case WheeledVehicle::DriveTypes::Drive4W: + { + PxVehicleDrive4WRawInputData rawInputData; + rawInputData.setDigitalAccel(throttle > deadZone); + rawInputData.setDigitalBrake(brake > deadZone); + rawInputData.setDigitalSteerLeft(wheelVehicle->_steering < -deadZone); + rawInputData.setDigitalSteerRight(wheelVehicle->_steering > deadZone); + rawInputData.setDigitalHandbrake(wheelVehicle->_handBrake > deadZone); + PxVehicleDrive4WSmoothDigitalRawInputsAndSetAnalogInputs(keySmoothing, steerVsForwardSpeed, rawInputData, LastDeltaTime, false, *(PxVehicleDrive4W*)drive); + break; + } + case WheeledVehicle::DriveTypes::DriveNW: + { + PxVehicleDriveNWRawInputData rawInputData; + rawInputData.setDigitalAccel(throttle > deadZone); + rawInputData.setDigitalBrake(brake > deadZone); + rawInputData.setDigitalSteerLeft(wheelVehicle->_steering < -deadZone); + rawInputData.setDigitalSteerRight(wheelVehicle->_steering > deadZone); + rawInputData.setDigitalHandbrake(wheelVehicle->_handBrake > deadZone); + PxVehicleDriveNWSmoothDigitalRawInputsAndSetAnalogInputs(keySmoothing, steerVsForwardSpeed, rawInputData, LastDeltaTime, false, *(PxVehicleDriveNW*)drive); + break; + } + } } } From 759845c37b175f91ce75ae868d3a8fca56166abe Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 20 Aug 2021 16:33:11 +0200 Subject: [PATCH 46/53] Add wheeled vehicle tire options --- Source/Engine/Physics/Actors/WheeledVehicle.cpp | 3 +++ Source/Engine/Physics/Actors/WheeledVehicle.h | 15 +++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/Source/Engine/Physics/Actors/WheeledVehicle.cpp b/Source/Engine/Physics/Actors/WheeledVehicle.cpp index 3d56be785..324d51c43 100644 --- a/Source/Engine/Physics/Actors/WheeledVehicle.cpp +++ b/Source/Engine/Physics/Actors/WheeledVehicle.cpp @@ -312,6 +312,9 @@ void WheeledVehicle::Setup() PxVehicleTireData tire; tire.mType = 0; + tire.mLatStiffX = wheel.TireLateralMax; + tire.mLatStiffY = wheel.TireLateralStiffness; + tire.mLongitudinalStiffnessPerUnitGravity = wheel.TireLongitudinalStiffness; PxVehicleWheelData wheelData; wheelData.mMass = wheel.Mass; diff --git a/Source/Engine/Physics/Actors/WheeledVehicle.h b/Source/Engine/Physics/Actors/WheeledVehicle.h index 59d1a54d2..dfdb1da50 100644 --- a/Source/Engine/Physics/Actors/WheeledVehicle.h +++ b/Source/Engine/Physics/Actors/WheeledVehicle.h @@ -233,6 +233,21 @@ public: /// The vertical offset from where suspension forces are applied. /// API_FIELD(Attributes="EditorDisplay(\"Suspension\"), EditorOrder(23)") float SuspensionForceOffset = 0.0f; + + /// + /// The tire lateral stiffness to have given lateral slip. + /// + API_FIELD(Attributes="EditorDisplay(\"Tire\"), EditorOrder(30)") float TireLateralStiffness = 17.0f; + + /// + /// The maximum tire load (normalized) at which tire cannot provide more lateral stiffness (no matter how much extra load is applied to it). + /// + API_FIELD(Attributes="EditorDisplay(\"Tire\"), EditorOrder(31)") float TireLateralMax = 2.0f; + + /// + /// The tire longitudinal stiffness to have given longitudinal slip. + /// + API_FIELD(Attributes="EditorDisplay(\"Tire\"), EditorOrder(32)") float TireLongitudinalStiffness = 1000.0f; }; /// From 7dfe709196ab0c908ed421d2cf1f4e6fd67da31d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 20 Aug 2021 16:33:31 +0200 Subject: [PATCH 47/53] Fix suspension force offset location --- Source/Engine/Physics/Actors/WheeledVehicle.cpp | 2 +- Source/Engine/Physics/Actors/WheeledVehicle.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Physics/Actors/WheeledVehicle.cpp b/Source/Engine/Physics/Actors/WheeledVehicle.cpp index 324d51c43..7c6106158 100644 --- a/Source/Engine/Physics/Actors/WheeledVehicle.cpp +++ b/Source/Engine/Physics/Actors/WheeledVehicle.cpp @@ -327,7 +327,7 @@ void WheeledVehicle::Setup() wheelData.mMaxHandBrakeTorque = M2ToCm2(wheel.MaxHandBrakeTorque); PxVec3 centreOffset = centerOfMassOffset.transformInv(offsets[i]); - PxVec3 forceAppPointOffset(centreOffset.z, wheel.SuspensionForceOffset, centreOffset.z); + PxVec3 forceAppPointOffset(centreOffset.x, wheel.SuspensionForceOffset, centreOffset.z); wheelsSimData->setTireData(i, tire); wheelsSimData->setWheelData(i, wheelData); diff --git a/Source/Engine/Physics/Actors/WheeledVehicle.h b/Source/Engine/Physics/Actors/WheeledVehicle.h index dfdb1da50..bbc52af5b 100644 --- a/Source/Engine/Physics/Actors/WheeledVehicle.h +++ b/Source/Engine/Physics/Actors/WheeledVehicle.h @@ -230,7 +230,7 @@ public: API_FIELD(Attributes="Limit(0), EditorDisplay(\"Suspension\"), EditorOrder(22)") float SuspensionMaxDrop = 10.0f; /// - /// The vertical offset from where suspension forces are applied. + /// The vertical offset from where suspension forces are applied (relative to the vehicle center of mass). The suspension force is applies on the vertical axis going though the wheel center. /// API_FIELD(Attributes="EditorDisplay(\"Suspension\"), EditorOrder(23)") float SuspensionForceOffset = 0.0f; From e312485a348dd581b6964ae269eca7491d1e8b78 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 21 Aug 2021 13:35:32 +0200 Subject: [PATCH 48/53] Bump up build number --- Flax.flaxproj | 2 +- Source/FlaxEngine.Gen.cs | 4 ++-- Source/FlaxEngine.Gen.h | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Flax.flaxproj b/Flax.flaxproj index 9583c5f1c..e9de8d595 100644 --- a/Flax.flaxproj +++ b/Flax.flaxproj @@ -3,7 +3,7 @@ "Version": { "Major": 1, "Minor": 2, - "Build": 6223 + "Build": 6224 }, "Company": "Flax", "Copyright": "Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.", diff --git a/Source/FlaxEngine.Gen.cs b/Source/FlaxEngine.Gen.cs index d90410354..a2b7102e3 100644 --- a/Source/FlaxEngine.Gen.cs +++ b/Source/FlaxEngine.Gen.cs @@ -13,5 +13,5 @@ using System.Runtime.InteropServices; [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: Guid("b8442186-4a70-7c85-704a-857c262d00f6")] -[assembly: AssemblyVersion("1.2.6223")] -[assembly: AssemblyFileVersion("1.2.6223")] +[assembly: AssemblyVersion("1.2.6224")] +[assembly: AssemblyFileVersion("1.2.6224")] diff --git a/Source/FlaxEngine.Gen.h b/Source/FlaxEngine.Gen.h index 890950c14..016f48c0e 100644 --- a/Source/FlaxEngine.Gen.h +++ b/Source/FlaxEngine.Gen.h @@ -3,11 +3,11 @@ #pragma once #define FLAXENGINE_NAME "FlaxEngine" -#define FLAXENGINE_VERSION Version(1, 2, 6223) -#define FLAXENGINE_VERSION_TEXT "1.2.6223" +#define FLAXENGINE_VERSION Version(1, 2, 6224) +#define FLAXENGINE_VERSION_TEXT "1.2.6224" #define FLAXENGINE_VERSION_MAJOR 1 #define FLAXENGINE_VERSION_MINOR 2 -#define FLAXENGINE_VERSION_BUILD 6223 +#define FLAXENGINE_VERSION_BUILD 6224 #define FLAXENGINE_COMPANY "Flax" #define FLAXENGINE_COPYRIGHT "Copyright (c) 2012-2021 Wojciech Figat. All rights reserved." From d4a4ae996e7ead23ec323010aa990820be192972 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 20 Aug 2021 16:32:47 +0200 Subject: [PATCH 49/53] Add vehicle telemetry logging debug option --- Source/Engine/Physics/Physics.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Source/Engine/Physics/Physics.cpp b/Source/Engine/Physics/Physics.cpp index a0b4d67dd..aca6f6242 100644 --- a/Source/Engine/Physics/Physics.cpp +++ b/Source/Engine/Physics/Physics.cpp @@ -31,6 +31,12 @@ // Temporary memory size used by the PhysX during the simulation. Must be multiply of 4kB and 16bit aligned. #define SCRATCH_BLOCK_SIZE (1024 * 128) +#define PHYSX_VEHICLE_DEBUG_TELEMETRY 0 + +#if PHYSX_VEHICLE_DEBUG_TELEMETRY +#include "Engine/Core/Utilities.h" +#endif + class PhysXAllocator : public PxAllocatorCallback { public: @@ -890,12 +896,18 @@ void Physics::CollectResults() auto drive = WheelVehiclesCache[ii]; auto& perVehicle = WheelVehiclesResultsPerVehicle[ii]; ii++; +#if PHYSX_VEHICLE_DEBUG_TELEMETRY + LOG(Info, "Vehicle[{}] Gear={}, RPM={}", ii, wheelVehicle->GetCurrentGear(), (int32)wheelVehicle->GetEngineRotationSpeed()); +#endif // Update wheels for (int32 j = 0; j < wheelVehicle->_wheelsData.Count(); j++) { auto& wheelData = wheelVehicle->_wheelsData[j]; auto& perWheel = perVehicle.wheelQueryResults[j]; +#if PHYSX_VEHICLE_DEBUG_TELEMETRY + LOG(Info, "Vehicle[{}] Wheel[{}] longitudinalSlip={}, lateralSlip={}, suspSpringForce={}", ii, j, Utilities::RoundTo2DecimalPlaces(perWheel.longitudinalSlip), Utilities::RoundTo2DecimalPlaces(perWheel.lateralSlip), (int32)perWheel.suspSpringForce); +#endif auto& state = wheelData.State; state.IsInAir = perWheel.isInAir; From 6fa1fc462f2c6d1b29a35024991308cdc3c0461f Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Mon, 23 Aug 2021 10:27:22 +0200 Subject: [PATCH 50/53] Add Ramsay quote --- Source/Editor/Windows/SplashScreen.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/Editor/Windows/SplashScreen.cpp b/Source/Editor/Windows/SplashScreen.cpp index 4a107e467..cf15dac5d 100644 --- a/Source/Editor/Windows/SplashScreen.cpp +++ b/Source/Editor/Windows/SplashScreen.cpp @@ -131,7 +131,8 @@ const Char* SplashScreenQuotes[] = TEXT("ZOINKS"), TEXT("Scooby dooby doo"), TEXT("You shall not load!"), - TEXT("The roof, the roof, the roof is on fire!") + TEXT("The roof, the roof, the roof is on fire!"), + TEXT("I've seen better documentation ...\nFrom ransomware gangs !") }; SplashScreen::~SplashScreen() From bee117f86bf653f16e7292e0ec272889c0e0c447 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 23 Aug 2021 10:54:19 +0200 Subject: [PATCH 51/53] Optimize Content Finder popup --- Source/Editor/Modules/ContentFindingModule.cs | 26 +++++++++++++------ .../Surface/ContextMenu/ContentFinder.cs | 9 +++++-- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/Source/Editor/Modules/ContentFindingModule.cs b/Source/Editor/Modules/ContentFindingModule.cs index b8f7f74a0..0669f3dd9 100644 --- a/Source/Editor/Modules/ContentFindingModule.cs +++ b/Source/Editor/Modules/ContentFindingModule.cs @@ -153,17 +153,16 @@ namespace FlaxEditor.Modules }; } } + Profiler.BeginEvent("ContentFinding.Search"); string type = ".*"; string name = charsToFind.Trim(); - if (charsToFind.Contains(':')) { var args = charsToFind.Split(':'); type = ".*" + args[1].Trim() + ".*"; name = ".*" + args[0].Trim() + ".*"; } - if (name.Equals(string.Empty)) name = ".*"; @@ -173,17 +172,28 @@ namespace FlaxEditor.Modules foreach (var project in Editor.Instance.ContentDatabase.Projects) { + Profiler.BeginEvent(project.Project.Name); ProcessItems(nameRegex, typeRegex, project.Folder.Children, matches); + Profiler.EndEvent(); } //ProcessSceneNodes(nameRegex, typeRegex, Editor.Instance.Scene.Root, matches); - ProcessActors(nameRegex, typeRegex, Editor.Instance.Scene.Root, matches); - - _quickActions.ForEach(action => { - if (nameRegex.Match(action.Name).Success && typeRegex.Match("Quick Action").Success) - matches.Add(new SearchResult { Name = action.Name, Type = "Quick Action", Item = action }); - }); + Profiler.BeginEvent("Actors"); + ProcessActors(nameRegex, typeRegex, Editor.Instance.Scene.Root, matches); + Profiler.EndEvent(); + } + { + Profiler.BeginEvent("QuickActions"); + _quickActions.ForEach(action => + { + if (nameRegex.Match(action.Name).Success && typeRegex.Match("Quick Action").Success) + matches.Add(new SearchResult { Name = action.Name, Type = "Quick Action", Item = action }); + }); + Profiler.EndEvent(); + } + + Profiler.EndEvent(); return matches; } diff --git a/Source/Editor/Surface/ContextMenu/ContentFinder.cs b/Source/Editor/Surface/ContextMenu/ContentFinder.cs index fc0f43472..3fe939468 100644 --- a/Source/Editor/Surface/ContextMenu/ContentFinder.cs +++ b/Source/Editor/Surface/ContextMenu/ContentFinder.cs @@ -107,14 +107,18 @@ namespace FlaxEditor.Surface.ContextMenu private void BuildList(List items) { _resultPanel.DisposeChildren(); + LockChildrenRecursive(); var dpiScale = DpiScale; + var window = RootWindow.Window; if (items.Count == 0) { Height = _searchBox.Height + 1; _resultPanel.ScrollBars = ScrollBars.None; - RootWindow.Window.ClientSize = new Vector2(RootWindow.Window.ClientSize.X, Height * dpiScale); + window.ClientSize = new Vector2(window.ClientSize.X, Height * dpiScale); + UnlockChildrenRecursive(); + PerformLayout(); return; } @@ -148,8 +152,9 @@ namespace FlaxEditor.Surface.ContextMenu MatchedItems.Add(searchItem); } - RootWindow.Window.ClientSize = new Vector2(RootWindow.Window.ClientSize.X, Height * dpiScale); + window.ClientSize = new Vector2(window.ClientSize.X, Height * dpiScale); + UnlockChildrenRecursive(); PerformLayout(); } From 347eaa9d075adbf5921f268731a283d5f721195f Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 23 Aug 2021 13:18:41 +0200 Subject: [PATCH 52/53] Optimize timeline background rendering when zoom is very high #519 --- Source/Editor/GUI/Timeline/GUI/Background.cs | 30 ++++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/Source/Editor/GUI/Timeline/GUI/Background.cs b/Source/Editor/GUI/Timeline/GUI/Background.cs index 8269fc29e..cc143e945 100644 --- a/Source/Editor/GUI/Timeline/GUI/Background.cs +++ b/Source/Editor/GUI/Timeline/GUI/Background.cs @@ -46,10 +46,16 @@ namespace FlaxEditor.GUI.Timeline.GUI var areaLeft = -X; var areaRight = Parent.Width + mediaBackground.ControlsBounds.BottomRight.X; var height = Height; - var leftSideMin = PointFromParent(Vector2.Zero); - var leftSideMax = BottomLeft; - var rightSideMin = UpperRight; - var rightSideMax = PointFromParent(Parent.BottomRight) + mediaBackground.ControlsBounds.BottomRight; + + // Calculate the timeline range in the view to optimize background drawing + Render2D.PeekClip(out var globalClipping); + Render2D.PeekTransform(out var globalTransform); + var globalRect = new Rectangle(globalTransform.M31 + areaLeft, globalTransform.M32, areaRight * globalTransform.M11, height * globalTransform.M22); + var globalMask = Rectangle.Shared(globalClipping, globalRect); + var globalTransformInv = Matrix3x3.Invert(globalTransform); + var localRect = Rectangle.FromPoints(Matrix3x3.Transform2D(globalMask.UpperLeft, globalTransformInv), Matrix3x3.Transform2D(globalMask.BottomRight, globalTransformInv)); + var localRectMin = localRect.UpperLeft; + var localRectMax = localRect.BottomRight; // Draw lines between tracks Render2D.DrawLine(new Vector2(areaLeft, 0.5f), new Vector2(areaRight, 0.5f), linesColor); @@ -77,8 +83,8 @@ namespace FlaxEditor.GUI.Timeline.GUI var minDistanceBetweenTicks = 50.0f; var maxDistanceBetweenTicks = 100.0f; var zoom = Timeline.UnitsPerSecond * _timeline.Zoom; - var left = Vector2.Min(leftSideMin, rightSideMax).X; - var right = Vector2.Max(leftSideMin, rightSideMax).X; + var left = Vector2.Min(localRectMin, localRectMax).X; + var right = Vector2.Max(localRectMin, localRectMax).X; var leftFrame = Mathf.Floor((left - Timeline.StartOffset) / zoom) * _timeline.FramesPerSecond; var rightFrame = Mathf.Ceil((right - Timeline.StartOffset) / zoom) * _timeline.FramesPerSecond; var min = leftFrame; @@ -146,9 +152,15 @@ namespace FlaxEditor.GUI.Timeline.GUI } // Darken area outside the duration - var outsideDurationAreaColor = new Color(0, 0, 0, 100); - Render2D.FillRectangle(new Rectangle(leftSideMin, leftSideMax.X - leftSideMin.X, height), outsideDurationAreaColor); - Render2D.FillRectangle(new Rectangle(rightSideMin, rightSideMax.X - rightSideMin.X, height), outsideDurationAreaColor); + { + var outsideDurationAreaColor = new Color(0, 0, 0, 100); + var leftSideMin = PointFromParent(Vector2.Zero); + var leftSideMax = BottomLeft; + var rightSideMin = UpperRight; + var rightSideMax = PointFromParent(Parent.BottomRight) + mediaBackground.ControlsBounds.BottomRight; + Render2D.FillRectangle(new Rectangle(leftSideMin, leftSideMax.X - leftSideMin.X, height), outsideDurationAreaColor); + Render2D.FillRectangle(new Rectangle(rightSideMin, rightSideMax.X - rightSideMin.X, height), outsideDurationAreaColor); + } // Draw time axis header var timeAxisHeaderOffset = -_timeline.MediaBackground.ViewOffset.Y; From 052366be66cb5306195f30fc673f7f22b1371668 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 23 Aug 2021 13:39:07 +0200 Subject: [PATCH 53/53] Optimize audio preview rendering when zoom is very high #519 --- .../Viewport/Previews/AudioClipPreview.cs | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/Source/Editor/Viewport/Previews/AudioClipPreview.cs b/Source/Editor/Viewport/Previews/AudioClipPreview.cs index 9428a2e3f..e039be4e5 100644 --- a/Source/Editor/Viewport/Previews/AudioClipPreview.cs +++ b/Source/Editor/Viewport/Previews/AudioClipPreview.cs @@ -145,18 +145,31 @@ namespace FlaxEditor.Viewport.Previews const uint maxSamplesPerIndex = 64; uint samplesPerIndexDiff = Math.Max(1, samplesPerIndex / Math.Min(samplesPerIndex, maxSamplesPerIndex)); + // Calculate the clip range in the view to optimize drawing (eg. if only part fo the clip is visible) + Render2D.PeekClip(out var globalClipping); + Render2D.PeekTransform(out var globalTransform); + var globalRect = new Rectangle(globalTransform.M31, globalTransform.M32, width * globalTransform.M11, height * globalTransform.M22); + var globalMask = Rectangle.Shared(globalClipping, globalRect); + var globalTransformInv = Matrix3x3.Invert(globalTransform); + var localRect = Rectangle.FromPoints(Matrix3x3.Transform2D(globalMask.UpperLeft, globalTransformInv), Matrix3x3.Transform2D(globalMask.BottomRight, globalTransformInv)); + var localRectMin = localRect.UpperLeft; + var localRectMax = localRect.BottomRight; + // Render each clip separately for (uint clipIndex = 0; clipIndex < Mathf.CeilToInt(clipsInView); clipIndex++) { - var clipX = clipWidth * clipIndex; - var clipRight = Mathf.Min(width, clipX + clipWidth); + var clipStart = clipWidth * clipIndex; + var clipEnd = clipStart + clipWidth; + var xStart = Mathf.Max(clipStart, localRectMin.X); + var xEnd = Mathf.Min(Mathf.Min(width, clipEnd), localRectMax.X); + var samplesOffset = (uint)((xStart - clipStart) * samplesPerIndex); // Render every audio channel separately for (uint channelIndex = 0; channelIndex < info.NumChannels; channelIndex++) { - uint currentSample = channelIndex; + uint currentSample = channelIndex + samplesOffset; float yCenter = Y + ((2 * channelIndex) + 1) * height / (2.0f * info.NumChannels); - for (float pixelX = clipX; pixelX < clipRight; pixelX++) + for (float pixelX = xStart; pixelX < xEnd; pixelX++) { float samplesSum = 0; int samplesInPixel = 0;