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

# Conflicts:
#	Source/Editor/Modules/ContentDatabaseModule.cs
#	Source/Editor/Surface/SurfaceUtils.cs
#	Source/Editor/Windows/Assets/MaterialInstanceWindow.cs
#	Source/Engine/Foliage/Foliage.cpp
#	Source/Engine/Graphics/Models/MeshBase.h
#	Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp
This commit is contained in:
Wojtek Figat
2024-09-12 13:38:20 +02:00
111 changed files with 1717 additions and 511 deletions

View File

@@ -1421,6 +1421,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
const auto cData = node->Values[4 + c * 2].AsFloat4();
// Get triangle coords
byte anims[3] = { a, b, c };
Float2 points[3] = {
Float2(aData.X, aData.Y),
Float2(bData.X, bData.Y),
@@ -1534,18 +1535,11 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
bestPoint = closest;
hasBest = true;
float d = Float2::Distance(s[0], s[1]);
if (Math::IsZero(d))
{
bestWeight = 0;
}
else
{
bestWeight = Float2::Distance(s[0], closest) / d;
}
bestAnims[0] = j;
bestAnims[1] = (j + 1) % 3;
const float d = Float2::Distance(s[0], s[1]);
bestWeight = d < ANIM_GRAPH_BLEND_THRESHOLD ? 0 : Float2::Distance(s[0], closest) / d;
bestAnims[0] = anims[j];
bestAnims[1] = anims[(j + 1) % 3];
}
}
}

View File

@@ -1471,7 +1471,8 @@ Asset::LoadResult VisualScript::load()
for (int32 i = 0; i < count; i++)
{
const int32 oldIndex = _oldParamsLayout.Find(Graph.Parameters[i].Identifier);
instanceParams[i] = oldIndex != -1 ? valuesCache[oldIndex] : Graph.Parameters[i].Value;
const bool useOldValue = oldIndex != -1 && valuesCache[oldIndex] != _oldParamsValues[i];
instanceParams[i] = useOldValue ? valuesCache[oldIndex] : Graph.Parameters[i].Value;
}
}
}
@@ -1486,6 +1487,8 @@ Asset::LoadResult VisualScript::load()
instanceParams[i] = Graph.Parameters[i].Value;
}
}
_oldParamsLayout.Clear();
_oldParamsValues.Clear();
}
#endif
@@ -1499,15 +1502,18 @@ void VisualScript::unload(bool isReloading)
{
// Cache existing instanced parameters IDs to restore values after asset reload (params order might be changed but the IDs are stable)
_oldParamsLayout.Resize(Graph.Parameters.Count());
_oldParamsValues.Resize(Graph.Parameters.Count());
for (int32 i = 0; i < Graph.Parameters.Count(); i++)
{
auto& param = Graph.Parameters[i];
_oldParamsLayout[i] = param.Identifier;
_oldParamsValues[i] = param.Value;
}
}
else
{
_oldParamsLayout.Clear();
_oldParamsValues.Clear();
}
#else
_instances.Clear();

View File

@@ -154,6 +154,7 @@ private:
Array<Field, InlinedAllocation<32>> _fields;
#if USE_EDITOR
Array<Guid> _oldParamsLayout;
Array<Variant> _oldParamsValues;
#endif
public:

View File

@@ -1,6 +1,10 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System;
#if FLAX_EDITOR
using System.ComponentModel;
using System.Globalization;
#endif
using System.Runtime.CompilerServices;
namespace FlaxEngine
@@ -11,6 +15,7 @@ namespace FlaxEngine
/// <typeparam name="T">Type of the asset instance type.</typeparam>
#if FLAX_EDITOR
[CustomEditor(typeof(FlaxEditor.CustomEditors.Editors.AssetRefEditor))]
[TypeConverter(typeof(TypeConverters.JsonAssetReferenceConverter))]
#endif
[Newtonsoft.Json.JsonConverter(typeof(Json.JsonAssetReferenceConverter))]
public struct JsonAssetReference<T> : IComparable, IComparable<JsonAssetReference<T>>, IEquatable<JsonAssetReference<T>>
@@ -125,7 +130,7 @@ namespace FlaxEngine
/// <inheritdoc />
public override string ToString()
{
return Asset?.ToString();
return Asset?.ToString() ?? "null";
}
/// <inheritdoc />
@@ -141,3 +146,33 @@ namespace FlaxEngine
}
}
}
#if FLAX_EDITOR
namespace FlaxEngine.TypeConverters
{
internal class JsonAssetReferenceConverter : TypeConverter
{
/// <inheritdoc />
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (value is string valueStr)
{
var result = Activator.CreateInstance(destinationType);
Json.JsonSerializer.ParseID(valueStr, out var id);
var asset = Content.LoadAsync<JsonAsset>(id);
destinationType.GetField("Asset").SetValue(result, asset);
return result;
}
return base.ConvertTo(context, culture, value, destinationType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType.Name.StartsWith("JsonAssetReference", StringComparison.Ordinal))
return true;
return base.CanConvertTo(context, destinationType);
}
}
}
#endif

View File

@@ -639,7 +639,17 @@ CreateAssetResult ImportModel::CreateAnimation(CreateAssetContext& context, Mode
// Save animation data
MemoryWriteStream stream(8182);
const int32 animIndex = options && options->ObjectIndex != -1 ? options->ObjectIndex : 0; // Single animation per asset
int32 animIndex = options ? options->ObjectIndex : -1; // Single animation per asset
if (animIndex == -1)
{
// Pick the longest animation by default (eg. to skip ref pose anim if exported as the first one)
animIndex = 0;
for (int32 i = 1; i < modelData.Animations.Count(); i++)
{
if (modelData.Animations[i].GetLength() > modelData.Animations[animIndex].GetLength())
animIndex = i;
}
}
if (modelData.Pack2AnimationHeader(&stream, animIndex))
return CreateAssetResult::Error;
if (context.AllocateChunk(0))

View File

@@ -865,6 +865,16 @@ namespace FlaxEngine
return new Float2(Mathf.Ceil(v.X), Mathf.Ceil(v.Y));
}
/// <summary>
/// Returns the vector with components containing the smallest integer smaller to or equal to the original value.
/// </summary>
/// <param name="v">The value.</param>
/// <returns>The result.</returns>
public static Float2 Floor(Float2 v)
{
return new Float2(Mathf.Floor(v.X), Mathf.Floor(v.Y));
}
/// <summary>
/// Breaks the components of the vector into an integral and a fractional part. Returns vector made of fractional parts.
/// </summary>

View File

@@ -7,6 +7,7 @@
#include "Matrix3x3.h"
#include "Math.h"
#include "../Types/String.h"
#include "Engine/Core/Math/Transform.h"
Quaternion Quaternion::Zero(0, 0, 0, 0);
Quaternion Quaternion::One(1, 1, 1, 1);
@@ -537,3 +538,14 @@ void Quaternion::RotationYawPitchRoll(float yaw, float pitch, float roll, Quater
result.Y = sinYawOver2 * cosPitchOver2 * cosRollOver2 - cosYawOver2 * sinPitchOver2 * sinRollOver2;
result.Z = cosYawOver2 * cosPitchOver2 * sinRollOver2 - sinYawOver2 * sinPitchOver2 * cosRollOver2;
}
Quaternion Quaternion::GetRotationFromNormal(const Vector3& normal, const Transform& reference)
{
Float3 up = reference.GetUp();
const float dot = Vector3::Dot(normal, up);
if (Math::NearEqual(Math::Abs(dot), 1))
{
up = reference.GetRight();
}
return Quaternion::LookRotation(normal, up);
}

