Merge remote-tracking branch 'origin/master' into 1.9

# Conflicts:
#	Flax.flaxproj
This commit is contained in:
Wojtek Figat
2024-05-22 16:15:14 +02:00
41 changed files with 295 additions and 114 deletions

View File

@@ -74,6 +74,7 @@
<s:String x:Key="/Default/CodeStyle/Naming/CppNaming/UserRules/=UNION/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CppNaming/UserRules/=UNION_005FMEMBER/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=AI/@EntryIndexedValue">AI</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ARGB/@EntryIndexedValue">ARGB</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=LO/@EntryIndexedValue">LO</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=RPC/@EntryIndexedValue">RPC</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SDK/@EntryIndexedValue">SDK</s:String>

View File

@@ -75,7 +75,11 @@ namespace FlaxEditor.CustomEditors.Dedicated
// Selecting actor prefab asset
var selectPrefab = panel.Button("Select Prefab");
selectPrefab.Button.Clicked += () => Editor.Instance.Windows.ContentWin.Select(prefab);
selectPrefab.Button.Clicked += () =>
{
Editor.Instance.Windows.ContentWin.ClearItemsSearch();
Editor.Instance.Windows.ContentWin.Select(prefab);
};
// Viewing changes applied to this actor
var viewChanges = panel.Button("View Changes");

View File

@@ -227,7 +227,7 @@ namespace FlaxEditor.CustomEditors.Editors
var collection = (CollectionAttribute)attributes?.FirstOrDefault(x => x is CollectionAttribute);
if (collection != null)
{
_canResize = !collection.ReadOnly;
_canResize = collection.CanResize;
_readOnly = collection.ReadOnly;
_minCount = collection.MinCount;
_maxCount = collection.MaxCount;

View File

@@ -189,6 +189,7 @@ namespace FlaxEditor.CustomEditors.Editors
var collection = (CollectionAttribute)attributes?.FirstOrDefault(x => x is CollectionAttribute);
if (collection != null)
{
_canEditKeys &= collection.CanReorderItems;
_readOnly = collection.ReadOnly;
_notNullItems = collection.NotNullItems;
if (collection.BackgroundColor.HasValue)

View File

@@ -39,7 +39,7 @@ namespace FlaxEditor.CustomEditors.Editors
if (watermarkAttribute is WatermarkAttribute watermark)
{
_watermarkText = watermark.WatermarkText;
var watermarkColor = watermark.WatermarkColor > 0 ? Color.FromRGBA(watermark.WatermarkColor) : FlaxEngine.GUI.Style.Current.ForegroundDisabled;
var watermarkColor = watermark.WatermarkColor > 0 ? Color.FromRGB(watermark.WatermarkColor) : FlaxEngine.GUI.Style.Current.ForegroundDisabled;
_watermarkColor = watermarkColor;
_element.TextBox.WatermarkText = watermark.WatermarkText;
_element.TextBox.WatermarkTextColor = watermarkColor;

View File

@@ -318,7 +318,7 @@ namespace FlaxEditor.CustomEditors
if (header.FontSize > 0)
element.Label.Font = new FontReference(element.Label.Font.Font, header.FontSize);
if (header.Color > 0)
element.Label.TextColor = Color.FromRGBA(header.Color);
element.Label.TextColor = Color.FromRGB(header.Color);
return element;
}

View File

@@ -130,7 +130,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
/// The keyframes.
/// </summary>
[EditorDisplay("Keyframes", EditorDisplayAttribute.InlineStyle), ExpandGroups]
[Collection(CanReorderItems = false, ReadOnly = true)]
[Collection(CanReorderItems = false, CanResize = true)]
public List<KeyValuePair<string, object>> Keyframes;
/// <inheritdoc />

View File

@@ -159,7 +159,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
/// The parameters values.
/// </summary>
[EditorDisplay("Parameters", EditorDisplayAttribute.InlineStyle), ExpandGroups]
[Collection(CanReorderItems = false, ReadOnly = true)]
[Collection(CanReorderItems = false, CanResize = true)]
public object[] Parameters;
/// <inheritdoc />

View File

@@ -253,7 +253,11 @@ namespace FlaxEditor
{
// Select node (with additive mode)
var selection = new List<SceneGraphNode>();
if (Root.GetKey(KeyboardKeys.Control))
if (Root.GetKey(KeyboardKeys.Shift) && transformGizmo.Selection.Contains(uiControlNode))
{
// Move whole selection
}
else if (Root.GetKey(KeyboardKeys.Control))
{
// Add/remove from selection
selection.AddRange(transformGizmo.Selection);
@@ -261,13 +265,14 @@ namespace FlaxEditor
selection.Remove(uiControlNode);
else
selection.Add(uiControlNode);
owner.Select(selection);
}
else
{
// Select
selection.Add(uiControlNode);
owner.Select(selection);
}
owner.Select(selection);
// Initialize control movement
_mouseMovesControl = true;

View File

@@ -202,6 +202,7 @@ namespace FlaxEditor.Modules
var prefabId = ((ActorNode)selection[0]).Actor.PrefabID;
var prefab = FlaxEngine.Content.LoadAsync<Prefab>(prefabId);
Editor.Windows.ContentWin.ClearItemsSearch();
Editor.Windows.ContentWin.Select(prefab);
}

View File

@@ -565,6 +565,7 @@ namespace FlaxEditor.Modules
if (item != null)
Editor.ContentEditing.Open(item);
});
cm.AddButton("Editor Options", () => Editor.Windows.EditorOptionsWin.Show());
// Scene
MenuScene = MainMenu.AddButton("Scene");
@@ -619,7 +620,6 @@ namespace FlaxEditor.Modules
_menuToolsTakeScreenshot = cm.AddButton("Take screenshot", inputOptions.TakeScreenshot, Editor.Windows.TakeScreenshot);
cm.AddSeparator();
cm.AddButton("Plugins", () => Editor.Windows.PluginsWin.Show());
cm.AddButton("Options", () => Editor.Windows.EditorOptionsWin.Show());
// Window
MenuWindow = MainMenu.AddButton("Window");

