Merge remote-tracking branch 'origin/master' into 1.10
# Conflicts: # Source/Engine/Platform/StringUtils.h
This commit is contained in:
7
.github/workflows/build_linux.yml
vendored
7
.github/workflows/build_linux.yml
vendored
@@ -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
|
||||
|
||||
4
.github/workflows/build_linux_sources.list
vendored
4
.github/workflows/build_linux_sources.list
vendored
@@ -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
|
||||
10
.github/workflows/cd.yml
vendored
10
.github/workflows/cd.yml
vendored
@@ -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
|
||||
|
||||
5
.github/workflows/tests.yml
vendored
5
.github/workflows/tests.yml
vendored
@@ -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: |
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -32,6 +32,14 @@ namespace FlaxEditor.Content
|
||||
/// <inheritdoc />
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -241,6 +241,15 @@ namespace FlaxEditor.GUI
|
||||
/// <param name="value">The keyframe value.</param>
|
||||
public abstract void SetKeyframeValue(int index, object value);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the existing keyframe value (as boxed object).
|
||||
/// </summary>
|
||||
/// <param name="index">The keyframe index.</param>
|
||||
/// <param name="value">The keyframe value.</param>
|
||||
/// <param name="tangentIn">The keyframe 'In' tangent value (boxed).</param>
|
||||
/// <param name="tangentOut">The keyframe 'Out' tangent value (boxed).</param>
|
||||
public abstract void SetKeyframeValue(int index, object value, object tangentIn, object tangentOut);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the keyframe point (in keyframes space).
|
||||
/// </summary>
|
||||
|
||||
@@ -1289,6 +1289,18 @@ namespace FlaxEditor.GUI
|
||||
OnEdited();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Float2 GetKeyframePoint(int index, int component)
|
||||
{
|
||||
@@ -2011,6 +2023,20 @@ namespace FlaxEditor.GUI
|
||||
OnEdited();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Float2 GetKeyframePoint(int index, int component)
|
||||
{
|
||||
|
||||
@@ -139,6 +139,8 @@ namespace FlaxEditor.GUI.Docking
|
||||
/// <inheritdoc />
|
||||
protected override void OnLastTabRemoved()
|
||||
{
|
||||
if (ChildPanelsCount > 0)
|
||||
return;
|
||||
// Close window
|
||||
_window?.Close();
|
||||
}
|
||||
|
||||
203
Source/Editor/GUI/Timeline/EditCurveTrackGizmo.cs
Normal file
203
Source/Editor/GUI/Timeline/EditCurveTrackGizmo.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Gizmo for editing position curve. Managed by the <see cref="SceneAnimationTimeline"/>.
|
||||
/// </summary>
|
||||
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<Vector3>)_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<Vector3>)_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<Vector3>)_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<Vector3>)_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<Vector3>)_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<TransformGizmo>();
|
||||
}
|
||||
|
||||
public override void OnActivated()
|
||||
{
|
||||
base.OnActivated();
|
||||
|
||||
ActiveMode = Mode.Translate;
|
||||
}
|
||||
|
||||
public override void OnDeactivated()
|
||||
{
|
||||
// Clear selection
|
||||
_track = null;
|
||||
_keyframe = -1;
|
||||
_item = -1;
|
||||
|
||||
base.OnDeactivated();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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<TransformGizmo>().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<Vector3>)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
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
if (_curveTrackGizmo != null)
|
||||
{
|
||||
_curveTrackGizmo.Destroy();
|
||||
_curveTrackGizmo = null;
|
||||
}
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnShowViewContextMenu(ContextMenu.ContextMenu menu)
|
||||
{
|
||||
|
||||
@@ -12,15 +12,17 @@ namespace FlaxEditor.Gizmo
|
||||
[HideInEditor]
|
||||
public abstract class GizmoBase
|
||||
{
|
||||
private IGizmoOwner _owner;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the gizmo owner.
|
||||
/// </summary>
|
||||
public IGizmoOwner Owner { get; }
|
||||
public IGizmoOwner Owner => _owner;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this gizmo is active.
|
||||
/// </summary>
|
||||
public bool IsActive => Owner.Gizmos.Active == this;
|
||||
public bool IsActive => _owner.Gizmos.Active == this;
|
||||
|
||||
/// <summary>
|
||||
/// 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -94,5 +96,25 @@ namespace FlaxEditor.Gizmo
|
||||
public virtual void Draw(ref RenderContext renderContext)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Activates thi gizmo mode.
|
||||
/// </summary>
|
||||
public void Activate()
|
||||
{
|
||||
_owner.Gizmos.Active = this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the gizmo from the owner.
|
||||
/// </summary>
|
||||
public void Destroy()
|
||||
{
|
||||
if (_owner != null)
|
||||
{
|
||||
_owner.Gizmos.Remove(this);
|
||||
_owner = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -13,6 +13,37 @@ using namespace pugi;
|
||||
|
||||
Array<ProjectInfo*> 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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -173,8 +173,8 @@ public:
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
|
||||
@@ -52,7 +52,7 @@ public:
|
||||
/// <summary>
|
||||
/// Gets the joint mode flags. Controls joint behaviour.
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
@@ -80,7 +80,7 @@ public:
|
||||
/// <summary>
|
||||
/// Gets the joint mode flags. Controls joint behaviour.
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<typename CharType>
|
||||
static bool ParseHex(const CharType* str, int32 length, uint32* result)
|
||||
template<typename T>
|
||||
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<typename CharType>
|
||||
static bool ParseHex(const CharType* str, uint32* result)
|
||||
template<typename T>
|
||||
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<typename T, typename U>
|
||||
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.
|
||||
|
||||
@@ -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<ShadowsCustomBuffer>(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];
|
||||
|
||||
@@ -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<float>(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<uint32>(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<uint32>(result));
|
||||
return true;
|
||||
}
|
||||
SET_CURRENT_IMG(timage);
|
||||
}
|
||||
|
||||
// Generate mip maps chain
|
||||
if (!keepAsIs && useMipLevels && options.GenerateMipMaps)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<float>(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)
|
||||
{
|
||||
|
||||
@@ -60,6 +60,51 @@ namespace FlaxEngine.GUI
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the text block or the nearest text block of the character at the given index.
|
||||
/// </summary>
|
||||
/// <param name="index">The character index.</param>
|
||||
/// <param name="result">The result text block descriptor.</param>
|
||||
/// <param name="snapToNext">If true, the when the index is between two text blocks, it will return the next block.</param>
|
||||
/// <returns>True if a text block is found, otherwise false.</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the text blocks.
|
||||
/// </summary>
|
||||
@@ -221,6 +266,35 @@ namespace FlaxEngine.GUI
|
||||
return base.OnMouseDoubleClick(location, button);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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;
|
||||
|
||||
@@ -397,6 +397,7 @@ namespace MF
|
||||
// Loop
|
||||
playerMF.Time.Ticks %= player.Duration.Ticks;
|
||||
playerMF.Seek = 1;
|
||||
player.PlayAudio();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
BIN
Source/Platforms/Linux/Binaries/ThirdParty/x64/libIrrXML.a
(Stored with Git LFS)
vendored
BIN
Source/Platforms/Linux/Binaries/ThirdParty/x64/libIrrXML.a
(Stored with Git LFS)
vendored
Binary file not shown.
BIN
Source/Platforms/Linux/Binaries/ThirdParty/x64/libassimp.a
(Stored with Git LFS)
vendored
BIN
Source/Platforms/Linux/Binaries/ThirdParty/x64/libassimp.a
(Stored with Git LFS)
vendored
Binary file not shown.
BIN
Source/Platforms/Mac/Binaries/ThirdParty/ARM64/libIrrXML.a
(Stored with Git LFS)
vendored
BIN
Source/Platforms/Mac/Binaries/ThirdParty/ARM64/libIrrXML.a
(Stored with Git LFS)
vendored
Binary file not shown.
BIN
Source/Platforms/Mac/Binaries/ThirdParty/ARM64/libassimp.a
(Stored with Git LFS)
vendored
BIN
Source/Platforms/Mac/Binaries/ThirdParty/ARM64/libassimp.a
(Stored with Git LFS)
vendored
Binary file not shown.
BIN
Source/Platforms/Mac/Binaries/ThirdParty/x64/libIrrXML.a
(Stored with Git LFS)
vendored
BIN
Source/Platforms/Mac/Binaries/ThirdParty/x64/libIrrXML.a
(Stored with Git LFS)
vendored
Binary file not shown.
BIN
Source/Platforms/Mac/Binaries/ThirdParty/x64/libassimp.a
(Stored with Git LFS)
vendored
BIN
Source/Platforms/Mac/Binaries/ThirdParty/x64/libassimp.a
(Stored with Git LFS)
vendored
Binary file not shown.
1
Source/ThirdParty/assimp/assimp.Build.cs
vendored
1
Source/ThirdParty/assimp/assimp.Build.cs
vendored
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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<string, string>
|
||||
{
|
||||
{ "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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user