View File

@@ -1483,6 +1483,45 @@ namespace FlaxEngine
return results;
}
/// <summary>
/// Gets rotation from a normal in relation to a transform.<br/>
/// This function is especially useful for axis aligned faces,
/// and with <seealso cref="Physics.RayCast(Vector3, Vector3, out RayCastHit, float, uint, bool)"/>.
///
/// <example><para><b>Example code:</b></para>
/// <code>
/// <see langword="public" /> <see langword="class" /> GetRotationFromNormalExample : <see cref="Script"/><br/>
/// <see langword="public" /> <see cref="Actor"/> RayOrigin;<br/>
/// <see langword="public" /> <see cref="Actor"/> SomeObject;<br/>
/// <see langword="public" /> <see langword="override" /> <see langword="void" /> <see cref="Script.OnFixedUpdate"/><br/>
/// {<br/>
/// <see langword="if" /> (<see cref="Physics"/>.RayCast(RayOrigin.Position, RayOrigin.Transform.Forward, out <see cref="RayCastHit"/> hit)
/// {<br/>
/// <see cref="Vector3"/> position = hit.Collider.Position;
/// <see cref="Transform"/> transform = hit.Collider.Transform;
/// <see cref="Vector3"/> point = hit.Point;
/// <see cref="Vector3"/> normal = hit.Normal;
/// <see cref="Quaternion"/> rot = <see cref="Quaternion"/>.GetRotationFromNormal(normal,transform);
/// SomeObject.Position = point;
/// SomeObject.Orientation = rot;
/// }
/// }
/// }
/// </code>
/// </example>
/// </summary>
/// <param name="normal">The normal vector.</param>
/// <param name="reference">The reference transform.</param>
/// <returns>The rotation from the normal vector.</returns>
public static Quaternion GetRotationFromNormal(Vector3 normal, Transform reference)
{
Float3 up = reference.Up;
var dot = Vector3.Dot(normal, up);
if (Mathf.NearEqual(Math.Abs(dot), 1))
up = reference.Right;
return LookRotation(normal, up);
}
/// <summary>
/// Adds two quaternions.
/// </summary>

View File

@@ -660,6 +660,14 @@ public:
// @param roll The roll of rotation (in radians)
// @param result When the method completes, contains the newly created quaternion
static void RotationYawPitchRoll(float yaw, float pitch, float roll, Quaternion& result);
/// <summary>
/// Gets rotation from a normal in relation to a transform. This function is especially useful for axis aligned faces, and with <seealso cref="Physics::RayCast"/>.
/// </summary>
/// <param name="normal">The normal vector.</param>
/// <param name="reference">The reference transform.</param>
/// <returns>The rotation from the normal vector.</returns>
static Quaternion GetRotationFromNormal(const Vector3& normal, const Transform& reference);
};
/// <summary>

View File

@@ -252,3 +252,9 @@ void Transform::Lerp(const Transform& t1, const Transform& t2, float amount, Tra
Quaternion::Slerp(t1.Orientation, t2.Orientation, amount, result.Orientation);
Float3::Lerp(t1.Scale, t2.Scale, amount, result.Scale);
}
Transform Transform::AlignRotationToNormalAndSnapToGrid(const Vector3& point, const Vector3& normal, const Vector3& normalOffset, const Transform& relativeTo, const Vector3& gridSize, const Float3& scale)
{
Quaternion rot = Quaternion::GetRotationFromNormal(normal, relativeTo);
return Transform(Vector3::SnapToGrid(point, gridSize, rot, relativeTo.Translation, normalOffset), rot, scale);
}

View File

@@ -478,6 +478,96 @@ namespace FlaxEngine
Float3.Lerp(ref start.Scale, ref end.Scale, amount, out result.Scale);
}
/// <summary>
/// Combines the functions:<br/>
/// <see cref="Vector3.SnapToGrid(FlaxEngine.Vector3,FlaxEngine.Vector3)"/>,<br/>
/// <see cref="Quaternion.GetRotationFromNormal"/>.
/// <example><para><b>Example code:</b></para>
/// <code>
/// <see langword="public" /> <see langword="class" /> AlignRotationToObjectAndSnapToGridExample : <see cref="Script"/><br/>
/// <see langword="public" /> <see cref="Vector3"/> Offset = new Vector3(0, 0, 50f);<br/>
/// <see langword="public" /> <see cref="Vector3"/> GridSize = <see cref="Vector3.One"/> * 20.0f;<br/>
/// <see langword="public" /> <see cref="Actor"/> RayOrigin;<br/>
/// <see langword="public" /> <see cref="Actor"/> SomeObject;<br/>
/// <see langword="public" /> <see langword="override" /> <see langword="void" /> <see cref="Script.OnFixedUpdate"/><br/>
/// {<br/>
/// <see langword="if" /> (<see cref="Physics"/>.RayCast(RayOrigin.Position, RayOrigin.Transform.Forward, out <see cref="RayCastHit"/> hit)
/// {<br/>
/// <see cref="Transform"/> transform = hit.Collider.Transform;
/// <see cref="Vector3"/> point = hit.Point;
/// <see cref="Vector3"/> normal = hit.Normal;
/// SomeObject.Transform = <see cref="Transform"/>.AlignRotationToNormalAndSnapToGrid
/// (
/// point,
/// normal,
/// Offset,
/// transform,
/// SomeObject.Scale,
/// GridSize,
/// Float3.One
/// );
/// }
/// }
/// }
/// </code>
/// </example>
/// </summary>
/// <param name="point">The position to snap.</param>
/// <param name="gridSize">The size of the grid.</param>
/// <param name="normalOffset">The local grid offset to apply after snapping.</param>
/// <param name="normal">The normal vector.</param>
/// <param name="relativeTo">The relative transform.</param>
/// <param name="scale">The scale to apply to the transform.</param>
/// <returns>The rotated and snapped transform.</returns>
public static Transform AlignRotationToNormalAndSnapToGrid(Vector3 point, Vector3 normal, Vector3 normalOffset, Transform relativeTo, Vector3 gridSize, Float3 scale)
{
Quaternion rot = Quaternion.GetRotationFromNormal(normal, relativeTo);
return new Transform(Vector3.SnapToGrid(point, gridSize, rot, relativeTo.Translation, normalOffset), rot, scale);
}
/// <summary>
/// Combines the functions:<br/>
/// <see cref="Vector3.SnapToGrid(FlaxEngine.Vector3,FlaxEngine.Vector3)"/>,<br/>
/// <see cref="Quaternion.GetRotationFromNormal"/>.
/// <example><para><b>Example code:</b></para>
/// <code>
/// <see langword="public" /> <see langword="class" /> AlignRotationToObjectAndSnapToGridExample : <see cref="Script"/><br/>
/// <see langword="public" /> <see cref="Vector3"/> Offset = new Vector3(0, 0, 50f);<br/>
/// <see langword="public" /> <see cref="Vector3"/> GridSize = <see cref="Vector3.One"/> * 20.0f;<br/>
/// <see langword="public" /> <see cref="Actor"/> RayOrigin;<br/>
/// <see langword="public" /> <see cref="Actor"/> SomeObject;<br/>
/// <see langword="public" /> <see langword="override" /> <see langword="void" /> <see cref="Script.OnFixedUpdate"/><br/>
/// {<br/>
/// <see langword="if" /> (<see cref="Physics"/>.RayCast(RayOrigin.Position, RayOrigin.Transform.Forward, out <see cref="RayCastHit"/> hit)
/// {<br/>
/// <see cref="Transform"/> transform = hit.Collider.Transform;
/// <see cref="Vector3"/> point = hit.Point;
/// <see cref="Vector3"/> normal = hit.Normal;
/// SomeObject.Transform = <see cref="Transform"/>.AlignRotationToNormalAndSnapToGrid
/// (
/// point,
/// normal,
/// Offset,
/// transform,
/// GridSize
/// );
/// }
/// }
/// }
/// </code>
/// </example>
/// </summary>
/// <param name="point">The position to snap.</param>
/// <param name="gridSize">The size of the grid.</param>
/// <param name="normalOffset">The local grid offset to apply after snapping.</param>
/// <param name="normal">The normal vector.</param>
/// <param name="relativeTo">The relative transform.</param>
/// <returns>The rotated and snapped transform with scale <see cref="Float3.One"/>.</returns>
public static Transform AlignRotationToNormalAndSnapToGrid(Vector3 point, Vector3 normal, Vector3 normalOffset, Transform relativeTo, Vector3 gridSize)
{
return AlignRotationToNormalAndSnapToGrid(point, normal, normalOffset, relativeTo, gridSize, Float3.One);
}
/// <summary>
/// Tests for equality between two objects.
/// </summary>