View File

@@ -82,7 +82,7 @@ namespace FlaxEditor.Tools.Foliage
}
}
[EditorOrder(20), EditorDisplay("Model"), Collection(ReadOnly = true), Tooltip("Model materials override collection. Can be used to change a specific material of the mesh to the custom one without editing the asset.")]
[EditorOrder(20), EditorDisplay("Model"), Collection(CanResize = true), Tooltip("Model materials override collection. Can be used to change a specific material of the mesh to the custom one without editing the asset.")]
public MaterialBase[] Materials
{
get

View File

@@ -155,8 +155,8 @@ namespace FlaxEditor.Utilities
var scene = Level.LoadSceneFromBytes(data.Bytes);
if (scene == null)
{
Profiler.EndEvent();
throw new Exception("Failed to deserialize scene");
Editor.LogError("Failed to restore scene");
continue;
}
// Restore `dirty` state

View File

@@ -131,7 +131,7 @@ namespace FlaxEditor.Windows
public void ClearItemsSearch()
{
// Skip if already cleared
if (_itemsSearchBox.TextLength == 0 && !_viewDropdown.HasSelection)
if (_itemsSearchBox.TextLength == 0)
return;
IsLayoutLocked = true;

View File

@@ -860,7 +860,7 @@ namespace FlaxEditor.Windows
{
var alpha = Mathf.Saturate(-animTime / fadeOutTime);
var rect = new Rectangle(new Float2(6), Size - 12);
var text = "Press Shift+F11 to unlock the mouse";
var text = $"Press {Editor.Options.Options.Input.DebuggerUnlockMouse} to unlock the mouse";
Render2D.DrawText(style.FontSmall, text, rect + new Float2(1.0f), style.Background * alpha, TextAlignment.Near, TextAlignment.Far);
Render2D.DrawText(style.FontSmall, text, rect, style.Foreground * alpha, TextAlignment.Near, TextAlignment.Far);
}

View File

@@ -163,7 +163,10 @@ namespace Serialization
{
const auto& keyframesArray = mKeyframes->value.GetArray();
auto& keyframes = v.GetKeyframes();
const int32 newCount = keyframesArray.Size() - keyframes.Count();
keyframes.Resize(keyframesArray.Size());
for (int32 i = 0; i < newCount; i++)
keyframes[keyframes.Count() + i - newCount] = KeyFrame(0.0f, AnimationUtils::GetZero<T>());
for (rapidjson::SizeType i = 0; i < keyframesArray.Size(); i++)
Deserialize(keyframesArray[i], keyframes[i], modifier);
}

View File

@@ -80,7 +80,6 @@ void InverseKinematics::SolveTwoBoneIK(Transform& rootTransform, Transform& midJ
// Calculate new positions for joint and end effector
Vector3 newEndEffectorPos = targetPosition;
Vector3 newMidJointPos = midJointPos;
if (toTargetLength >= totalLimbLength)
{
// Target is beyond the reach of the limb
@@ -90,13 +89,9 @@ void InverseKinematics::SolveTwoBoneIK(Transform& rootTransform, Transform& midJ
Vector3 rootToPole = (poleVector - rootTransform.Translation).GetNormalized();
Vector3 slightBendDirection = Vector3::Cross(rootToEnd, rootToPole);
if (slightBendDirection.LengthSquared() < ZeroTolerance * ZeroTolerance)
{
slightBendDirection = Vector3::Up;
}
else
{
slightBendDirection.Normalize();
}
// Calculate the direction from root to mid joint with a slight offset towards the pole vector
Vector3 midJointDirection = Vector3::Cross(slightBendDirection, rootToEnd).GetNormalized();
@@ -117,9 +112,16 @@ void InverseKinematics::SolveTwoBoneIK(Transform& rootTransform, Transform& midJ
projJointDist *= -1.0f;
newMidJointPos = rootTransform.Translation + projJointDist * toTargetDir + jointLineDist * bendDirection;
}
// TODO: fix the new IK impl (https://github.com/FlaxEngine/FlaxEngine/pull/2421) to properly work for character from https://github.com/PrecisionRender/CharacterControllerPro
#define OLD 0
// Update root joint orientation
{
#if OLD
const Vector3 oldDir = (midJointPos - rootTransform.Translation).GetNormalized();
const Vector3 newDir = (newMidJointPos - rootTransform.Translation).GetNormalized();
const Quaternion deltaRotation = Quaternion::FindBetween(oldDir, newDir);
rootTransform.Orientation = deltaRotation * rootTransform.Orientation;
#else
// Vector from root joint to mid joint (local Y-axis direction)
Vector3 localY = (newMidJointPos - rootTransform.Translation).GetNormalized();
@@ -136,20 +138,23 @@ void InverseKinematics::SolveTwoBoneIK(Transform& rootTransform, Transform& midJ
localZ = Vector3::Cross(localX, localY).GetNormalized();
// Construct a rotation from the orthogonal basis vectors
Quaternion newRootJointOrientation = Quaternion::LookRotation(localZ, localY);
// Apply the new rotation to the root joint
rootTransform.Orientation = newRootJointOrientation;
rootTransform.Orientation = Quaternion::LookRotation(localZ, localY);
#endif
}
// Update mid joint orientation to point Y-axis towards the end effector and Z-axis perpendicular to the IK plane
{
#if OLD
const Vector3 oldDir = (endEffectorTransform.Translation - midJointPos).GetNormalized();
const Vector3 newDir = (newEndEffectorPos - newMidJointPos).GetNormalized();
const Quaternion deltaRotation = Quaternion::FindBetween(oldDir, newDir);
midJointTransform.Orientation = deltaRotation * midJointTransform.Orientation;
#else
// Vector from mid joint to end effector (local Y-axis direction after rotation)
Vector3 midToEnd = (newEndEffectorPos - newMidJointPos).GetNormalized();
// Calculate the plane normal using the root, mid joint, and end effector positions (will be the local Z-axis direction)
Vector3 rootToMid = (newMidJointPos - rootTransform.Translation).GetNormalized();
Vector3 planeNormal = Vector3::Cross(rootToMid, midToEnd).GetNormalized();
// Vector from mid joint to end effector (local Y-axis direction)
Vector3 localY = (newEndEffectorPos - newMidJointPos).GetNormalized();
@@ -157,21 +162,18 @@ void InverseKinematics::SolveTwoBoneIK(Transform& rootTransform, Transform& midJ
// Calculate the plane normal using the root, mid joint, and end effector positions (local Z-axis direction)
Vector3 localZ = Vector3::Cross(rootToMid, localY).GetNormalized();
//// Calculate the local X-axis direction, should be perpendicular to the Y and Z axes
// Calculate the local X-axis direction, should be perpendicular to the Y and Z axes
Vector3 localX = Vector3::Cross(localY, localZ).GetNormalized();
// Correct the local Z-axis direction based on the cross product of X and Y to ensure orthogonality
localZ = Vector3::Cross(localX, localY).GetNormalized();
// Construct a rotation from the orthogonal basis vectors
// The axes are used differently here than a standard LookRotation to align Z towards the end and Y perpendicular
Quaternion newMidJointOrientation = Quaternion::LookRotation(localZ, localY); // Assuming FromLookRotation creates a rotation with the first vector as forward and the second as up
// Apply the new rotation to the mid joint
midJointTransform.Orientation = newMidJointOrientation;
midJointTransform.Translation = newMidJointPos;
midJointTransform.Orientation = Quaternion::LookRotation(localZ, localY);
#endif
}
// Update end effector transform
// Update mid and end locations
midJointTransform.Translation = newMidJointPos;
endEffectorTransform.Translation = newEndEffectorPos;
}

View File

@@ -187,6 +187,9 @@ Asset::LoadResult Material::load()
#endif
)
{
// Guard file with the lock during shader generation (prevents FlaxStorage::Tick from messing with the file)
auto lock = Storage->Lock();
// Prepare
MaterialGenerator generator;
generator.Error.Bind(&OnGeneratorError);

View File

@@ -252,6 +252,7 @@ void ContentStorageSystem::Job(int32 index)
{
PROFILE_CPU_NAMED("ContentStorage.Job");
const double time = Platform::GetTimeSeconds();
ScopeLock lock(Locker);
for (auto i = StorageMap.Begin(); i.IsNotEnd(); ++i)
{
@@ -271,7 +272,7 @@ void ContentStorageSystem::Job(int32 index)
}
else
{
storage->Tick();
storage->Tick(time);
}
}
}

View File

@@ -1337,9 +1337,6 @@ bool FlaxStorage::CloseFileHandles()
{
if (Platform::AtomicRead(&_chunksLock) == 0 && Platform::AtomicRead(&_files) == 0)
{
Array<FileReadStream*, InlinedAllocation<8>> streams;
_file.GetValues(streams);
ASSERT(streams.Count() == 0);
return false;
}
PROFILE_CPU();
@@ -1401,19 +1398,18 @@ void FlaxStorage::Dispose()
_version = 0;
}
void FlaxStorage::Tick()
void FlaxStorage::Tick(double time)
{
// Check if chunks are locked
// Skip if file is in use
if (Platform::AtomicRead(&_chunksLock) != 0)
return;
const double now = Platform::GetTimeSeconds();
bool wasAnyUsed = false;
const float unusedDataChunksLifetime = ContentStorageManager::UnusedDataChunksLifetime.GetTotalSeconds();
for (int32 i = 0; i < _chunks.Count(); i++)
{
auto chunk = _chunks.Get()[i];
const bool wasUsed = (now - chunk->LastAccessTime) < unusedDataChunksLifetime;
const bool wasUsed = (time - chunk->LastAccessTime) < unusedDataChunksLifetime;
if (!wasUsed && chunk->IsLoaded())
{
chunk->Unload();
@@ -1421,7 +1417,7 @@ void FlaxStorage::Tick()
wasAnyUsed |= wasUsed;
}
// Release file handles in none of chunks was not used
// Release file handles in none of chunks is in use
if (!wasAnyUsed && Platform::AtomicRead(&_chunksLock) == 0)
{
CloseFileHandles();

View File

@@ -416,7 +416,7 @@ public:
/// <summary>
/// Ticks this instance.
/// </summary>
void Tick();
void Tick(double time);
#if USE_EDITOR
void OnRename(const StringView& newPath);

View File

@@ -18,7 +18,7 @@ namespace FlaxEditor.Content.Settings
/// <summary>
/// The layers names.
/// </summary>
[EditorOrder(10), EditorDisplay("Layers", EditorDisplayAttribute.InlineStyle), Collection(ReadOnly = true, Display = CollectionAttribute.DisplayType.Inline)]
[EditorOrder(10), EditorDisplay("Layers", EditorDisplayAttribute.InlineStyle), Collection(CanResize = true, Display = CollectionAttribute.DisplayType.Inline)]
public string[] Layers = new string[32];
/// <summary>

View File

@@ -32,41 +32,39 @@ Color::Color(const Color32& color)
{
}
Color Color::FromHex(const String& hexString, bool& isValid)
Color Color::FromHex(const String& hex, bool& isValid)
{
int32 r, g, b, a = 255;
isValid = true;
int32 startIndex = !hexString.IsEmpty() && hexString[0] == Char('#') ? 1 : 0;
if (hexString.Length() == 3 + startIndex)
int32 startIndex = !hex.IsEmpty() && hex[0] == Char('#') ? 1 : 0;
if (hex.Length() == 3 + startIndex)
{
r = StringUtils::HexDigit(hexString[startIndex++]);
g = StringUtils::HexDigit(hexString[startIndex++]);
b = StringUtils::HexDigit(hexString[startIndex]);
r = StringUtils::HexDigit(hex[startIndex++]);
g = StringUtils::HexDigit(hex[startIndex++]);
b = StringUtils::HexDigit(hex[startIndex]);
r = (r << 4) + r;
g = (g << 4) + g;
b = (b << 4) + b;
}
else if (hexString.Length() == 6 + startIndex)
else if (hex.Length() == 6 + startIndex)
{
r = (StringUtils::HexDigit(hexString[startIndex + 0]) << 4) + StringUtils::HexDigit(hexString[startIndex + 1]);
g = (StringUtils::HexDigit(hexString[startIndex + 2]) << 4) + StringUtils::HexDigit(hexString[startIndex + 3]);
b = (StringUtils::HexDigit(hexString[startIndex + 4]) << 4) + StringUtils::HexDigit(hexString[startIndex + 5]);
r = (StringUtils::HexDigit(hex[startIndex + 0]) << 4) + StringUtils::HexDigit(hex[startIndex + 1]);
g = (StringUtils::HexDigit(hex[startIndex + 2]) << 4) + StringUtils::HexDigit(hex[startIndex + 3]);
b = (StringUtils::HexDigit(hex[startIndex + 4]) << 4) + StringUtils::HexDigit(hex[startIndex + 5]);
}
else if (hexString.Length() == 8 + startIndex)
else if (hex.Length() == 8 + startIndex)
{
r = (StringUtils::HexDigit(hexString[startIndex + 0]) << 4) + StringUtils::HexDigit(hexString[startIndex + 1]);
g = (StringUtils::HexDigit(hexString[startIndex + 2]) << 4) + StringUtils::HexDigit(hexString[startIndex + 3]);
b = (StringUtils::HexDigit(hexString[startIndex + 4]) << 4) + StringUtils::HexDigit(hexString[startIndex + 5]);
a = (StringUtils::HexDigit(hexString[startIndex + 6]) << 4) + StringUtils::HexDigit(hexString[startIndex + 7]);
r = (StringUtils::HexDigit(hex[startIndex + 0]) << 4) + StringUtils::HexDigit(hex[startIndex + 1]);
g = (StringUtils::HexDigit(hex[startIndex + 2]) << 4) + StringUtils::HexDigit(hex[startIndex + 3]);
b = (StringUtils::HexDigit(hex[startIndex + 4]) << 4) + StringUtils::HexDigit(hex[startIndex + 5]);
a = (StringUtils::HexDigit(hex[startIndex + 6]) << 4) + StringUtils::HexDigit(hex[startIndex + 7]);
}
else
{
r = g = b = 0;
isValid = false;
}
return FromBytes(r, g, b, a);
}
@@ -122,8 +120,9 @@ String Color::ToHexString() const
const byte r = static_cast<byte>(R * MAX_uint8);
const byte g = static_cast<byte>(G * MAX_uint8);
const byte b = static_cast<byte>(B * MAX_uint8);
const byte a = static_cast<byte>(A * MAX_uint8);
Char result[6];
Char result[8];
result[0] = digits[r >> 4 & 0x0f];
result[1] = digits[r & 0x0f];
@@ -134,7 +133,10 @@ String Color::ToHexString() const
result[4] = digits[b >> 4 & 0x0f];
result[5] = digits[b & 0x0f];
return String(result, 6);
result[6] = digits[a >> 4 & 0x0f];
result[7] = digits[a & 0x0f];
return String(result, 8);
}
bool Color::IsTransparent() const

View File

@@ -230,36 +230,93 @@ namespace FlaxEngine
}
/// <summary>
/// Creates <see cref="Color"/> from the RGB value and separate alpha channel.
/// Creates <see cref="Color"/> from the RGB value (bottom bits contain Blue) and separate alpha channel.
/// </summary>
/// <param name="rgb">The packed RGB value.</param>
/// <param name="rgb">The packed RGB value (bottom bits contain Blue).</param>
/// <param name="a">The alpha channel value.</param>
/// <returns>The color.</returns>
public static Color FromRGB(uint rgb, float a = 1.0f)
{
return new Color(
((rgb >> 16) & 0xff) / 255.0f,
((rgb >> 8) & 0xff) / 255.0f,
(rgb & 0xff) / 255.0f,
a);
return new Color(((rgb >> 16) & 0xff) / 255.0f, ((rgb >> 8) & 0xff) / 255.0f, (rgb & 0xff) / 255.0f, a);
}
/// <summary>
/// Creates <see cref="Color"/> from the RGBA value.
/// Creates <see cref="Color"/> from the ARGB value (bottom bits contain Blue).
/// </summary>
/// <param name="rgb">The packed RGBA value.</param>
/// <param name="argb">The packed ARGB value (bottom bits contain Blue).</param>
/// <returns>The color.</returns>
public static Color FromRGBA(uint rgb)
public static Color FromARGB(uint argb)
{
return new Color(
((rgb >> 16) & 0xff) / 255.0f,
((rgb >> 8) & 0xff) / 255.0f,
(rgb & 0xff) / 255.0f,
((rgb >> 24) & 0xff) / 255.0f);
return new Color(((argb >> 16) & 0xff) / 255.0f, ((argb >> 8) & 0xff) / 255.0f, ((argb >> 0) & 0xff) / 255.0f, ((argb >> 24) & 0xff) / 255.0f);
}
/// <summary>
/// Gets the color value as the hexadecimal string.
/// Creates <see cref="Color"/> from the RGBA value (bottom bits contain Alpha).
/// </summary>
/// <param name="rgba">The packed RGBA value (bottom bits Alpha Red).</param>
/// <returns>The color.</returns>
public static Color FromRGBA(uint rgba)
{
return new Color(((rgba >> 24) & 0xff) / 255.0f, ((rgba >> 16) & 0xff) / 255.0f, ((rgba >> 8) & 0xff) / 255.0f, ((rgba >> 0) & 0xff) / 255.0f);
}
/// <summary>
/// Creates <see cref="Color"/> from the Hex string.
/// </summary>
/// <param name="hex">The hexadecimal color string.</param>
/// <returns>The output color value.</returns>
public static Color FromHex(string hex)
{
FromHex(hex, out var color);
return color;
}
/// <summary>
/// Creates <see cref="Color"/> from the Hex string.
/// </summary>
/// <param name="hex">The hexadecimal color string.</param>
/// <param name="color">The output color value. Valid if method returns true.</param>
/// <returns>True if method was able to convert color, otherwise false.</returns>
public static bool FromHex(string hex, out Color color)
{
int r, g, b, a = 255;
bool isValid = true;
int startIndex = hex.Length != 0 && hex[0] == '#' ? 1 : 0;
if (hex.Length == 3 + startIndex)
{
r = StringUtils.HexDigit(hex[startIndex++]);
g = StringUtils.HexDigit(hex[startIndex++]);
b = StringUtils.HexDigit(hex[startIndex]);
r = (r << 4) + r;
g = (g << 4) + g;
b = (b << 4) + b;
}
else if (hex.Length == 6 + startIndex)
{
r = (StringUtils.HexDigit(hex[startIndex + 0]) << 4) + StringUtils.HexDigit(hex[startIndex + 1]);
g = (StringUtils.HexDigit(hex[startIndex + 2]) << 4) + StringUtils.HexDigit(hex[startIndex + 3]);
b = (StringUtils.HexDigit(hex[startIndex + 4]) << 4) + StringUtils.HexDigit(hex[startIndex + 5]);
}
else if (hex.Length == 8 + startIndex)
{
r = (StringUtils.HexDigit(hex[startIndex + 0]) << 4) + StringUtils.HexDigit(hex[startIndex + 1]);
g = (StringUtils.HexDigit(hex[startIndex + 2]) << 4) + StringUtils.HexDigit(hex[startIndex + 3]);
b = (StringUtils.HexDigit(hex[startIndex + 4]) << 4) + StringUtils.HexDigit(hex[startIndex + 5]);
a = (StringUtils.HexDigit(hex[startIndex + 6]) << 4) + StringUtils.HexDigit(hex[startIndex + 7]);
}
else
{
r = g = b = 0;
isValid = false;
}
color = new Color(r, g, b, a);
return isValid;
}
/// <summary>
/// Gets the color value as the hexadecimal string (in RGBA order).
/// </summary>
/// <returns>Hex string.</returns>
public string ToHexString()
@@ -287,7 +344,7 @@ namespace FlaxEngine
return new string(result);
}
/// <summary>
/// Creates <see cref="Color"/> from the text string (hex or color name).
/// </summary>

View File

@@ -126,9 +126,9 @@ public:
}
/// <summary>
/// Initializes from packed RGB value of the color and separate alpha channel value.
/// Initializes from packed RGB value (bottom bits contain Blue) of the color and separate alpha channel value.
/// </summary>
/// <param name="rgb">The packed RGB value.</param>
/// <param name="rgb">The packed RGB value (bottom bits contain Blue).</param>
/// <param name="a">The alpha channel.</param>
/// <returns>The color.</returns>
static Color FromRGB(uint32 rgb, float a = 1.0f)
@@ -137,22 +137,32 @@ public:
}
/// <summary>
/// Initializes from packed RGBA value.
/// Initializes from packed ARGB value (bottom bits contain Blue).
/// </summary>
/// <param name="rgba">The packed RGBA value.</param>
/// <param name="argb">The packed ARGB value (bottom bits contain Blue).</param>
/// <returns>The color.</returns>
static Color FromARGB(uint32 argb)
{
return Color((float)((argb >> 16) & 0xff) / 255.0f,(float)((argb >> 8) & 0xff) / 255.0f, (float)((argb >> 0) & 0xff) / 255.0f, (float)((argb >> 24) & 0xff) / 255.0f);
}
/// <summary>
/// Initializes from packed RGBA value (bottom bits contain Alpha).
/// </summary>
/// <param name="rgba">The packed RGBA value (bottom bits contain Alpha).</param>
/// <returns>The color.</returns>
static Color FromRGBA(uint32 rgba)
{
return Color(static_cast<float>(rgba >> 16 & 0xff) / 255.0f, static_cast<float>(rgba >> 8 & 0xff) / 255.0f, static_cast<float>(rgba & 0xff) / 255.0f, static_cast<float>(rgba >> 24 & 0xff) / 255.0f);
return Color((float)((rgba >> 24) & 0xff) / 255.0f,(float)((rgba >> 16) & 0xff) / 255.0f, (float)((rgba >> 8) & 0xff) / 255.0f, (float)((rgba >> 0) & 0xff) / 255.0f);
}
static Color FromHex(const String& hexString)
static Color FromHex(const String& hex)
{
bool isValid;
return FromHex(hexString, isValid);
return FromHex(hex, isValid);
}
static Color FromHex(const String& hexString, bool& isValid);
static Color FromHex(const String& hex, bool& isValid);
/// <summary>
/// Creates RGB color from Hue[0-360], Saturation[0-1] and Value[0-1].

View File

@@ -42,7 +42,7 @@ void RenderTask::DrawAll()
// Sort tasks (by Order property)
Sorting::QuickSortObj(Tasks.Get(), Tasks.Count());
// Render all that shit
// Render all tasks
for (auto task : Tasks)
{
if (task->CanDraw())

View File

@@ -90,7 +90,7 @@ public:
/// <summary>
/// Gets the brush proxies per surface.
/// </summary>
API_PROPERTY(Attributes="Serialize, EditorOrder(100), EditorDisplay(\"Surfaces\", EditorDisplayAttribute.InlineStyle), Collection(CanReorderItems = false, NotNullItems = true, ReadOnly = true)")
API_PROPERTY(Attributes="Serialize, EditorOrder(100), EditorDisplay(\"Surfaces\", EditorDisplayAttribute.InlineStyle), Collection(CanReorderItems = false, NotNullItems = true, CanResize = true)")
Array<BrushSurface> GetSurfaces() const;
/// <summary>

View File

@@ -349,6 +349,11 @@ void Camera::Draw(RenderContext& renderContext)
_previewModel->Draw(renderContext, draw);
}
}
// Load preview model if it doesnt exist. Ex: prefabs
else if (EnumHasAnyFlags(renderContext.View.Flags, ViewFlags::EditorSprites) && !_previewModel)
{
_previewModel = Content::LoadAsyncInternal<Model>(TEXT("Editor/Camera/O_Camera"));
}
}
#include "Engine/Debug/DebugDraw.h"

View File

@@ -41,7 +41,7 @@ public:
/// <summary>
/// Gets the model entries collection. Each entry contains data how to render meshes using this entry (transformation, material, shadows casting, etc.).
/// </summary>
API_PROPERTY(Attributes="Serialize, EditorOrder(1000), EditorDisplay(\"Entries\", EditorDisplayAttribute.InlineStyle), Collection(CanReorderItems=false, NotNullItems=true, ReadOnly=true, Spacing=10)")
API_PROPERTY(Attributes="Serialize, EditorOrder(1000), EditorDisplay(\"Entries\", EditorDisplayAttribute.InlineStyle), Collection(CanReorderItems=false, NotNullItems=true, CanResize=false, Spacing=10)")
FORCE_INLINE const Array<ModelInstanceEntry>& GetEntries() const
{
return Entries;

View File

@@ -1392,12 +1392,20 @@ void NetworkReplicator::SetObjectOwnership(ScriptingObject* obj, uint32 ownerCli
if (ownerClientId == NetworkManager::LocalClientId)
{
// Ensure local client owns that object actually
CHECK(localRole == NetworkObjectRole::OwnedAuthoritative);
if (localRole != NetworkObjectRole::OwnedAuthoritative)
{
LOG(Error, "Cannot change overship of object (Id={}) to the local client (Id={}) if the local role is not set to OwnedAuthoritative.", obj->GetID(), ownerClientId);
return;
}
}
else
{
// Ensure local client doesn't own that object since it's owned by other client
CHECK(localRole != NetworkObjectRole::OwnedAuthoritative);
if (localRole == NetworkObjectRole::OwnedAuthoritative)
{
LOG(Error, "Cannot change overship of object (Id={}) to the remote client (Id={}) if the local role is set to OwnedAuthoritative.", obj->GetID(), ownerClientId);
return;
}
}
#endif
item.HasOwnership = true;
@@ -1421,7 +1429,13 @@ void NetworkReplicator::SetObjectOwnership(ScriptingObject* obj, uint32 ownerCli
if (item.OwnerClientId != ownerClientId)
{
// Change role locally
CHECK(localRole != NetworkObjectRole::OwnedAuthoritative);
#if !BUILD_RELEASE
if (localRole == NetworkObjectRole::OwnedAuthoritative)
{
LOG(Error, "Cannot change overship of object (Id={}) to the remote client (Id={}) if the local role is set to OwnedAuthoritative.", obj->GetID(), ownerClientId);
return;
}
#endif
if (Hierarchy && item.Role == NetworkObjectRole::OwnedAuthoritative)
Hierarchy->RemoveObject(obj);
item.OwnerClientId = ownerClientId;
@@ -1433,7 +1447,13 @@ void NetworkReplicator::SetObjectOwnership(ScriptingObject* obj, uint32 ownerCli
else
{
// Allow to change local role of the object (except ownership)
CHECK(localRole != NetworkObjectRole::OwnedAuthoritative);
#if !BUILD_RELEASE
if (localRole == NetworkObjectRole::OwnedAuthoritative)
{
LOG(Error, "Cannot change overship of object (Id={}) to the remote client (Id={}) if the local role is set to OwnedAuthoritative.", obj->GetID(), ownerClientId);
return;
}
#endif
if (Hierarchy && it->Item.Role == NetworkObjectRole::OwnedAuthoritative)
Hierarchy->RemoveObject(obj);
item.Role = localRole;
@@ -2211,7 +2231,6 @@ void NetworkInternal::OnNetworkMessageObjectRpc(NetworkEvent& event, NetworkClie
if (!obj)
return;
// Validate RPC
if (info->Server && NetworkManager::IsClient())
{
@@ -2234,7 +2253,7 @@ void NetworkInternal::OnNetworkMessageObjectRpc(NetworkEvent& event, NetworkClie
// Execute RPC
info->Execute(obj, stream, info->Tag);
}
else if(info->Channel != static_cast<uint8>(NetworkChannelType::Unreliable) && info->Channel != static_cast<uint8>(NetworkChannelType::UnreliableOrdered))
else if (info->Channel != static_cast<uint8>(NetworkChannelType::Unreliable) && info->Channel != static_cast<uint8>(NetworkChannelType::UnreliableOrdered))
{
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Unknown object {} RPC {}::{}", msgData.ObjectId, String(msgData.RpcTypeName), String(msgData.RpcName));
}

View File

@@ -371,7 +371,7 @@ void ParticleEffect::Sync()
SceneRenderTask* ParticleEffect::GetRenderTask() const
{
const uint64 minFrame = Engine::UpdateCount - 2;
const uint64 minFrame = Engine::FrameCount - 2;
// Custom task
const auto customViewRenderTask = CustomViewRenderTask.Get();

View File

@@ -155,9 +155,9 @@ bool FontManager::AddNewEntry(Font* font, Char c, FontCharacterEntry& entry)
// Get the index to the glyph in the font face
const FT_UInt glyphIndex = FT_Get_Char_Index(face, c);
#if !BUILD_RELEASE
if (glyphIndex == 0)
if (glyphIndex == 0 && c >= '!')
{
LOG(Warning, "Font `{}` doesn't contain character `\\u{:x}`, consider choosing another font. ", String(face->family_name), c);
LOG(Warning, "Font `{}` doesn't contain character `\\u{:x}`, consider choosing another font.", String(face->family_name), c);
}
#endif

View File

@@ -339,6 +339,32 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont
}
setup.UseTemporalAAJitter = aaMode == AntialiasingMode::TemporalAntialiasing;
// Disable TAA jitter in debug modes
switch (renderContext.View.Mode)
{
case ViewMode::Unlit:
case ViewMode::Diffuse:
case ViewMode::Normals:
case ViewMode::Depth:
case ViewMode::Emissive:
case ViewMode::AmbientOcclusion:
case ViewMode::Metalness:
case ViewMode::Roughness:
case ViewMode::Specular:
case ViewMode::SpecularColor:
case ViewMode::SubsurfaceColor:
case ViewMode::ShadingModel:
case ViewMode::Reflections:
case ViewMode::GlobalSDF:
case ViewMode::GlobalSurfaceAtlas:
case ViewMode::LightmapUVsDensity:
case ViewMode::MaterialComplexity:
case ViewMode::Wireframe:
case ViewMode::NoPostFx:
setup.UseTemporalAAJitter = false;
break;
}
// Customize setup (by postfx or custom gameplay effects)
renderContext.Task->SetupRender(renderContext);
for (PostProcessEffect* e : renderContext.List->PostFx)
@@ -506,8 +532,13 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont
EyeAdaptationPass::Instance()->Render(renderContext, lightBuffer);
PostProcessingPass::Instance()->Render(renderContext, lightBuffer, tempBuffer, colorGradingLUT);
RenderTargetPool::Release(colorGradingLUT);
RenderTargetPool::Release(lightBuffer);
context->ResetRenderTarget();
if (aaMode == AntialiasingMode::TemporalAntialiasing)
{
TAA::Instance()->Render(renderContext, tempBuffer, lightBuffer->View());
Swap(lightBuffer, tempBuffer);
}
RenderTargetPool::Release(lightBuffer);
context->SetRenderTarget(task->GetOutputView());
context->SetViewportAndScissors(task->GetOutputViewport());
context->Draw(tempBuffer);

View File

@@ -46,6 +46,11 @@ namespace FlaxEngine
/// </summary>
public bool CanReorderItems = true;
/// <summary>
/// Gets or sets whether items can be added or removed from this collection.
/// </summary>
public bool CanResize = true;
/// <summary>
/// Gets or sets whether the items of this collection can be null. If <c>true</c>, applications using this collection should prevent user to add null items to the collection.
/// </summary>

View File

@@ -23,11 +23,14 @@ namespace FlaxEngine
public int FontSize;
/// <summary>
/// The custom header color (as 32-bit uint).
/// The custom header color (as 32-bit uint in RGB order, bottom bits contain Blue).
/// </summary>
public uint Color;
private HeaderAttribute()
/// <summary>
/// Initializes a new instance of the <see cref="HeaderAttribute"/> class.
/// </summary>
public HeaderAttribute()
{
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
namespace FlaxEngine;
@@ -15,7 +15,7 @@ public class WatermarkAttribute : Attribute
public string WatermarkText;
/// <summary>
/// The watermark color.
/// The watermark color (as 32-bit uint in RGB order, bottom bits contain Blue).
/// </summary>
public uint WatermarkColor;
@@ -26,7 +26,7 @@ public class WatermarkAttribute : Attribute
public WatermarkAttribute(string text)
{
WatermarkText = text;
WatermarkColor = 0; // default color of watermark in textbox
WatermarkColor = 0; // Default color of watermark in textbox
}
/// <summary>

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#if FLAX_TESTS
using System;
using NUnit.Framework;
namespace FlaxEngine.Tests
@@ -37,6 +38,15 @@ namespace FlaxEngine.Tests
Assert.AreEqual(Color.Maroon, ColorHSV.FromColor(Color.Maroon).ToColor());
Assert.AreEqual(new Color(184, 209, 219, 255).ToRgba(), ColorHSV.FromColor(new Color(184, 209, 219, 255)).ToColor().ToRgba());
}
[Test]
public void TestHexConversion()
{
string hex = Color.Blue.AlphaMultiplied(0.5f).ToHexString();
Color col1 = Color.FromHex(hex);
Color col2 = Color.FromRGBA(0x0000FF7F);
Assert.AreEqual((Color32)col1, (Color32)col2);
}
}
}
#endif

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
namespace FlaxEngine.GUI
{
@@ -204,7 +205,9 @@ namespace FlaxEngine.GUI
{
_navigationHeldTimeUp = _navigationHeldTimeDown = _navigationHeldTimeLeft = _navigationHeldTimeRight = 0;
_navigationRateTimeUp = _navigationRateTimeDown = _navigationRateTimeLeft = _navigationRateTimeRight = 0;
return;
}
if (ContainsFocus || IndexInParent == 0)
{
UpdateNavigation(deltaTime, _canvas.NavigateUp.Name, NavDirection.Up, ref _navigationHeldTimeUp, ref _navigationRateTimeUp);
UpdateNavigation(deltaTime, _canvas.NavigateDown.Name, NavDirection.Down, ref _navigationHeldTimeDown, ref _navigationRateTimeDown);
@@ -216,13 +219,30 @@ namespace FlaxEngine.GUI
base.Update(deltaTime);
}
private void ConditionalNavigate(NavDirection direction)
{
// Only currently focused canvas updates its navigation
if (!ContainsFocus)
{
// Special case when no canvas nor game UI is focused so let the first canvas to start the navigation into the UI
if (IndexInParent == 0 && Parent is CanvasContainer canvasContainer && !canvasContainer.ContainsFocus && GameRoot.ContainsFocus)
{
// Nothing is focused so go to the first control
var focused = OnNavigate(direction, Float2.Zero, this, new List<Control>());
focused?.NavigationFocus();
return;
}
}
Navigate(direction);
}
private void UpdateNavigation(float deltaTime, string actionName, NavDirection direction, ref float heldTime, ref float rateTime)
{
if (Input.GetAction(actionName))
{
if (heldTime <= Mathf.Epsilon)
{
Navigate(direction);
ConditionalNavigate(direction);
}
if (heldTime > _canvas.NavigationInputRepeatDelay)
{
@@ -230,7 +250,7 @@ namespace FlaxEngine.GUI
}
if (rateTime > _canvas.NavigationInputRepeatRate)
{
Navigate(direction);
ConditionalNavigate(direction);
rateTime = 0;
}
heldTime += deltaTime;

View File

@@ -143,7 +143,7 @@ namespace FlaxEngine.GUI
}
/// <summary>
/// Gets or sets the custom material used to render the text. It must has domain set to GUI and have a public texture parameter named Font used to sample font atlas texture with font characters data.
/// Gets or sets the custom material used to render the text. It has to have domain set to GUI and have a public texture parameter named Font used to sample font atlas texture with font characters data.
/// </summary>
[EditorDisplay("Text Style"), EditorOrder(2025)]
public MaterialBase Material { get; set; }

View File

@@ -197,7 +197,7 @@ namespace FlaxEngine.GUI
textBlock.Range = new TextRange
{
StartIndex = start + line.FirstCharIndex,
EndIndex = start + line.LastCharIndex,
EndIndex = start + line.LastCharIndex + 1,
};
if (i != 0)
{

View File

@@ -1590,14 +1590,18 @@ namespace Flax.Build.Plugins
context.Failed = true;
return;
}
if (method.IsVirtual)
{
MonoCecil.CompilationError($"Not supported virtual RPC method '{method.FullName}'.", method);
context.Failed = true;
return;
}
if (method.CustomAttributes.FirstOrDefault(x => x.AttributeType.FullName == "System.Runtime.CompilerServices.AsyncStateMachineAttribute") != null)
{
MonoCecil.CompilationError($"Not supported async RPC method '{method.FullName}'.", method);
context.Failed = true;
return;
}
ModuleDefinition module = type.Module;
var voidType = module.TypeSystem.Void;
if (method.ReturnType != voidType)
@@ -1606,7 +1610,6 @@ namespace Flax.Build.Plugins
context.Failed = true;
return;
}
if (method.IsStatic)
{
MonoCecil.CompilationError($"Not supported static RPC method '{method.FullName}'.", method);
@@ -1634,7 +1637,6 @@ namespace Flax.Build.Plugins
context.Failed = true;
return;
}
if (!methodRPC.IsServer && !methodRPC.IsClient)
{
MonoCecil.CompilationError($"Network RPC {method.Name} in {type.FullName} needs to have Server or Client specifier.", method);