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

# Conflicts:
#	Flax.flaxproj
This commit is contained in:
Wojtek Figat
2024-03-19 20:23:34 +01:00
82 changed files with 2434 additions and 1379 deletions

View File

@@ -121,6 +121,37 @@ namespace FlaxEditor.Utilities
["e"] = Math.E,
["infinity"] = double.MaxValue,
["-infinity"] = -double.MaxValue,
["m"] = Units.Meters2Units,
["cm"] = Units.Meters2Units / 100,
["km"] = Units.Meters2Units * 1000,
["s"] = 1,
["ms"] = 0.001,
["min"] = 60,
["h"] = 3600,
["cm²"] = (Units.Meters2Units / 100) * (Units.Meters2Units / 100),
["cm³"] = (Units.Meters2Units / 100) * (Units.Meters2Units / 100) * (Units.Meters2Units / 100),
["dm²"] = (Units.Meters2Units / 10) * (Units.Meters2Units / 10),
["dm³"] = (Units.Meters2Units / 10) * (Units.Meters2Units / 10) * (Units.Meters2Units / 10),
["l"] = (Units.Meters2Units / 10) * (Units.Meters2Units / 10) * (Units.Meters2Units / 10),
["m²"] = Units.Meters2Units * Units.Meters2Units,
["m³"] = Units.Meters2Units * Units.Meters2Units * Units.Meters2Units,
["kg"] = 1,
["g"] = 0.001,
["n"] = Units.Meters2Units
};
/// <summary>
/// List known units which cannot be handled as a variable easily because they contain operator symbols (mostly a forward slash). The value is the factor to calculate game units.
/// </summary>
private static readonly IDictionary<string, double> UnitSymbols = new Dictionary<string, double>
{
["cm/s"] = Units.Meters2Units / 100,
["cm/s²"] = Units.Meters2Units / 100,
["m/s"] = Units.Meters2Units,
["m/s²"] = Units.Meters2Units,
["km/h"] = 1 / 3.6 * Units.Meters2Units,
// Nm is here because these values are compared case-sensitive, and we don't want to confuse nanometers and Newtonmeters
["Nm"] = Units.Meters2Units * Units.Meters2Units,
};
/// <summary>
@@ -156,7 +187,7 @@ namespace FlaxEditor.Utilities
if (Operators.ContainsKey(str))
return TokenType.Operator;
if (char.IsLetter(c))
if (char.IsLetter(c) || c == '²' || c == '³')
return TokenType.Variable;
throw new ParsingException("wrong character");
@@ -170,7 +201,24 @@ namespace FlaxEditor.Utilities
public static IEnumerable<Token> Tokenize(string text)
{
// Prepare text
text = text.Replace(',', '.');
text = text.Replace(',', '.').Replace("°", "");
foreach (var kv in UnitSymbols)
{
int idx;
do
{
idx = text.IndexOf(kv.Key, StringComparison.InvariantCulture);
if (idx > 0)
{
if (DetermineType(text[idx - 1]) != TokenType.Number)
throw new ParsingException($"unit found without a number: {kv.Key} at {idx} in {text}");
if (Mathf.Abs(kv.Value - 1) < Mathf.Epsilon)
text = text.Remove(idx, kv.Key.Length);
else
text = text.Replace(kv.Key, "*" + kv.Value);
}
} while (idx > 0);
}
// Necessary to correctly parse negative numbers
var previous = TokenType.WhiteSpace;
@@ -240,6 +288,11 @@ namespace FlaxEditor.Utilities
}
else if (type == TokenType.Variable)
{
if (previous == TokenType.Number)
{
previous = TokenType.Operator;
yield return new Token(TokenType.Operator, "*");
}
// Continue till the end of the variable
while (i + 1 < text.Length && DetermineType(text[i + 1]) == TokenType.Variable)
{
@@ -335,7 +388,7 @@ namespace FlaxEditor.Utilities
}
else
{
throw new ParsingException("unknown variable");
throw new ParsingException($"unknown variable : {token.Value}");
}
}
else
@@ -372,6 +425,15 @@ namespace FlaxEditor.Utilities
}
}
// if stack has more than one item we're not finished with evaluating
// we assume the remaining values are all factors to be multiplied
if (stack.Count > 1)
{
var v1 = stack.Pop();
while (stack.Count > 0)
v1 *= stack.Pop();
return v1;
}
return stack.Pop();
}