View File

@@ -291,6 +291,20 @@ public:
return result;
}
/// <summary>
/// Combines the functions: <br/>
/// <see cref="SnapToGrid"/>,<br/>
/// <see cref="GetRotationFromNormal"/>.
/// </summary>
/// <param name="point">The position to snap.</param>
/// <param name="gridSize">The size of the grid.</param>
/// <param name="normalOffset">The local grid offset to apply after snapping.</param>
/// <param name="normal">The normal vector.</param>
/// <param name="relativeTo">The relative transform.</param>
/// <param name="scale">The scale to apply to the transform.</param>
/// <returns>The rotated and snapped transform.</returns>
static Transform AlignRotationToNormalAndSnapToGrid(const Vector3& point, const Vector3& normal, const Vector3& normalOffset, const Transform& relativeTo, const Vector3& gridSize, const Float3& scale = Float3::One);
public:
FORCE_INLINE Transform operator*(const Transform& other) const
{

View File

@@ -324,6 +324,20 @@ float Float3::Angle(const Float3& from, const Float3& to)
return Math::Acos(dot);
}
template<>
Float3 Float3::SnapToGrid(const Float3& pos, const Float3& gridSize)
{
return Float3(Math::Ceil((pos.X - (gridSize.X * 0.5f)) / gridSize.X) * gridSize.X,
Math::Ceil((pos.Y - (gridSize.Y * 0.5f)) / gridSize.Y) * gridSize.Y,
Math::Ceil((pos.Z - (gridSize.Z * 0.5f)) / gridSize.Z) * gridSize.Z);
}
template<>
Float3 Float3::SnapToGrid(const Float3& point, const Float3& gridSize, const Quaternion& gridOrientation, const Float3& gridOrigin, const Float3& offset)
{
return (gridOrientation * (gridOrientation.Conjugated() * SnapToGrid(point - gridOrigin, gridSize) + offset)) + gridOrigin;
}
// Double
static_assert(sizeof(Double3) == 24, "Invalid Double3 type size.");
@@ -638,6 +652,20 @@ double Double3::Angle(const Double3& from, const Double3& to)
return Math::Acos(dot);
}
template<>
Double3 Double3::SnapToGrid(const Double3& pos, const Double3& gridSize)
{
return Double3(Math::Ceil((pos.X - (gridSize.X * 0.5)) / gridSize.X) * gridSize.X,
Math::Ceil((pos.Y - (gridSize.Y * 0.5)) / gridSize.Y) * gridSize.Y,
Math::Ceil((pos.Z - (gridSize.Z * 0.5)) / gridSize.Z) * gridSize.Z);
}
template<>
Double3 Double3::SnapToGrid(const Double3& point, const Double3& gridSize, const Quaternion& gridOrientation, const Double3& gridOrigin, const Double3& offset)
{
return (gridOrientation * (gridOrientation.Conjugated() * SnapToGrid(point - gridOrigin, gridSize) + offset)) + gridOrigin;
}
// Int
static_assert(sizeof(Int3) == 12, "Invalid Int3 type size.");
@@ -852,3 +880,17 @@ int32 Int3::Angle(const Int3& from, const Int3& to)
{
return 0;
}
template<>
Int3 Int3::SnapToGrid(const Int3& pos, const Int3& gridSize)
{
return Int3(((pos.X - (gridSize.X / 2)) / gridSize.X) * gridSize.X,
((pos.Y - (gridSize.Y / 2)) / gridSize.Y) * gridSize.Y,
((pos.Z - (gridSize.Z / 2)) / gridSize.Z) * gridSize.Z);
}
template<>
Int3 Int3::SnapToGrid(const Int3& point, const Int3& gridSize, const Quaternion& gridOrientation, const Int3& gridOrigin, const Int3& offset)
{
return (gridOrientation * (gridOrientation.Conjugated() * SnapToGrid(point - gridOrigin, gridSize) + offset)) + gridOrigin;
}

View File

