diff --git a/Source/Editor/Gizmo/TransformGizmoBase.Draw.cs b/Source/Editor/Gizmo/TransformGizmoBase.Draw.cs index 5fb5c12f2..b3609783f 100644 --- a/Source/Editor/Gizmo/TransformGizmoBase.Draw.cs +++ b/Source/Editor/Gizmo/TransformGizmoBase.Draw.cs @@ -54,7 +54,10 @@ namespace FlaxEditor.Gizmo if (!_isActive || !IsActive) return; - Matrix m1, m2, m3; + // As all axisMesh have the same pivot, add a little offset to the x axisMesh, this way SortDrawCalls is able to sort the draw order + // https://github.com/FlaxEngine/FlaxEngine/issues/680 + + Matrix m1, m2, m3 , mx1; bool isXAxis = _activeAxis == Axis.X || _activeAxis == Axis.XY || _activeAxis == Axis.ZX; bool isYAxis = _activeAxis == Axis.Y || _activeAxis == Axis.XY || _activeAxis == Axis.YZ; bool isZAxis = _activeAxis == Axis.Z || _activeAxis == Axis.YZ || _activeAxis == Axis.ZX; @@ -70,6 +73,8 @@ namespace FlaxEditor.Gizmo break; Matrix.Scaling(gizmoModelsScale2RealGizmoSize, out m3); Matrix.Multiply(ref m3, ref world, out m1); + mx1 = m1; + mx1.M41 += 0.05f; var axisMesh = _modelTranslateAxis.LODs[0].Meshes[0]; var boxMesh = _modelBox.LODs[0].Meshes[0]; var boxSize = 10.0f; @@ -90,7 +95,7 @@ namespace FlaxEditor.Gizmo boxMesh.Draw(ref renderContext, _activeAxis == Axis.YZ ? _materialWireFocus : _materialWire, ref m3); // X axis - axisMesh.Draw(ref renderContext, isXAxis ? _materialAxisFocus : _materialAxisX, ref m1); + axisMesh.Draw(ref renderContext, isXAxis ? _materialAxisFocus : _materialAxisX, ref mx1); // Y axis Matrix.RotationZ(Mathf.PiOverTwo, out m2); @@ -143,12 +148,15 @@ namespace FlaxEditor.Gizmo break; Matrix.Scaling(gizmoModelsScale2RealGizmoSize, out m3); Matrix.Multiply(ref m3, ref world, out m1); + mx1 = m1; + mx1.M41 -= 0.05f; + var axisMesh = _modelScaleAxis.LODs[0].Meshes[0]; var boxMesh = _modelBox.LODs[0].Meshes[0]; // X axis Matrix.RotationY(-Mathf.PiOverTwo, out m2); - Matrix.Multiply(ref m2, ref m1, out m3); + Matrix.Multiply(ref m2, ref mx1, out m3); axisMesh.Draw(ref renderContext, isXAxis ? _materialAxisFocus : _materialAxisX, ref m3); // Y axis diff --git a/Source/Editor/Gizmo/TransformGizmoBase.cs b/Source/Editor/Gizmo/TransformGizmoBase.cs index 02b9c1e8f..221ab7e86 100644 --- a/Source/Editor/Gizmo/TransformGizmoBase.cs +++ b/Source/Editor/Gizmo/TransformGizmoBase.cs @@ -86,7 +86,7 @@ namespace FlaxEditor.Gizmo /// /// Starts the objects transforming (optionally with duplicate). /// - protected void StartTransforming() + public void StartTransforming() { // Check if can start new action var count = SelectionCount; @@ -119,7 +119,7 @@ namespace FlaxEditor.Gizmo /// /// Ends the objects transforming. /// - protected void EndTransforming() + public void EndTransforming() { // Check if wasn't working at all if (!_isTransforming) @@ -287,12 +287,29 @@ namespace FlaxEditor.Gizmo delta *= 0.5f; if ((isScaling ? ScaleSnapEnabled : TranslationSnapEnable) || Owner.UseSnapping) { - float snapValue = isScaling ? ScaleSnapValue : TranslationSnapValue; + var snapValue = new Vector3(isScaling ? ScaleSnapValue : TranslationSnapValue); _translationScaleSnapDelta += delta; + if (!isScaling && snapValue.X < 0.0f) + { + // Snap to object bounding box + GetSelectedObjectsBounds(out var b, out _); + if (b.Minimum.X < 0.0f) + snapValue.X = (Real)Math.Abs(b.Minimum.X) + b.Maximum.X; + else + snapValue.X = (Real)b.Minimum.X - b.Maximum.X; + if (b.Minimum.Y < 0.0f) + snapValue.Y = (Real)Math.Abs(b.Minimum.Y) + b.Maximum.Y; + else + snapValue.Y = (Real)b.Minimum.Y - b.Maximum.Y; + if (b.Minimum.Z < 0.0f) + snapValue.Z = (Real)Math.Abs(b.Minimum.Z) + b.Maximum.Z; + else + snapValue.Z = (Real)b.Minimum.Z - b.Maximum.Z; + } delta = new Vector3( - (int)(_translationScaleSnapDelta.X / snapValue) * snapValue, - (int)(_translationScaleSnapDelta.Y / snapValue) * snapValue, - (int)(_translationScaleSnapDelta.Z / snapValue) * snapValue); + (int)(_translationScaleSnapDelta.X / snapValue.X) * snapValue.X, + (int)(_translationScaleSnapDelta.Y / snapValue.Y) * snapValue.Y, + (int)(_translationScaleSnapDelta.Z / snapValue.Z) * snapValue.Z); _translationScaleSnapDelta -= delta; } @@ -446,10 +463,9 @@ namespace FlaxEditor.Gizmo } // Apply transformation (but to the parents, not whole selection pool) - if (anyValid) + if (anyValid || (!_isTransforming && Owner.UseDuplicate)) { StartTransforming(); - LastDelta = new Transform(translationDelta, rotationDelta, scaleDelta); OnApplyTransformation(ref translationDelta, ref rotationDelta, ref scaleDelta); } diff --git a/Source/Editor/Options/InputOptions.cs b/Source/Editor/Options/InputOptions.cs index 0a1a78cab..f02d91da4 100644 --- a/Source/Editor/Options/InputOptions.cs +++ b/Source/Editor/Options/InputOptions.cs @@ -68,6 +68,10 @@ namespace FlaxEditor.Options [EditorDisplay("Common"), EditorOrder(220)] public InputBinding ContentFinder = new InputBinding(KeyboardKeys.O, KeyboardKeys.Control); + [DefaultValue(typeof(InputBinding), "R")] + [EditorDisplay("Common"), EditorOrder(230)] + public InputBinding RotateSelection = new InputBinding(KeyboardKeys.R); + #endregion #region Scene diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs index a375bdd60..c725c3b8b 100644 --- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs +++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs @@ -319,6 +319,8 @@ namespace FlaxEditor.Viewport { TooltipText = "Position snapping values" }; + if (TransformGizmo.TranslationSnapValue < 0.0f) + _translateSnapping.Text = "Bounding Box"; for (int i = 0; i < EditorViewportTranslateSnapValues.Length; i++) { @@ -326,6 +328,9 @@ namespace FlaxEditor.Viewport var button = translateSnappingCM.AddButton(v.ToString()); button.Tag = v; } + var buttonBB = translateSnappingCM.AddButton("Bounding Box").LinkTooltip("Snaps the selection based on it's bounding volume"); + buttonBB.Tag = -1.0f; + translateSnappingCM.ButtonClicked += OnWidgetTranslateSnapClick; translateSnappingCM.VisibleChanged += OnWidgetTranslateSnapShowHide; _translateSnapping.Parent = translateSnappingWidget; @@ -378,6 +383,7 @@ namespace FlaxEditor.Viewport InputActions.Add(options => options.RotateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate); InputActions.Add(options => options.ScaleMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale); InputActions.Add(options => options.FocusSelection, FocusSelection); + InputActions.Add(options => options.RotateSelection, RotateSelection); InputActions.Add(options => options.Delete, _editor.SceneEditing.Delete); } @@ -539,28 +545,24 @@ namespace FlaxEditor.Viewport private void OnTranslateSnappingToggle(ViewportWidgetButton button) { TransformGizmo.TranslationSnapEnable = !TransformGizmo.TranslationSnapEnable; - // cache value _editor.ProjectCache.SetCustomData("TranslateSnapState", TransformGizmo.TranslationSnapEnable.ToString()); } private void OnRotateSnappingToggle(ViewportWidgetButton button) { TransformGizmo.RotationSnapEnabled = !TransformGizmo.RotationSnapEnabled; - // cache value _editor.ProjectCache.SetCustomData("RotationSnapState", TransformGizmo.RotationSnapEnabled.ToString()); } private void OnScaleSnappingToggle(ViewportWidgetButton button) { TransformGizmo.ScaleSnapEnabled = !TransformGizmo.ScaleSnapEnabled; - // cache value _editor.ProjectCache.SetCustomData("ScaleSnapState", TransformGizmo.ScaleSnapEnabled.ToString()); } private void OnTransformSpaceToggle(ViewportWidgetButton button) { TransformGizmo.ToggleTransformSpace(); - // cache value _editor.ProjectCache.SetCustomData("TransformSpaceState", TransformGizmo.ActiveTransformSpace.ToString()); } @@ -591,7 +593,6 @@ namespace FlaxEditor.Viewport var v = (float)button.Tag; TransformGizmo.ScaleSnapValue = v; _scaleSnapping.Text = v.ToString(); - // cache value _editor.ProjectCache.SetCustomData("ScaleSnapValue", TransformGizmo.ScaleSnapValue.ToString("N")); } @@ -630,7 +631,6 @@ namespace FlaxEditor.Viewport var v = (float)button.Tag; TransformGizmo.RotationSnapValue = v; _rotateSnapping.Text = v.ToString(); - // cache value _editor.ProjectCache.SetCustomData("RotationSnapValue", TransformGizmo.RotationSnapValue.ToString("N")); } @@ -667,8 +667,10 @@ namespace FlaxEditor.Viewport { var v = (float)button.Tag; TransformGizmo.TranslationSnapValue = v; - _translateSnapping.Text = v.ToString(); - // cache value + if (v < 0.0f) + _translateSnapping.Text = "Bounding Box"; + else + _translateSnapping.Text = v.ToString(); _editor.ProjectCache.SetCustomData("TranslateSnapValue", TransformGizmo.TranslationSnapValue.ToString("N")); } @@ -696,6 +698,51 @@ namespace FlaxEditor.Viewport Gizmos.ForEach(x => x.OnSelectionChanged(selection)); } + /// + /// Press "R" to rotate the selected gizmo objects 45 degrees. + /// + public void RotateSelection() + { + var win = (WindowRootControl)Root; + var selection = _editor.SceneEditing.Selection; + var isShiftDown = win.GetKey(KeyboardKeys.Shift); + + Quaternion rotationDelta; + if (isShiftDown) + rotationDelta = Quaternion.Euler(0.0f, -45.0f, 0.0f); + else + rotationDelta = Quaternion.Euler(0.0f, 45.0f, 0.0f); + + bool useObjCenter = TransformGizmo.ActivePivot == TransformGizmoBase.PivotType.ObjectCenter; + Vector3 gizmoPosition = TransformGizmo.Position; + + // Rotate selected objects + bool isPlayMode = Editor.Instance.StateMachine.IsPlayMode; + TransformGizmo.StartTransforming(); + for (int i = 0; i < selection.Count; i++) + { + var obj = selection[i]; + if (isPlayMode && obj.CanTransform == false) + continue; + var trans = obj.Transform; + var pivotOffset = trans.Translation - gizmoPosition; + if (useObjCenter || pivotOffset.IsZero) + { + trans.Orientation *= Quaternion.Invert(trans.Orientation) * rotationDelta * trans.Orientation; + } + else + { + Matrix.RotationQuaternion(ref trans.Orientation, out var transWorld); + Matrix.RotationQuaternion(ref rotationDelta, out var deltaWorld); + Matrix world = transWorld * Matrix.Translation(pivotOffset) * deltaWorld * Matrix.Translation(-pivotOffset); + trans.SetRotation(ref world); + trans.Translation += world.TranslationVector; + } + obj.Transform = trans; + } + TransformGizmo.EndTransforming(); + } + /// /// Focuses the viewport on the current selection of the gizmo. /// diff --git a/Source/Engine/Audio/AudioSource.cpp b/Source/Engine/Audio/AudioSource.cpp index 870a1f41a..59d14e7d9 100644 --- a/Source/Engine/Audio/AudioSource.cpp +++ b/Source/Engine/Audio/AudioSource.cpp @@ -130,6 +130,10 @@ void AudioSource::Play() { // Request faster streaming update Clip->RequestStreamingUpdate(); + + // If we are looping and streaming also update streaming buffers + if(_loop) + RequestStreamingBuffersUpdate(); } } else diff --git a/Source/Engine/Content/Cache/AssetsCache.cpp b/Source/Engine/Content/Cache/AssetsCache.cpp index 459a9f31f..efff16a38 100644 --- a/Source/Engine/Content/Cache/AssetsCache.cpp +++ b/Source/Engine/Content/Cache/AssetsCache.cpp @@ -281,7 +281,6 @@ bool AssetsCache::FindAsset(const StringView& path, AssetInfo& info) auto& e = i->Value; if (e.Info.Path == path) { - // Validate file exists if (!IsEntryValid(e)) { LOG(Warning, "Missing file from registry: \'{0}\':{1}:{2}", e.Info.Path, e.Info.ID, e.Info.TypeName); @@ -304,15 +303,11 @@ bool AssetsCache::FindAsset(const StringView& path, AssetInfo& info) bool AssetsCache::FindAsset(const Guid& id, AssetInfo& info) { PROFILE_CPU(); - bool result = false; - ScopeLock lock(_locker); - auto e = _registry.TryGet(id); if (e != nullptr) { - // Validate entry if (!IsEntryValid(*e)) { LOG(Warning, "Missing file from registry: \'{0}\':{1}:{2}", e->Info.Path, e->Info.ID, e->Info.TypeName); @@ -325,7 +320,6 @@ bool AssetsCache::FindAsset(const Guid& id, AssetInfo& info) info = e->Info; } } - return result; } @@ -379,8 +373,26 @@ void AssetsCache::RegisterAssets(FlaxStorage* storage) // Check if storage contains ID which has been already registered if (FindAsset(e.ID, info)) { +#if PLATFORM_WINDOWS + // On Windows - if you start your project using a shortcut/VS commandline -project, and using a upper/lower drive letter, it could the cache (case doesn't matter on OS) + if (StringUtils::CompareIgnoreCase(storagePath.GetText(), info.Path.GetText()) != 0) + { + LOG(Warning, "Founded duplicated asset \'{0}\'. Locations: \'{1}\' and \'{2}\'", e.ID, storagePath, info.Path); + duplicatedEntries.Add(i); + } + else + { + // Remove from registry so we can add it again later with the original ID, so we don't loose relations + for (auto j = _registry.Begin(); j.IsNotEnd(); ++j) + { + if (StringUtils::CompareIgnoreCase(j->Value.Info.Path.GetText(), storagePath.GetText()) == 0) + _registry.Remove(j); + } + } +#else LOG(Warning, "Founded duplicated asset \'{0}\'. Locations: \'{1}\' and \'{2}\'", e.ID, storagePath, info.Path); duplicatedEntries.Add(i); +#endif } } @@ -551,7 +563,6 @@ bool AssetsCache::RenameAsset(const StringView& oldPath, const StringView& newPa bool AssetsCache::IsEntryValid(Entry& e) { #if ENABLE_ASSETS_DISCOVERY - // Check if file exists if (FileSystem::FileExists(e.Info.Path)) { @@ -601,10 +612,8 @@ bool AssetsCache::IsEntryValid(Entry& e) return false; #else - // In game we don't care about it because all cached asset entries are valid (precached) // Skip only entries with missing file return e.Info.Path.HasChars(); - #endif } diff --git a/Source/Engine/Core/Types/String.cpp b/Source/Engine/Core/Types/String.cpp index 43fbcf051..340430d32 100644 --- a/Source/Engine/Core/Types/String.cpp +++ b/Source/Engine/Core/Types/String.cpp @@ -215,7 +215,7 @@ bool String::IsANSI() const bool result = true; for (int32 i = 0; i < _length; i++) { - if (_data[i] > 255) + if (_data[i] > 127) { result = false; break; diff --git a/Source/Engine/Scripting/Scripting.cpp b/Source/Engine/Scripting/Scripting.cpp index cc128c548..ac7a6f8e8 100644 --- a/Source/Engine/Scripting/Scripting.cpp +++ b/Source/Engine/Scripting/Scripting.cpp @@ -868,6 +868,20 @@ ScriptingObject* Scripting::TryFindObject(Guid id, MClass* type) return result; } +ScriptingObject* Scripting::TryFindObject(MClass* mclass) +{ + if (mclass == nullptr) + return nullptr; + ScopeLock lock(_objectsLocker); + for (auto i = _objectsDictionary.Begin(); i.IsNotEnd(); ++i) + { + const auto obj = i->Value; + if (obj->GetClass() == mclass) + return obj; + } + return nullptr; +} + ScriptingObject* Scripting::FindObject(const MObject* managedInstance) { if (managedInstance == nullptr) diff --git a/Source/Engine/Scripting/Scripting.h b/Source/Engine/Scripting/Scripting.h index 81739e1c8..dc13b696c 100644 --- a/Source/Engine/Scripting/Scripting.h +++ b/Source/Engine/Scripting/Scripting.h @@ -85,7 +85,7 @@ public: /// The full name of the type eg: System.Int64. /// The MClass object or null if missing. static MClass* FindClass(const StringAnsiView& fullname); - + #if USE_MONO /// /// Finds the class from the given Mono class object within whole assembly. @@ -144,6 +144,12 @@ public: /// The found object or null if missing. static ScriptingObject* FindObject(Guid id, MClass* type = nullptr); + /// + /// Tries to find the object by the given class. + /// + /// The found object or null if missing. + static ScriptingObject* TryFindObject(MClass* type); + /// /// Tries to find the object by the given identifier. /// diff --git a/Source/Engine/Tests/TestModelTool.cpp b/Source/Engine/Tests/TestModelTool.cpp new file mode 100644 index 000000000..13aa2bf15 --- /dev/null +++ b/Source/Engine/Tests/TestModelTool.cpp @@ -0,0 +1,24 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +#include "Engine/Tools/ModelTool/ModelTool.h" +#include + +TEST_CASE("ModelTool") +{ + SECTION("Test DetectLodIndex") + { + CHECK(ModelTool::DetectLodIndex(TEXT("mesh")) == 0); + CHECK(ModelTool::DetectLodIndex(TEXT("mesh LOD")) == 0); + CHECK(ModelTool::DetectLodIndex(TEXT("mesh LOD0")) == 0); + CHECK(ModelTool::DetectLodIndex(TEXT("mesh LOD1")) == 1); + CHECK(ModelTool::DetectLodIndex(TEXT("mesh_LOD1")) == 1); + CHECK(ModelTool::DetectLodIndex(TEXT("mesh_lod1")) == 1); + CHECK(ModelTool::DetectLodIndex(TEXT("mesh_lod2")) == 2); + CHECK(ModelTool::DetectLodIndex(TEXT("lod0")) == 0); + CHECK(ModelTool::DetectLodIndex(TEXT("lod1")) == 1); + CHECK(ModelTool::DetectLodIndex(TEXT("lod_2")) == 2); + CHECK(ModelTool::DetectLodIndex(TEXT("mesh_lod_0")) == 0); + CHECK(ModelTool::DetectLodIndex(TEXT("mesh_lod_1")) == 1); + CHECK(ModelTool::DetectLodIndex(TEXT("mesh lod_2")) == 2); + } +} diff --git a/Source/Engine/Tests/Tests.Build.cs b/Source/Engine/Tests/Tests.Build.cs index 3d91dc7fd..ad64962a0 100644 --- a/Source/Engine/Tests/Tests.Build.cs +++ b/Source/Engine/Tests/Tests.Build.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using Flax.Build; +using Flax.Build.NativeCpp; /// /// Engine tests module. @@ -14,6 +15,14 @@ public class Tests : EngineModule Deploy = false; } + /// + public override void Setup(BuildOptions options) + { + base.Setup(options); + + options.PrivateDependencies.Add("ModelTool"); + } + /// public override void GetFilesToDeploy(List files) { diff --git a/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp b/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp index 26d79cfc4..6d06961dd 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp @@ -641,6 +641,7 @@ bool ModelTool::ImportDataAssimp(const char* path, ImportedModelData& data, Opti aiProcess_JoinIdenticalVertices | aiProcess_LimitBoneWeights | aiProcess_Triangulate | + aiProcess_SortByPType | aiProcess_GenUVCoords | aiProcess_FindDegenerates | aiProcess_FindInvalidData | diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index b5a4282c2..53fd90de4 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -1552,21 +1552,21 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& op int32 ModelTool::DetectLodIndex(const String& nodeName) { - const int32 index = nodeName.FindLast(TEXT("LOD")); + int32 index = nodeName.FindLast(TEXT("LOD"), StringSearchCase::IgnoreCase); if (index != -1) { + // Some models use LOD_0 to identify LOD levels + if (nodeName.Length() > index + 4 && nodeName[index + 3] == '_') + index++; + int32 num; if (!StringUtils::Parse(nodeName.Get() + index + 3, &num)) { if (num >= 0 && num < MODEL_MAX_LODS) - { return num; - } - LOG(Warning, "Invalid mesh level of detail index at node \'{0}\'. Maximum supported amount of LODs is {1}.", nodeName, MODEL_MAX_LODS); } } - return 0; }