diff --git a/Source/Editor/Gizmo/TransformGizmoBase.Draw.cs b/Source/Editor/Gizmo/TransformGizmoBase.Draw.cs index 5fb5c12f2..6957a9525 100644 --- a/Source/Editor/Gizmo/TransformGizmoBase.Draw.cs +++ b/Source/Editor/Gizmo/TransformGizmoBase.Draw.cs @@ -54,7 +54,12 @@ namespace FlaxEditor.Gizmo if (!_isActive || !IsActive) return; - Matrix m1, m2, m3; + //PE: As all axisMesh have the same pivot, add a little offset to the x axisMesh, + //PE: This way SortDrawCalls is able to sort the draw order. + //PE: https://github.com/FlaxEngine/FlaxEngine/issues/680 + //PE: @Artist To fix the rotate, add new "wider" circleMesh, so direction is visible. + + 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 +75,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 +97,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 +150,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..13ebece7c 100644 --- a/Source/Editor/Gizmo/TransformGizmoBase.cs +++ b/Source/Editor/Gizmo/TransformGizmoBase.cs @@ -289,10 +289,29 @@ namespace FlaxEditor.Gizmo { float snapValue = isScaling ? ScaleSnapValue : TranslationSnapValue; _translationScaleSnapDelta += delta; - delta = new Vector3( - (int)(_translationScaleSnapDelta.X / snapValue) * snapValue, - (int)(_translationScaleSnapDelta.Y / snapValue) * snapValue, - (int)(_translationScaleSnapDelta.Z / snapValue) * snapValue); + if (!isScaling && snapValue < 0.0f) + { + //PE: Snap to object bounding box + GetSelectedObjectsBounds(out var b, out _); + float X, Y, Z; + if (b.Minimum.X < 0.0f) X = (float) Math.Abs(b.Minimum.X) + b.Maximum.X; + else X = (float) b.Minimum.X - b.Maximum.X; + if (b.Minimum.Y < 0.0f) Y = (float) Math.Abs(b.Minimum.Y) + b.Maximum.Y; + else Y = (float) b.Minimum.Y - b.Maximum.Y; + if (b.Minimum.Z < 0.0f) Z = (float) Math.Abs(b.Minimum.Z) + b.Maximum.Z; + else Z = (float) b.Minimum.Z - b.Maximum.Z; + delta = new Vector3( + (int)(_translationScaleSnapDelta.X / X) * X, + (int)(_translationScaleSnapDelta.Y / Y) * Y, + (int)(_translationScaleSnapDelta.Z / Z) * Z); + } + else + { + delta = new Vector3( + (int)(_translationScaleSnapDelta.X / snapValue) * snapValue, + (int)(_translationScaleSnapDelta.Y / snapValue) * snapValue, + (int)(_translationScaleSnapDelta.Z / snapValue) * snapValue); + } _translationScaleSnapDelta -= delta; } @@ -446,7 +465,7 @@ namespace FlaxEditor.Gizmo } // Apply transformation (but to the parents, not whole selection pool) - if (anyValid) + if (anyValid || (!_isTransforming && Owner.UseDuplicate) ) { StartTransforming(); 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..066ab1c00 100644 --- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs +++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs @@ -326,6 +326,10 @@ namespace FlaxEditor.Viewport var button = translateSnappingCM.AddButton(v.ToString()); button.Tag = v; } + var buttonBB = translateSnappingCM.AddButton("Bounding Box"); + buttonBB.Tag = -1.0f; + + translateSnappingCM.ButtonClicked += OnWidgetTranslateSnapClick; translateSnappingCM.VisibleChanged += OnWidgetTranslateSnapShowHide; _translateSnapping.Parent = translateSnappingWidget; @@ -378,6 +382,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); } @@ -667,7 +672,10 @@ namespace FlaxEditor.Viewport { var v = (float)button.Tag; TransformGizmo.TranslationSnapValue = v; - _translateSnapping.Text = v.ToString(); + if (v < 0.0f) + _translateSnapping.Text = "Bounding Box"; + else + _translateSnapping.Text = v.ToString(); // cache value _editor.ProjectCache.SetCustomData("TranslateSnapValue", TransformGizmo.TranslationSnapValue.ToString("N")); } @@ -696,6 +704,49 @@ 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; + + //PE: Rotate selected objects. + bool isPlayMode = Editor.Instance.StateMachine.IsPlayMode; + for (int i = 0; i < selection.Count; i++) + { + var obj = selection[i]; + if (isPlayMode && obj.CanTransform == false) + continue; + var trans = obj.Transform; + Vector3 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; + } + } + /// /// 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..c01e20692 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(); + + // PE: 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..44461a68b 100644 --- a/Source/Engine/Content/Cache/AssetsCache.cpp +++ b/Source/Engine/Content/Cache/AssetsCache.cpp @@ -379,8 +379,27 @@ void AssetsCache::RegisterAssets(FlaxStorage* storage) // Check if storage contains ID which has been already registered if (FindAsset(e.ID, info)) { + #if PLATFORM_WINDOWS + //PE: Windows - if you start your project using a shortcut/VS commandline -project , and using a upper/lower drive letter, it could ruin your scene. + //PE: On windows case do not matter so... + if (storagePath.ToLower() != info.Path.ToLower()) + { + LOG(Warning, "Founded duplicated asset \'{0}\'. Locations: \'{1}\' and \'{2}\'", e.ID, storagePath, info.Path); + duplicatedEntries.Add(i); + } + else + { + //PE: Remove from _registry so we can add it again later with the original ID, so we dont loose relations. + for (auto i = _registry.Begin(); i.IsNotEnd(); ++i) + { + if (i->Value.Info.Path.ToLower() == storagePath.ToLower()) + _registry.Remove(i); + } + } + #else LOG(Warning, "Founded duplicated asset \'{0}\'. Locations: \'{1}\' and \'{2}\'", e.ID, storagePath, info.Path); duplicatedEntries.Add(i); + #endif } } diff --git a/Source/Engine/Core/Types/String.cpp b/Source/Engine/Core/Types/String.cpp index 43fbcf051..7a8f1b1ed 100644 --- a/Source/Engine/Core/Types/String.cpp +++ b/Source/Engine/Core/Types/String.cpp @@ -215,7 +215,8 @@ bool String::IsANSI() const bool result = true; for (int32 i = 0; i < _length; i++) { - if (_data[i] > 255) + //PE: Ansi is max 7 bit so... + if (_data[i] > 127) { result = false; break; diff --git a/Source/Engine/Scripting/Scripting.cpp b/Source/Engine/Scripting/Scripting.cpp index cc128c548..5af41502a 100644 --- a/Source/Engine/Scripting/Scripting.cpp +++ b/Source/Engine/Scripting/Scripting.cpp @@ -868,6 +868,23 @@ 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..41e5d7119 100644 --- a/Source/Engine/Scripting/Scripting.h +++ b/Source/Engine/Scripting/Scripting.h @@ -144,6 +144,14 @@ 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 = nullptr); + + /// /// Tries to find the object by the given identifier. /// diff --git a/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp b/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp index 26d79cfc4..cc0c68d79 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 | //PE: Added aiProcess_SortByPType so we can ignore meshes with non triangle faces. aiProcess_GenUVCoords | aiProcess_FindDegenerates | aiProcess_FindInvalidData | diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index b5a4282c2..571c9a7bd 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -1552,10 +1552,14 @@ 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")); if (index != -1) { int32 num; + //PE: Many models use LOD_0... to indentify LOD levels. + if (nodeName.Length() > 4 && nodeName[3] == '_') + index++; + if (!StringUtils::Parse(nodeName.Get() + index + 3, &num)) { if (num >= 0 && num < MODEL_MAX_LODS)