@@ -1672,7 +1672,7 @@ namespace FlaxEngine
}
/// <summary>
/// Snaps the input position into the grid.
/// Snaps the input position onto the grid.
/// </summary>
/// <param name="pos">The position to snap.</param>
/// <param name="gridSize">The size of the grid.</param>
@@ -1685,6 +1685,44 @@ namespace FlaxEngine
return pos;
}
/// <summary>
/// Snaps the <paramref name="point"/> onto the rotated grid.<br/>
/// For world aligned grid snapping use <b><see cref="SnapToGrid(FlaxEngine.Vector3,FlaxEngine.Vector3)"/></b> instead.
/// <example><para><b>Example code:</b></para>
/// <code>
/// <see langword="public" /> <see langword="class" /> SnapToGridExample : <see cref="Script"/><br/>
/// <see langword="public" /> <see cref="Vector3"/> GridSize = <see cref="Vector3.One"/> * 20.0f;<br/>
/// <see langword="public" /> <see cref="Actor"/> RayOrigin;<br/>
/// <see langword="public" /> <see cref="Actor"/> SomeObject;<br/>
/// <see langword="public" /> <see langword="override" /> <see langword="void" /> <see cref="Script.OnFixedUpdate"/><br/>
/// {<br/>
/// <see langword="if" /> (<see cref="Physics"/>.RayCast(RayOrigin.Position, RayOrigin.Transform.Forward, out <see cref="RayCastHit"/> hit)
/// {<br/>
/// <see cref="Vector3"/> position = hit.Collider.Position;
/// <see cref="FlaxEngine.Transform"/> transform = hit.Collider.Transform;
/// <see cref="Vector3"/> point = hit.Point;
/// <see cref="Vector3"/> normal = hit.Normal;
/// //Get rotation from normal relative to collider transform
/// <see cref="Quaternion"/> rot = <see cref="Quaternion"/>.GetRotationFromNormal(normal, transform);
/// point = <see cref="Vector3"/>.SnapToGrid(point, GridSize, rot, position);
/// SomeObject.Position = point;
/// }
/// }
/// }
/// </code>
/// </example>
/// </summary>
/// <param name="point">The position to snap.</param>
/// <param name="gridSize">The size of the grid.</param>
/// <param name="gridOrientation">The rotation of the grid.</param>
/// <param name="gridOrigin">The center point of the grid.</param>
/// <param name="offset">The local position offset applied to the snapped position before grid rotation.</param>
/// <returns>The position snapped to the grid.</returns>
public static Vector3 SnapToGrid(Vector3 point, Vector3 gridSize, Quaternion gridOrientation, Vector3 gridOrigin, Vector3 offset)
{
return ((SnapToGrid(point - gridOrigin, gridSize) * gridOrientation.Conjugated() + offset) * gridOrientation) + gridOrigin;
}
/// <summary>
/// Adds two vectors.
/// </summary>

View File

@@ -927,6 +927,25 @@ public:
/// <param name="to">The second vector.</param>
/// <returns>The angle (in radians).</returns>
static FLAXENGINE_API T Angle(const Vector3Base& from, const Vector3Base& to);
/// <summary>
/// Snaps the input position onto the grid.
/// </summary>
/// <param name="pos">The position to snap.</param>
/// <param name="gridSize">The size of the grid.</param>
/// <returns>The position snapped to the grid.</returns>
static FLAXENGINE_API Vector3Base SnapToGrid(const Vector3Base& pos, const Vector3Base& gridSize);
/// <summary>
/// Snaps the <paramref name="point"/> onto the rotated grid. For world aligned grid snapping use <b><see cref="SnapToGrid"/></b> instead.
/// </summary>
/// <param name="point">The position to snap.</param>
/// <param name="gridSize">The size of the grid.</param>
/// <param name="gridOrigin">The center point of the grid.</param>
/// <param name="gridOrientation">The rotation of the grid.</param>
/// <param name="offset">The local position offset applied to the snapped position before grid rotation.</param>
/// <returns>The position snapped to the grid.</returns>
static FLAXENGINE_API Vector3Base SnapToGrid(const Vector3Base& point, const Vector3Base& gridSize, const Quaternion& gridOrientation, const Vector3Base& gridOrigin = Zero, const Vector3Base& offset = Zero);
};
template<typename T>

View File

