diff --git a/Source/Editor/Content/GUI/ContentView.cs b/Source/Editor/Content/GUI/ContentView.cs
index a7dc7b18f..06df7d2a2 100644
--- a/Source/Editor/Content/GUI/ContentView.cs
+++ b/Source/Editor/Content/GUI/ContentView.cs
@@ -720,14 +720,14 @@ namespace FlaxEditor.Content.GUI
if (itemsToFit < 1)
itemsToFit = 1;
float itemsWidth = width / Mathf.Max(itemsToFit, 1);
- itemsWidth = Mathf.Floor(itemsWidth);
float itemsHeight = itemsWidth / defaultItemsWidth * (ContentItem.DefaultHeight * viewScale);
- itemsHeight = Mathf.Floor(itemsHeight);
+ var flooredItemsWidth = Mathf.Floor(itemsWidth);
+ var flooredItemsHeight = Mathf.Floor(itemsHeight);
x = itemsToFit == 1 ? 0 : itemsWidth / itemsToFit;
for (int i = 0; i < _children.Count; i++)
{
var c = _children[i];
- c.Bounds = new Rectangle(x, y, itemsWidth, itemsHeight);
+ c.Bounds = new Rectangle(Mathf.Floor(x), Mathf.Floor(y), flooredItemsWidth, flooredItemsHeight);
x += itemsWidth + itemsWidth / itemsToFit;
if (x + itemsWidth > width)
diff --git a/Source/Editor/Gizmo/TransformGizmoBase.Draw.cs b/Source/Editor/Gizmo/TransformGizmoBase.Draw.cs
index 5fb5c12f2..f149185f1 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/Surface/Archetypes/Textures.cs b/Source/Editor/Surface/Archetypes/Textures.cs
index 2b566b0bd..6e4e34f43 100644
--- a/Source/Editor/Surface/Archetypes/Textures.cs
+++ b/Source/Editor/Surface/Archetypes/Textures.cs
@@ -400,7 +400,7 @@ namespace FlaxEditor.Surface.Archetypes
Elements = new[]
{
NodeElementArchetype.Factory.Input(0, "Texture", true, typeof(FlaxEngine.Object), 0),
- NodeElementArchetype.Factory.Input(1, "Scale", true, typeof(float), 1, 0),
+ NodeElementArchetype.Factory.Input(1, "Scale", true, typeof(Float3), 1, 0),
NodeElementArchetype.Factory.Input(2, "Blend", true, typeof(float), 2, 1),
NodeElementArchetype.Factory.Output(0, "Color", typeof(Float3), 3)
}
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/Editor/Windows/GameWindow.cs b/Source/Editor/Windows/GameWindow.cs
index 4f9a18218..23558c396 100644
--- a/Source/Editor/Windows/GameWindow.cs
+++ b/Source/Editor/Windows/GameWindow.cs
@@ -21,7 +21,8 @@ namespace FlaxEditor.Windows
private readonly GameRoot _guiRoot;
private bool _showGUI = true;
private bool _showDebugDraw = false;
- private bool _isMaximized = false;
+ private bool _isMaximized = false, _isUnlockingMouse = false;
+ private bool _cursorVisible = true;
private float _gameStartTime;
private GUI.Docking.DockState _maximizeRestoreDockState;
private GUI.Docking.DockPanel _maximizeRestoreDockTo;
@@ -460,14 +461,20 @@ namespace FlaxEditor.Windows
{
if (Editor.StateMachine.IsPlayMode)
{
- Screen.CursorVisible = true;
- Focus(null);
- Editor.Windows.MainWindow.Focus();
- if (Editor.Windows.PropertiesWin.IsDocked)
- Editor.Windows.PropertiesWin.Focus();
+ // Cache cursor
+ _cursorVisible = Screen.CursorVisible;
+ _cursorLockMode = Screen.CursorLock;
Screen.CursorVisible = true;
if (Screen.CursorLock == CursorLockMode.Clipped)
Screen.CursorLock = CursorLockMode.None;
+
+ // Defocus
+ _isUnlockingMouse = true;
+ Focus(null);
+ _isUnlockingMouse = false;
+ Editor.Windows.MainWindow.Focus();
+ if (Editor.Windows.PropertiesWin.IsDocked)
+ Editor.Windows.PropertiesWin.Focus();
}
}
@@ -552,9 +559,11 @@ namespace FlaxEditor.Windows
Root.MousePosition = center;
}
- // Restore lock mode
+ // Restore cursor
if (_cursorLockMode != CursorLockMode.None)
Screen.CursorLock = _cursorLockMode;
+ if (!_cursorVisible)
+ Screen.CursorVisible = false;
}
}
@@ -563,11 +572,16 @@ namespace FlaxEditor.Windows
{
base.OnEndContainsFocus();
- // Restore cursor visibility (could be hidden by the game)
- Screen.CursorVisible = true;
+ if (!_isUnlockingMouse)
+ {
+ // Cache cursor
+ _cursorVisible = Screen.CursorVisible;
+ _cursorLockMode = Screen.CursorLock;
- // Cache lock mode
- _cursorLockMode = Screen.CursorLock;
+ // Restore cursor visibility (could be hidden by the game)
+ if (!_cursorVisible)
+ Screen.CursorVisible = true;
+ }
}
///
diff --git a/Source/Engine/Audio/AudioClip.cpp b/Source/Engine/Audio/AudioClip.cpp
index 9efe88a2a..fd8311132 100644
--- a/Source/Engine/Audio/AudioClip.cpp
+++ b/Source/Engine/Audio/AudioClip.cpp
@@ -20,10 +20,11 @@ bool AudioClip::StreamingTask::Run()
{
AssetReference ref = _asset.Get();
if (ref == nullptr)
- {
return true;
- }
+ ScopeLock lock(ref->Locker);
const auto& queue = ref->StreamingQueue;
+ if (queue.Count() == 0)
+ return false;
auto clip = ref.Get();
// Update the buffers
@@ -108,7 +109,6 @@ bool AudioClip::StreamingTask::Run()
for (int32 sourceIndex = 0; sourceIndex < Audio::Sources.Count(); sourceIndex++)
{
// TODO: collect refs to audio clip from sources and use faster iteration (but do it thread-safe)
-
const auto src = Audio::Sources[sourceIndex];
if (src->Clip == clip && src->GetState() == AudioSource::States::Playing)
{
diff --git a/Source/Engine/Audio/AudioSource.cpp b/Source/Engine/Audio/AudioSource.cpp
index 870a1f41a..10e2cf429 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
@@ -368,6 +372,7 @@ void AudioSource::Update()
if (!UseStreaming() || SourceIDs.IsEmpty())
return;
auto clip = Clip.Get();
+ clip->Locker.Lock();
// Handle streaming buffers queue submit (ensure that clip has loaded the first chunk with audio data)
if (_needToUpdateStreamingBuffers && clip->Buffers[_streamingFirstChunk] != AUDIO_BUFFER_ID_INVALID)
@@ -437,6 +442,8 @@ void AudioSource::Update()
clip->RequestStreamingUpdate();
}
}
+
+ clip->Locker.Unlock();
}
void AudioSource::OnEnable()
diff --git a/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp b/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp
index aa607421e..ef1e84675 100644
--- a/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp
+++ b/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp
@@ -139,10 +139,12 @@ namespace ALC
alSourcef(sourceID, AL_GAIN, source->GetVolume());
alSourcef(sourceID, AL_PITCH, source->GetPitch());
+ alSourcef(sourceID, AL_SEC_OFFSET, 0.0f);
alSourcef(sourceID, AL_REFERENCE_DISTANCE, source->GetMinDistance());
alSourcef(sourceID, AL_ROLLOFF_FACTOR, source->GetAttenuation());
alSourcei(sourceID, AL_LOOPING, loop);
alSourcei(sourceID, AL_SOURCE_RELATIVE, !is3D);
+ alSourcei(sourceID, AL_BUFFER, 0);
alSource3f(sourceID, AL_POSITION, FLAX_POS_TO_OAL(position));
alSource3f(sourceID, AL_VELOCITY, FLAX_POS_TO_OAL(velocity));
}
diff --git a/Source/Engine/Content/Cache/AssetsCache.cpp b/Source/Engine/Content/Cache/AssetsCache.cpp
index 7889b6a84..8138fc94e 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/Engine/Screen.cpp b/Source/Engine/Engine/Screen.cpp
index b6cec67c6..a527bbcfe 100644
--- a/Source/Engine/Engine/Screen.cpp
+++ b/Source/Engine/Engine/Screen.cpp
@@ -15,7 +15,8 @@
Nullable Fullscreen;
Nullable Size;
-static CursorLockMode CursorLock = CursorLockMode::None;
+bool CursorVisible = true;
+CursorLockMode CursorLock = CursorLockMode::None;
class ScreenService : public EngineService
{
@@ -25,6 +26,7 @@ public:
{
}
+ void Update() override;
void Draw() override;
};
@@ -88,12 +90,7 @@ Float2 Screen::GameViewportToScreen(const Float2& viewportPos)
bool Screen::GetCursorVisible()
{
-#if USE_EDITOR
- const auto win = Editor::Managed->GetGameWindow(true);
-#else
- const auto win = Engine::MainWindow;
-#endif
- return win ? win->GetCursor() != CursorType::Hidden : true;
+ return CursorVisible;
}
void Screen::SetCursorVisible(const bool value)
@@ -107,6 +104,7 @@ void Screen::SetCursorVisible(const bool value)
{
win->SetCursor(value ? CursorType::Default : CursorType::Hidden);
}
+ CursorVisible = value;
}
CursorLockMode Screen::GetCursorLock()
@@ -137,6 +135,14 @@ void Screen::SetCursorLock(CursorLockMode mode)
CursorLock = mode;
}
+void ScreenService::Update()
+{
+#if USE_EDITOR
+ // Sync current cursor state in Editor (eg. when viewport focus can change)
+ Screen::SetCursorVisible(CursorVisible);
+#endif
+}
+
void ScreenService::Draw()
{
#if USE_EDITOR
diff --git a/Source/Engine/Scripting/BinaryModule.cpp b/Source/Engine/Scripting/BinaryModule.cpp
index 44ec43ad9..eae964e29 100644
--- a/Source/Engine/Scripting/BinaryModule.cpp
+++ b/Source/Engine/Scripting/BinaryModule.cpp
@@ -697,6 +697,7 @@ ManagedBinaryModule::ManagedBinaryModule(MAssembly* assembly)
// Bind for C# assembly events
assembly->Loading.Bind(this);
assembly->Loaded.Bind(this);
+ assembly->Unloading.Bind(this);
assembly->Unloaded.Bind(this);
if (Assembly->IsLoaded())
@@ -1117,17 +1118,24 @@ void ManagedBinaryModule::InitType(MClass* mclass)
#endif
}
-void ManagedBinaryModule::OnUnloaded(MAssembly* assembly)
+void ManagedBinaryModule::OnUnloading(MAssembly* assembly)
{
PROFILE_CPU();
- // Clear managed-only types
+ // Clear managed types typenames
for (int32 i = _firstManagedTypeIndex; i < Types.Count(); i++)
{
const ScriptingType& type = Types[i];
const MString typeName(type.Fullname.Get(), type.Fullname.Length());
TypeNameToTypeIndex.Remove(typeName);
}
+}
+
+void ManagedBinaryModule::OnUnloaded(MAssembly* assembly)
+{
+ PROFILE_CPU();
+
+ // Clear managed-only types
Types.Resize(_firstManagedTypeIndex);
for (int32 i = 0; i < _managedMemoryBlocks.Count(); i++)
Allocator::Free(_managedMemoryBlocks[i]);
diff --git a/Source/Engine/Scripting/BinaryModule.h b/Source/Engine/Scripting/BinaryModule.h
index ede625cc1..7c69a2dc6 100644
--- a/Source/Engine/Scripting/BinaryModule.h
+++ b/Source/Engine/Scripting/BinaryModule.h
@@ -309,6 +309,7 @@ private:
void OnLoading(MAssembly* assembly);
void OnLoaded(MAssembly* assembly);
void InitType(MClass* mclass);
+ void OnUnloading(MAssembly* assembly);
void OnUnloaded(MAssembly* assembly);
public:
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/MaterialGenerator/MaterialGenerator.Textures.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp
index a29b3d0d9..0c71dc1ba 100644
--- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp
+++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp
@@ -467,7 +467,7 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value)
}
const auto texture = eatBox(textureBox->GetParent(), textureBox->FirstConnection());
- const auto scale = tryGetValue(scaleBox, node->Values[0]).AsFloat();
+ const auto scale = tryGetValue(scaleBox, node->Values[0]).AsFloat3();
const auto blend = tryGetValue(blendBox, node->Values[1]).AsFloat();
auto result = writeLocal(Value::InitForZero(ValueType::Float4), node);
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 a7db7215f..61a8dd974 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;
}
diff --git a/Source/Engine/UI/GUI/Common/TextBoxBase.cs b/Source/Engine/UI/GUI/Common/TextBoxBase.cs
index 41d8b9b44..d78bd3d6b 100644
--- a/Source/Engine/UI/GUI/Common/TextBoxBase.cs
+++ b/Source/Engine/UI/GUI/Common/TextBoxBase.cs
@@ -129,6 +129,16 @@ namespace FlaxEngine.GUI
///
public event Action TextBoxEditEnd;
+ ///
+ /// Event fired when a key is down.
+ ///
+ public event Action KeyDown;
+
+ ///
+ /// Event fired when a key is up.
+ ///
+ public event Action KeyUp;
+
///
/// Gets or sets a value indicating whether this is a multiline text box control.
///
@@ -1168,12 +1178,20 @@ namespace FlaxEngine.GUI
return true;
}
+ ///
+ public override void OnKeyUp(KeyboardKeys key)
+ {
+ base.OnKeyUp(key);
+ KeyUp?.Invoke(key);
+ }
+
///
public override bool OnKeyDown(KeyboardKeys key)
{
var window = Root;
bool shiftDown = window.GetKey(KeyboardKeys.Shift);
bool ctrDown = window.GetKey(KeyboardKeys.Control);
+ KeyDown?.Invoke(key);
switch (key)
{
diff --git a/Source/Engine/UI/GUI/Control.cs b/Source/Engine/UI/GUI/Control.cs
index 86c242609..890908c0e 100644
--- a/Source/Engine/UI/GUI/Control.cs
+++ b/Source/Engine/UI/GUI/Control.cs
@@ -948,10 +948,6 @@ namespace FlaxEngine.GUI
{
// Set flag
_isDragOver = true;
-
- // Hide tooltip
- Tooltip?.Hide();
-
return DragDropEffect.None;
}
@@ -998,6 +994,8 @@ namespace FlaxEngine.GUI
[NoAnimate]
public virtual void DoDragDrop(DragData data)
{
+ // Hide tooltip
+ Tooltip?.Hide();
Root.DoDragDrop(data);
}