View File

@@ -0,0 +1,41 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
namespace FlaxEditor.Utilities;
/// <summary>
/// Units display utilities for Editor.
/// </summary>
public class Units
{
/// <summary>
/// Factor of units per meter.
/// </summary>
public static readonly float Meters2Units = 100f;
/// <summary>
/// False to always show game units without any postfix.
/// </summary>
public static bool UseUnitsFormatting = true;
/// <summary>
/// Add a space between numbers and units for readability.
/// </summary>
public static bool SeparateValueAndUnit = true;
/// <summary>
/// If set to true, the distance unit is chosen on the magnitude, otherwise it's meters.
/// </summary>
public static bool AutomaticUnitsFormatting = true;
/// <summary>
/// Return the unit according to user settings.
/// </summary>
/// <param name="unit">The unit name.</param>
/// <returns>The formatted text.</returns>
public static string Unit(string unit)
{
if (SeparateValueAndUnit)
return $" {unit}";
return unit;
}
}

View File

@@ -243,6 +243,63 @@ namespace FlaxEditor.Utilities
500000, 1000000, 5000000, 10000000, 100000000
};
internal delegate void DrawCurveTick(float tick, float strength);
internal static Int2 DrawCurveTicks(DrawCurveTick drawTick, float[] tickSteps, ref float[] tickStrengths, float min, float max, float pixelRange, float minDistanceBetweenTicks = 20, float maxDistanceBetweenTicks = 60)
{
if (tickStrengths == null || tickStrengths.Length != tickSteps.Length)
tickStrengths = new float[tickSteps.Length];
// Find the strength for each modulo number tick marker
var pixelsInRange = pixelRange / (max - min);
var smallestTick = 0;
var biggestTick = tickSteps.Length - 1;
for (int i = tickSteps.Length - 1; i >= 0; i--)
{
// Calculate how far apart these modulo tick steps are spaced
float tickSpacing = tickSteps[i] * pixelsInRange;
// Calculate the strength of the tick markers based on the spacing
tickStrengths[i] = Mathf.Saturate((tickSpacing - minDistanceBetweenTicks) / (maxDistanceBetweenTicks - minDistanceBetweenTicks));
// Beyond threshold the ticks don't get any bigger or fatter
if (tickStrengths[i] >= 1)
biggestTick = i;
// Do not show small tick markers
if (tickSpacing <= minDistanceBetweenTicks)
{
smallestTick = i;
break;
}
}
var tickLevels = biggestTick - smallestTick + 1;
// Draw all tick levels
for (int level = 0; level < tickLevels; level++)
{
float strength = tickStrengths[smallestTick + level];
if (strength <= Mathf.Epsilon)
continue;
// Draw all ticks
int l = Mathf.Clamp(smallestTick + level, 0, tickSteps.Length - 1);
var lStep = tickSteps[l];
var lNextStep = tickSteps[l + 1];
int startTick = Mathf.FloorToInt(min / lStep);
int endTick = Mathf.CeilToInt(max / lStep);
for (int i = startTick; i <= endTick; i++)
{
if (l < biggestTick && (i % Mathf.RoundToInt(lNextStep / lStep) == 0))
continue;
var tick = i * lStep;
drawTick(tick, strength);
}
}
return new Int2(smallestTick, biggestTick);
}
/// <summary>
/// Determines whether the specified path string contains any invalid character.
/// </summary>
@@ -1187,6 +1244,71 @@ namespace FlaxEditor.Utilities
return StringUtils.GetPathWithoutExtension(path);
}
private static string InternalFormat(double value, string format, FlaxEngine.Utils.ValueCategory category)
{
switch (category)
{
case FlaxEngine.Utils.ValueCategory.Distance:
if (!Units.AutomaticUnitsFormatting)
return (value / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("m");
var absValue = Mathf.Abs(value);
// in case a unit != cm this would be (value / Meters2Units * 100)
if (absValue < Units.Meters2Units)
return value.ToString(format, CultureInfo.InvariantCulture) + Units.Unit("cm");
if (absValue < Units.Meters2Units * 1000)
return (value / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("m");
return (value / 1000 / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("km");
case FlaxEngine.Utils.ValueCategory.Angle: return value.ToString(format, CultureInfo.InvariantCulture) + "°";
case FlaxEngine.Utils.ValueCategory.Time: return value.ToString(format, CultureInfo.InvariantCulture) + Units.Unit("s");
// some fonts have a symbol for that: "\u33A7"
case FlaxEngine.Utils.ValueCategory.Speed: return (value / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("m/s");
case FlaxEngine.Utils.ValueCategory.Acceleration: return (value / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("m/s²");
case FlaxEngine.Utils.ValueCategory.Area: return (value / Units.Meters2Units / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("m²");
case FlaxEngine.Utils.ValueCategory.Volume: return (value / Units.Meters2Units / Units.Meters2Units / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("m³");
case FlaxEngine.Utils.ValueCategory.Mass: return value.ToString(format, CultureInfo.InvariantCulture) + Units.Unit("kg");
case FlaxEngine.Utils.ValueCategory.Force: return (value / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("N");
case FlaxEngine.Utils.ValueCategory.Torque: return (value / Units.Meters2Units / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("Nm");
case FlaxEngine.Utils.ValueCategory.None:
default: return FormatFloat(value);
}
}
/// <summary>
/// Format a float value either as-is, with a distance unit or with a degree sign.
/// </summary>
/// <param name="value">The value to format.</param>
/// <param name="category">The value type: none means just a number, distance will format in cm/m/km, angle with an appended degree sign.</param>
/// <returns>The formatted string.</returns>
public static string FormatFloat(float value, FlaxEngine.Utils.ValueCategory category)
{
if (float.IsPositiveInfinity(value) || value == float.MaxValue)
return "Infinity";
if (float.IsNegativeInfinity(value) || value == float.MinValue)
return "-Infinity";
if (!Units.UseUnitsFormatting || category == FlaxEngine.Utils.ValueCategory.None)
return FormatFloat(value);
const string format = "G7";
return InternalFormat(value, format, category);
}
/// <summary>
/// Format a double value either as-is, with a distance unit or with a degree sign
/// </summary>
/// <param name="value">The value to format.</param>
/// <param name="category">The value type: none means just a number, distance will format in cm/m/km, angle with an appended degree sign.</param>
/// <returns>The formatted string.</returns>
public static string FormatFloat(double value, FlaxEngine.Utils.ValueCategory category)
{
if (double.IsPositiveInfinity(value) || value == double.MaxValue)
return "Infinity";
if (double.IsNegativeInfinity(value) || value == double.MinValue)
return "-Infinity";
if (!Units.UseUnitsFormatting || category == FlaxEngine.Utils.ValueCategory.None)
return FormatFloat(value);
const string format = "G15";
return InternalFormat(value, format, category);
}
/// <summary>
/// Formats the floating point value (double precision) into the readable text representation.
/// </summary>
@@ -1198,7 +1320,7 @@ namespace FlaxEditor.Utilities
return "Infinity";
if (float.IsNegativeInfinity(value) || value == float.MinValue)
return "-Infinity";
string str = value.ToString("r", CultureInfo.InvariantCulture);
string str = value.ToString("R", CultureInfo.InvariantCulture);
return FormatFloat(str, value < 0);
}
@@ -1213,7 +1335,7 @@ namespace FlaxEditor.Utilities
return "Infinity";
if (double.IsNegativeInfinity(value) || value == double.MinValue)
return "-Infinity";
string str = value.ToString("r", CultureInfo.InvariantCulture);
string str = value.ToString("R", CultureInfo.InvariantCulture);
return FormatFloat(str, value < 0);
}

View File

@@ -187,6 +187,8 @@ void ViewportIconsRendererService::DrawIcons(RenderContext& renderContext, Scene
void ViewportIconsRendererService::DrawIcons(RenderContext& renderContext, Actor* actor, Mesh::DrawInfo& draw)
{
if (!actor || !actor->IsActiveInHierarchy())
return;
auto& view = renderContext.View;
const BoundingFrustum frustum = view.Frustum;
Matrix m1, m2, world;
@@ -208,8 +210,7 @@ void ViewportIconsRendererService::DrawIcons(RenderContext& renderContext, Actor
draw.DrawState = &drawState;
draw.Deformation = nullptr;
// Support custom icons through types, but not onces that were added through actors,
// since they cant register while in prefab view anyway
// Support custom icons through types, but not ones that were added through actors, since they cant register while in prefab view anyway
if (ActorTypeToTexture.TryGet(actor->GetTypeHandle(), texture))
{
// Use custom texture