diff --git a/.github/workflows/build_linux.yml b/.github/workflows/build_linux.yml
index b3348c288..6290ff525 100644
--- a/.github/workflows/build_linux.yml
+++ b/.github/workflows/build_linux.yml
@@ -10,15 +10,12 @@ jobs:
# Editor
editor-linux:
name: Editor (Linux, Development x64)
- runs-on: "ubuntu-20.04"
+ runs-on: "ubuntu-24.04"
steps:
- name: Checkout repo
uses: actions/checkout@v3
- name: Install dependencies
run: |
- sudo rm -f /etc/apt/sources.list.d/*
- sudo cp -f .github/workflows/build_linux_sources.list /etc/apt/sources.list
- sudo apt-get update
sudo apt-get install libx11-dev libxcursor-dev libxinerama-dev build-essential gettext libtool libtool-bin libpulse-dev libasound2-dev libjack-dev portaudio19-dev
- name: Setup Vulkan
uses: ./.github/actions/vulkan
@@ -41,7 +38,7 @@ jobs:
# Game
game-linux:
name: Game (Linux, Release x64)
- runs-on: "ubuntu-20.04"
+ runs-on: "ubuntu-24.04"
steps:
- name: Checkout repo
uses: actions/checkout@v3
diff --git a/.github/workflows/build_linux_sources.list b/.github/workflows/build_linux_sources.list
deleted file mode 100644
index 4d8f94f35..000000000
--- a/.github/workflows/build_linux_sources.list
+++ /dev/null
@@ -1,4 +0,0 @@
-deb http://archive.ubuntu.com/ubuntu/ focal main restricted universe multiverse
-deb http://archive.ubuntu.com/ubuntu/ focal-updates main restricted universe multiverse
-deb http://archive.ubuntu.com/ubuntu/ focal-security main restricted universe multiverse
-deb http://archive.ubuntu.com/ubuntu/ focal-backports main restricted universe multiverse
diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml
index d83b40363..4b4f9a827 100644
--- a/.github/workflows/cd.yml
+++ b/.github/workflows/cd.yml
@@ -76,7 +76,7 @@ jobs:
# Linux
package-linux-editor:
name: Editor (Linux)
- runs-on: "ubuntu-20.04"
+ runs-on: "ubuntu-24.04"
steps:
- name: Checkout repo
uses: actions/checkout@v3
@@ -86,9 +86,6 @@ jobs:
git lfs pull
- name: Install dependencies
run: |
- sudo rm -f /etc/apt/sources.list.d/*
- sudo cp -f .github/workflows/build_linux_sources.list /etc/apt/sources.list
- sudo apt-get update
sudo apt-get install libx11-dev libxcursor-dev libxinerama-dev build-essential gettext libtool libtool-bin libpulse-dev libasound2-dev libjack-dev portaudio19-dev
- name: Setup Vulkan
uses: ./.github/actions/vulkan
@@ -110,7 +107,7 @@ jobs:
path: Output/FlaxEditorLinux.zip
package-linux-game:
name: Game (Linux)
- runs-on: "ubuntu-20.04"
+ runs-on: "ubuntu-24.04"
steps:
- name: Checkout repo
uses: actions/checkout@v3
@@ -120,9 +117,6 @@ jobs:
git lfs pull
- name: Install dependencies
run: |
- sudo rm -f /etc/apt/sources.list.d/*
- sudo cp -f .github/workflows/build_linux_sources.list /etc/apt/sources.list
- sudo apt-get update
sudo apt-get install libx11-dev libxcursor-dev libxinerama-dev build-essential gettext libtool libtool-bin libpulse-dev libasound2-dev libjack-dev portaudio19-dev
- name: Setup Vulkan
uses: ./.github/actions/vulkan
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index e36642cdf..b828974a0 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -10,7 +10,7 @@ jobs:
# Tests on Linux
tests-linux:
name: Tests (Linux)
- runs-on: "ubuntu-20.04"
+ runs-on: "ubuntu-24.04"
steps:
- name: Checkout repo
uses: actions/checkout@v3
@@ -28,9 +28,6 @@ jobs:
git lfs pull
- name: Install dependencies
run: |
- sudo rm -f /etc/apt/sources.list.d/*
- sudo cp -f .github/workflows/build_linux_sources.list /etc/apt/sources.list
- sudo apt-get update
sudo apt-get install libx11-dev libxcursor-dev libxinerama-dev build-essential gettext libtool libtool-bin libpulse-dev libasound2-dev libjack-dev portaudio19-dev
- name: Build
run: |
diff --git a/Flax.flaxproj b/Flax.flaxproj
index ca1a1aff8..33dc6e45b 100644
--- a/Flax.flaxproj
+++ b/Flax.flaxproj
@@ -4,7 +4,7 @@
"Major": 1,
"Minor": 9,
"Revision": 0,
- "Build": 6604
+ "Build": 6605
},
"Company": "Flax",
"Copyright": "Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.",
diff --git a/Source/Editor/Content/Proxy/FileProxy.cs b/Source/Editor/Content/Proxy/FileProxy.cs
index 4e12ab588..d162e5f35 100644
--- a/Source/Editor/Content/Proxy/FileProxy.cs
+++ b/Source/Editor/Content/Proxy/FileProxy.cs
@@ -32,6 +32,14 @@ namespace FlaxEditor.Content
///
public override EditorWindow Open(Editor editor, ContentItem item)
{
+#if PLATFORM_WINDOWS
+ CreateProcessSettings settings = new CreateProcessSettings
+ {
+ ShellExecute = true,
+ FileName = item.Path
+ };
+ Platform.CreateProcess(ref settings);
+#endif
return null;
}
diff --git a/Source/Editor/GUI/CurveEditor.Base.cs b/Source/Editor/GUI/CurveEditor.Base.cs
index 6a919f0c2..25de316fa 100644
--- a/Source/Editor/GUI/CurveEditor.Base.cs
+++ b/Source/Editor/GUI/CurveEditor.Base.cs
@@ -241,6 +241,15 @@ namespace FlaxEditor.GUI
/// The keyframe value.
public abstract void SetKeyframeValue(int index, object value);
+ ///
+ /// Sets the existing keyframe value (as boxed object).
+ ///
+ /// The keyframe index.
+ /// The keyframe value.
+ /// The keyframe 'In' tangent value (boxed).
+ /// The keyframe 'Out' tangent value (boxed).
+ public abstract void SetKeyframeValue(int index, object value, object tangentIn, object tangentOut);
+
///
/// Gets the keyframe point (in keyframes space).
///
diff --git a/Source/Editor/GUI/CurveEditor.cs b/Source/Editor/GUI/CurveEditor.cs
index bc96cf3bc..3499ef5d3 100644
--- a/Source/Editor/GUI/CurveEditor.cs
+++ b/Source/Editor/GUI/CurveEditor.cs
@@ -1289,6 +1289,18 @@ namespace FlaxEditor.GUI
OnEdited();
}
+ ///
+ public override void SetKeyframeValue(int index, object value, object tangentIn, object tangentOut)
+ {
+ var k = _keyframes[index];
+ k.Value = (T)value;
+ _keyframes[index] = k;
+
+ UpdateKeyframes();
+ UpdateTooltips();
+ OnEdited();
+ }
+
///
public override Float2 GetKeyframePoint(int index, int component)
{
@@ -2011,6 +2023,20 @@ namespace FlaxEditor.GUI
OnEdited();
}
+ ///
+ public override void SetKeyframeValue(int index, object value, object tangentIn, object tangentOut)
+ {
+ var k = _keyframes[index];
+ k.Value = (T)value;
+ k.TangentIn = (T)tangentIn;
+ k.TangentOut = (T)tangentOut;
+ _keyframes[index] = k;
+
+ UpdateKeyframes();
+ UpdateTooltips();
+ OnEdited();
+ }
+
///
public override Float2 GetKeyframePoint(int index, int component)
{
diff --git a/Source/Editor/GUI/Docking/FloatWindowDockPanel.cs b/Source/Editor/GUI/Docking/FloatWindowDockPanel.cs
index 302508f5c..a1dc73068 100644
--- a/Source/Editor/GUI/Docking/FloatWindowDockPanel.cs
+++ b/Source/Editor/GUI/Docking/FloatWindowDockPanel.cs
@@ -139,6 +139,8 @@ namespace FlaxEditor.GUI.Docking
///
protected override void OnLastTabRemoved()
{
+ if (ChildPanelsCount > 0)
+ return;
// Close window
_window?.Close();
}
diff --git a/Source/Editor/GUI/Timeline/EditCurveTrackGizmo.cs b/Source/Editor/GUI/Timeline/EditCurveTrackGizmo.cs
new file mode 100644
index 000000000..7cb1e7c5b
--- /dev/null
+++ b/Source/Editor/GUI/Timeline/EditCurveTrackGizmo.cs
@@ -0,0 +1,203 @@
+// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
+
+using FlaxEditor.Gizmo;
+using FlaxEditor.GUI.Timeline.Tracks;
+using FlaxEditor.GUI.Timeline.Undo;
+using FlaxEditor.SceneGraph;
+using FlaxEngine;
+
+namespace FlaxEditor.GUI.Timeline
+{
+ ///
+ /// Gizmo for editing position curve. Managed by the .
+ ///
+ sealed class EditCurveTrackGizmo : TransformGizmoBase
+ {
+ public const float KeyframeSize = 10.0f;
+ public const float TangentSize = 6.0f;
+
+ private readonly SceneAnimationTimeline _timeline;
+ private CurvePropertyTrack _track;
+ private int _keyframe = -1;
+ private int _item = -1;
+ private byte[] _curveEditingStartData;
+
+ public int Keyframe => _keyframe;
+ public int Item => _item;
+
+ public EditCurveTrackGizmo(IGizmoOwner owner, SceneAnimationTimeline timeline)
+ : base(owner)
+ {
+ _timeline = timeline;
+ }
+
+ public void SelectKeyframe(CurvePropertyTrack track, int keyframe, int item)
+ {
+ _track = track;
+ _keyframe = keyframe;
+ _item = item;
+ }
+
+ public override BoundingSphere FocusBounds
+ {
+ get
+ {
+ if (_track == null)
+ return BoundingSphere.Empty;
+ var curve = (BezierCurveEditor)_track.Curve;
+ var keyframes = curve.Keyframes;
+ var k = keyframes[_keyframe];
+ if (_item == 1)
+ k.Value += k.TangentIn;
+ else if (_item == 2)
+ k.Value += k.TangentOut;
+ return new BoundingSphere(k.Value, KeyframeSize);
+ }
+ }
+
+ protected override int SelectionCount => _track != null ? 1 : 0;
+
+ protected override SceneGraphNode GetSelectedObject(int index)
+ {
+ return null;
+ }
+
+ protected override Transform GetSelectedTransform(int index)
+ {
+ if (_track == null)
+ return Transform.Identity;
+ var curve = (BezierCurveEditor)_track.Curve;
+ var keyframes = curve.Keyframes;
+ var k = keyframes[_keyframe];
+ if (_item == 1)
+ k.Value += k.TangentIn;
+ else if (_item == 2)
+ k.Value += k.TangentOut;
+ return new Transform(k.Value);
+ }
+
+ protected override void GetSelectedObjectsBounds(out BoundingBox bounds, out bool navigationDirty)
+ {
+ bounds = BoundingBox.Empty;
+ navigationDirty = false;
+ if (_track == null)
+ return;
+ var curve = (BezierCurveEditor)_track.Curve;
+ var keyframes = curve.Keyframes;
+ var k = keyframes[_keyframe];
+ if (_item == 1)
+ k.Value += k.TangentIn;
+ else if (_item == 2)
+ k.Value += k.TangentOut;
+ bounds = new BoundingBox(k.Value - KeyframeSize, k.Value + KeyframeSize);
+ }
+
+ protected override bool IsSelected(SceneGraphNode obj)
+ {
+ return false;
+ }
+
+ protected override void OnStartTransforming()
+ {
+ base.OnStartTransforming();
+
+ // Start undo
+ _curveEditingStartData = EditTrackAction.CaptureData(_track);
+ }
+
+ protected override void OnEndTransforming()
+ {
+ base.OnEndTransforming();
+
+ // End undo
+ var after = EditTrackAction.CaptureData(_track);
+ if (!Utils.ArraysEqual(_curveEditingStartData, after))
+ _track.Timeline.AddBatchedUndoAction(new EditTrackAction(_track.Timeline, _track, _curveEditingStartData, after));
+ _curveEditingStartData = null;
+ }
+
+ protected override void OnApplyTransformation(ref Vector3 translationDelta, ref Quaternion rotationDelta, ref Vector3 scaleDelta)
+ {
+ base.OnApplyTransformation(ref translationDelta, ref rotationDelta, ref scaleDelta);
+
+ var curve = (BezierCurveEditor)_track.Curve;
+ var keyframes = curve.Keyframes;
+ var k = keyframes[_keyframe];
+ if (_item == 0)
+ k.Value += translationDelta;
+ else if (_item == 1)
+ k.TangentIn += translationDelta;
+ else if (_item == 2)
+ k.TangentOut += translationDelta;
+ curve.SetKeyframeValue(_keyframe, k.Value, k.TangentIn, k.TangentOut);
+ }
+
+ public override void Pick()
+ {
+ if (_track == null)
+ return;
+ var selectRay = Owner.MouseRay;
+ var curve = (BezierCurveEditor)_track.Curve;
+ var keyframes = curve.Keyframes;
+ for (var i = 0; i < keyframes.Count; i++)
+ {
+ var k = keyframes[i];
+
+ var sphere = new BoundingSphere(k.Value, KeyframeSize);
+ if (sphere.Intersects(ref selectRay))
+ {
+ SelectKeyframe(_track, i, 0);
+ return;
+ }
+
+ if (!k.TangentIn.IsZero)
+ {
+ var t = k.Value + k.TangentIn;
+ var box = BoundingBox.FromSphere(new BoundingSphere(t, TangentSize));
+ if (box.Intersects(ref selectRay))
+ {
+ SelectKeyframe(_track, i, 1);
+ return;
+ }
+ }
+
+ if (!k.TangentOut.IsZero)
+ {
+ var t = k.Value + k.TangentOut;
+ var box = BoundingBox.FromSphere(new BoundingSphere(t, TangentSize));
+ if (box.Intersects(ref selectRay))
+ {
+ SelectKeyframe(_track, i, 2);
+ return;
+ }
+ }
+ }
+ }
+
+ public override void Update(float dt)
+ {
+ base.Update(dt);
+
+ // Deactivate when track gets deselected
+ if (_track != null && !_timeline.SelectedTracks.Contains(_track))
+ Owner.Gizmos.Active = Owner.Gizmos.Get();
+ }
+
+ public override void OnActivated()
+ {
+ base.OnActivated();
+
+ ActiveMode = Mode.Translate;
+ }
+
+ public override void OnDeactivated()
+ {
+ // Clear selection
+ _track = null;
+ _keyframe = -1;
+ _item = -1;
+
+ base.OnDeactivated();
+ }
+ }
+}
diff --git a/Source/Editor/GUI/Timeline/SceneAnimationTimeline.cs b/Source/Editor/GUI/Timeline/SceneAnimationTimeline.cs
index ceac9931b..9f3f49e34 100644
--- a/Source/Editor/GUI/Timeline/SceneAnimationTimeline.cs
+++ b/Source/Editor/GUI/Timeline/SceneAnimationTimeline.cs
@@ -1,11 +1,12 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System;
+using FlaxEngine;
using FlaxEditor.Content;
+using FlaxEditor.Gizmo;
using FlaxEditor.GUI.Drag;
using FlaxEditor.GUI.Timeline.Tracks;
using FlaxEditor.SceneGraph;
-using FlaxEngine;
namespace FlaxEditor.GUI.Timeline
{
@@ -25,6 +26,7 @@ namespace FlaxEditor.GUI.Timeline
}
private SceneAnimationPlayer _player;
+ private EditCurveTrackGizmo _curveTrackGizmo;
private bool _showSelected3dTrack = true;
internal Guid _id;
@@ -239,6 +241,19 @@ namespace FlaxEditor.GUI.Timeline
}
}
+ private void SelectKeyframeGizmo(CurvePropertyTrack track, int keyframe, int item)
+ {
+ var mainGizmo = Editor.Instance.MainTransformGizmo;
+ if (!mainGizmo.IsActive)
+ return; // Skip when using vertex painting or terrain or foliage tools
+ if (_curveTrackGizmo == null)
+ {
+ _curveTrackGizmo = new EditCurveTrackGizmo(mainGizmo.Owner, this);
+ }
+ _curveTrackGizmo.SelectKeyframe(track, keyframe, item);
+ _curveTrackGizmo.Activate();
+ }
+
///
public override void OnPlay()
{
@@ -289,8 +304,17 @@ namespace FlaxEditor.GUI.Timeline
UpdatePlaybackState();
// Draw all selected 3D position tracks as Bezier curve in editor viewport
- if (ShowSelected3dTrack)
+ if (!VisibleInHierarchy || !EnabledInHierarchy)
{
+ // Disable curve transform gizmo to normal gizmo
+ if (_curveTrackGizmo != null && _curveTrackGizmo.IsActive)
+ _curveTrackGizmo.Owner.Gizmos.Get().Activate();
+ }
+ else if (ShowSelected3dTrack)
+ {
+ bool select = FlaxEngine.Input.GetMouseButtonDown(MouseButton.Left);
+ Ray selectRay = Editor.Instance.MainTransformGizmo.Owner.MouseRay;
+ const float coveredAlpha = 0.1f;
foreach (var track in SelectedTracks)
{
if (
@@ -302,31 +326,44 @@ namespace FlaxEditor.GUI.Timeline
{
var curve = (BezierCurveEditor)curveTrack.Curve;
var keyframes = curve.Keyframes;
+ var selectedKeyframe = _curveTrackGizmo?.Keyframe ?? -1;
+ var selectedItem = _curveTrackGizmo?.Item ?? -1;
for (var i = 0; i < keyframes.Count; i++)
{
var k = keyframes[i];
- DebugDraw.DrawSphere(new BoundingSphere(k.Value, 10.0f), Color.Red);
- DebugDraw.DrawSphere(new BoundingSphere(k.Value, 9.5f), new Color(1, 0, 0, 0.1f), 0, false);
+ var selected = selectedKeyframe == i && selectedItem == 0;
+ var sphere = new BoundingSphere(k.Value, EditCurveTrackGizmo.KeyframeSize);
+ DebugDraw.DrawSphere(sphere, selected ? Color.Yellow : Color.Red);
+ sphere.Radius *= 0.95f;
+ DebugDraw.DrawSphere(sphere, new Color(1, 0, 0, coveredAlpha), 0, false);
+ if (select && sphere.Intersects(ref selectRay))
+ SelectKeyframeGizmo(curveTrack, i, 0);
if (!k.TangentIn.IsZero)
{
+ selected = selectedKeyframe == i && selectedItem == 1;
var t = k.Value + k.TangentIn;
DebugDraw.DrawLine(k.Value, t, Color.Yellow);
- DebugDraw.DrawLine(k.Value, t, Color.Yellow.AlphaMultiplied(0.1f), 0, false);
- var box = BoundingBox.FromSphere(new BoundingSphere(t, 6.0f));
- DebugDraw.DrawBox(box, Color.AliceBlue);
- DebugDraw.DrawBox(box, Color.AliceBlue.AlphaMultiplied(0.1f), 0, false);
+ DebugDraw.DrawLine(k.Value, t, Color.Yellow.AlphaMultiplied(coveredAlpha), 0, false);
+ var box = BoundingBox.FromSphere(new BoundingSphere(t, EditCurveTrackGizmo.TangentSize));
+ DebugDraw.DrawBox(box, selected ? Color.Yellow : Color.AliceBlue);
+ DebugDraw.DrawBox(box, Color.AliceBlue.AlphaMultiplied(coveredAlpha), 0, false);
+ if (select && box.Intersects(ref selectRay))
+ SelectKeyframeGizmo(curveTrack, i, 2);
}
if (!k.TangentOut.IsZero)
{
+ selected = selectedKeyframe == i && selectedItem == 2;
var t = k.Value + k.TangentOut;
DebugDraw.DrawLine(k.Value, t, Color.Yellow);
- DebugDraw.DrawLine(k.Value, t, Color.Yellow.AlphaMultiplied(0.1f), 0, false);
- var box = BoundingBox.FromSphere(new BoundingSphere(t, 6.0f));
- DebugDraw.DrawBox(box, Color.AliceBlue);
- DebugDraw.DrawBox(box, Color.AliceBlue.AlphaMultiplied(0.1f), 0, false);
+ DebugDraw.DrawLine(k.Value, t, Color.Yellow.AlphaMultiplied(coveredAlpha), 0, false);
+ var box = BoundingBox.FromSphere(new BoundingSphere(t, EditCurveTrackGizmo.TangentSize));
+ DebugDraw.DrawBox(box, selected ? Color.Yellow : Color.AliceBlue);
+ DebugDraw.DrawBox(box, Color.AliceBlue.AlphaMultiplied(coveredAlpha), 0, false);
+ if (select && box.Intersects(ref selectRay))
+ SelectKeyframeGizmo(curveTrack, i, 2);
}
if (i != 0)
@@ -341,6 +378,18 @@ namespace FlaxEditor.GUI.Timeline
}
}
+ ///
+ public override void OnDestroy()
+ {
+ if (_curveTrackGizmo != null)
+ {
+ _curveTrackGizmo.Destroy();
+ _curveTrackGizmo = null;
+ }
+
+ base.OnDestroy();
+ }
+
///
protected override void OnShowViewContextMenu(ContextMenu.ContextMenu menu)
{
diff --git a/Source/Editor/Gizmo/GizmoBase.cs b/Source/Editor/Gizmo/GizmoBase.cs
index af119444d..72990ea1f 100644
--- a/Source/Editor/Gizmo/GizmoBase.cs
+++ b/Source/Editor/Gizmo/GizmoBase.cs
@@ -12,15 +12,17 @@ namespace FlaxEditor.Gizmo
[HideInEditor]
public abstract class GizmoBase
{
+ private IGizmoOwner _owner;
+
///
/// Gets the gizmo owner.
///
- public IGizmoOwner Owner { get; }
+ public IGizmoOwner Owner => _owner;
///
/// Gets a value indicating whether this gizmo is active.
///
- public bool IsActive => Owner.Gizmos.Active == this;
+ public bool IsActive => _owner.Gizmos.Active == this;
///
/// Gets a value indicating whether this gizmo is using mouse currently (eg. user moving objects).
@@ -39,8 +41,8 @@ namespace FlaxEditor.Gizmo
protected GizmoBase(IGizmoOwner owner)
{
// Link
- Owner = owner;
- Owner.Gizmos.Add(this);
+ _owner = owner;
+ _owner.Gizmos.Add(this);
}
///
@@ -94,5 +96,25 @@ namespace FlaxEditor.Gizmo
public virtual void Draw(ref RenderContext renderContext)
{
}
+
+ ///
+ /// Activates thi gizmo mode.
+ ///
+ public void Activate()
+ {
+ _owner.Gizmos.Active = this;
+ }
+
+ ///
+ /// Removes the gizmo from the owner.
+ ///
+ public void Destroy()
+ {
+ if (_owner != null)
+ {
+ _owner.Gizmos.Remove(this);
+ _owner = null;
+ }
+ }
}
}
diff --git a/Source/Editor/Modules/WindowsModule.cs b/Source/Editor/Modules/WindowsModule.cs
index 02dd6d151..b9f3e246b 100644
--- a/Source/Editor/Modules/WindowsModule.cs
+++ b/Source/Editor/Modules/WindowsModule.cs
@@ -950,7 +950,7 @@ namespace FlaxEditor.Modules
MainWindow = null;
// Capture project icon screenshot (not in play mode and if editor was used for some time)
- if (!Editor.StateMachine.IsPlayMode && Time.TimeSinceStartup >= 5.0f)
+ if (!Editor.StateMachine.IsPlayMode && Time.TimeSinceStartup >= 5.0f && GPUDevice.Instance?.RendererType != RendererType.Null)
{
Editor.Log("Capture project icon screenshot");
_projectIconScreenshotTimeout = Time.TimeSinceStartup + 0.8f; // wait 800ms for a screenshot task
diff --git a/Source/Editor/ProjectInfo.cpp b/Source/Editor/ProjectInfo.cpp
index 28fc0301c..2fe8e2b1e 100644
--- a/Source/Editor/ProjectInfo.cpp
+++ b/Source/Editor/ProjectInfo.cpp
@@ -13,6 +13,37 @@ using namespace pugi;
Array ProjectInfo::ProjectsCache;
+struct XmlCharAsChar
+{
+#if PLATFORM_TEXT_IS_CHAR16
+ Char* Str = nullptr;
+
+ XmlCharAsChar(const pugi::char_t* str)
+ {
+ if (!str)
+ return;
+ int32 length = 0;
+ while (str[length])
+ length++;
+ Str = (Char*)Platform::Allocate(length * sizeof(Char), sizeof(Char));
+ for (int32 i = 0; i <= length; i++)
+ Str[i] = (Char)str[i];
+ }
+
+ ~XmlCharAsChar()
+ {
+ Platform::Free(Str);
+ }
+#else
+ const Char* Str;
+
+ XmlCharAsChar(const pugi::char_t* str)
+ : Str(str)
+ {
+ }
+#endif
+};
+
void ShowProjectLoadError(const Char* errorMsg, const String& projectRootFolder)
{
Platform::Error(String::Format(TEXT("Failed to load project. {0}\nPath: '{1}'"), errorMsg, projectRootFolder));
@@ -28,14 +59,14 @@ Vector3 GetVector3FromXml(const xml_node& parent, const PUGIXML_CHAR* name, cons
const auto z = node.child_value(PUGIXML_TEXT("Z"));
if (x && y && z)
{
- Vector3 v;
- if (!StringUtils::Parse(x, &v.X) && !StringUtils::Parse(y, &v.Y) && !StringUtils::Parse(z, &v.Z))
+ XmlCharAsChar xs(x), ys(y), zs(z);
+ Float3 v;
+ if (!StringUtils::Parse(xs.Str, &v.X) && !StringUtils::Parse(ys.Str, &v.Y) && !StringUtils::Parse(zs.Str, &v.Z))
{
- return v;
+ return (Vector3)v;
}
}
}
-
return defaultValue;
}
@@ -44,8 +75,9 @@ int32 GetIntFromXml(const xml_node& parent, const PUGIXML_CHAR* name, const int3
const auto node = parent.child_value(name);
if (node)
{
+ XmlCharAsChar s(node);
int32 v;
- if (!StringUtils::Parse(node, &v))
+ if (!StringUtils::Parse(s.Str, &v))
{
return v;
}
diff --git a/Source/Editor/Windows/Assets/SceneAnimationWindow.cs b/Source/Editor/Windows/Assets/SceneAnimationWindow.cs
index 9b0875752..20219a079 100644
--- a/Source/Editor/Windows/Assets/SceneAnimationWindow.cs
+++ b/Source/Editor/Windows/Assets/SceneAnimationWindow.cs
@@ -97,6 +97,9 @@ namespace FlaxEditor.Windows.Assets
[EditorDisplay(null, "1920x1080 (Full HD)")]
_1920x1080,
+ [EditorDisplay(null, "2560x1440 (2K)")]
+ _2560x1440,
+
[EditorDisplay(null, "3840x2160 (4K)")]
_3840x2160,
@@ -173,6 +176,7 @@ namespace FlaxEditor.Windows.Assets
case ResolutionModes._640x480: return new Int2(640, 480);
case ResolutionModes._1280x720: return new Int2(1280, 720);
case ResolutionModes._1920x1080: return new Int2(1920, 1080);
+ case ResolutionModes._2560x1440: return new Int2(2560, 1440);
case ResolutionModes._3840x2160: return new Int2(3840, 2160);
case ResolutionModes._7680x4320: return new Int2(7680, 4320);
case ResolutionModes.Custom: return new Int2(Width, Height);
@@ -456,8 +460,12 @@ namespace FlaxEditor.Windows.Assets
_window.Editor.StateMachine.CurrentState.UpdateFPS();
for (int i = 0; i < _stagingTextures.Length; i++)
{
- _stagingTextures[i].Texture.ReleaseGPU();
- Object.Destroy(ref _stagingTextures[i].Texture);
+ var texture = _stagingTextures[i].Texture;
+ if (texture)
+ {
+ texture.ReleaseGPU();
+ Object.Destroy(ref texture);
+ }
}
if (_progress != null)
{
diff --git a/Source/Editor/Windows/GameWindow.cs b/Source/Editor/Windows/GameWindow.cs
index 34a97286c..56e4c4380 100644
--- a/Source/Editor/Windows/GameWindow.cs
+++ b/Source/Editor/Windows/GameWindow.cs
@@ -553,14 +553,14 @@ namespace FlaxEditor.Windows
});
_defaultViewportScaling.Add(new ViewportScaleOptions
{
- Label = "1920x1080 Resolution",
+ Label = "1920x1080 Resolution (Full HD)",
ScaleType = ViewportScaleType.Resolution,
Size = new Int2(1920, 1080),
Active = false,
});
_defaultViewportScaling.Add(new ViewportScaleOptions
{
- Label = "2560x1440 Resolution",
+ Label = "2560x1440 Resolution (2K)",
ScaleType = ViewportScaleType.Resolution,
Size = new Int2(2560, 1440),
Active = false,
diff --git a/Source/Engine/Core/Math/Color32.h b/Source/Engine/Core/Math/Color32.h
index 48910131f..17d5d65d1 100644
--- a/Source/Engine/Core/Math/Color32.h
+++ b/Source/Engine/Core/Math/Color32.h
@@ -80,12 +80,12 @@ public:
public:
bool operator==(const Color32& other) const
{
- return R == other.R && G == other.G && B == other.B && A == other.A;
+ return Raw == other.Raw;
}
bool operator!=(const Color32& other) const
{
- return R != other.R || G != other.G || B != other.B || A != other.A;
+ return Raw != other.Raw;
}
Color32 operator+(const Color32& b) const
@@ -138,7 +138,7 @@ public:
// Returns true if color is fully transparent (all components are equal zero).
bool IsTransparent() const
{
- return R + G + B + A == 0;
+ return Raw == 0;
}
// Returns true if color has opacity channel in use (different from 255).
@@ -222,7 +222,7 @@ namespace Math
{
FORCE_INLINE static bool NearEqual(const Color32& a, const Color32& b)
{
- return a == b;
+ return a.Raw == b.Raw;
}
}
diff --git a/Source/Engine/Level/Actors/Light.h b/Source/Engine/Level/Actors/Light.h
index 3138d996f..bcb914740 100644
--- a/Source/Engine/Level/Actors/Light.h
+++ b/Source/Engine/Level/Actors/Light.h
@@ -173,8 +173,8 @@ public:
///
/// Frequency of shadow updates at the maximum distance from the view at which shadows are still rendered. This value is multiplied by ShadowsUpdateRate and allows scaling the update rate in-between the shadow range. For example, if light is near view, it will get normal shadow updates but will reduce this rate when far from view. See ShadowsUpdateRate to learn more.
///
- API_FIELD(Attributes="EditorOrder(105), EditorDisplay(\"Shadow\", \"Update Rate At Distance\"), Limit(0.0f, 1.0f)")
- float ShadowsUpdateRateAtDistance = 0.5f;
+ API_FIELD(Attributes="EditorOrder(101), EditorDisplay(\"Shadow\", \"Update Rate At Distance\"), Limit(0.0f, 1.0f)")
+ float ShadowsUpdateRateAtDistance = 1.0f;
///
/// Defines the resolution of the shadow map texture used to draw objects projection from light-point-of-view. Higher values increase shadow quality at cost of performance.
diff --git a/Source/Engine/Physics/Joints/DistanceJoint.h b/Source/Engine/Physics/Joints/DistanceJoint.h
index 0f0f59b01..d39b70d25 100644
--- a/Source/Engine/Physics/Joints/DistanceJoint.h
+++ b/Source/Engine/Physics/Joints/DistanceJoint.h
@@ -52,7 +52,7 @@ public:
///
/// Gets the joint mode flags. Controls joint behaviour.
///
- API_PROPERTY(Attributes="EditorOrder(100), DefaultValue(DistanceJointFlag.MinDistance | DistanceJointFlag.MaxDistance)")
+ API_PROPERTY(Attributes="EditorOrder(100), EditorDisplay(\"Joint\"), DefaultValue(DistanceJointFlag.MinDistance | DistanceJointFlag.MaxDistance)")
FORCE_INLINE DistanceJointFlag GetFlags() const
{
return _flags;
diff --git a/Source/Engine/Physics/Joints/HingeJoint.h b/Source/Engine/Physics/Joints/HingeJoint.h
index 21f5026e4..c0bef43f5 100644
--- a/Source/Engine/Physics/Joints/HingeJoint.h
+++ b/Source/Engine/Physics/Joints/HingeJoint.h
@@ -80,7 +80,7 @@ public:
///
/// Gets the joint mode flags. Controls joint behaviour.
///
- API_PROPERTY(Attributes="EditorOrder(100), DefaultValue(HingeJointFlag.Limit | HingeJointFlag.Drive)")
+ API_PROPERTY(Attributes="EditorOrder(100), EditorDisplay(\"Joint\"), DefaultValue(HingeJointFlag.Limit | HingeJointFlag.Drive)")
FORCE_INLINE HingeJointFlag GetFlags() const
{
return _flags;
diff --git a/Source/Engine/Platform/Defines.h b/Source/Engine/Platform/Defines.h
index 1f12f63d2..2fa85aa67 100644
--- a/Source/Engine/Platform/Defines.h
+++ b/Source/Engine/Platform/Defines.h
@@ -186,8 +186,8 @@ API_ENUM() enum class ArchitectureType
#ifndef PLATFORM_ARCH_ARM64
#define PLATFORM_ARCH_ARM64 0
#endif
-#ifndef PLATFORM_WCHAR_IS_CHAR16
-#define PLATFORM_WCHAR_IS_CHAR16 0
+#ifndef PLATFORM_TEXT_IS_CHAR16
+#define PLATFORM_TEXT_IS_CHAR16 0
#endif
#ifndef PLATFORM_DEBUG_BREAK
#define PLATFORM_DEBUG_BREAK
diff --git a/Source/Engine/Platform/StringUtils.h b/Source/Engine/Platform/StringUtils.h
index eb567b651..248f0c3d7 100644
--- a/Source/Engine/Platform/StringUtils.h
+++ b/Source/Engine/Platform/StringUtils.h
@@ -196,12 +196,12 @@ public:
static int32 HexDigit(Char c);
// Parses text to unsigned integer value. Returns true if failed to convert the value.
- template
- static bool ParseHex(const CharType* str, int32 length, uint32* result)
+ template
+ static bool ParseHex(const T* str, int32 length, uint32* result)
{
uint32 sum = 0;
- const CharType* p = str;
- const CharType* end = str + length;
+ const T* p = str;
+ const T* end = str + length;
if (*p == '0' && *(p + 1) == 'x')
p += 2;
while (*p && p < end)
@@ -221,10 +221,10 @@ public:
}
// Parses text to unsigned integer value. Returns true if failed to convert the value.
- template
- static bool ParseHex(const CharType* str, uint32* result)
+ template
+ static bool ParseHex(const T* str, uint32* result)
{
- return ParseHex(str, Length(str), result);
+ return StringUtils::ParseHex(str, StringUtils::Length(str), result);
}
// Parses text to the unsigned integer value. Returns true if failed to convert the value.
@@ -247,7 +247,7 @@ public:
static bool Parse(const T* str, uint32 length, uint32* result)
{
uint64 tmp;
- const bool b = Parse(str, length, &tmp);
+ const bool b = StringUtils::Parse(str, length, &tmp);
*result = (uint32)tmp;
return b;
}
@@ -255,7 +255,7 @@ public:
static bool Parse(const T* str, uint32 length, uint16* result)
{
uint64 tmp;
- const bool b = Parse(str, length, &tmp);
+ const bool b = StringUtils::Parse(str, length, &tmp);
*result = (uint16)tmp;
return b;
}
@@ -263,7 +263,7 @@ public:
static bool Parse(const T* str, uint32 length, uint8* result)
{
uint64 tmp;
- const bool b = Parse(str, length, &tmp);
+ const bool b = StringUtils::Parse(str, length, &tmp);
*result = (uint8)tmp;
return b;
}
@@ -296,7 +296,7 @@ public:
static bool Parse(const T* str, uint32 length, int32* result)
{
int64 tmp;
- const bool b = Parse(str, length, &tmp);
+ const bool b = StringUtils::Parse(str, length, &tmp);
*result = (int32)tmp;
return b;
}
@@ -304,7 +304,7 @@ public:
static bool Parse(const T* str, uint32 length, int16* result)
{
int64 tmp;
- const bool b = Parse(str, length, &tmp);
+ const bool b = StringUtils::Parse(str, length, &tmp);
*result = (int16)tmp;
return b;
}
@@ -312,7 +312,7 @@ public:
static bool Parse(const T* str, uint32 length, int8* result)
{
int64 tmp;
- const bool b = Parse(str, length, &tmp);
+ const bool b = StringUtils::Parse(str, length, &tmp);
*result = (int8)tmp;
return b;
}
@@ -321,7 +321,7 @@ public:
template
static bool Parse(const T* str, U* result)
{
- return Parse(str, Length(str), result);
+ return StringUtils::Parse(str, StringUtils::Length(str), result);
}
// Parses text to the scalar value. Returns true if failed to convert the value.
diff --git a/Source/Engine/Renderer/ShadowsPass.cpp b/Source/Engine/Renderer/ShadowsPass.cpp
index b30005b51..0487d7ce2 100644
--- a/Source/Engine/Renderer/ShadowsPass.cpp
+++ b/Source/Engine/Renderer/ShadowsPass.cpp
@@ -330,6 +330,7 @@ public:
for (auto it = Lights.Begin(); it.IsNotEnd(); ++it)
{
auto& atlasLight = it->Value;
+ atlasLight.StaticState = ShadowAtlasLight::Unused;
atlasLight.Cache.StaticValid = false;
for (int32 i = 0; i < atlasLight.TilesCount; i++)
atlasLight.Tiles[i].ClearDynamic();
@@ -370,7 +371,8 @@ public:
for (auto& e : Lights)
{
auto& atlasLight = e.Value;
- if (atlasLight.StaticState == ShadowAtlasLight::CopyStaticShadow && atlasLight.Bounds.Intersects(bounds))
+ if ((atlasLight.StaticState == ShadowAtlasLight::CopyStaticShadow || atlasLight.StaticState == ShadowAtlasLight::NoStaticGeometry)
+ && atlasLight.Bounds.Intersects(bounds))
{
// Invalidate static shadow
atlasLight.Cache.StaticValid = false;
@@ -719,8 +721,14 @@ bool ShadowsPass::SetupLight(ShadowsCustomBuffer& shadows, RenderContext& render
}
switch (atlasLight.StaticState)
{
- case ShadowAtlasLight::CopyStaticShadow:
case ShadowAtlasLight::NoStaticGeometry:
+ // Light was modified so attempt to find the static shadow again
+ if (!atlasLight.Cache.StaticValid && atlasLight.HasStaticShadowContext)
+ {
+ atlasLight.StaticState = ShadowAtlasLight::WaitForGeometryCheck;
+ break;
+ }
+ case ShadowAtlasLight::CopyStaticShadow:
case ShadowAtlasLight::FailedToInsertTiles:
// Skip collecting static draws
atlasLight.HasStaticShadowContext = false;
@@ -728,7 +736,7 @@ bool ShadowsPass::SetupLight(ShadowsCustomBuffer& shadows, RenderContext& render
}
if (atlasLight.HasStaticShadowContext)
{
- // If rendering finds any static draws then it's set to true
+ // If rendering finds any static draws then it will be set to true
for (auto& tile : atlasLight.Tiles)
tile.HasStaticGeometry = false;
}
@@ -1367,7 +1375,7 @@ void ShadowsPass::RenderShadowMaps(RenderContextBatch& renderContextBatch)
const ShadowsCustomBuffer* shadowsPtr = renderContext.Buffers->FindCustomBuffer(TEXT("Shadows"), false);
if (shadowsPtr == nullptr || shadowsPtr->Lights.IsEmpty() || shadowsPtr->LastFrameUsed != Engine::FrameCount)
return;
- PROFILE_GPU_CPU("ShadowMaps");
+ PROFILE_GPU_CPU("Shadow Maps");
const ShadowsCustomBuffer& shadows = *shadowsPtr;
GPUContext* context = GPUDevice::Instance->GetMainContext();
context->ResetSR();
@@ -1384,9 +1392,29 @@ void ShadowsPass::RenderShadowMaps(RenderContextBatch& renderContextBatch)
for (auto& e : shadows.Lights)
{
ShadowAtlasLight& atlasLight = e.Value;
- if (atlasLight.StaticState != ShadowAtlasLight::UpdateStaticShadow || !atlasLight.HasStaticShadowContext || atlasLight.ContextCount == 0)
+ if (!atlasLight.HasStaticShadowContext || atlasLight.ContextCount == 0)
continue;
int32 contextIndex = 0;
+
+ if (atlasLight.StaticState == ShadowAtlasLight::WaitForGeometryCheck)
+ {
+ // Check for any static geometry to use in static shadow map
+ for (int32 tileIndex = 0; tileIndex < atlasLight.TilesCount; tileIndex++)
+ {
+ ShadowAtlasLightTile& tile = atlasLight.Tiles[tileIndex];
+ contextIndex++; // Skip dynamic context
+ auto& shadowContextStatic = renderContextBatch.Contexts[atlasLight.ContextIndex + contextIndex++];
+ if (!shadowContextStatic.List->DrawCallsLists[(int32)DrawCallsListType::Depth].IsEmpty() || !shadowContextStatic.List->ShadowDepthDrawCallsList.IsEmpty())
+ {
+ tile.HasStaticGeometry = true;
+ }
+ }
+ }
+
+ if (atlasLight.StaticState != ShadowAtlasLight::UpdateStaticShadow)
+ continue;
+
+ contextIndex = 0;
for (int32 tileIndex = 0; tileIndex < atlasLight.TilesCount; tileIndex++)
{
ShadowAtlasLightTile& tile = atlasLight.Tiles[tileIndex];
diff --git a/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp b/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp
index 094daefda..dc2b20778 100644
--- a/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp
+++ b/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp
@@ -661,7 +661,7 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path
if (sourceWidth != width || sourceHeight != height)
{
// During resizing we need to keep texture aspect ratio
- const bool keepAspectRatio = false; // TODO: expose as import option
+ const bool keepAspectRatio = options.KeepAspectRatio;
if (keepAspectRatio)
{
const float aspectRatio = static_cast(sourceWidth) / sourceHeight;
@@ -727,8 +727,10 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path
}
bool keepAsIs = false;
- if (!options.FlipY &&
- !options.InvertGreenChannel &&
+ if (!options.FlipY &&
+ !options.FlipX &&
+ !options.InvertGreenChannel &&
+ !options.ReconstructZChannel &&
options.Compress &&
type == ImageType::DDS &&
mipLevels == sourceMipLevels &&
@@ -787,7 +789,7 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path
SET_CURRENT_IMG(tmpImg);
}
- // Check flip/rotate source image
+ // Check flip/rotate Y source image
if (!keepAsIs && options.FlipY)
{
auto& tmpImg = GET_TMP_IMG();
@@ -801,6 +803,20 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path
SET_CURRENT_IMG(tmpImg);
}
+ // Check flip/rotate X source image
+ if (!keepAsIs && options.FlipX)
+ {
+ auto& tmpImg = GET_TMP_IMG();
+ DirectX::TEX_FR_FLAGS flags = DirectX::TEX_FR_FLIP_HORIZONTAL;
+ result = FlipRotate(currentImage->GetImages(), currentImage->GetImageCount(), currentImage->GetMetadata(), flags, tmpImg);
+ if (FAILED(result))
+ {
+ errorMsg = String::Format(TEXT("Cannot rotate/flip texture, error: {0:x}"), static_cast(result));
+ return true;
+ }
+ SET_CURRENT_IMG(tmpImg);
+ }
+
// Check if invert green channel
if (!keepAsIs && options.InvertGreenChannel)
{
@@ -829,6 +845,43 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path
SET_CURRENT_IMG(timage);
}
+ // Reconstruct Z Channel
+ if (!keepAsIs & options.ReconstructZChannel)
+ {
+ auto& timage = GET_TMP_IMG();
+ bool isunorm = (DirectX::FormatDataType(sourceDxgiFormat) == DirectX::FORMAT_TYPE_UNORM) != 0;
+ result = TransformImage(currentImage->GetImages(), currentImage->GetImageCount(), currentImage->GetMetadata(),
+ [&](DirectX::XMVECTOR* outPixels, const DirectX::XMVECTOR* inPixels, size_t w, size_t y)
+ {
+ static const DirectX::XMVECTORU32 s_selectz = { { { DirectX::XM_SELECT_0, DirectX::XM_SELECT_0, DirectX::XM_SELECT_1, DirectX::XM_SELECT_0 } } };
+
+ UNREFERENCED_PARAMETER(y);
+
+ for (size_t j = 0; j < w; ++j)
+ {
+ const DirectX::XMVECTOR value = inPixels[j];
+ DirectX::XMVECTOR z;
+ if (isunorm)
+ {
+ DirectX::XMVECTOR x2 = DirectX::XMVectorMultiplyAdd(value, DirectX::g_XMTwo, DirectX::g_XMNegativeOne);
+ x2 = DirectX::XMVectorSqrt(DirectX::XMVectorSubtract(DirectX::g_XMOne, DirectX::XMVector2Dot(x2, x2)));
+ z = DirectX::XMVectorMultiplyAdd(x2, DirectX::g_XMOneHalf, DirectX::g_XMOneHalf);
+ }
+ else
+ {
+ z = DirectX::XMVectorSqrt(DirectX::XMVectorSubtract(DirectX::g_XMOne, DirectX::XMVector2Dot(value, value)));
+ }
+ outPixels[j] = DirectX::XMVectorSelect(value, z, s_selectz);
+ }
+ }, timage);
+ if (FAILED(result))
+ {
+ errorMsg = String::Format(TEXT("Cannot reconstruct z channel in texture, error: {0:x}"), static_cast(result));
+ return true;
+ }
+ SET_CURRENT_IMG(timage);
+ }
+
// Generate mip maps chain
if (!keepAsIs && useMipLevels && options.GenerateMipMaps)
{
diff --git a/Source/Engine/Tools/TextureTool/TextureTool.cpp b/Source/Engine/Tools/TextureTool/TextureTool.cpp
index ff0a66177..550e26699 100644
--- a/Source/Engine/Tools/TextureTool/TextureTool.cpp
+++ b/Source/Engine/Tools/TextureTool/TextureTool.cpp
@@ -73,12 +73,21 @@ void TextureTool::Options::Serialize(SerializeStream& stream, const void* otherO
stream.JKEY("FlipY");
stream.Bool(FlipY);
+ stream.JKEY("FlipX");
+ stream.Bool(FlipX);
+
stream.JKEY("InvertGreenChannel");
stream.Bool(InvertGreenChannel);
+ stream.JKEY("ReconstructZChannel");
+ stream.Bool(ReconstructZChannel);
+
stream.JKEY("Resize");
stream.Bool(Resize);
+ stream.JKEY("KeepAspectRatio");
+ stream.Bool(KeepAspectRatio);
+
stream.JKEY("PreserveAlphaCoverage");
stream.Bool(PreserveAlphaCoverage);
@@ -133,8 +142,11 @@ void TextureTool::Options::Deserialize(DeserializeStream& stream, ISerializeModi
sRGB = JsonTools::GetBool(stream, "sRGB", sRGB);
GenerateMipMaps = JsonTools::GetBool(stream, "GenerateMipMaps", GenerateMipMaps);
FlipY = JsonTools::GetBool(stream, "FlipY", FlipY);
+ FlipX = JsonTools::GetBool(stream, "FlipX", FlipX);
InvertGreenChannel = JsonTools::GetBool(stream, "InvertGreenChannel", InvertGreenChannel);
+ ReconstructZChannel = JsonTools::GetBool(stream, "ReconstructZChannel", ReconstructZChannel);
Resize = JsonTools::GetBool(stream, "Resize", Resize);
+ KeepAspectRatio = JsonTools::GetBool(stream, "KeepAspectRatio", KeepAspectRatio);
PreserveAlphaCoverage = JsonTools::GetBool(stream, "PreserveAlphaCoverage", PreserveAlphaCoverage);
PreserveAlphaCoverageReference = JsonTools::GetFloat(stream, "PreserveAlphaCoverageReference", PreserveAlphaCoverageReference);
TextureGroup = JsonTools::GetInt(stream, "TextureGroup", TextureGroup);
diff --git a/Source/Engine/Tools/TextureTool/TextureTool.h b/Source/Engine/Tools/TextureTool/TextureTool.h
index 85d8b2d20..13bac0dc5 100644
--- a/Source/Engine/Tools/TextureTool/TextureTool.h
+++ b/Source/Engine/Tools/TextureTool/TextureTool.h
@@ -53,14 +53,22 @@ API_CLASS(Namespace="FlaxEngine.Tools", Static) class FLAXENGINE_API TextureTool
API_FIELD(Attributes="EditorOrder(60)")
bool GenerateMipMaps = true;
- // True if flip Y coordinate of the texture.
+ // True if flip Y coordinate of the texture (Flips over X axis).
API_FIELD(Attributes="EditorOrder(70)")
bool FlipY = false;
+ // True if flip X coordinate of the texture (Flips over Y axis).
+ API_FIELD(Attributes="EditorOrder(71)")
+ bool FlipX = false;
+
// True if to invert the green channel on a normal map. Good for OpenGL to DirectX conversion.
- API_FIELD(Attributes = "EditorOrder(71)")
+ API_FIELD(Attributes = "EditorOrder(72)")
bool InvertGreenChannel = false;
+ // Rebuild Z (blue) channel assuming X/Y are normals.
+ API_FIELD(Attributes = "EditorOrder(73)")
+ bool ReconstructZChannel = false;
+
// Texture size scale. Allows increasing or decreasing the imported texture resolution. Default is 1.
API_FIELD(Attributes="EditorOrder(80), Limit(0.0001f, 1000.0f, 0.01f)")
float Scale = 1.0f;
@@ -73,6 +81,10 @@ API_CLASS(Namespace="FlaxEngine.Tools", Static) class FLAXENGINE_API TextureTool
API_FIELD(Attributes="EditorOrder(100)")
bool Resize = false;
+ // Keeps the aspect ratio when resizing.
+ API_FIELD(Attributes="EditorOrder(101), VisibleIf(nameof(Resize))")
+ bool KeepAspectRatio = false;
+
// The width of the imported texture. If Resize property is set to true then texture will be resized during the import to this value during the import, otherwise it will be ignored.
API_FIELD(Attributes="HideInEditor")
int32 SizeX = 1024;
diff --git a/Source/Engine/Tools/TextureTool/TextureTool.stb.cpp b/Source/Engine/Tools/TextureTool/TextureTool.stb.cpp
index 1301327ef..8ca585c32 100644
--- a/Source/Engine/Tools/TextureTool/TextureTool.stb.cpp
+++ b/Source/Engine/Tools/TextureTool/TextureTool.stb.cpp
@@ -458,7 +458,7 @@ bool TextureTool::ImportTextureStb(ImageType type, const StringView& path, Textu
{
if (!options.InternalLoad.IsBinded() || options.InternalLoad(textureData))
return true;
- if (options.FlipY)
+ if (options.FlipY || options.FlipX)
{
// TODO: impl this
errorMsg = TEXT("Flipping images imported from Internal source is not supported by stb.");
@@ -489,7 +489,7 @@ bool TextureTool::ImportTextureStb(ImageType type, const StringView& path, Textu
if (sourceWidth != width || sourceHeight != height)
{
// During resizing we need to keep texture aspect ratio
- const bool keepAspectRatio = false; // TODO: expose as import option
+ const bool keepAspectRatio = options.KeepAspectRatio; // TODO: expose as import option
if (keepAspectRatio)
{
const float aspectRatio = static_cast(sourceWidth) / sourceHeight;
@@ -536,6 +536,22 @@ bool TextureTool::ImportTextureStb(ImageType type, const StringView& path, Textu
return true;
}
+ if (options.FlipX)
+ {
+ // TODO: impl this
+ LOG(Warning, "Option 'Flip X' is not supported");
+ }
+ if (options.InvertGreenChannel)
+ {
+ // TODO: impl this
+ LOG(Warning, "Option 'Invert Green Channel' is not supported");
+ }
+ if (options.ReconstructZChannel)
+ {
+ // TODO: impl this
+ LOG(Warning, "Option 'Reconstruct Z Channel' is not supported");
+ }
+
// Generate mip maps chain
if (useMipLevels && options.GenerateMipMaps)
{
diff --git a/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs b/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs
index df5582cc3..4a49f5bad 100644
--- a/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs
+++ b/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs
@@ -60,6 +60,51 @@ namespace FlaxEngine.GUI
return false;
}
+ ///
+ /// Gets the text block or the nearest text block of the character at the given index.
+ ///
+ /// The character index.
+ /// The result text block descriptor.
+ /// If true, the when the index is between two text blocks, it will return the next block.
+ /// True if a text block is found, otherwise false.
+ public bool GetNearestTextBlock(int index, out TextBlock result, bool snapToNext = false)
+ {
+ var textBlocksSpan = CollectionsMarshal.AsSpan(_textBlocks);
+ int blockCount = _textBlocks.Count;
+
+ for (int i = 0; i < blockCount; i++)
+ {
+ ref TextBlock currentBlock = ref textBlocksSpan[i];
+
+ if (currentBlock.Range.Contains(index))
+ {
+ result = currentBlock;
+ return true;
+ }
+
+ if (i < blockCount - 1)
+ {
+ ref TextBlock nextBlock = ref textBlocksSpan[i + 1];
+ if (index >= currentBlock.Range.EndIndex && index < nextBlock.Range.StartIndex)
+ {
+ result = snapToNext ? nextBlock : currentBlock;
+ return true;
+ }
+ }
+ }
+
+ // Handle case when index is outside all text ranges
+ if (index >= 0 && blockCount > 0)
+ {
+ result = (index <= textBlocksSpan[0].Range.StartIndex) ? textBlocksSpan[0] : textBlocksSpan[blockCount - 1];
+ return true;
+ }
+
+ // If no text block is found
+ result = new TextBlock();
+ return false;
+ }
+
///
/// Updates the text blocks.
///
@@ -221,6 +266,35 @@ namespace FlaxEngine.GUI
return base.OnMouseDoubleClick(location, button);
}
+ ///
+ protected override void SetSelection(int start, int end, bool withScroll = true)
+ {
+ int snappedStart = start;
+ int snappedEnd = end;
+
+ if (start != -1 && end != -1)
+ {
+ bool movingBack = (_selectionStart != -1 && _selectionEnd != -1) && (end < _selectionEnd || start < _selectionStart);
+
+ GetNearestTextBlock(start, out TextBlock startTextBlock, !movingBack);
+ GetNearestTextBlock(end, out TextBlock endTextBlock, !movingBack);
+
+ snappedStart = startTextBlock.Range.Contains(start) ? start : (movingBack ? startTextBlock.Range.EndIndex - 1 : startTextBlock.Range.StartIndex);
+ snappedEnd = endTextBlock.Range.Contains(end) ? end : (movingBack ? endTextBlock.Range.EndIndex - 1 : endTextBlock.Range.StartIndex);
+
+ snappedStart = movingBack ? Math.Min(start, snappedStart) : Math.Max(start, snappedStart);
+ snappedEnd = movingBack ? Math.Min(end, snappedEnd) : Math.Max(end, snappedEnd);
+
+ // Don't snap if selection is right in the end of the text
+ if (start == _text.Length)
+ snappedStart = _text.Length;
+ if (end == _text.Length)
+ snappedEnd = _text.Length;
+ }
+
+ base.SetSelection(snappedStart, snappedEnd, withScroll);
+ }
+
///
public override void DrawSelf()
{
@@ -289,8 +363,8 @@ namespace FlaxEngine.GUI
// Selection
if (hasSelection && textBlock.Style.BackgroundSelectedBrush != null && textBlock.Range.Intersect(ref selection))
{
- var leftEdge = selection.StartIndex <= textBlock.Range.StartIndex ? textBlock.Bounds.UpperLeft : font.GetCharPosition(_text, selection.StartIndex);
- var rightEdge = selection.EndIndex >= textBlock.Range.EndIndex ? textBlock.Bounds.UpperRight : font.GetCharPosition(_text, selection.EndIndex);
+ var leftEdge = selection.StartIndex <= textBlock.Range.StartIndex ? textBlock.Bounds.UpperLeft : GetCharPosition(selection.StartIndex, out _);
+ var rightEdge = selection.EndIndex >= textBlock.Range.EndIndex ? textBlock.Bounds.UpperRight : GetCharPosition(selection.EndIndex, out _);
float height = font.Height;
float alpha = Mathf.Min(1.0f, Mathf.Cos(_animateTime * BackgroundSelectedFlashSpeed) * 0.5f + 1.3f);
alpha *= alpha;
diff --git a/Source/Engine/Video/MF/VideoBackendMF.cpp b/Source/Engine/Video/MF/VideoBackendMF.cpp
index 727a94158..d396d7672 100644
--- a/Source/Engine/Video/MF/VideoBackendMF.cpp
+++ b/Source/Engine/Video/MF/VideoBackendMF.cpp
@@ -397,6 +397,7 @@ namespace MF
// Loop
playerMF.Time.Ticks %= player.Duration.Ticks;
playerMF.Seek = 1;
+ player.PlayAudio();
}
else
{
diff --git a/Source/Platforms/Linux/Binaries/ThirdParty/x64/libIrrXML.a b/Source/Platforms/Linux/Binaries/ThirdParty/x64/libIrrXML.a
deleted file mode 100644
index 92c1baf0b..000000000
--- a/Source/Platforms/Linux/Binaries/ThirdParty/x64/libIrrXML.a
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:4eb25101716011a5b4c872c5cd303c3292a61e5f661e9296d95502b5705e2e53
-size 181458
diff --git a/Source/Platforms/Linux/Binaries/ThirdParty/x64/libassimp.a b/Source/Platforms/Linux/Binaries/ThirdParty/x64/libassimp.a
index 4dccc7f4b..92b3c5545 100644
--- a/Source/Platforms/Linux/Binaries/ThirdParty/x64/libassimp.a
+++ b/Source/Platforms/Linux/Binaries/ThirdParty/x64/libassimp.a
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:8cbd73a154e270cc03d3da292b2c3cc73ee473b221f43722d060dc114916d6d5
-size 7996212
+oid sha256:b3510d6c5585f08fc9fcbf2044bb0dc0238e2501c1914e3b98aef36bc8cd2711
+size 10303430
diff --git a/Source/Platforms/Mac/Binaries/ThirdParty/ARM64/libIrrXML.a b/Source/Platforms/Mac/Binaries/ThirdParty/ARM64/libIrrXML.a
deleted file mode 100644
index 6bcd58a96..000000000
--- a/Source/Platforms/Mac/Binaries/ThirdParty/ARM64/libIrrXML.a
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:6aa0849de08ac7f7332c63941b6ed028c329971b7d0ce9de44580e46b7560f2d
-size 519048
diff --git a/Source/Platforms/Mac/Binaries/ThirdParty/ARM64/libassimp.a b/Source/Platforms/Mac/Binaries/ThirdParty/ARM64/libassimp.a
index 622b54e48..0aeab8ca5 100644
--- a/Source/Platforms/Mac/Binaries/ThirdParty/ARM64/libassimp.a
+++ b/Source/Platforms/Mac/Binaries/ThirdParty/ARM64/libassimp.a
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:05ed7fbba9701903c4d488eebda74ff71316af984bda5b125ff4822e53c05f3a
-size 66547240
+oid sha256:512690a2639b29649b19531e8f24f0e50600c5c225c674568d28e168b5078838
+size 7427096
diff --git a/Source/Platforms/Mac/Binaries/ThirdParty/x64/libIrrXML.a b/Source/Platforms/Mac/Binaries/ThirdParty/x64/libIrrXML.a
deleted file mode 100644
index 792b3daf5..000000000
--- a/Source/Platforms/Mac/Binaries/ThirdParty/x64/libIrrXML.a
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:4b67f00759cb59c91e5fe3fcf8bc107c6ac5f54c1929cea246892298ec40f6ec
-size 543624
diff --git a/Source/Platforms/Mac/Binaries/ThirdParty/x64/libassimp.a b/Source/Platforms/Mac/Binaries/ThirdParty/x64/libassimp.a
index da1ab7c0b..ae7e0edd9 100644
--- a/Source/Platforms/Mac/Binaries/ThirdParty/x64/libassimp.a
+++ b/Source/Platforms/Mac/Binaries/ThirdParty/x64/libassimp.a
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:ead9834575a8b7dcab9f8539b59342a850d72d968ab4bdf6e1775510c7a38659
+oid sha256:1fc8320db7b98865e00e750277dec9aff82a3842454cb784d944c44b4aacfcbc
size 7830128
diff --git a/Source/ThirdParty/assimp/assimp.Build.cs b/Source/ThirdParty/assimp/assimp.Build.cs
index dc0f0f06c..2276ca2e2 100644
--- a/Source/ThirdParty/assimp/assimp.Build.cs
+++ b/Source/ThirdParty/assimp/assimp.Build.cs
@@ -37,7 +37,6 @@ public class assimp : DepsModule
case TargetPlatform.Linux:
case TargetPlatform.Mac:
options.OutputFiles.Add(Path.Combine(depsRoot, "libassimp.a"));
- options.OutputFiles.Add(Path.Combine(depsRoot, "libIrrXML.a"));
break;
default: throw new InvalidPlatformException(options.Platform.Target);
}
diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/Assimp.cs b/Source/Tools/Flax.Build/Deps/Dependencies/Assimp.cs
index 3cc1bacf0..652bbccb9 100644
--- a/Source/Tools/Flax.Build/Deps/Dependencies/Assimp.cs
+++ b/Source/Tools/Flax.Build/Deps/Dependencies/Assimp.cs
@@ -1,6 +1,6 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
-using System;
+using System.Collections.Generic;
using System.IO;
using Flax.Build;
@@ -122,13 +122,19 @@ namespace Flax.Deps.Dependencies
}
case TargetPlatform.Linux:
{
+ var envVars = new Dictionary
+ {
+ { "CC", "clang-13" },
+ { "CC_FOR_BUILD", "clang-13" },
+ { "CXX", "clang++-13" },
+ };
+
// Build for Linux
- RunCmake(root, platform, TargetArchitecture.x64, " -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF " + globalConfig);
- Utilities.Run("make", null, null, root, Utilities.RunOptions.ThrowExceptionOnError);
+ RunCmake(root, platform, TargetArchitecture.x64, " -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF " + globalConfig, envVars);
+ Utilities.Run("make", null, null, root, Utilities.RunOptions.ThrowExceptionOnError, envVars);
configHeaderFilePath = Path.Combine(root, "include", "assimp", "config.h");
var depsFolder = GetThirdPartyFolder(options, platform, TargetArchitecture.x64);
Utilities.FileCopy(Path.Combine(root, "lib", "libassimp.a"), Path.Combine(depsFolder, "libassimp.a"));
- Utilities.FileCopy(Path.Combine(root, "lib", "libIrrXML.a"), Path.Combine(depsFolder, "libIrrXML.a"));
break;
}
case TargetPlatform.Mac:
@@ -141,7 +147,7 @@ namespace Flax.Deps.Dependencies
configHeaderFilePath = Path.Combine(root, "include", "assimp", "config.h");
var depsFolder = GetThirdPartyFolder(options, platform, architecture);
Utilities.FileCopy(Path.Combine(root, "lib", "libassimp.a"), Path.Combine(depsFolder, "libassimp.a"));
- Utilities.FileCopy(Path.Combine(root, "lib", "libIrrXML.a"), Path.Combine(depsFolder, "libIrrXML.a"));
+ Utilities.Run("make", "clean", null, root, Utilities.RunOptions.ThrowExceptionOnError);
}
break;
}