@@ -479,6 +479,10 @@ void Foliage::DrawType(RenderContext& renderContext, const FoliageType& type, Dr
batch.DrawCall.InstanceCount = 1;
auto& firstInstance = batch.Instances[0];
firstInstance.Load(batch.DrawCall);
#if USE_EDITOR
if (renderContext.View.Mode == ViewMode::LightmapUVsDensity)
batch.DrawCall.Surface.LODDitherFactor = type.ScaleInLightmap; // See LightmapUVsDensityMaterialShader
#endif
if (EnumHasAnyFlags(drawModes, DrawPass::Forward))
{

View File

@@ -39,11 +39,13 @@ public:
FORCE_INLINE GPUPipelineState* Get(int index) const
{
ASSERT_LOW_LAYER(index >= 0 && index < Size);
return States[index];
}
FORCE_INLINE GPUPipelineState*& operator[](int32 index)
{
ASSERT_LOW_LAYER(index >= 0 && index < Size);
return States[index];
}
@@ -129,6 +131,7 @@ public:
public:
FORCE_INLINE GPUShaderProgramCS* Get(const int index) const
{
ASSERT_LOW_LAYER(index >= 0 && index < Size);
return Shaders[index];
}

View File

@@ -480,6 +480,8 @@ void Mesh::Draw(const RenderContext& renderContext, const DrawInfo& info, float
const ViewMode viewMode = renderContext.View.Mode;
if (viewMode == ViewMode::LightmapUVsDensity || viewMode == ViewMode::LODPreview)
GBufferPass::AddIndexBufferToModelLOD(_indexBuffer, &((Model*)_model)->LODs[_lodIndex]);
if (viewMode == ViewMode::LightmapUVsDensity)
drawCall.Surface.LODDitherFactor = info.LightmapScale; // See LightmapUVsDensityMaterialShader
#endif
// Push draw call to the render list
@@ -541,6 +543,8 @@ void Mesh::Draw(const RenderContextBatch& renderContextBatch, const DrawInfo& in
const ViewMode viewMode = renderContextBatch.GetMainContext().View.Mode;
if (viewMode == ViewMode::LightmapUVsDensity || viewMode == ViewMode::LODPreview)
GBufferPass::AddIndexBufferToModelLOD(_indexBuffer, &((Model*)_model)->LODs[_lodIndex]);
if (viewMode == ViewMode::LightmapUVsDensity)
drawCall.Surface.LODDitherFactor = info.LightmapScale; // See LightmapUVsDensityMaterialShader
#endif
// Push draw call to the render lists

View File

@@ -241,5 +241,9 @@ public:
/// The object sorting key.
/// </summary>
int8 SortOrder;
#if USE_EDITOR
float LightmapScale = -1.0f;
#endif
};
};

View File

@@ -52,6 +52,11 @@ API_ENUM() enum class ToneMappingMode
/// The ACES Filmic reference tonemapper (approximation).
/// </summary>
ACES = 2,
/// <summary>
/// The AGX tonemapper.
/// </summary>
AGX = 3,
};
/// <summary>

View File

@@ -213,13 +213,13 @@ protected:
const int32 dstMips = dstTexture->MipLevels();
GPUTexture* srcTexture = _streamingTexture->GetTexture();
const int32 srcMips = srcTexture->MipLevels();
const int32 srcMissingMips = srcMips - srcTexture->ResidentMipLevels();
const int32 mipCount = Math::Min(dstMips, srcMips);
ASSERT(mipCount > 0);
for (int32 mipIndex = 0; mipIndex < mipCount; mipIndex++)
for (int32 mipIndex = srcMissingMips; mipIndex < mipCount; mipIndex++)
{
context->GPU->CopySubresource(dstTexture, dstMips - mipIndex - 1, srcTexture, srcMips - mipIndex - 1);
}
_uploadedMipCount = mipCount;
_uploadedMipCount = mipCount - srcMissingMips;
return Result::Ok;
}
@@ -238,10 +238,10 @@ protected:
void OnSync() override
{
_newTexture->SetResidentMipLevels(_uploadedMipCount);
Swap(_streamingTexture->_texture, _newTexture);
_streamingTexture->GetTexture()->SetResidentMipLevels(_uploadedMipCount);
_streamingTexture->ResidencyChanged();
SAFE_DELETE_GPU_RESOURCE(_newTexture);
_streamingTexture->ResidencyChanged();
// Base
GPUTask::OnSync();

View File

@@ -27,32 +27,18 @@ struct AxisEvaluation
struct ActionData
{
bool Active;
uint64 FrameIndex;
InputActionState State;
ActionData()
{
Active = false;
FrameIndex = 0;
State = InputActionState::Waiting;
}
bool Active = false;
uint64 FrameIndex = 0;
InputActionState State = InputActionState::Waiting;
};
struct AxisData
{
float Value;
float ValueRaw;
float PrevKeyValue;
uint64 FrameIndex;
AxisData()
{
Value = 0.0f;
ValueRaw = 0.0f;
PrevKeyValue = 0.0f;
FrameIndex = 0;
}
float Value = 0.0f;
float ValueRaw = 0.0f;
float PrevValue = 0.0f;
float PrevKeyValue = 0.0f;
uint64 FrameIndex = 0;
};
namespace InputImpl
@@ -990,6 +976,7 @@ void InputService::Update()
// Setup axis data
data.PrevKeyValue = e.PrevKeyValue;
data.PrevValue = data.Value;
data.ValueRaw = e.RawValue;
data.Value = e.Value;
@@ -1025,7 +1012,7 @@ void InputService::Update()
{
for (auto i = Axes.Begin(); i.IsNotEnd(); ++i)
{
if (Math::NotNearEqual(i->Value.Value, i->Value.PrevKeyValue))
if (Math::NotNearEqual(i->Value.Value, i->Value.PrevValue))
{
Input::AxisValueChanged(i->Key);
}

View File

@@ -237,7 +237,10 @@ Ray Camera::ConvertMouseToRay(const Float2& mousePosition, const Viewport& viewp
viewport.Unproject(nearPoint, ivp, nearPoint);
viewport.Unproject(farPoint, ivp, farPoint);
return Ray(nearPoint, Vector3::Normalize(farPoint - nearPoint));
Vector3 dir = Vector3::Normalize(farPoint - nearPoint);
if (dir.IsZero())
return Ray::Identity;
return Ray(nearPoint, dir);
}
Viewport Camera::GetViewport() const
@@ -303,6 +306,8 @@ void Camera::GetMatrices(Matrix& view, Matrix& projection, const Viewport& viewp
void Camera::OnPreviewModelLoaded()
{
_previewModelBuffer.Setup(_previewModel.Get());
if (_previewModelBuffer.Count() > 0)
_previewModelBuffer.At(0).ReceiveDecals = false;
UpdateCache();
}

View File

@@ -150,35 +150,30 @@ float Spline::GetSplineLength() const
{
float sum = 0.0f;
constexpr int32 slices = 20;
constexpr float step = 1.0f / (float)slices;
Vector3 prevPoint = Vector3::Zero;
if (Curve.GetKeyframes().Count() != 0)
{
const auto& a = Curve[0];
prevPoint = a.Value.Translation * _transform.Scale;
}
constexpr float step = 1.0f / (float)(slices - 1);
const Vector3 scale = _transform.Scale;
for (int32 i = 1; i < Curve.GetKeyframes().Count(); i++)
{
const auto& a = Curve[i - 1];
const auto& b = Curve[i];
Vector3 prevPoint = a.Value.Translation * scale;
const float length = Math::Abs(b.Time - a.Time);
Vector3 leftTangent, rightTangent;
AnimationUtils::GetTangent(a.Value.Translation, a.TangentOut.Translation, length, leftTangent);
AnimationUtils::GetTangent(b.Value.Translation, b.TangentIn.Translation, length, rightTangent);
// TODO: implement sth more analytical than brute-force solution
for (int32 slice = 0; slice < slices; slice++)
for (int32 slice = 1; slice < slices; slice++)
{
const float t = (float)slice * step;
Vector3 pos;
AnimationUtils::Bezier(a.Value.Translation, leftTangent, rightTangent, b.Value.Translation, t, pos);
pos *= _transform.Scale;
sum += (float)Vector3::DistanceSquared(pos, prevPoint);
pos *= scale;
sum += (float)Vector3::Distance(pos, prevPoint);
prevPoint = pos;
}
}
return Math::Sqrt(sum);
return sum;
}
float Spline::GetSplineSegmentLength(int32 index) const
@@ -188,28 +183,28 @@ float Spline::GetSplineSegmentLength(int32 index) const
CHECK_RETURN(index > 0 && index < GetSplinePointsCount(), 0.0f);
float sum = 0.0f;
constexpr int32 slices = 20;
constexpr float step = 1.0f / (float)slices;
constexpr float step = 1.0f / (float)(slices - 1);
const auto& a = Curve[index - 1];
const auto& b = Curve[index];
Vector3 startPoint = a.Value.Translation * _transform.Scale;
const Vector3 scale = _transform.Scale;
Vector3 prevPoint = a.Value.Translation * scale;
{
const float length = Math::Abs(b.Time - a.Time);
Vector3 leftTangent, rightTangent;
AnimationUtils::GetTangent(a.Value.Translation, a.TangentOut.Translation, length, leftTangent);
AnimationUtils::GetTangent(b.Value.Translation, b.TangentIn.Translation, length, rightTangent);
// TODO: implement sth more analytical than brute-force solution
for (int32 slice = 0; slice < slices; slice++)
for (int32 slice = 1; slice < slices; slice++)
{
const float t = (float)slice * step;
Vector3 pos;
AnimationUtils::Bezier(a.Value.Translation, leftTangent, rightTangent, b.Value.Translation, t, pos);
pos *= _transform.Scale;
sum += (float)Vector3::DistanceSquared(pos, startPoint);
startPoint = pos;
pos *= scale;
sum += (float)Vector3::Distance(pos, prevPoint);
prevPoint = pos;
}
}
return Math::Sqrt(sum);
return sum;
}
float Spline::GetSplineTime(int32 index) const

View File

@@ -357,6 +357,10 @@ void StaticModel::Draw(RenderContext& renderContext)
draw.ForcedLOD = _forcedLod;
draw.SortOrder = _sortOrder;
draw.VertexColors = _vertexColorsCount ? _vertexColorsBuffer : nullptr;
#if USE_EDITOR
if (HasStaticFlag(StaticFlags::Lightmap))
draw.LightmapScale = _scaleInLightmap;
#endif
Model->Draw(renderContext, draw);
@@ -391,6 +395,10 @@ void StaticModel::Draw(RenderContextBatch& renderContextBatch)
draw.ForcedLOD = _forcedLod;
draw.SortOrder = _sortOrder;
draw.VertexColors = _vertexColorsCount ? _vertexColorsBuffer : nullptr;
#if USE_EDITOR
if (HasStaticFlag(StaticFlags::Lightmap))
draw.LightmapScale = _scaleInLightmap;
#endif
Model->Draw(renderContextBatch, draw);

View File

@@ -7,9 +7,7 @@ using System.Runtime.CompilerServices;
namespace FlaxEngine
{
#if FLAX_EDITOR
[TypeConverter(typeof(TypeConverters.TagConverter))]
#endif
partial struct Tag : IEquatable<Tag>, IEquatable<string>, IComparable, IComparable<Tag>, IComparable<string>
{
/// <summary>
@@ -254,7 +252,6 @@ namespace FlaxEngine
}
}
#if FLAX_EDITOR
namespace FlaxEngine.TypeConverters
{
internal class TagConverter : TypeConverter
@@ -291,4 +288,3 @@ namespace FlaxEngine.TypeConverters
}
}
}
#endif

View File

@@ -1503,6 +1503,8 @@ NetworkStream* NetworkReplicator::BeginInvokeRPC()
bool NetworkReplicator::EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, Span<uint32> targetIds)
{
if (targetIds.IsValid() && targetIds.Length() == 0)
return true; // Target list is provided, but it's empty so nobody will get this RPC
Scripting::ObjectsLookupIdMapping.Set(nullptr);
const NetworkRpcInfo* info = NetworkRpcInfo::RPCsTable.TryGet(NetworkRpcName(type, name));
if (!info || !obj || NetworkManager::IsOffline())

View File

@@ -66,12 +66,14 @@ bool GPUParticles::Init(ParticleEmitter* owner, MemoryReadStream& shaderCacheStr
LOG(Warning, "Missing valid GPU particles constant buffer.");
return true;
}
if (cb0->GetSize() < sizeof(GPUParticlesData))
const int32 cbSize = cb0->GetSize();
if (cbSize < sizeof(GPUParticlesData))
{
LOG(Warning, "Invalid size GPU particles constant buffer. required {0} bytes but got {1}", sizeof(GPUParticlesData), cb0->GetSize());
LOG(Warning, "Invalid size GPU particles constant buffer. required {0} bytes but got {1}", sizeof(GPUParticlesData), cbSize);
return true;
}
_cbData.Resize(cb0->GetSize());
_cbData.Resize(cbSize);
Platform::MemoryClear(_cbData.Get(), cbSize);
// Load material parameters
if (_params.Load(materialParamsStream))

View File

@@ -15,7 +15,7 @@ private:
bool _useVolumeTexture;
PixelFormat _lutFormat;
AssetReference<Shader> _shader;
GPUPipelineStatePermutationsPs<3> _psLut;
GPUPipelineStatePermutationsPs<4> _psLut;
public:

View File

@@ -15,7 +15,6 @@
#include "Engine/Renderer/DrawCall.h"
#include "Engine/Foliage/Foliage.h"
#include "Engine/ShadowsOfMordor/Builder.Config.h"
#include "Engine/Level/Level.h"
#include "Engine/Level/Scene/Scene.h"
#include "Engine/Level/Actors/StaticModel.h"
@@ -70,40 +69,6 @@ DrawPass LightmapUVsDensityMaterialShader::GetDrawModes() const
return DrawPass::GBuffer;
}
namespace
{
Actor* FindActorByDrawCall(Actor* actor, const DrawCall& drawCall, float& scaleInLightmap)
{
// TODO: large-worlds
const auto asStaticModel = ScriptingObject::Cast<StaticModel>(actor);
if (asStaticModel && asStaticModel->GetPerInstanceRandom() == drawCall.PerInstanceRandom && asStaticModel->GetPosition() == drawCall.ObjectPosition)
{
scaleInLightmap = asStaticModel->GetScaleInLightmap();
return asStaticModel;
}
const auto asFoliage = ScriptingObject::Cast<Foliage>(actor);
if (asFoliage)
{
for (auto i = asFoliage->Instances.Begin(); i.IsNotEnd(); ++i)
{
auto& instance = *i;
if (instance.Random == drawCall.PerInstanceRandom && instance.Transform.Translation == drawCall.ObjectPosition)
{
scaleInLightmap = asFoliage->FoliageTypes[instance.Type].ScaleInLightmap;
return asFoliage;
}
}
}
for (Actor* child : actor->Children)
{
const auto other = FindActorByDrawCall(child, drawCall, scaleInLightmap);
if (other)
return other;
}
return nullptr;
}
}
void LightmapUVsDensityMaterialShader::Bind(BindParameters& params)
{
// Prepare
@@ -121,33 +86,6 @@ void LightmapUVsDensityMaterialShader::Bind(BindParameters& params)
_ps->Init(psDesc);
}
// Find the static model that produced this draw call
const Actor* drawCallActor = nullptr;
float scaleInLightmap = 1.0f;
if (params.RenderContext.Task)
{
// Skip this lookup as it's too slow
/*if (params.RenderContext.Task->ActorsSource & ActorsSources::CustomActors)
{
for (auto actor : params.RenderContext.Task->CustomActors)
{
drawCallActor = FindActorByDrawCall(actor, drawCall, scaleInLightmap);
if (drawCallActor)
break;
}
}
if (!drawCallActor && params.RenderContext.Task->ActorsSource & ActorsSources::Scenes)
{
for (auto& scene : Level::Scenes)
{
drawCallActor = FindActorByDrawCall(scene, drawCall, scaleInLightmap);
if (drawCallActor)
break;
}
}*/
}
// Bind constants
if (cb && cb->GetSize())
{
@@ -166,19 +104,15 @@ void LightmapUVsDensityMaterialShader::Bind(BindParameters& params)
data.LightmapSize = 1024.0f;
data.LightmapArea = drawCall.Surface.LightmapUVsArea;
const ModelLOD* drawCallModelLod;
if (GBufferPass::IndexBufferToModelLOD.TryGet(drawCall.Geometry.IndexBuffer, drawCallModelLod))
float scaleInLightmap = drawCall.Surface.LODDitherFactor; // Reuse field
if (scaleInLightmap < 0.0f)
data.LightmapSize = -1.0f; // Not using lightmap
else if (GBufferPass::IndexBufferToModelLOD.TryGet(drawCall.Geometry.IndexBuffer, drawCallModelLod))
{
// Calculate current lightmap slot size for the object (matches the ShadowsOfMordor calculations when baking the lighting)
float globalObjectsScale = 1.0f;
int32 atlasSize = 1024;
int32 chartsPadding = 3;
const Scene* drawCallScene = drawCallActor ? drawCallActor->GetScene() : (Level::Scenes.Count() != 0 ? Level::Scenes[0] : nullptr);
if (drawCallScene)
{
globalObjectsScale = drawCallScene->Info.LightmapSettings.GlobalObjectsScale;
atlasSize = (int32)drawCallScene->Info.LightmapSettings.AtlasSize;
chartsPadding = drawCallScene->Info.LightmapSettings.ChartsPadding;
}
BoundingBox box = drawCallModelLod->GetBox(drawCall.World);
Float3 size = box.GetSize();
float dimensionsCoeff = size.AverageArithmetic();

View File

@@ -479,7 +479,7 @@ namespace FlaxEngine.Json
/// <inheritdoc />
public override bool CanConvert(Type objectType)
{
return objectType.Name.StartsWith("JsonAssetReference");
return objectType.Name.StartsWith("JsonAssetReference", StringComparison.Ordinal);
}
}

View File

@@ -187,7 +187,7 @@ public:
/// <summary>
/// Gets the list with physical materials used to define the terrain collider physical properties - each for terrain layer (layer index matches index in this array).
/// </summary>
API_PROPERTY(Attributes="EditorOrder(520), EditorDisplay(\"Collision\"), Collection(MinCount = 8, MaxCount = 8)")
API_PROPERTY(Attributes="EditorOrder(520), EditorDisplay(\"Collision\"), Collection(MinCount=8, MaxCount=8)")
FORCE_INLINE const Array<JsonAssetReference<PhysicalMaterial>, FixedAllocation<8>>& GetPhysicalMaterials() const
{
return _physicalMaterials;
@@ -199,6 +199,27 @@ public:
API_PROPERTY()
void SetPhysicalMaterials(const Array<JsonAssetReference<PhysicalMaterial>, FixedAllocation<8>>& value);
/// <summary>
/// Gets the physical material used to define the terrain collider physical properties.
/// [Deprecated on 16.02.2024, expires on 16.02.2026]
/// </summary>
API_PROPERTY(Attributes="HideInEditor, NoSerialize")
DEPRECATED("Use PhysicalMaterials instead.") FORCE_INLINE JsonAssetReference<PhysicalMaterial>& GetPhysicalMaterial()
{
return _physicalMaterials[0];
}
/// <summary>
/// Sets the physical materials used to define the terrain collider physical properties.
/// [Deprecated on 16.02.2024, expires on 16.02.2026]
/// </summary>
DEPRECATED("Use PhysicalMaterials instead.") API_PROPERTY()
void SetPhysicalMaterial(const JsonAssetReference<PhysicalMaterial>& value)
{
for (auto& e : _physicalMaterials)
e = value;
}
/// <summary>
/// Gets the terrain Level Of Detail count.
/// </summary>

View File

@@ -124,6 +124,10 @@ void TerrainChunk::Draw(const RenderContext& renderContext) const
}
drawCall.WorldDeterminantSign = RenderTools::GetWorldDeterminantSign(drawCall.World);
drawCall.PerInstanceRandom = _perInstanceRandom;
#if USE_EDITOR
if (renderContext.View.Mode == ViewMode::LightmapUVsDensity)
drawCall.Surface.LODDitherFactor = 1.0f; // See LightmapUVsDensityMaterialShader
#endif
// Add half-texel offset for heightmap sampling in vertex shader
//const float lodHeightmapSize = Math::Max(1, drawCall.TerrainData.Heightmap->Width() >> lod);
@@ -181,6 +185,10 @@ void TerrainChunk::Draw(const RenderContext& renderContext, MaterialBase* materi
}
drawCall.WorldDeterminantSign = RenderTools::GetWorldDeterminantSign(drawCall.World);
drawCall.PerInstanceRandom = _perInstanceRandom;
#if USE_EDITOR
if (renderContext.View.Mode == ViewMode::LightmapUVsDensity)
drawCall.Surface.LODDitherFactor = 1.0f; // See LightmapUVsDensityMaterialShader
#endif
// Add half-texel offset for heightmap sampling in vertex shader
//const float lodHeightmapSize = Math::Max(1, drawCall.TerrainData.Heightmap->Width() >> lod);

View File

@@ -7,6 +7,12 @@
#include "Engine/Scripting/ManagedCLR/MUtils.h"
#include <ThirdParty/catch2/catch.hpp>
Foo::Foo(const SpawnParams& params)
: ScriptingObject(params)
, FooInterface(nullptr)
{
}
TestNesting::TestNesting(const SpawnParams& params)
: SerializableScriptingObject(params)
{

View File

@@ -8,6 +8,21 @@
#include "Engine/Scripting/ScriptingObject.h"
#include "Engine/Scripting/SerializableScriptingObject.h"
// Test interface (name conflict with namespace)
API_INTERFACE(Namespace="Foo") class FLAXENGINE_API IFoo
{
DECLARE_SCRIPTING_TYPE_MINIMAL(IFoo);
};
// Test class (name conflict with namespace)
API_CLASS(Namespace="Foo") class FLAXENGINE_API Foo : public ScriptingObject
{
DECLARE_SCRIPTING_TYPE(Foo);
// Test field.
API_FIELD() IFoo* FooInterface;
};
// Test compilation with nested types.
API_CLASS() class TestNesting : public SerializableScriptingObject
{

View File

@@ -192,15 +192,10 @@ void MaterialGenerator::prepareLayer(MaterialLayer* layer, bool allowVisiblePara
// For all not root layers (sub-layers) we won't to change theirs ID in order to prevent duplicated ID)
m.SrcId = param->Identifier;
if (isRooLayer)
m.DstId = param->Identifier;
if (!isRooLayer)
{
// Use the same ID (so we can edit it)
m.DstId = param->Identifier;
}
else
{
// Generate new ID
m.DstId = param->Identifier;
// Generate new ID (stable permutation based on the original ID)
m.DstId.A += _parameters.Count() * 17 + 13;
}
layer->ParamIdsMappings.Add(m);

View File

@@ -644,20 +644,12 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value)
// Flipbook
case 10:
{
// Get input values
auto uv = Value::Cast(tryGetValue(node->GetBox(0), getUVs), VariantType::Float2);
auto frame = Value::Cast(tryGetValue(node->GetBox(1), node->Values[0]), VariantType::Float);
auto framesXY = Value::Cast(tryGetValue(node->GetBox(2), node->Values[1]), VariantType::Float2);
auto invertX = Value::Cast(tryGetValue(node->GetBox(3), node->Values[2]), VariantType::Float);
auto invertY = Value::Cast(tryGetValue(node->GetBox(4), node->Values[3]), VariantType::Float);
// Write operations
auto framesCount = writeLocal(VariantType::Float, String::Format(TEXT("{0}.x * {1}.y"), framesXY.Value, framesXY.Value), node);
frame = writeLocal(VariantType::Float, String::Format(TEXT("fmod(floor({0}), {1})"), frame.Value, framesCount.Value), node);
auto framesXYInv = writeOperation2(node, Value::One.AsFloat2(), framesXY, '/');
auto frameY = writeLocal(VariantType::Float, String::Format(TEXT("abs({0} * {1}.y - (floor({2} * {3}.x) + {0} * 1))"), invertY.Value, framesXY.Value, frame.Value, framesXYInv.Value), node);
auto frameX = writeLocal(VariantType::Float, String::Format(TEXT("abs({0} * {1}.x - (({2} - {1}.x * floor({2} * {3}.x)) + {0} * 1))"), invertX.Value, framesXY.Value, frame.Value, framesXYInv.Value), node);
value = writeLocal(VariantType::Float2, String::Format(TEXT("({3} + float2({0}, {1})) * {2}"), frameX.Value, frameY.Value, framesXYInv.Value, uv.Value), node);
value = writeLocal(VariantType::Float2, String::Format(TEXT("Flipbook({0}, {1}, {2}, float2({3}, {4}))"), uv.Value, frame.Value, framesXY.Value, invertX.Value, invertY.Value), node);
break;
}
// Sample Global SDF

View File

@@ -1379,14 +1379,16 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option
}
if (EnumHasAnyFlags(options.ImportTypes, ImportDataTypes::Animations))
{
int32 index = 0;
for (auto& animation : data.Animations)
{
LOG(Info, "Imported animation '{}' has {} channels, duration: {} frames, frames per second: {}", animation.Name, animation.Channels.Count(), animation.Duration, animation.FramesPerSecond);
LOG(Info, "Imported animation '{}' at index {} has {} channels, duration: {} frames ({} seconds), frames per second: {}", animation.Name, index, animation.Channels.Count(), animation.Duration, animation.GetLength(), animation.FramesPerSecond);
if (animation.Duration <= ZeroTolerance || animation.FramesPerSecond <= ZeroTolerance)
{
errorMsg = TEXT("Invalid animation duration.");
return true;
}
index++;
}
}
switch (options.Type)

View File

@@ -924,6 +924,19 @@ namespace FlaxEngine.GUI
return newLineLoc;
}
private int FindNextLineBegin()
{
int caretPos = CaretPosition;
if (caretPos + 2 > TextLength)
return TextLength;
int newLineLoc = _text.IndexOf('\n', caretPos + 2);
if (newLineLoc == -1)
newLineLoc = TextLength;
else
newLineLoc++;
return newLineLoc;
}
private int FindLineDownChar(int index)
{
if (!IsMultiline)
@@ -1423,6 +1436,30 @@ namespace FlaxEngine.GUI
return true;
}
case KeyboardKeys.PageDown:
{
if (IsScrollable && IsMultiline)
{
var location = GetCharPosition(_selectionStart, out var height);
var sizeHeight = Size.Y / height;
location.Y += height * (int)sizeHeight;
TargetViewOffset = Vector2.Clamp(new Float2(0, location.Y), Float2.Zero, TextSize - new Float2(0, Size.Y));
SetSelection(HitTestText(location));
}
return true;
}
case KeyboardKeys.PageUp:
{
if (IsScrollable && IsMultiline)
{
var location = GetCharPosition(_selectionStart, out var height);
var sizeHeight = Size.Y / height;
location.Y -= height * (int)sizeHeight;
TargetViewOffset = Vector2.Clamp(new Float2(0, location.Y), Float2.Zero, TextSize - new Float2(0, Size.Y));
SetSelection(HitTestText(location));
}
return true;
}
case KeyboardKeys.Delete:
{
if (IsReadOnly)
@@ -1491,8 +1528,13 @@ namespace FlaxEngine.GUI
return true;
case KeyboardKeys.End:
{
// Select text from the current cursor point to the beginning of a new line
if (shiftDown && _selectionStart != -1)
SetSelection(_selectionStart, FindNextLineBegin());
// Move caret after last character
SetSelection(TextLength);
else
SetSelection(TextLength);
return true;
}
case KeyboardKeys.Tab:

View File

@@ -750,7 +750,7 @@ namespace FlaxEngine.GUI
{
if (base.IsTouchOver)
return true;
for (int i = 0; i < _children.Count && _children.Count > 0; i++)
for (int i = 0; i < _children.Count; i++)
{
if (_children[i].IsTouchOver)
return true;
@@ -960,7 +960,7 @@ namespace FlaxEngine.GUI
public override void OnMouseLeave()
{
// Check all children collisions with mouse and fire events for them
for (int i = 0; i < _children.Count && _children.Count > 0; i++)
for (int i = 0; i < _children.Count; i++)
{
var child = _children[i];
if (child.Visible && child.Enabled && child.IsMouseOver)
@@ -1063,7 +1063,7 @@ namespace FlaxEngine.GUI
if (base.IsTouchPointerOver(pointerId))
return true;
for (int i = 0; i < _children.Count && _children.Count > 0; i++)
for (int i = 0; i < _children.Count; i++)
{
if (_children[i].IsTouchPointerOver(pointerId))
return true;
@@ -1168,7 +1168,7 @@ namespace FlaxEngine.GUI
/// <inheritdoc />
public override void OnTouchLeave(int pointerId)
{
for (int i = 0; i < _children.Count && _children.Count > 0; i++)
for (int i = 0; i < _children.Count; i++)
{
var child = _children[i];
if (child.Visible && child.Enabled && child.IsTouchPointerOver(pointerId))
@@ -1183,7 +1183,7 @@ namespace FlaxEngine.GUI
/// <inheritdoc />
public override bool OnCharInput(char c)
{
for (int i = 0; i < _children.Count && _children.Count > 0; i++)
for (int i = 0; i < _children.Count; i++)
{
var child = _children[i];
if (child.Enabled && child.ContainsFocus)
@@ -1197,7 +1197,7 @@ namespace FlaxEngine.GUI
/// <inheritdoc />
public override bool OnKeyDown(KeyboardKeys key)
{
for (int i = 0; i < _children.Count && _children.Count > 0; i++)
for (int i = 0; i < _children.Count; i++)
{
var child = _children[i];
if (child.Enabled && child.ContainsFocus)
@@ -1211,7 +1211,7 @@ namespace FlaxEngine.GUI
/// <inheritdoc />
public override void OnKeyUp(KeyboardKeys key)
{
for (int i = 0; i < _children.Count && _children.Count > 0; i++)
for (int i = 0; i < _children.Count; i++)
{
var child = _children[i];
if (child.Enabled && child.ContainsFocus)
@@ -1294,7 +1294,7 @@ namespace FlaxEngine.GUI
base.OnDragLeave();
// Check all children collisions with mouse and fire events for them
for (int i = 0; i < _children.Count && _children.Count > 0; i++)
for (int i = 0; i < _children.Count; i++)
{
var child = _children[i];
if (child.IsDragOver)

View File

@@ -409,9 +409,24 @@ void GraphUtilities::ApplySomeMathHere(Variant& v, Variant& a, Variant& b, MathO
case VariantType::Uint:
v.AsUint = (uint32)op((float)a.AsUint, (float)b.AsUint);
break;
case VariantType::Int64:
v.AsUint = (int64)op((float)a.AsInt64, (float)b.AsInt64);
break;
case VariantType::Uint64:
v.AsUint = (uint64)op((float)a.AsUint64, (float)b.AsUint64);
break;
case VariantType::Int16:
v.AsUint = (int16)op((float)a.AsInt16, (float)b.AsInt16);
break;
case VariantType::Uint16:
v.AsUint = (uint16)op((float)a.AsUint16, (float)b.AsUint16);
break;
case VariantType::Float:
v.AsFloat = op(a.AsFloat, b.AsFloat);
break;
case VariantType::Double:
v.AsDouble = op((float)a.AsDouble, (float)b.AsDouble);
break;
case VariantType::Float2:
{
Float2& vv = *(Float2*)v.AsData;