Merge remote-tracking branch 'origin/gi' into large-worlds

# Conflicts:
#	Source/Engine/Core/Math/Vector3.h
This commit is contained in:
Wojtek Figat
2022-05-21 19:45:13 +02:00
280 changed files with 7660 additions and 2355 deletions

1
.gitignore vendored
View File

@@ -148,5 +148,6 @@ bin/
obj/
*.vcxproj.filters
.vscode/
.idea/
*.code-workspace

View File

@@ -76,8 +76,8 @@ struct VertexOutput
#if USE_CUSTOM_VERTEX_INTERPOLATORS
float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9;
#endif
float3 InstanceOrigin : TEXCOORD6;
float InstanceParams : TEXCOORD7; // x-PerInstanceRandom
nointerpolation float3 InstanceOrigin : TEXCOORD6;
nointerpolation float InstanceParams : TEXCOORD7; // x-PerInstanceRandom
};
// Interpolants passed to the pixel shader
@@ -94,8 +94,8 @@ struct PixelInput
#if USE_CUSTOM_VERTEX_INTERPOLATORS
float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9;
#endif
float3 InstanceOrigin : TEXCOORD6;
float InstanceParams : TEXCOORD7; // x-PerInstanceRandom
nointerpolation float3 InstanceOrigin : TEXCOORD6;
nointerpolation float InstanceParams : TEXCOORD7; // x-PerInstanceRandom
bool IsFrontFace : SV_IsFrontFace;
};

View File

@@ -43,8 +43,8 @@ struct GeometryData
#endif
float3 WorldNormal : TEXCOORD3;
float4 WorldTangent : TEXCOORD4;
float3 InstanceOrigin : TEXCOORD5;
float2 InstanceParams : TEXCOORD6; // x-PerInstanceRandom, y-LODDitherFactor
nointerpolation float3 InstanceOrigin : TEXCOORD5;
nointerpolation float2 InstanceParams : TEXCOORD6; // x-PerInstanceRandom, y-LODDitherFactor
float3 PrevWorldPosition : TEXCOORD7;
};

BIN
Content/Editor/Primitives/Capsule.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Editor/Primitives/Cone.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Editor/Primitives/Cube.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Editor/Primitives/Cylinder.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Editor/Primitives/Plane.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Editor/Primitives/Sphere.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Engine/Models/Quad.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Engine/Models/Sphere.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Shaders/GI/GlobalSurfaceAtlas.flax (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Content/Shaders/GlobalSignDistanceField.flax (Stored with Git LFS) Normal file

Binary file not shown.

View File

@@ -2,8 +2,8 @@
"Name": "Flax",
"Version": {
"Major": 1,
"Minor": 3,
"Build": 6229
"Minor": 4,
"Build": 6332
},
"Company": "Flax",
"Copyright": "Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.",

View File

@@ -254,6 +254,7 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=comperand/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=coord/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=cubemap/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=defragmentation/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Delaunay/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Defocus/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Deinitialize/@EntryIndexedValue">True</s:Boolean>
@@ -360,6 +361,7 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=Upgrader/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=upgraders/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Visject/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=voxel/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=voxels/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=vsync/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=vtable/@EntryIndexedValue">True</s:Boolean>

View File

@@ -18,6 +18,11 @@ indent_size = 4
indent_style = space
indent_size = 4
# Shader files
[*.{hlsl,shader,glsl}]
indent_style = space
indent_size = 4
# XAML files
[*.xaml]
indent_style = space

View File

@@ -90,219 +90,270 @@ namespace FlaxEditor.Content.Import
public class ModelImportSettings
{
/// <summary>
/// Gets or sets the type of the imported asset.
/// Type of the imported asset.
/// </summary>
[EditorOrder(0), Tooltip("Type of the imported asset")]
[EditorOrder(0)]
public ModelType Type { get; set; } = ModelType.Model;
/// <summary>
/// True if calculate model normals, otherwise will import them.
/// Enable model normal vectors recalculating.
/// </summary>
[EditorOrder(20), DefaultValue(false), EditorDisplay("Geometry"), Tooltip("Enable model normal vectors recalculating")]
[EditorDisplay("Geometry"), VisibleIf(nameof(ShowGeometry))]
[EditorOrder(20), DefaultValue(false)]
public bool CalculateNormals { get; set; } = false;
/// <summary>
/// Calculated normals smoothing angle.
/// Specifies the maximum angle (in degrees) that may be between two face normals at the same vertex position that their are smoothed together. The default value is 175.
/// </summary>
[VisibleIf("CalculateNormals")]
[EditorOrder(30), DefaultValue(175.0f), Limit(0, 175, 0.1f), EditorDisplay("Geometry"), Tooltip("Specifies the maximum angle (in degrees) that may be between two face normals at the same vertex position that their are smoothed together. The default value is 175.")]
[EditorDisplay("Geometry"), VisibleIf(nameof(ShowSmoothingNormalsAngle))]
[EditorOrder(30), DefaultValue(175.0f), Limit(0, 175, 0.1f)]
public float SmoothingNormalsAngle { get; set; } = 175.0f;
private bool ShowSmoothingNormalsAngle => ShowGeometry && CalculateNormals;
/// <summary>
/// If checked, the imported normal vectors of the mesh will be flipped (scaled by -1).
/// </summary>
[EditorOrder(35), DefaultValue(false), EditorDisplay("Geometry"), Tooltip("If checked, the imported normal vectors of the mesh will be flipped (scaled by -1).")]
[EditorDisplay("Geometry"), VisibleIf(nameof(ShowGeometry))]
[EditorOrder(35), DefaultValue(false)]
public bool FlipNormals { get; set; } = false;
/// <summary>
/// True if calculate model tangents, otherwise will import them.
/// Enable model tangent vectors recalculating.
/// </summary>
[EditorOrder(40), DefaultValue(false), EditorDisplay("Geometry"), Tooltip("Enable model tangent vectors recalculating")]
[EditorDisplay("Geometry"), VisibleIf(nameof(ShowGeometry))]
[EditorOrder(40), DefaultValue(false)]
public bool CalculateTangents { get; set; } = false;
/// <summary>
/// Calculated normals smoothing angle.
/// Specifies the maximum angle (in degrees) that may be between two vertex tangents that their tangents and bi-tangents are smoothed. The default value is 45.
/// </summary>
[VisibleIf("CalculateTangents")]
[EditorOrder(45), DefaultValue(45.0f), Limit(0, 45, 0.1f), EditorDisplay("Geometry"), Tooltip("Specifies the maximum angle (in degrees) that may be between two vertex tangents that their tangents and bi-tangents are smoothed. The default value is 45.")]
[EditorDisplay("Geometry"), VisibleIf(nameof(ShowSmoothingTangentsAngle))]
[EditorOrder(45), DefaultValue(45.0f), Limit(0, 45, 0.1f)]
public float SmoothingTangentsAngle { get; set; } = 45.0f;
private bool ShowSmoothingTangentsAngle => ShowGeometry && CalculateTangents;
/// <summary>
/// Enable/disable meshes geometry optimization.
/// </summary>
[EditorOrder(50), DefaultValue(true), EditorDisplay("Geometry"), Tooltip("Enable/disable meshes geometry optimization")]
[EditorDisplay("Geometry"), VisibleIf(nameof(ShowGeometry))]
[EditorOrder(50), DefaultValue(true)]
public bool OptimizeMeshes { get; set; } = true;
/// <summary>
/// Enable/disable geometry merge for meshes with the same materials.
/// </summary>
[EditorOrder(60), DefaultValue(true), EditorDisplay("Geometry"), Tooltip("Enable/disable geometry merge for meshes with the same materials")]
[EditorDisplay("Geometry"), VisibleIf(nameof(ShowGeometry))]
[EditorOrder(60), DefaultValue(true)]
public bool MergeMeshes { get; set; } = true;
/// <summary>
/// Enable/disable importing meshes Level of Details.
/// </summary>
[EditorOrder(70), DefaultValue(true), EditorDisplay("Geometry", "Import LODs"), Tooltip("Enable/disable importing meshes Level of Details")]
[EditorDisplay("Geometry", "Import LODs"), VisibleIf(nameof(ShowGeometry))]
[EditorOrder(70), DefaultValue(true)]
public bool ImportLODs { get; set; } = true;
/// <summary>
/// Enable/disable importing vertex colors (channel 0 only).
/// </summary>
[EditorOrder(80), DefaultValue(true), EditorDisplay("Geometry"), Tooltip("Enable/disable importing vertex colors (channel 0 only)")]
[EditorDisplay("Geometry"), VisibleIf(nameof(ShowModel))]
[EditorOrder(80), DefaultValue(true)]
public bool ImportVertexColors { get; set; } = true;
/// <summary>
/// Enable/disable importing blend shapes (morph targets).
/// </summary>
[EditorOrder(85), DefaultValue(false), EditorDisplay("Geometry"), Tooltip("Enable/disable importing blend shapes (morph targets).")]
[EditorDisplay("Geometry"), VisibleIf(nameof(ShowSkinnedModel))]
[EditorOrder(85), DefaultValue(false)]
public bool ImportBlendShapes { get; set; } = false;
/// <summary>
/// The lightmap UVs source.
/// </summary>
[EditorOrder(90), DefaultValue(ModelLightmapUVsSource.Disable), EditorDisplay("Geometry", "Lightmap UVs Source"), Tooltip("Model lightmap UVs source")]
[EditorDisplay("Geometry", "Lightmap UVs Source"), VisibleIf(nameof(ShowModel))]
[EditorOrder(90), DefaultValue(ModelLightmapUVsSource.Disable)]
public ModelLightmapUVsSource LightmapUVsSource { get; set; } = ModelLightmapUVsSource.Disable;
/// <summary>
/// If specified, all meshes which name starts with this prefix will be imported as a separate collision data (excluded used for rendering).
/// </summary>
[EditorOrder(100), DefaultValue(""), EditorDisplay("Geometry")]
[EditorDisplay("Geometry"), VisibleIf(nameof(ShowGeometry))]
[EditorOrder(100), DefaultValue("")]
public string CollisionMeshesPrefix { get; set; }
/// <summary>
/// Custom uniform import scale.
/// </summary>
[EditorOrder(500), DefaultValue(1.0f), EditorDisplay("Transform"), Tooltip("Custom uniform import scale")]
[EditorOrder(500), DefaultValue(1.0f), EditorDisplay("Transform")]
public float Scale { get; set; } = 1.0f;
/// <summary>
/// Custom import geometry rotation.
/// </summary>
[DefaultValue(typeof(Quaternion), "0,0,0,1")]
[EditorOrder(510), EditorDisplay("Transform"), Tooltip("Custom import geometry rotation")]
[EditorOrder(510), EditorDisplay("Transform")]
public Quaternion Rotation { get; set; } = Quaternion.Identity;
/// <summary>
/// Custom import geometry offset.
/// </summary>
[DefaultValue(typeof(Vector3), "0,0,0")]
[EditorOrder(520), EditorDisplay("Transform"), Tooltip("Custom import geometry offset")]
[EditorOrder(520), EditorDisplay("Transform")]
public Vector3 Translation { get; set; } = Vector3.Zero;
/// <summary>
/// If checked, the imported geometry will be shifted to the center of mass.
/// </summary>
[EditorOrder(530), DefaultValue(false), EditorDisplay("Transform"), Tooltip("If checked, the imported geometry will be shifted to the center of mass.")]
[EditorOrder(530), DefaultValue(false), EditorDisplay("Transform")]
public bool CenterGeometry { get; set; } = false;
/// <summary>
/// The imported animation duration mode.
/// Imported animation duration mode. Can use the original value or overriden by settings.
/// </summary>
[EditorOrder(1000), DefaultValue(AnimationDuration.Imported), EditorDisplay("Animation"), Tooltip("Imported animation duration mode. Can use the original value or overriden by settings.")]
[EditorDisplay("Animation"), VisibleIf(nameof(ShowAnimation))]
[EditorOrder(1000), DefaultValue(AnimationDuration.Imported)]
public AnimationDuration Duration { get; set; } = AnimationDuration.Imported;
/// <summary>
/// The imported animation first frame index. Used only if Duration mode is set to Custom.
/// Imported animation first frame index. Used only if Duration mode is set to Custom.
/// </summary>
[EditorOrder(1010), DefaultValue(0.0f), Limit(0), EditorDisplay("Animation"), Tooltip("Imported animation first frame index. Used only if Duration mode is set to Custom.")]
[EditorDisplay("Animation"), VisibleIf(nameof(ShowFramesRange))]
[EditorOrder(1010), DefaultValue(0.0f), Limit(0)]
public float FramesRangeStart { get; set; } = 0;
/// <summary>
/// The imported animation end frame index. Used only if Duration mode is set to Custom.
/// Imported animation last frame index. Used only if Duration mode is set to Custom.
/// </summary>
[EditorOrder(1020), DefaultValue(0.0f), Limit(0), EditorDisplay("Animation"), Tooltip("Imported animation last frame index. Used only if Duration mode is set to Custom.")]
[EditorDisplay("Animation"), VisibleIf(nameof(ShowFramesRange))]
[EditorOrder(1020), DefaultValue(0.0f), Limit(0)]
public float FramesRangeEnd { get; set; } = 0;
private bool ShowFramesRange => ShowAnimation && Duration == AnimationDuration.Custom;
/// <summary>
/// The imported animation default frame rate. Can specify the default frames per second amount for imported animation. If value is 0 then the original animation frame rate will be used.
/// </summary>
[EditorOrder(1025), DefaultValue(0.0f), Limit(0, 1000, 0.01f), EditorDisplay("Animation"), Tooltip("The imported animation default frame rate. Can specify the default frames per second amount for imported animation. If value is 0 then the original animation frame rate will be used.")]
[EditorDisplay("Animation"), VisibleIf(nameof(ShowAnimation))]
[EditorOrder(1025), DefaultValue(0.0f), Limit(0, 1000, 0.01f)]
public float DefaultFrameRate { get; set; } = 0.0f;
/// <summary>
/// The imported animation sampling rate. If value is 0 then the original animation speed will be used.
/// </summary>
[EditorOrder(1030), DefaultValue(0.0f), Limit(0, 1000, 0.01f), EditorDisplay("Animation"), Tooltip("The imported animation sampling rate. If value is 0 then the original animation speed will be used.")]
[EditorDisplay("Animation"), VisibleIf(nameof(ShowAnimation))]
[EditorOrder(1030), DefaultValue(0.0f), Limit(0, 1000, 0.01f)]
public float SamplingRate { get; set; } = 0.0f;
/// <summary>
/// The imported animation will have removed tracks with no keyframes or unspecified data.
/// </summary>
[EditorOrder(1040), DefaultValue(true), EditorDisplay("Animation"), Tooltip("The imported animation will have removed tracks with no keyframes or unspecified data.")]
[EditorDisplay("Animation"), VisibleIf(nameof(ShowAnimation))]
[EditorOrder(1040), DefaultValue(true)]
public bool SkipEmptyCurves { get; set; } = true;
/// <summary>
/// The imported animation channels will be optimized to remove redundant keyframes.
/// </summary>
[EditorOrder(1050), DefaultValue(true), EditorDisplay("Animation"), Tooltip("The imported animation channels will be optimized to remove redundant keyframes.")]
[EditorDisplay("Animation"), VisibleIf(nameof(ShowAnimation))]
[EditorOrder(1050), DefaultValue(true)]
public bool OptimizeKeyframes { get; set; } = true;
/// <summary>
/// Enables root motion extraction support from this animation.
/// </summary>
[EditorOrder(1060), DefaultValue(false), EditorDisplay("Animation"), Tooltip("Enables root motion extraction support from this animation.")]
[EditorDisplay("Animation"), VisibleIf(nameof(ShowAnimation))]
[EditorOrder(1060), DefaultValue(false)]
public bool EnableRootMotion { get; set; } = false;
/// <summary>
/// The custom node name to be used as a root motion source. If not specified the actual root node will be used.
/// </summary>
[EditorOrder(1070), DefaultValue(typeof(string), ""), EditorDisplay("Animation"), Tooltip("The custom node name to be used as a root motion source. If not specified the actual root node will be used.")]
[EditorDisplay("Animation"), VisibleIf(nameof(ShowAnimation))]
[EditorOrder(1070), DefaultValue(typeof(string), "")]
public string RootNodeName { get; set; }
/// <summary>
/// If checked, the importer will generate a sequence of LODs based on the base LOD index.
/// </summary>
[EditorOrder(1100), DefaultValue(false), EditorDisplay("Level Of Detail", "Generate LODs"), Tooltip("If checked, the importer will generate a sequence of LODs based on the base LOD index.")]
[EditorDisplay("Level Of Detail", "Generate LODs"), VisibleIf(nameof(ShowGeometry))]
[EditorOrder(1100), DefaultValue(false)]
public bool GenerateLODs { get; set; } = false;
/// <summary>
/// The index of the LOD from the source model data to use as a reference for following LODs generation.
/// </summary>
[EditorOrder(1110), DefaultValue(0), Limit(0, Model.MaxLODs - 1), EditorDisplay("Level Of Detail", "Base LOD"), Tooltip("The index of the LOD from the source model data to use as a reference for following LODs generation.")]
[EditorDisplay("Level Of Detail", "Base LOD"), VisibleIf(nameof(ShowGeometry))]
[EditorOrder(1110), DefaultValue(0), Limit(0, Model.MaxLODs - 1)]
public int BaseLOD { get; set; } = 0;
/// <summary>
/// The amount of LODs to include in the model (all remaining ones starting from Base LOD will be generated).
/// </summary>
[EditorOrder(1120), DefaultValue(4), Limit(1, Model.MaxLODs), EditorDisplay("Level Of Detail", "LOD Count"), Tooltip("The amount of LODs to include in the model (all remaining ones starting from Base LOD will be generated).")]
[EditorDisplay("Level Of Detail", "LOD Count"), VisibleIf(nameof(ShowGeometry))]
[EditorOrder(1120), DefaultValue(4), Limit(1, Model.MaxLODs)]
public int LODCount { get; set; } = 4;
/// <summary>
/// The target amount of triangles for the generated LOD (based on the higher LOD). Normalized to range 0-1. For instance 0.4 cuts the triangle count to 40%.
/// </summary>
[EditorOrder(1130), DefaultValue(0.5f), Limit(0, 1, 0.001f), EditorDisplay("Level Of Detail"), Tooltip("The target amount of triangles for the generated LOD (based on the higher LOD). Normalized to range 0-1. For instance 0.4 cuts the triangle count to 40%.")]
[EditorDisplay("Level Of Detail"), VisibleIf(nameof(ShowGeometry))]
[EditorOrder(1130), DefaultValue(0.5f), Limit(0, 1, 0.001f)]
public float TriangleReduction { get; set; } = 0.5f;
/// <summary>
/// If checked, the importer will create materials for model meshes as specified in the file.
/// </summary>
[EditorOrder(400), DefaultValue(true), EditorDisplay("Materials"), Tooltip("If checked, the importer will create materials for model meshes as specified in the file.")]
[EditorDisplay("Materials"), VisibleIf(nameof(ShowGeometry))]
[EditorOrder(400), DefaultValue(true)]
public bool ImportMaterials { get; set; } = true;
/// <summary>
/// If checked, the importer will import texture files used by the model and any embedded texture resources.
/// </summary>
[EditorOrder(410), DefaultValue(true), EditorDisplay("Materials"), Tooltip("If checked, the importer will import texture files used by the model and any embedded texture resources.")]
[EditorDisplay("Materials"), VisibleIf(nameof(ShowGeometry))]
[EditorOrder(410), DefaultValue(true)]
public bool ImportTextures { get; set; } = true;
/// <summary>
/// If checked, the importer will try to restore the model material slots.
/// </summary>
[EditorOrder(420), DefaultValue(true), EditorDisplay("Materials", "Restore Materials On Reimport"), Tooltip("If checked, the importer will try to restore the assigned materials to the model slots.")]
[EditorDisplay("Materials", "Restore Materials On Reimport"), VisibleIf(nameof(ShowGeometry))]
[EditorOrder(420), DefaultValue(true)]
public bool RestoreMaterialsOnReimport { get; set; } = true;
/// <summary>
/// If checked, enables generation of Signed Distance Field (SDF).
/// </summary>
[EditorDisplay("SDF"), VisibleIf(nameof(ShowModel))]
[EditorOrder(1500), DefaultValue(false)]
public bool GenerateSDF { get; set; } = false;
/// <summary>
/// Resolution scale for generated Signed Distance Field (SDF) texture. Higher values improve accuracy but increase memory usage and reduce performance.
/// </summary>
[EditorDisplay("SDF"), VisibleIf(nameof(ShowModel))]
[EditorOrder(1510), DefaultValue(1.0f), Limit(0.0001f, 100.0f)]
public float SDFResolution { get; set; } = 1.0f;
/// <summary>
/// If checked, the imported mesh/animations are splitted into separate assets. Used if ObjectIndex is set to -1.
/// </summary>
[EditorOrder(2000), DefaultValue(false), EditorDisplay("Splitting"), Tooltip("If checked, the imported mesh/animations are splitted into separate assets. Used if ObjectIndex is set to -1.")]
[EditorOrder(2000), DefaultValue(false), EditorDisplay("Splitting")]
public bool SplitObjects { get; set; } = false;
/// <summary>
/// The zero-based index for the mesh/animation clip to import. If the source file has more than one mesh/animation it can be used to pick a desire object. Default -1 imports all objects.
/// </summary>
[EditorOrder(2010), DefaultValue(-1), EditorDisplay("Splitting"), Tooltip("The zero-based index for the mesh/animation clip to import. If the source file has more than one mesh/animation it can be used to pick a desire object. Default -1 imports all objects.")]
[EditorOrder(2010), DefaultValue(-1), EditorDisplay("Splitting")]
public int ObjectIndex { get; set; } = -1;
private bool ShowGeometry => Type == ModelType.Model || Type == ModelType.SkinnedModel;
private bool ShowModel => Type == ModelType.Model;
private bool ShowSkinnedModel => Type == ModelType.SkinnedModel;
private bool ShowAnimation => Type == ModelType.Animation;
[StructLayout(LayoutKind.Sequential)]
internal struct InternalOptions
{
@@ -350,6 +401,10 @@ namespace FlaxEditor.Content.Import
public byte ImportTextures;
public byte RestoreMaterialsOnReimport;
// SDF
public byte GenerateSDF;
public float SDFResolution;
// Splitting
public byte SplitObjects;
public int ObjectIndex;
@@ -392,6 +447,8 @@ namespace FlaxEditor.Content.Import
ImportMaterials = (byte)(ImportMaterials ? 1 : 0),
ImportTextures = (byte)(ImportTextures ? 1 : 0),
RestoreMaterialsOnReimport = (byte)(RestoreMaterialsOnReimport ? 1 : 0),
GenerateSDF = (byte)(GenerateSDF ? 1 : 0),
SDFResolution = SDFResolution,
SplitObjects = (byte)(SplitObjects ? 1 : 0),
ObjectIndex = ObjectIndex,
};
@@ -431,25 +488,22 @@ namespace FlaxEditor.Content.Import
ImportMaterials = options.ImportMaterials != 0;
ImportTextures = options.ImportTextures != 0;
RestoreMaterialsOnReimport = options.RestoreMaterialsOnReimport != 0;
GenerateSDF = options.GenerateSDF != 0;
SDFResolution = options.SDFResolution;
SplitObjects = options.SplitObjects != 0;
ObjectIndex = options.ObjectIndex;
}
/// <summary>
/// Tries the restore the asset import options from the target resource file.
/// Tries the restore the asset import options from the target resource file. Applies the project default options too.
/// </summary>
/// <param name="options">The options.</param>
/// <param name="assetPath">The asset path.</param>
/// <returns>True settings has been restored, otherwise false.</returns>
public static bool TryRestore(ref ModelImportSettings options, string assetPath)
public static void TryRestore(ref ModelImportSettings options, string assetPath)
{
if (ModelImportEntry.Internal_GetModelImportOptions(assetPath, out var internalOptions))
{
// Restore settings
options.FromInternal(ref internalOptions);
return true;
}
return false;
ModelImportEntry.Internal_GetModelImportOptions(assetPath, out var internalOptions);
options.FromInternal(ref internalOptions);
}
}
@@ -495,7 +549,7 @@ namespace FlaxEditor.Content.Import
#region Internal Calls
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern bool Internal_GetModelImportOptions(string path, out ModelImportSettings.InternalOptions result);
internal static extern void Internal_GetModelImportOptions(string path, out ModelImportSettings.InternalOptions result);
#endregion
}

View File

@@ -93,5 +93,29 @@ namespace FlaxEditor.Content
public virtual void OnThumbnailDrawCleanup(ThumbnailRequest request)
{
}
/// <summary>
/// Initializes rendering settings for asset preview drawing for a thumbnail.
/// </summary>
/// <param name="preview">The asset preview.</param>
protected void InitAssetPreview(Viewport.Previews.AssetPreview preview)
{
preview.RenderOnlyWithWindow = false;
preview.UseAutomaticTaskManagement = false;
preview.AnchorPreset = AnchorPresets.StretchAll;
preview.Offsets = Margin.Zero;
var task = preview.Task;
task.Enabled = false;
var view = task.View;
view.IsSingleFrame = true; // Disable LOD transitions
task.View = view;
var eyeAdaptation = preview.PostFxVolume.EyeAdaptation;
eyeAdaptation.Mode = EyeAdaptationMode.None;
eyeAdaptation.OverrideFlags |= EyeAdaptationSettingsOverride.Mode;
preview.PostFxVolume.EyeAdaptation = eyeAdaptation;
}
}
}

View File

@@ -44,19 +44,8 @@ namespace FlaxEditor.Content
{
if (_preview == null)
{
_preview = new CubeTexturePreview(false)
{
RenderOnlyWithWindow = false,
UseAutomaticTaskManagement = false,
AnchorPreset = AnchorPresets.StretchAll,
Offsets = Margin.Zero,
};
_preview.Task.Enabled = false;
var eyeAdaptation = _preview.PostFxVolume.EyeAdaptation;
eyeAdaptation.Mode = EyeAdaptationMode.None;
eyeAdaptation.OverrideFlags |= EyeAdaptationSettingsOverride.Mode;
_preview.PostFxVolume.EyeAdaptation = eyeAdaptation;
_preview = new CubeTexturePreview(false);
InitAssetPreview(_preview);
}
// TODO: disable streaming for asset during thumbnail rendering (and restore it after)

View File

@@ -163,7 +163,7 @@ namespace FlaxEditor.Content
public sealed class SpawnableJsonAssetProxy<T> : JsonAssetProxy where T : new()
{
/// <inheritdoc />
public override string Name { get; } = CustomEditors.CustomEditorsUtil.GetPropertyNameUI(typeof(T).Name);
public override string Name { get; } = Utilities.Utils.GetPropertyNameUI(typeof(T).Name);
/// <inheritdoc />
public override bool CanCreate(ContentFolder targetLocation)

View File

@@ -51,19 +51,8 @@ namespace FlaxEditor.Content
{
if (_preview == null)
{
_preview = new MaterialPreview(false)
{
RenderOnlyWithWindow = false,
UseAutomaticTaskManagement = false,
AnchorPreset = AnchorPresets.StretchAll,
Offsets = Margin.Zero,
};
_preview.Task.Enabled = false;
var eyeAdaptation = _preview.PostFxVolume.EyeAdaptation;
eyeAdaptation.Mode = EyeAdaptationMode.None;
eyeAdaptation.OverrideFlags |= EyeAdaptationSettingsOverride.Mode;
_preview.PostFxVolume.EyeAdaptation = eyeAdaptation;
_preview = new MaterialPreview(false);
InitAssetPreview(_preview);
}
// TODO: disable streaming for dependant assets during thumbnail rendering (and restore it after)

View File

@@ -97,19 +97,8 @@ namespace FlaxEditor.Content
{
if (_preview == null)
{
_preview = new MaterialPreview(false)
{
RenderOnlyWithWindow = false,
UseAutomaticTaskManagement = false,
AnchorPreset = AnchorPresets.StretchAll,
Offsets = Margin.Zero,
};
_preview.Task.Enabled = false;
var eyeAdaptation = _preview.PostFxVolume.EyeAdaptation;
eyeAdaptation.Mode = EyeAdaptationMode.None;
eyeAdaptation.OverrideFlags |= EyeAdaptationSettingsOverride.Mode;
_preview.PostFxVolume.EyeAdaptation = eyeAdaptation;
_preview = new MaterialPreview(false);
InitAssetPreview(_preview);
}
// TODO: disable streaming for dependant assets during thumbnail rendering (and restore it after)

View File

@@ -58,19 +58,8 @@ namespace FlaxEditor.Content
{
if (_preview == null)
{
_preview = new ModelPreview(false)
{
RenderOnlyWithWindow = false,
UseAutomaticTaskManagement = false,
AnchorPreset = AnchorPresets.StretchAll,
Offsets = Margin.Zero,
};
_preview.Task.Enabled = false;
var eyeAdaptation = _preview.PostFxVolume.EyeAdaptation;
eyeAdaptation.Mode = EyeAdaptationMode.None;
eyeAdaptation.OverrideFlags |= EyeAdaptationSettingsOverride.Mode;
_preview.PostFxVolume.EyeAdaptation = eyeAdaptation;
_preview = new ModelPreview(false);
InitAssetPreview(_preview);
}
// TODO: disable streaming for asset during thumbnail rendering (and restore it after)

View File

@@ -52,19 +52,8 @@ namespace FlaxEditor.Content
{
if (_preview == null)
{
_preview = new ParticleEmitterPreview(false)
{
RenderOnlyWithWindow = false,
UseAutomaticTaskManagement = false,
AnchorPreset = AnchorPresets.StretchAll,
Offsets = Margin.Zero,
};
_preview.Task.Enabled = false;
var eyeAdaptation = _preview.PostFxVolume.EyeAdaptation;
eyeAdaptation.Mode = EyeAdaptationMode.None;
eyeAdaptation.OverrideFlags |= EyeAdaptationSettingsOverride.Mode;
_preview.PostFxVolume.EyeAdaptation = eyeAdaptation;
_preview = new ParticleEmitterPreview(false);
InitAssetPreview(_preview);
}
// Mark for initial warmup

View File

@@ -83,19 +83,8 @@ namespace FlaxEditor.Content
{
if (_preview == null)
{
_preview = new ParticleEmitterPreview(false)
{
RenderOnlyWithWindow = false,
UseAutomaticTaskManagement = false,
AnchorPreset = AnchorPresets.StretchAll,
Offsets = Margin.Zero,
};
_preview.Task.Enabled = false;
var eyeAdaptation = _preview.PostFxVolume.EyeAdaptation;
eyeAdaptation.Mode = EyeAdaptationMode.None;
eyeAdaptation.OverrideFlags |= EyeAdaptationSettingsOverride.Mode;
_preview.PostFxVolume.EyeAdaptation = eyeAdaptation;
_preview = new ParticleEmitterPreview(false);
InitAssetPreview(_preview);
}
// Mark for initial warmup

View File

@@ -94,19 +94,8 @@ namespace FlaxEditor.Content
{
if (_preview == null)
{
_preview = new PrefabPreview(false)
{
RenderOnlyWithWindow = false,
UseAutomaticTaskManagement = false,
AnchorPreset = AnchorPresets.StretchAll,
Offsets = Margin.Zero,
};
_preview.Task.Enabled = false;
var eyeAdaptation = _preview.PostFxVolume.EyeAdaptation;
eyeAdaptation.Mode = EyeAdaptationMode.None;
eyeAdaptation.OverrideFlags |= EyeAdaptationSettingsOverride.Mode;
_preview.PostFxVolume.EyeAdaptation = eyeAdaptation;
_preview = new PrefabPreview(false);
InitAssetPreview(_preview);
}
// TODO: disable streaming for asset during thumbnail rendering (and restore it after)

View File

@@ -35,7 +35,7 @@ namespace FlaxEditor.Content
/// <inheritdoc />
public override string Name => "Settings";
//public override string Name { get; } = CustomEditors.CustomEditorsUtil.GetPropertyNameUI(_type.Name);
//public override string Name { get; } = Utilities.Utils.GetPropertyNameUI(_type.Name);
/// <inheritdoc />
public override bool CanCreate(ContentFolder targetLocation)

View File

@@ -44,19 +44,8 @@ namespace FlaxEditor.Content
{
if (_preview == null)
{
_preview = new AnimatedModelPreview(false)
{
RenderOnlyWithWindow = false,
UseAutomaticTaskManagement = false,
AnchorPreset = AnchorPresets.StretchAll,
Offsets = Margin.Zero,
};
_preview.Task.Enabled = false;
var eyeAdaptation = _preview.PostFxVolume.EyeAdaptation;
eyeAdaptation.Mode = EyeAdaptationMode.None;
eyeAdaptation.OverrideFlags |= EyeAdaptationSettingsOverride.Mode;
_preview.PostFxVolume.EyeAdaptation = eyeAdaptation;
_preview = new AnimatedModelPreview(false);
InitAssetPreview(_preview);
}
// TODO: disable streaming for asset during thumbnail rendering (and restore it after)

View File

@@ -68,6 +68,8 @@ bool DeployDataStep::Perform(CookingData& data)
data.AddRootEngineAsset(TEXT("Shaders/MotionBlur"));
data.AddRootEngineAsset(TEXT("Shaders/BitonicSort"));
data.AddRootEngineAsset(TEXT("Shaders/GPUParticlesSorting"));
data.AddRootEngineAsset(TEXT("Shaders/GlobalSignDistanceField"));
data.AddRootEngineAsset(TEXT("Shaders/GI/GlobalSurfaceAtlas"));
data.AddRootEngineAsset(TEXT("Shaders/Quad"));
data.AddRootEngineAsset(TEXT("Shaders/Reflections"));
data.AddRootEngineAsset(TEXT("Shaders/Shadows"));

View File

@@ -4,7 +4,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using FlaxEditor.CustomEditors.Editors;
using FlaxEditor.Scripting;
using FlaxEngine;
@@ -13,8 +12,6 @@ namespace FlaxEditor.CustomEditors
{
internal static class CustomEditorsUtil
{
private static readonly StringBuilder CachedSb = new StringBuilder(256);
internal static readonly Dictionary<Type, string> InBuildTypeNames = new Dictionary<Type, string>()
{
{ typeof(bool), "bool" },
@@ -46,51 +43,6 @@ namespace FlaxEditor.CustomEditors
return result;
}
/// <summary>
/// Gets the property name for UI. Removes unnecessary characters and filters text. Makes it more user-friendly.
/// </summary>
/// <param name="name">The name.</param>
/// <returns>The result.</returns>
public static string GetPropertyNameUI(string name)
{
int length = name.Length;
StringBuilder sb = CachedSb;
sb.Clear();
sb.EnsureCapacity(length + 8);
int startIndex = 0;
// Skip some prefixes
if (name.StartsWith("g_") || name.StartsWith("m_"))
startIndex = 2;
// Filter text
for (int i = startIndex; i < length; i++)
{
var c = name[i];
// Space before word starting with uppercase letter
if (char.IsUpper(c) && i > 0)
{
if (i + 1 < length && !char.IsUpper(name[i + 1]))
sb.Append(' ');
}
// Space instead of underscore
else if (c == '_')
{
if (sb.Length > 0)
sb.Append(' ');
continue;
}
// Space before digits sequence
else if (i > 1 && char.IsDigit(c) && !char.IsDigit(name[i - 1]))
sb.Append(' ');
sb.Append(c);
}
return sb.ToString();
}
internal static CustomEditor CreateEditor(ValueContainer values, CustomEditor overrideEditor, bool canUseRefPicker = true)
{
// Check if use provided editor

View File

@@ -209,13 +209,13 @@ namespace FlaxEditor.CustomEditors.Dedicated
if (editor is RemovedScriptDummy removed)
{
node.TextColor = Color.OrangeRed;
node.Text = CustomEditorsUtil.GetPropertyNameUI(removed.PrefabObject.GetType().Name);
node.Text = Utilities.Utils.GetPropertyNameUI(removed.PrefabObject.GetType().Name);
}
// Actor or Script
else if (editor.Values[0] is SceneObject sceneObject)
{
node.TextColor = sceneObject.HasPrefabLink ? FlaxEngine.GUI.Style.Current.ProgressNormal : FlaxEngine.GUI.Style.Current.BackgroundSelected;
node.Text = CustomEditorsUtil.GetPropertyNameUI(sceneObject.GetType().Name);
node.Text = Utilities.Utils.GetPropertyNameUI(sceneObject.GetType().Name);
}
// Array Item
else if (editor.ParentEditor?.Values?.Type.IsArray ?? false)
@@ -225,7 +225,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
// Common type
else if (editor.Values.Info != ScriptMemberInfo.Null)
{
node.Text = CustomEditorsUtil.GetPropertyNameUI(editor.Values.Info.Name);
node.Text = Utilities.Utils.GetPropertyNameUI(editor.Values.Info.Name);
}
// Custom type
else if (editor.Values[0] != null)

View File

@@ -26,6 +26,26 @@ namespace FlaxEditor.CustomEditors.Dedicated
}
}
private object ParameterGet(object instance, GraphParameter parameter, object tag)
{
if (instance is ParticleEffect particleEffect && particleEffect && parameter && tag is ParticleEffectParameter effectParameter)
return particleEffect.GetParameterValue(effectParameter.TrackName, parameter.Name);
return null;
}
private void ParameterSet(object instance, object value, GraphParameter parameter, object tag)
{
if (instance is ParticleEffect particleEffect && particleEffect && parameter && tag is ParticleEffectParameter effectParameter)
particleEffect.SetParameterValue(effectParameter.TrackName, parameter.Name, value);
}
private object ParameterDefaultValue(object instance, GraphParameter parameter, object tag)
{
if (tag is ParticleEffectParameter effectParameter)
return effectParameter.DefaultValue;
return null;
}
/// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout)
{
@@ -48,11 +68,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
group.Panel.Open(false);
var data = SurfaceUtils.InitGraphParameters(parametersGroup);
SurfaceUtils.DisplayGraphParameters(group, data,
(instance, parameter, tag) => ((ParticleEffect)instance).GetParameterValue(trackName, parameter.Name),
(instance, value, parameter, tag) => ((ParticleEffect)instance).SetParameterValue(trackName, parameter.Name, value),
Values,
(instance, parameter, tag) => ((ParticleEffectParameter)tag).DefaultValue);
SurfaceUtils.DisplayGraphParameters(group, data, ParameterGet, ParameterSet, Values, ParameterDefaultValue);
}
}

View File

@@ -273,7 +273,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
cm.AddItem(item);
}
cm.ItemClicked += item => action((string)item.Tag);
cm.SortChildren();
cm.SortItems();
cm.Show(button.Parent, button.BottomLeft);
}

View File

@@ -71,7 +71,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
cm.AddItem(new TypeSearchPopup.TypeItemView(scripts[i]));
}
cm.ItemClicked += item => AddScript((ScriptType)item.Tag);
cm.SortChildren();
cm.SortItems();
cm.Show(this, button.BottomLeft);
}
@@ -611,7 +611,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
var editor = CustomEditorsUtil.CreateEditor(scriptType, false);
// Create group
var title = CustomEditorsUtil.GetPropertyNameUI(scriptType.Name);
var title = Utilities.Utils.GetPropertyNameUI(scriptType.Name);
var group = layout.Group(title, editor);
if ((Presenter.Features & FeatureFlags.CacheExpandedGroups) != 0)
{

View File

@@ -32,7 +32,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
if (_presets != value)
{
_presets = value;
TooltipText = CustomEditorsUtil.GetPropertyNameUI(_presets.ToString());
TooltipText = Utilities.Utils.GetPropertyNameUI(_presets.ToString());
}
}
}
@@ -642,7 +642,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
cm.AddItem(new TypeSearchPopup.TypeItemView(controlTypes[i]));
}
cm.ItemClicked += controlType => SetType((ScriptType)controlType.Tag);
cm.SortChildren();
cm.SortItems();
cm.Show(button.Parent, button.BottomLeft);
}

View File

@@ -1,5 +1,6 @@
// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
using System;
using System.Linq;
using FlaxEditor.CustomEditors.Elements;
using FlaxEngine;
@@ -66,7 +67,13 @@ namespace FlaxEditor.CustomEditors.Editors
}
else
{
_element.Value = (double)Values[0];
var value = Values[0];
if (value is double asDouble)
_element.Value = (float)asDouble;
else if (value is float asFloat)
_element.Value = asFloat;
else
throw new Exception(string.Format("Invalid value type {0}.", value?.GetType().ToString() ?? "<null>"));
}
}
}

View File

@@ -137,7 +137,7 @@ namespace FlaxEditor.CustomEditors.Editors
ExpandGroups = attributes.FirstOrDefault(x => x is ExpandGroupsAttribute) != null;
IsReadOnly |= !info.HasSet;
DisplayName = Display?.Name ?? CustomEditorsUtil.GetPropertyNameUI(info.Name);
DisplayName = Display?.Name ?? Utilities.Utils.GetPropertyNameUI(info.Name);
var editor = Editor.Instance;
TooltipText = editor.CodeDocs.GetTooltip(info, attributes);
_membersOrder = editor.Options.Options.General.ScriptMembersOrder;
@@ -229,6 +229,7 @@ namespace FlaxEditor.CustomEditors.Editors
}
}
private static HashSet<PropertiesList> _visibleIfPropertiesListsCache;
private VisibleIfCache[] _visibleIfCaches;
private bool _isNull;
@@ -761,8 +762,13 @@ namespace FlaxEditor.CustomEditors.Editors
if (_visibleIfCaches != null)
{
if (_visibleIfPropertiesListsCache == null)
_visibleIfPropertiesListsCache = new HashSet<PropertiesList>();
else
_visibleIfPropertiesListsCache.Clear();
try
{
// Update VisibleIf rules
for (int i = 0; i < _visibleIfCaches.Length; i++)
{
ref var c = ref _visibleIfCaches[i];
@@ -798,6 +804,21 @@ namespace FlaxEditor.CustomEditors.Editors
{
c.Group.Panel.Visible = visible;
}
if (c.PropertiesList != null)
_visibleIfPropertiesListsCache.Add(c.PropertiesList.Properties);
}
// Hide properties lists with all labels being hidden
foreach (var propertiesList in _visibleIfPropertiesListsCache)
{
propertiesList.Visible = propertiesList.Children.Any(c => c.Visible);
}
// Hide group panels with all properties lists hidden
foreach (var propertiesList in _visibleIfPropertiesListsCache)
{
if (propertiesList.Parent is DropPanel dropPanel)
dropPanel.Visible = propertiesList.Visible || !dropPanel.Children.All(c => c is PropertiesList && !c.Visible);
}
}
catch (Exception ex)

View File

@@ -16,26 +16,6 @@ namespace FlaxEditor.CustomEditors.Elements
/// </summary>
public readonly Button Button = new Button();
/// <summary>
/// Initializes the element.
/// </summary>
/// <param name="text">The text.</param>
public void Init(string text)
{
Button.Text = text;
}
/// <summary>
/// Initializes the element.
/// </summary>
/// <param name="text">The text.</param>
/// <param name="color">The color.</param>
public void Init(string text, Color color)
{
Button.Text = text;
Button.SetColors(color);
}
/// <inheritdoc />
public override Control Control => Button;
}

View File

@@ -1,5 +1,8 @@
// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
using System;
using FlaxEditor.GUI;
using FlaxEditor.GUI.ContextMenu;
using FlaxEngine;
using FlaxEngine.GUI;
@@ -11,23 +14,49 @@ namespace FlaxEditor.CustomEditors.Elements
/// <seealso cref="FlaxEditor.CustomEditors.LayoutElement" />
public class LabelElement : LayoutElement
{
private Action<ContextMenu> _customContextualOptions;
/// <summary>
/// The label.
/// </summary>
public readonly Label Label;
public readonly ClickableLabel Label;
/// <summary>
/// Initializes a new instance of the <see cref="CheckBoxElement"/> class.
/// </summary>
public LabelElement()
{
Label = new Label(0, 0, 100, 18)
Label = new ClickableLabel
{
HorizontalAlignment = TextAlignment.Near
Size = new Vector2(100, 18),
HorizontalAlignment = TextAlignment.Near,
};
// TODO: auto height for label
}
/// <summary>
/// Adds a simple context menu with utility to copy label text. Can be extended with more options.
/// </summary>
public LabelElement AddCopyContextMenu(Action<ContextMenu> customOptions = null)
{
Label.RightClick += OnRightClick;
_customContextualOptions = customOptions;
return this;
}
private void OnRightClick()
{
var menu = new ContextMenu();
menu.AddButton("Copy text").Clicked += OnCopyText;
_customContextualOptions?.Invoke(menu);
menu.Show(Label, Label.PointFromScreen(Input.MouseScreenPosition));
}
private void OnCopyText()
{
Clipboard.Text = Label.Text;
}
/// <inheritdoc />
public override Control Control => Label;
}

View File

@@ -133,11 +133,13 @@ namespace FlaxEditor.CustomEditors
/// Adds new button element.
/// </summary>
/// <param name="text">The text.</param>
/// <param name="tooltip">The tooltip text.</param>
/// <returns>The created element.</returns>
public ButtonElement Button(string text)
public ButtonElement Button(string text, string tooltip = null)
{
var element = new ButtonElement();
element.Init(text);
element.Button.Text = text;
element.Button.TooltipText = tooltip;
OnAddElement(element);
return element;
}
@@ -147,11 +149,14 @@ namespace FlaxEditor.CustomEditors
/// </summary>
/// <param name="text">The text.</param>
/// <param name="color">The color.</param>
/// <param name="tooltip">The tooltip text.</param>
/// <returns>The created element.</returns>
public ButtonElement Button(string text, Color color)
public ButtonElement Button(string text, Color color, string tooltip = null)
{
ButtonElement element = new ButtonElement();
element.Init(text, color);
element.Button.Text = text;
element.Button.TooltipText = tooltip;
element.Button.SetColors(color);
OnAddElement(element);
return element;
}

View File

@@ -261,7 +261,7 @@ namespace FlaxEditor.GUI
switch (formatMode)
{
case EnumDisplayAttribute.FormatMode.Default:
name = CustomEditorsUtil.GetPropertyNameUI(field.Name);
name = Utilities.Utils.GetPropertyNameUI(field.Name);
break;
case EnumDisplayAttribute.FormatMode.None:
name = field.Name;

View File

@@ -263,6 +263,19 @@ namespace FlaxEditor.GUI
PerformLayout(true);
_searchBox.Focus();
}
/// <summary>
/// Sorts the items list (by item name by default).
/// </summary>
public void SortItems()
{
ItemsPanel.SortChildren();
if (_categoryPanels != null)
{
for (int i = 0; i < _categoryPanels.Count; i++)
_categoryPanels[i].SortChildren();
}
}
/// <summary>
/// Adds the item to the view and registers for the click event.

View File

@@ -67,7 +67,7 @@ namespace FlaxEditor.GUI
{
Find(Level.GetScene(i));
}
SortChildren();
SortItems();
}
private void OnItemClicked(Item item)

View File

@@ -122,7 +122,7 @@ namespace FlaxEditor.GUI
if (project.Content != null)
FindAssets(project.Content.Folder);
}
SortChildren();
SortItems();
}
private void OnItemClicked(Item item)

View File

@@ -78,7 +78,7 @@ namespace FlaxEditor.GUI
{
Find(Level.GetScene(i));
}
SortChildren();
SortItems();
}
private void OnItemClicked(Item item)

View File

@@ -95,7 +95,7 @@ namespace FlaxEditor.GUI
}
}
}
SortChildren();
SortItems();
}
private void OnItemClicked(Item item)

View File

@@ -177,7 +177,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
if (SubTracks.Any(x => x is IObjectTrack y && y.Object == script))
continue;
var name = CustomEditorsUtil.GetPropertyNameUI(script.GetType().Name);
var name = Utilities.Utils.GetPropertyNameUI(script.GetType().Name);
menu.AddButton(name, OnAddScriptTrack).Tag = script;
}
}

View File

@@ -29,6 +29,7 @@
#include "Engine/Level/Actor.h"
#include "Engine/Level/Prefabs/Prefab.h"
#include "Engine/Core/Config/GameSettings.h"
#include "Engine/Core/Config/GraphicsSettings.h"
#include "Engine/Core/Cache.h"
#include "Engine/CSG/CSGBuilder.h"
#include "Engine/Debug/DebugLog.h"
@@ -196,6 +197,10 @@ struct InternalModelOptions
byte ImportTextures;
byte RestoreMaterialsOnReimport;
// SDF
byte GenerateSDF;
float SDFResolution;
// Splitting
byte SplitObjects;
int32 ObjectIndex;
@@ -235,6 +240,8 @@ struct InternalModelOptions
to->ImportMaterials = from->ImportMaterials;
to->ImportTextures = from->ImportTextures;
to->RestoreMaterialsOnReimport = from->RestoreMaterialsOnReimport;
to->GenerateSDF = from->GenerateSDF;
to->SDFResolution = from->SDFResolution;
to->SplitObjects = from->SplitObjects;
to->ObjectIndex = from->ObjectIndex;
}
@@ -274,6 +281,8 @@ struct InternalModelOptions
to->ImportMaterials = from->ImportMaterials;
to->ImportTextures = from->ImportTextures;
to->RestoreMaterialsOnReimport = from->RestoreMaterialsOnReimport;
to->GenerateSDF = from->GenerateSDF;
to->SDFResolution = from->SDFResolution;
to->SplitObjects = from->SplitObjects;
to->ObjectIndex = from->ObjectIndex;
}
@@ -626,22 +635,23 @@ public:
return false;
}
static bool GetModelImportOptions(MonoString* pathObj, InternalModelOptions* result)
static void GetModelImportOptions(MonoString* pathObj, InternalModelOptions* result)
{
// Initialize defaults
ImportModelFile::Options options;
if (const auto* graphicsSettings = GraphicsSettings::Get())
{
options.GenerateSDF = graphicsSettings->GenerateSDFOnModelImport;
}
// Get options from model
String path;
MUtils::ToString(pathObj, path);
FileSystem::NormalizePath(path);
ImportModelFile::TryGetImportOptions(path, options);
ImportModelFile::Options options;
if (ImportModelFile::TryGetImportOptions(path, options))
{
// Convert into managed storage
InternalModelOptions::Convert(&options, result);
return true;
}
return false;
// Convert into managed storage
InternalModelOptions::Convert(&options, result);
}
static bool GetAudioImportOptions(MonoString* pathObj, InternalAudioOptions* result)
@@ -852,7 +862,7 @@ public:
continue;
switch (e.Type)
{
// Keyboard events
// Keyboard events
case InputDevice::EventType::Char:
window->OnCharInput(e.CharData.Char);
break;
@@ -862,7 +872,7 @@ public:
case InputDevice::EventType::KeyUp:
window->OnKeyUp(e.KeyData.Key);
break;
// Mouse events
// Mouse events
case InputDevice::EventType::MouseDown:
window->OnMouseDown(window->ScreenToClient(e.MouseData.Position), e.MouseData.Button);
break;

View File

@@ -27,6 +27,17 @@ namespace FlaxEditor.Modules.SourceCodeEditing
{
}
/// <summary>
/// Gets the tooltip text for the type.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="attributes">The type attributes. Optional, if null type attributes will be used.</param>
/// <returns>The documentation tooltip.</returns>
public string GetTooltip(Type type, object[] attributes = null)
{
return GetTooltip(new ScriptType(type), attributes);
}
/// <summary>
/// Gets the tooltip text for the type.
/// </summary>
@@ -62,6 +73,19 @@ namespace FlaxEditor.Modules.SourceCodeEditing
return text;
}
/// <summary>
/// Gets the tooltip text for the type member.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="memberName">The member name.</param>
/// <param name="attributes">The member attributes. Optional, if null member attributes will be used.</param>
/// <returns>The documentation tooltip.</returns>
public string GetTooltip(Type type, string memberName, object[] attributes = null)
{
var member = new ScriptType(type).GetMember(memberName, MemberTypes.All, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance);
return GetTooltip(member, attributes);
}
/// <summary>
/// Gets the tooltip text for the type member.
/// </summary>

View File

@@ -58,6 +58,7 @@ namespace FlaxEditor.Modules
private ContextMenuButton _menuToolsBakeAllEnvProbes;
private ContextMenuButton _menuToolsBuildCSGMesh;
private ContextMenuButton _menuToolsBuildNavMesh;
private ContextMenuButton _menuToolsBuildAllMesgesSDF;
private ContextMenuButton _menuToolsCancelBuilding;
private ContextMenuButton _menuToolsSetTheCurrentSceneViewAsDefault;
private ContextMenuChildMenu _menuWindowApplyWindowLayout;
@@ -484,6 +485,7 @@ namespace FlaxEditor.Modules
_menuToolsBakeAllEnvProbes = cm.AddButton("Bake all env probes", BakeAllEnvProbes);
_menuToolsBuildCSGMesh = cm.AddButton("Build CSG mesh", BuildCSG);
_menuToolsBuildNavMesh = cm.AddButton("Build Nav Mesh", BuildNavMesh);
_menuToolsBuildAllMesgesSDF = cm.AddButton("Build all meshes SDF", BuildAllMeshesSDF);
cm.AddSeparator();
cm.AddButton("Game Cooker", Editor.Windows.GameCookerWin.FocusOrShow);
_menuToolsCancelBuilding = cm.AddButton("Cancel building game", () => GameCooker.Cancel());
@@ -709,6 +711,7 @@ namespace FlaxEditor.Modules
_menuToolsBakeLightmaps.Text = isBakingLightmaps ? "Cancel baking lightmaps" : "Bake lightmaps";
_menuToolsClearLightmaps.Enabled = canEdit;
_menuToolsBakeAllEnvProbes.Enabled = canEdit;
_menuToolsBuildAllMesgesSDF.Enabled = canEdit && !isBakingLightmaps;
_menuToolsBuildCSGMesh.Enabled = canEdit;
_menuToolsBuildNavMesh.Enabled = canEdit;
_menuToolsCancelBuilding.Enabled = GameCooker.IsRunning;
@@ -836,6 +839,24 @@ namespace FlaxEditor.Modules
Editor.Scene.MarkSceneEdited(scenes);
}
private void BuildAllMeshesSDF()
{
// TODO: async maybe with progress reporting?
Editor.Scene.ExecuteOnGraph(node =>
{
if (node is StaticModelNode staticModelNode && staticModelNode.Actor is StaticModel staticModel)
{
if (staticModel.DrawModes.HasFlag(DrawPass.GlobalSDF) && staticModel.Model != null && !staticModel.Model.IsVirtual && staticModel.Model.SDF.Texture == null)
{
Editor.Log("Generating SDF for " + staticModel.Model);
if (!staticModel.Model.GenerateSDF())
staticModel.Model.Save();
}
}
return true;
});
}
private void SetTheCurrentSceneViewAsDefault()
{
var projectInfo = Editor.GameProject;

View File

@@ -236,6 +236,39 @@ namespace FlaxEditor.Surface.Archetypes
}
}
internal enum MaterialTemplateInputsMapping
{
/// <summary>
/// Constant buffers.
/// </summary>
Constants = 1,
/// <summary>
/// Shader resources such as textures and buffers.
/// </summary>
ShaderResources = 2,
/// <summary>
/// Pre-processor definitions.
/// </summary>
Defines = 3,
/// <summary>
/// Included files.
/// </summary>
Includes = 7,
/// <summary>
/// Default location after all shader resources and methods but before actual material code.
/// </summary>
Utilities = 8,
/// <summary>
/// Shader functions location after all material shaders.
/// </summary>
Shaders = 9,
}
/// <summary>
/// The nodes for that group.
/// </summary>
@@ -814,17 +847,20 @@ namespace FlaxEditor.Surface.Archetypes
Title = "Custom Global Code",
Description = "Custom global HLSL shader code expression (placed before material shader code). Can contain includes to shader utilities or declare functions to reuse later.",
Flags = NodeFlags.MaterialGraph,
Size = new Vector2(300, 220),
Size = new Vector2(300, 240),
DefaultValues = new object[]
{
"// Here you can add HLSL code\nfloat4 GetCustomColor()\n{\n\treturn float4(1, 0, 0, 1);\n}",
true,
(int)MaterialTemplateInputsMapping.Utilities,
},
Elements = new[]
{
NodeElementArchetype.Factory.Bool(0, 0, 1),
NodeElementArchetype.Factory.Text(20, 0, "Enabled"),
NodeElementArchetype.Factory.TextBox(0, 20, 300, 200, 0),
NodeElementArchetype.Factory.Text(0, 20, "Location"),
NodeElementArchetype.Factory.Enum(50, 20, 120, 2, typeof(MaterialTemplateInputsMapping)),
NodeElementArchetype.Factory.TextBox(0, 40, 300, 200, 0),
}
},
};

View File

@@ -427,6 +427,20 @@ namespace FlaxEditor.Surface.Archetypes
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 4),
}
},
new NodeArchetype
{
TypeID = 49,
Title = "Rotate Vector",
Description = "Rotates given vector using the Quaternion",
Flags = NodeFlags.AllGraphs,
Size = new Vector2(200, 40),
Elements = new[]
{
NodeElementArchetype.Factory.Input(0, "Quaternion", true, typeof(Quaternion), 0),
NodeElementArchetype.Factory.Input(1, "Vector", true, typeof(Vector3), 1),
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(Vector3), 2),
}
},
};
}
}

View File

@@ -888,7 +888,6 @@ namespace FlaxEditor.Surface.Archetypes
NodeElementArchetype.Factory.ComboBox(0, -10.0f, 160, 2, typeof(ParticleModelFacingMode)),
},
},
new NodeArchetype
{
TypeID = 214,
@@ -912,6 +911,20 @@ namespace FlaxEditor.Surface.Archetypes
NodeElementArchetype.Factory.Input(-0.5f + 2.0f, "Velocity Scale", true, typeof(float), 2, 4),
},
},
new NodeArchetype
{
TypeID = 215,
Create = CreateParticleModuleNode,
Title = "Position (Global SDF)",
Description = "Places the particles on Global SDF surface (uses current particle position to snap it to SDF)",
Flags = DefaultModuleFlags,
Size = new Vector2(200, 0 * Surface.Constants.LayoutOffsetY),
DefaultValues = new object[]
{
true,
(int)ModuleType.Initialize,
},
},
GetParticleAttribute(ModuleType.Initialize, 250, "Set Position", "Sets the particle position", typeof(Vector3), Vector3.Zero),
GetParticleAttribute(ModuleType.Initialize, 251, "Set Lifetime", "Sets the particle lifetime (in seconds)", typeof(float), 10.0f),
GetParticleAttribute(ModuleType.Initialize, 252, "Set Age", "Sets the particle age (in seconds)", typeof(float), 0.0f),
@@ -1344,7 +1357,7 @@ namespace FlaxEditor.Surface.Archetypes
true,
(int)ModuleType.Update,
false, // Invert
0.0f, // Radius
5.0f, // Radius
0.0f, // Roughness
0.1f, // Elasticity
0.0f, // Friction
@@ -1362,6 +1375,59 @@ namespace FlaxEditor.Surface.Archetypes
NodeElementArchetype.Factory.Input(-0.5f + 0, "Surface Thickness", true, typeof(float), 5, 8),
},
},
new NodeArchetype
{
TypeID = 335,
Create = CreateParticleModuleNode,
Title = "Conform to Global SDF",
Description = "Applies the force vector to particles to conform around Global SDF",
Flags = DefaultModuleFlags,
Size = new Vector2(200, 4 * Surface.Constants.LayoutOffsetY),
DefaultValues = new object[]
{
true,
(int)ModuleType.Update,
5.0f,
2000.0f,
1.0f,
5000.0f,
},
Elements = new[]
{
NodeElementArchetype.Factory.Input(-0.5f, "Attraction Speed", true, typeof(float), 0, 2),
NodeElementArchetype.Factory.Input(-0.5f + 1.0f, "Attraction Force", true, typeof(float), 1, 3),
NodeElementArchetype.Factory.Input(-0.5f + 2.0f, "Stick Distance", true, typeof(float), 2, 4),
NodeElementArchetype.Factory.Input(-0.5f + 3.0f, "Stick Force", true, typeof(float), 3, 5),
},
},
new NodeArchetype
{
TypeID = 336,
Create = CreateParticleModuleNode,
Title = "Collision (Global SDF)",
Description = "Collides particles with the scene Global SDF",
Flags = DefaultModuleFlags,
Size = new Vector2(200, 5 * Surface.Constants.LayoutOffsetY),
DefaultValues = new object[]
{
true,
(int)ModuleType.Update,
false, // Invert
5.0f, // Radius
0.4f, // Roughness
0.1f, // Elasticity
0.0f, // Friction
0.0f, // Lifetime Loss
},
Elements = new[]
{
NodeElementArchetype.Factory.Input(-0.5f + 0, "Radius", true, typeof(float), 0, 3),
NodeElementArchetype.Factory.Input(-0.5f + 1, "Roughness", true, typeof(float), 1, 4),
NodeElementArchetype.Factory.Input(-0.5f + 2, "Elasticity", true, typeof(float), 2, 5),
NodeElementArchetype.Factory.Input(-0.5f + 3, "Friction", true, typeof(float), 3, 6),
NodeElementArchetype.Factory.Input(-0.5f + 4, "Lifetime Loss", true, typeof(float), 4, 7),
},
},
GetParticleAttribute(ModuleType.Update, 350, "Set Position", "Sets the particle position", typeof(Vector3), Vector3.Zero),
GetParticleAttribute(ModuleType.Update, 351, "Set Lifetime", "Sets the particle lifetime (in seconds)", typeof(float), 10.0f),
GetParticleAttribute(ModuleType.Update, 352, "Set Age", "Sets the particle age (in seconds)", typeof(float), 0.0f),

View File

@@ -126,7 +126,7 @@ namespace FlaxEditor.Surface.Archetypes
});
}
cm.ItemClicked += item => AddModule((ushort)item.Tag);
cm.SortChildren();
cm.SortItems();
cm.Show(this, button.BottomLeft);
}

View File

@@ -358,6 +358,33 @@ namespace FlaxEditor.Surface.Archetypes
NodeElementArchetype.Factory.Input(1, "Location", true, null, 2),
}
},
new NodeArchetype
{
TypeID = 14,
Title = "Sample Global SDF",
Description = "Samples the Global SDF to get the distance to the closest surface (in world-space). Requires models SDF to be generated and checking `Enable Global SDF` in Graphics Settings.",
Flags = NodeFlags.MaterialGraph | NodeFlags.ParticleEmitterGraph,
Size = new Vector2(200, 20),
Elements = new[]
{
NodeElementArchetype.Factory.Output(0, "Distance", typeof(float), 0),
NodeElementArchetype.Factory.Input(0, "World Position", true, typeof(Vector3), 1),
}
},
new NodeArchetype
{
TypeID = 15,
Title = "Sample Global SDF Gradient",
Description = "Samples the Global SDF to get the gradient and distance to the closest surface (in world-space). Normalize gradient to get SDF surface normal vector. Requires models SDF to be generated and checking `Enable Global SDF` in Graphics Settings.",
Flags = NodeFlags.MaterialGraph | NodeFlags.ParticleEmitterGraph,
Size = new Vector2(260, 40),
Elements = new[]
{
NodeElementArchetype.Factory.Output(0, "Gradient", typeof(Vector3), 0),
NodeElementArchetype.Factory.Output(1, "Distance", typeof(float), 2),
NodeElementArchetype.Factory.Input(0, "World Position", true, typeof(Vector3), 1),
}
},
};
}
}

View File

@@ -41,7 +41,7 @@ namespace FlaxEditor.Surface
for (int i = 0; i < options.Count; i++)
{
var type = options[i];
_options[i] = new OptionType(CustomEditorsUtil.GetPropertyNameUI(type.Name), type, Creator);
_options[i] = new OptionType(Utilities.Utils.GetPropertyNameUI(type.Name), type, Creator);
}
base.Initialize(layout);

View File

@@ -457,7 +457,7 @@ namespace FlaxEditor.Surface
if (field.Name.Equals("value__"))
continue;
var name = CustomEditorsUtil.GetPropertyNameUI(field.Name);
var name = Utilities.Utils.GetPropertyNameUI(field.Name);
values.Add(name);
}
return ComboBox(x, y, width, valueIndex, values.ToArray());

View File

@@ -466,7 +466,7 @@ namespace FlaxEditor.Surface
cm.AddItem(item);
}
cm.ItemClicked += OnAddParameterItemClicked;
cm.SortChildren();
cm.SortItems();
cm.Show(button.Parent, button.BottomLeft);
}

View File

@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Tree;
using FlaxEditor.SceneGraph;
@@ -28,6 +29,8 @@ namespace FlaxEditor.Utilities
/// </summary>
public static class Utils
{
private static readonly StringBuilder CachedSb = new StringBuilder(256);
private static readonly string[] MemorySizePostfixes =
{
"B",
@@ -863,6 +866,62 @@ namespace FlaxEditor.Utilities
}
}
/// <summary>
/// Gets the property name for UI. Removes unnecessary characters and filters text. Makes it more user-friendly.
/// </summary>
/// <param name="name">The name.</param>
/// <returns>The result.</returns>
public static string GetPropertyNameUI(string name)
{
int length = name.Length;
StringBuilder sb = CachedSb;
sb.Clear();
sb.EnsureCapacity(length + 8);
int startIndex = 0;
// Skip some prefixes
if (name.StartsWith("g_") || name.StartsWith("m_"))
startIndex = 2;
// Filter text
var lastChar = '\0';
for (int i = startIndex; i < length; i++)
{
var c = name[i];
// Space before word starting with uppercase letter
if (char.IsUpper(c) && i > 0)
{
if (lastChar != ' ' && (!char.IsUpper(name[i - 1]) || (i + 1 != length && !char.IsUpper(name[i + 1]))))
{
lastChar = ' ';
sb.Append(lastChar);
}
}
// Space instead of underscore
else if (c == '_')
{
if (sb.Length > 0 && lastChar != ' ')
{
lastChar = ' ';
sb.Append(lastChar);
}
continue;
}
// Space before digits sequence
else if (i > 1 && char.IsDigit(c) && !char.IsDigit(name[i - 1]) && lastChar != ' ')
{
lastChar = ' ';
sb.Append(lastChar);
}
lastChar = c;
sb.Append(lastChar);
}
return sb.ToString();
}
/// <summary>
/// Creates the search popup with a tree.
/// </summary>

View File

@@ -1407,6 +1407,8 @@ namespace FlaxEditor.Viewport
new ViewModeOptions(ViewMode.LODPreview, "LOD Preview"),
new ViewModeOptions(ViewMode.MaterialComplexity, "Material Complexity"),
new ViewModeOptions(ViewMode.QuadOverdraw, "Quad Overdraw"),
new ViewModeOptions(ViewMode.GlobalSDF, "Global SDF"),
new ViewModeOptions(ViewMode.GlobalSurfaceAtlas, "Global Surface Atlas"),
};
private void WidgetCamSpeedShowHide(Control cm)

View File

@@ -46,7 +46,7 @@ namespace FlaxEditor.Windows.Assets
var group = layout.Group("General");
group.Label("Format: " + texture.Format);
group.Label(string.Format("Size: {0}x{1}", texture.Width, texture.Height));
group.Label(string.Format("Size: {0}x{1}", texture.Width, texture.Height)).AddCopyContextMenu();
group.Label("Mip levels: " + texture.MipLevels);
group.Label("Memory usage: " + Utilities.Utils.FormatBytesCount(texture.TotalMemoryUsage));
}

View File

@@ -38,7 +38,7 @@ namespace FlaxEditor.Windows.Assets
PreviewLight.ShadowsDistance = 2000.0f;
Task.ViewFlags |= ViewFlags.Shadows;
}
public override void Draw()
{
base.Draw();
@@ -75,10 +75,7 @@ namespace FlaxEditor.Windows.Assets
base.OnClean();
}
/// <summary>
/// Updates the highlight/isolate effects on UI.
/// </summary>
public void UpdateEffectsOnUI()
private void UpdateEffectsOnUI()
{
Window._skipEffectsGuiEvents = true;
@@ -97,10 +94,7 @@ namespace FlaxEditor.Windows.Assets
Window._skipEffectsGuiEvents = false;
}
/// <summary>
/// Updates the material slots UI parts. Should be called after material slot rename.
/// </summary>
public void UpdateMaterialSlotsUI()
private void UpdateMaterialSlotsUI()
{
Window._skipEffectsGuiEvents = true;
@@ -123,12 +117,7 @@ namespace FlaxEditor.Windows.Assets
Window._skipEffectsGuiEvents = false;
}
/// <summary>
/// Sets the material slot index to the mesh.
/// </summary>
/// <param name="mesh">The mesh.</param>
/// <param name="newSlotIndex">New index of the material slot to use.</param>
public void SetMaterialSlot(Mesh mesh, int newSlotIndex)
private void SetMaterialSlot(Mesh mesh, int newSlotIndex)
{
if (Window._skipEffectsGuiEvents)
return;
@@ -139,11 +128,7 @@ namespace FlaxEditor.Windows.Assets
Window.MarkAsEdited();
}
/// <summary>
/// Sets the material slot to isolate.
/// </summary>
/// <param name="mesh">The mesh.</param>
public void SetIsolate(Mesh mesh)
private void SetIsolate(Mesh mesh)
{
if (Window._skipEffectsGuiEvents)
return;
@@ -153,11 +138,7 @@ namespace FlaxEditor.Windows.Assets
UpdateEffectsOnUI();
}
/// <summary>
/// Sets the material slot index to highlight.
/// </summary>
/// <param name="mesh">The mesh.</param>
public void SetHighlight(Mesh mesh)
private void SetHighlight(Mesh mesh)
{
if (Window._skipEffectsGuiEvents)
return;
@@ -169,6 +150,8 @@ namespace FlaxEditor.Windows.Assets
private class ProxyEditor : ProxyEditorBase
{
private CustomEditors.Elements.IntegerValueElement _sdfModelLodIndex;
public override void Initialize(LayoutElementsContainer layout)
{
var proxy = (MeshesPropertiesProxy)Values[0];
@@ -191,13 +174,60 @@ namespace FlaxEditor.Windows.Assets
minScreenSize.FloatValue.MinValue = 0.0f;
minScreenSize.FloatValue.MaxValue = 1.0f;
minScreenSize.FloatValue.Value = proxy.Asset.MinScreenSize;
minScreenSize.FloatValue.ValueChanged += () =>
minScreenSize.FloatValue.BoxValueChanged += b =>
{
proxy.Asset.MinScreenSize = minScreenSize.FloatValue.Value;
proxy.Asset.MinScreenSize = b.Value;
proxy.Window.MarkAsEdited();
};
}
// SDF
{
var group = layout.Group("SDF");
var sdf = proxy.Asset.SDF;
if (sdf.Texture != null)
{
var size = sdf.Texture.Size3;
group.Label($"SDF Texture {size.X}x{size.Y}x{size.Z} ({Utilities.Utils.FormatBytesCount(sdf.Texture.MemoryUsage)})").AddCopyContextMenu();
}
else
{
group.Label("No SDF");
}
var resolution = group.FloatValue("Resolution Scale", proxy.Window.Editor.CodeDocs.GetTooltip(typeof(ModelImportSettings), nameof(ModelImportSettings.SDFResolution)));
resolution.FloatValue.MinValue = 0.0001f;
resolution.FloatValue.MaxValue = 100.0f;
resolution.FloatValue.Value = sdf.Texture != null ? sdf.ResolutionScale : 1.0f;
resolution.FloatValue.BoxValueChanged += b => { proxy.Window._importSettings.SDFResolution = b.Value; };
proxy.Window._importSettings.SDFResolution = sdf.ResolutionScale;
var backfacesThreshold = group.FloatValue("Backfaces Threshold", "Custom threshold (in range 0-1) for adjusting mesh internals detection based on the percentage of test rays hit triangle backfaces. Use lower value for more dense mesh.");
backfacesThreshold.FloatValue.MinValue = 0.001f;
backfacesThreshold.FloatValue.MaxValue = 1.0f;
backfacesThreshold.FloatValue.Value = proxy.Window._backfacesThreshold;
backfacesThreshold.FloatValue.BoxValueChanged += b => { proxy.Window._backfacesThreshold = b.Value; };
var lodIndex = group.IntegerValue("LOD Index", "Index of the model Level of Detail to use for SDF data building. By default uses the lowest quality LOD for fast building.");
lodIndex.IntValue.MinValue = 0;
lodIndex.IntValue.MaxValue = lods.Length - 1;
lodIndex.IntValue.Value = sdf.Texture != null ? sdf.LOD : 6;
_sdfModelLodIndex = lodIndex;
var buttons = group.CustomContainer<UniformGridPanel>();
var gridControl = buttons.CustomControl;
gridControl.ClipChildren = false;
gridControl.Height = Button.DefaultHeight;
gridControl.SlotsHorizontally = 2;
gridControl.SlotsVertically = 1;
var rebuildButton = buttons.Button("Rebuild", "Rebuilds the model SDF.").Button;
rebuildButton.Clicked += OnRebuildSDF;
var removeButton = buttons.Button("Remove", "Removes the model SDF data from the asset.").Button;
removeButton.Enabled = sdf.Texture != null;
removeButton.Clicked += OnRemoveSDF;
}
// Group per LOD
for (int lodIndex = 0; lodIndex < lods.Length; lodIndex++)
{
@@ -218,15 +248,15 @@ namespace FlaxEditor.Windows.Assets
vertexCount += mesh.VertexCount;
}
group.Label(string.Format("Triangles: {0:N0} Vertices: {1:N0}", triangleCount, vertexCount));
group.Label("Size: " + lod.Box.Size);
group.Label(string.Format("Triangles: {0:N0} Vertices: {1:N0}", triangleCount, vertexCount)).AddCopyContextMenu();
group.Label("Size: " + lod.Box.Size).AddCopyContextMenu();
var screenSize = group.FloatValue("Screen Size", "The screen size to switch LODs. Bottom limit of the model screen size to render this LOD.");
screenSize.FloatValue.MinValue = 0.0f;
screenSize.FloatValue.MaxValue = 10.0f;
screenSize.FloatValue.Value = lod.ScreenSize;
screenSize.FloatValue.ValueChanged += () =>
screenSize.FloatValue.BoxValueChanged += b =>
{
lod.ScreenSize = screenSize.FloatValue.Value;
lod.ScreenSize = b.Value;
proxy.Window.MarkAsEdited();
};
@@ -234,7 +264,7 @@ namespace FlaxEditor.Windows.Assets
for (int meshIndex = 0; meshIndex < meshes.Length; meshIndex++)
{
var mesh = meshes[meshIndex];
group.Label($"Mesh {meshIndex} (tris: {mesh.TriangleCount:N0}, verts: {mesh.VertexCount:N0})");
group.Label($"Mesh {meshIndex} (tris: {mesh.TriangleCount:N0}, verts: {mesh.VertexCount:N0})").AddCopyContextMenu();
// Material Slot
var materialSlot = group.ComboBox("Material Slot", "Material slot used by this mesh during rendering");
@@ -260,6 +290,22 @@ namespace FlaxEditor.Windows.Assets
proxy.UpdateMaterialSlotsUI();
}
private void OnRebuildSDF()
{
var proxy = (MeshesPropertiesProxy)Values[0];
proxy.Asset.GenerateSDF(proxy.Window._importSettings.SDFResolution, _sdfModelLodIndex.Value, true, proxy.Window._backfacesThreshold);
proxy.Window.MarkAsEdited();
Presenter.BuildLayoutOnUpdate();
}
private void OnRemoveSDF()
{
var proxy = (MeshesPropertiesProxy)Values[0];
proxy.Asset.SetSDF(new ModelBase.SDFData());
proxy.Window.MarkAsEdited();
Presenter.BuildLayoutOnUpdate();
}
internal override void RefreshInternal()
{
// Skip updates when model is not loaded
@@ -645,14 +691,14 @@ namespace FlaxEditor.Windows.Assets
[CustomEditor(typeof(ProxyEditor))]
private sealed class ImportPropertiesProxy : PropertiesProxyBase
{
private ModelImportSettings ImportSettings = new ModelImportSettings();
private ModelImportSettings ImportSettings;
/// <inheritdoc />
public override void OnLoad(ModelWindow window)
{
base.OnLoad(window);
ModelImportSettings.TryRestore(ref ImportSettings, window.Item.Path);
ImportSettings = window._importSettings;
}
public void Reimport()
@@ -675,7 +721,7 @@ namespace FlaxEditor.Windows.Assets
{
var group = layout.Group("Import Settings");
var importSettingsField = typeof(ImportPropertiesProxy).GetField("ImportSettings", BindingFlags.NonPublic | BindingFlags.Instance);
var importSettingsField = typeof(ImportPropertiesProxy).GetField(nameof(ImportSettings), BindingFlags.NonPublic | BindingFlags.Instance);
var importSettingsValues = new ValueContainer(new ScriptMemberInfo(importSettingsField)) { proxy.ImportSettings };
group.Object(importSettingsValues);
@@ -730,6 +776,8 @@ namespace FlaxEditor.Windows.Assets
private readonly ModelPreview _preview;
private StaticModel _highlightActor;
private MeshDataCache _meshData;
private ModelImportSettings _importSettings = new ModelImportSettings();
private float _backfacesThreshold = 0.6f;
/// <inheritdoc />
public ModelWindow(Editor editor, AssetItem item)
@@ -868,6 +916,7 @@ namespace FlaxEditor.Windows.Assets
{
_refreshOnLODsLoaded = true;
_preview.ViewportCamera.SetArcBallView(Asset.GetBox());
ModelImportSettings.TryRestore(ref _importSettings, Item.Path);
UpdateEffectsOnAsset();
// TODO: disable streaming for this model

View File

@@ -262,6 +262,9 @@ namespace FlaxEditor.Windows.Assets
base.OnSurfaceEditingStart();
}
/// <inheritdoc />
protected override bool CanEditSurfaceOnAssetLoadError => true;
/// <inheritdoc />
protected override bool SaveToOriginal()
{

View File

@@ -219,7 +219,7 @@ namespace FlaxEditor.Windows.Assets
vertexCount += mesh.VertexCount;
}
group.Label(string.Format("Triangles: {0:N0} Vertices: {1:N0}", triangleCount, vertexCount));
group.Label(string.Format("Triangles: {0:N0} Vertices: {1:N0}", triangleCount, vertexCount)).AddCopyContextMenu();
group.Label("Size: " + lod.Box.Size);
var screenSize = group.FloatValue("Screen Size", "The screen size to switch LODs. Bottom limit of the model screen size to render this LOD.");
screenSize.FloatValue.MinValue = 0.0f;
@@ -235,7 +235,7 @@ namespace FlaxEditor.Windows.Assets
for (int meshIndex = 0; meshIndex < meshes.Length; meshIndex++)
{
var mesh = meshes[meshIndex];
group.Label($"Mesh {meshIndex} (tris: {mesh.TriangleCount:N0}, verts: {mesh.VertexCount:N0})");
group.Label($"Mesh {meshIndex} (tris: {mesh.TriangleCount:N0}, verts: {mesh.VertexCount:N0})").AddCopyContextMenu();
// Material Slot
var materialSlot = group.ComboBox("Material Slot", "Material slot used by this mesh during rendering");

View File

@@ -36,9 +36,9 @@ namespace FlaxEditor.Windows.Assets
// Texture info
var general = layout.Group("General");
general.Label("Format: " + texture.Format);
general.Label(string.Format("Size: {0}x{1}", texture.Width, texture.Height));
general.Label(string.Format("Size: {0}x{1}", texture.Width, texture.Height)).AddCopyContextMenu();
general.Label("Mip levels: " + texture.MipLevels);
general.Label("Memory usage: " + Utilities.Utils.FormatBytesCount(texture.TotalMemoryUsage));
general.Label("Memory usage: " + Utilities.Utils.FormatBytesCount(texture.TotalMemoryUsage)).AddCopyContextMenu();
// Texture properties
var properties = layout.Group("Properties");

View File

@@ -258,7 +258,7 @@ namespace FlaxEditor.Windows.Assets
cm.AddItem(item);
}
cm.ItemClicked += itemClicked;
cm.SortChildren();
cm.SortItems();
cm.Show(window, window.PointFromScreen(Input.MouseScreenPosition));
}

View File

@@ -280,7 +280,7 @@ namespace FlaxEditor.Windows
name = "Mac";
break;
default:
name = CustomEditorsUtil.GetPropertyNameUI(_platform.ToString());
name = Utilities.Utils.GetPropertyNameUI(_platform.ToString());
break;
}
var group = layout.Group(name);

View File

@@ -223,7 +223,7 @@ namespace FlaxEditor.Windows
if (!QueryFilterHelper.Match(filterText, text, out QueryFilterHelper.Range[] ranges))
continue;
var item = _groupSearch.AddChild(CreateActorItem(CustomEditors.CustomEditorsUtil.GetPropertyNameUI(text), actorType));
var item = _groupSearch.AddChild(CreateActorItem(Utilities.Utils.GetPropertyNameUI(text), actorType));
var highlights = new List<Rectangle>(ranges.Length);
var style = Style.Current;

View File

@@ -324,15 +324,14 @@ void Asset::Reload()
Content::AssetReloading(this);
OnReloading(this);
// Fire unload event
// TODO: maybe just call release storage ref or sth? we cannot call onUnload because managed asset objects gets invalidated
//onUnload_MainThread();
ScopeLock lock(Locker);
// Unload current data
unload(true);
_isLoaded = false;
if (IsLoaded())
{
// Unload current data
unload(true);
_isLoaded = false;
}
// Start reloading process
startLoading();

View File

@@ -83,7 +83,7 @@ void Animation::ClearCache()
// Free memory
MappingCache.Clear();
MappingCache.Cleanup();
MappingCache.SetCapacity(0);
}
const Animation::NodeToChannel* Animation::GetMapping(SkinnedModel* obj)

View File

@@ -48,6 +48,11 @@ const MaterialInfo& Material::GetInfo() const
return EmptyInfo;
}
GPUShader* Material::GetShader() const
{
return _materialShader ? _materialShader->GetShader() : nullptr;
}
bool Material::IsReady() const
{
return _materialShader && _materialShader->IsReady();

View File

@@ -45,6 +45,7 @@ public:
// [IMaterial]
const MaterialInfo& GetInfo() const override;
GPUShader* GetShader() const override;
bool IsReady() const override;
DrawPass GetDrawModes() const override;
bool CanUseLightmap() const override;

View File

@@ -143,6 +143,11 @@ const MaterialInfo& MaterialInstance::GetInfo() const
return EmptyInfo;
}
GPUShader* MaterialInstance::GetShader() const
{
return _baseMaterial ? _baseMaterial->GetShader() : nullptr;
}
bool MaterialInstance::IsReady() const
{
return IsLoaded() && _baseMaterial && _baseMaterial->IsReady();

View File

@@ -59,6 +59,7 @@ public:
// [IMaterial]
const MaterialInfo& GetInfo() const override;
GPUShader* GetShader() const override;
bool IsReady() const override;
DrawPass GetDrawModes() const override;
bool CanUseLightmap() const override;

View File

@@ -7,13 +7,22 @@
#include "Engine/Content/WeakAssetReference.h"
#include "Engine/Content/Upgraders/ModelAssetUpgrader.h"
#include "Engine/Content/Factories/BinaryAssetFactory.h"
#include "Engine/Debug/DebugDraw.h"
#include "Engine/Graphics/RenderTools.h"
#include "Engine/Graphics/RenderTask.h"
#include "Engine/Graphics/Models/ModelInstanceEntry.h"
#include "Engine/Streaming/StreamingGroup.h"
#include "Engine/Debug/Exceptions/ArgumentOutOfRangeException.h"
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Graphics/Async/GPUTask.h"
#include "Engine/Graphics/Async/Tasks/GPUUploadTextureMipTask.h"
#include "Engine/Graphics/Textures/GPUTexture.h"
#include "Engine/Graphics/Textures/TextureData.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Renderer/DrawCall.h"
#include "Engine/Threading/Threading.h"
#include "Engine/Tools/ModelTool/ModelTool.h"
#include "Engine/Tools/ModelTool/MeshAccelerationStructure.h"
#if GPU_ENABLE_ASYNC_RESOURCES_CREATION
#include "Engine/Threading/ThreadPoolTask.h"
#define STREAM_TASK_BASE ThreadPoolTask
@@ -37,18 +46,11 @@ REGISTER_BINARY_ASSET_ABSTRACT(ModelBase, "FlaxEngine.ModelBase");
class StreamModelLODTask : public STREAM_TASK_BASE
{
private:
WeakAssetReference<Model> _asset;
int32 _lodIndex;
FlaxStorage::LockData _dataLock;
public:
/// <summary>
/// Init
/// </summary>
/// <param name="model">Parent model</param>
/// <param name="lodIndex">LOD to stream index</param>
StreamModelLODTask(Model* model, int32 lodIndex)
: _asset(model)
, _lodIndex(lodIndex)
@@ -57,23 +59,16 @@ public:
}
public:
// [ThreadPoolTask]
bool HasReference(Object* resource) const override
{
return _asset == resource;
}
protected:
// [ThreadPoolTask]
bool Run() override
{
AssetReference<Model> model = _asset.Get();
if (model == nullptr)
{
return true;
}
// Get data
BytesContainer data;
@@ -96,6 +91,7 @@ protected:
// Update residency level
model->_loadedLODs++;
model->ResidencyChanged();
return false;
}
@@ -116,11 +112,54 @@ protected:
}
};
class StreamModelSDFTask : public GPUUploadTextureMipTask
{
private:
WeakAssetReference<Model> _asset;
FlaxStorage::LockData _dataLock;
public:
StreamModelSDFTask(Model* model, GPUTexture* texture, const Span<byte>& data, int32 mipIndex, int32 rowPitch, int32 slicePitch)
: GPUUploadTextureMipTask(texture, mipIndex, data, rowPitch, slicePitch, false)
, _asset(model)
, _dataLock(model->Storage->Lock())
{
}
bool HasReference(Object* resource) const override
{
return _asset == resource;
}
Result run(GPUTasksContext* context) override
{
AssetReference<Model> model = _asset.Get();
if (model == nullptr)
return Result::MissingResources;
return GPUUploadTextureMipTask::run(context);
}
void OnEnd() override
{
_dataLock.Release();
// Base
GPUUploadTextureMipTask::OnEnd();
}
};
REGISTER_BINARY_ASSET_WITH_UPGRADER(Model, "FlaxEngine.Model", ModelAssetUpgrader, true);
static byte EnableModelSDF = 0;
Model::Model(const SpawnParams& params, const AssetInfo* info)
: ModelBase(params, info, StreamingGroups::Instance()->Models())
{
if (EnableModelSDF == 0 && GPUDevice::Instance)
{
const bool enable = GPUDevice::Instance->GetFeatureLevel() >= FeatureLevel::SM5;
EnableModelSDF = enable ? 1 : 2;
}
}
Model::~Model()
@@ -183,7 +222,7 @@ void Model::Draw(const RenderContext& renderContext, const Mesh::DrawInfo& info)
if (lodIndex == -1)
{
// Handling model fade-out transition
if (modelFrame == frame && info.DrawState->PrevLOD != -1)
if (modelFrame == frame && info.DrawState->PrevLOD != -1 && !renderContext.View.IsSingleFrame)
{
// Check if start transition
if (info.DrawState->LODTransition == 255)
@@ -212,8 +251,11 @@ void Model::Draw(const RenderContext& renderContext, const Mesh::DrawInfo& info)
lodIndex += info.LODBias + renderContext.View.ModelLODBias;
lodIndex = ClampLODIndex(lodIndex);
if (renderContext.View.IsSingleFrame)
{
}
// Check if it's the new frame and could update the drawing state (note: model instance could be rendered many times per frame to different viewports)
if (modelFrame == frame)
else if (modelFrame == frame)
{
// Check if start transition
if (info.DrawState->PrevLOD != lodIndex && info.DrawState->LODTransition == 255)
@@ -229,7 +271,7 @@ void Model::Draw(const RenderContext& renderContext, const Mesh::DrawInfo& info)
info.DrawState->PrevLOD = lodIndex;
}
}
// Check if there was a gap between frames in drawing this model instance
// Check if there was a gap between frames in drawing this model instance
else if (modelFrame < frame || info.DrawState->PrevLOD == -1)
{
// Reset state
@@ -238,7 +280,7 @@ void Model::Draw(const RenderContext& renderContext, const Mesh::DrawInfo& info)
}
// Draw
if (info.DrawState->PrevLOD == lodIndex)
if (info.DrawState->PrevLOD == lodIndex || renderContext.View.IsSingleFrame)
{
LODs[lodIndex].Draw(renderContext, info, 0.0f);
}
@@ -521,17 +563,50 @@ bool Model::Save(bool withMeshDataFromGpu, const StringView& path)
lodChunk->Data.Copy(meshesStream.GetHandle(), meshesStream.GetPosition());
}
}
// Download SDF data
if (SDF.Texture)
{
auto sdfChunk = GET_CHUNK(15);
if (sdfChunk == nullptr)
return true;
MemoryWriteStream sdfStream;
sdfStream.WriteInt32(1); // Version
ModelSDFHeader data(SDF, SDF.Texture->GetDescription());
sdfStream.Write(&data);
TextureData sdfTextureData;
if (SDF.Texture->DownloadData(sdfTextureData))
return true;
for (int32 mipLevel = 0; mipLevel < sdfTextureData.Items[0].Mips.Count(); mipLevel++)
{
auto& mip = sdfTextureData.Items[0].Mips[mipLevel];
ModelSDFMip mipData(mipLevel, mip);
sdfStream.Write(&mipData);
sdfStream.Write(mip.Data.Get(), mip.Data.Length());
}
sdfChunk->Data.Copy(sdfStream.GetHandle(), sdfStream.GetPosition());
}
}
else
{
ASSERT(!IsVirtual());
// Load all chunks with a mesh data
for (int32 lodIndex = 0; lodIndex < LODs.Count(); lodIndex++)
{
if (LoadChunk(MODEL_LOD_TO_CHUNK_INDEX(lodIndex)))
return true;
}
if (SDF.Texture)
{
// SDF data from file (only if has no cached texture data)
if (LoadChunk(15))
return true;
}
else
{
// No SDF texture
ReleaseChunk(15);
}
}
// Set mesh header data
@@ -560,6 +635,51 @@ bool Model::Save(bool withMeshDataFromGpu, const StringView& path)
#endif
bool Model::GenerateSDF(float resolutionScale, int32 lodIndex, bool cacheData, float backfacesThreshold)
{
if (EnableModelSDF == 2)
return true; // Not supported
ScopeLock lock(Locker);
if (!HasAnyLODInitialized())
return true;
if (IsInMainThread() && IsVirtual())
{
// TODO: could be supported if algorithm could run on a GPU and called during rendering
LOG(Warning, "Cannot generate SDF for virtual models on a main thread.");
return true;
}
lodIndex = Math::Clamp(lodIndex, HighestResidentLODIndex(), LODs.Count() - 1);
// Generate SDF
#if USE_EDITOR
cacheData &= Storage != nullptr; // Cache only if has storage linked
MemoryWriteStream sdfStream;
MemoryWriteStream* outputStream = cacheData ? &sdfStream : nullptr;
#else
class MemoryWriteStream* outputStream = nullptr;
#endif
if (ModelTool::GenerateModelSDF(this, nullptr, resolutionScale, lodIndex, &SDF, outputStream, GetPath(), backfacesThreshold))
return true;
#if USE_EDITOR
// Set asset data
if (cacheData)
GetOrCreateChunk(15)->Data.Copy(sdfStream.GetHandle(), sdfStream.GetPosition());
#endif
return false;
}
void Model::SetSDF(const SDFData& sdf)
{
ScopeLock lock(Locker);
if (SDF.Texture == sdf.Texture)
return;
SAFE_DELETE_GPU_RESOURCE(SDF.Texture);
SDF = sdf;
ReleaseChunk(15);
}
bool Model::Init(const Span<int32>& meshesCountPerLod)
{
if (meshesCountPerLod.IsInvalid() || meshesCountPerLod.Length() > MODEL_MAX_LODS)
@@ -574,14 +694,12 @@ bool Model::Init(const Span<int32>& meshesCountPerLod)
// Setup
MaterialSlots.Resize(1);
MinScreenSize = 0.0f;
SAFE_DELETE_GPU_RESOURCE(SDF.Texture);
// Setup LODs
for (int32 lodIndex = 0; lodIndex < LODs.Count(); lodIndex++)
{
LODs[lodIndex].Dispose();
}
LODs.Resize(meshesCountPerLod.Length());
_loadedLODs = meshesCountPerLod.Length();
// Setup meshes
for (int32 lodIndex = 0; lodIndex < meshesCountPerLod.Length(); lodIndex++)
@@ -600,6 +718,10 @@ bool Model::Init(const Span<int32>& meshesCountPerLod)
}
}
// Update resource residency
_loadedLODs = meshesCountPerLod.Length();
ResidencyChanged();
return false;
}
@@ -716,6 +838,7 @@ Task* Model::CreateStreamingTask(int32 residency)
for (int32 i = HighestResidentLODIndex(); i < LODs.Count() - residency; i++)
LODs[i].Unload();
_loadedLODs = residency;
ResidencyChanged();
}
return result;
@@ -810,6 +933,47 @@ Asset::LoadResult Model::load()
}
}
// Load SDF
auto chunk15 = GetChunk(15);
if (chunk15 && chunk15->IsLoaded() && EnableModelSDF == 1)
{
MemoryReadStream sdfStream(chunk15->Get(), chunk15->Size());
int32 version;
sdfStream.ReadInt32(&version);
switch (version)
{
case 1:
{
ModelSDFHeader data;
sdfStream.Read(&data);
if (!SDF.Texture)
SDF.Texture = GPUTexture::New();
if (SDF.Texture->Init(GPUTextureDescription::New3D(data.Width, data.Height, data.Depth, data.Format, GPUTextureFlags::ShaderResource, data.MipLevels)))
return LoadResult::Failed;
SDF.LocalToUVWMul = data.LocalToUVWMul;
SDF.LocalToUVWAdd = data.LocalToUVWAdd;
SDF.WorldUnitsPerVoxel = data.WorldUnitsPerVoxel;
SDF.MaxDistance = data.MaxDistance;
SDF.LocalBoundsMin = data.LocalBoundsMin;
SDF.LocalBoundsMax = data.LocalBoundsMax;
SDF.ResolutionScale = data.ResolutionScale;
SDF.LOD = data.LOD;
for (int32 mipLevel = 0; mipLevel < data.MipLevels; mipLevel++)
{
ModelSDFMip mipData;
sdfStream.Read(&mipData);
void* mipBytes = sdfStream.Read(mipData.SlicePitch);
auto task = ::New<StreamModelSDFTask>(this, SDF.Texture, Span<byte>((byte*)mipBytes, mipData.SlicePitch), mipData.MipIndex, mipData.RowPitch, mipData.SlicePitch);
task->Start();
}
break;
}
default:
LOG(Warning, "Unknown SDF data version {0} in {1}", version, ToString());
break;
}
}
#if BUILD_DEBUG || BUILD_DEVELOPMENT
// Validate LODs
for (int32 lodIndex = 1; lodIndex < LODs.Count(); lodIndex++)
@@ -840,6 +1004,7 @@ void Model::unload(bool isReloading)
}
// Cleanup
SAFE_DELETE_GPU_RESOURCE(SDF.Texture);
MaterialSlots.Resize(0);
for (int32 i = 0; i < LODs.Count(); i++)
LODs[i].Dispose();
@@ -862,7 +1027,7 @@ bool Model::init(AssetInitData& initData)
AssetChunksFlag Model::getChunksToPreload() const
{
// Note: we don't preload any LODs here because it's done by the Streaming Manager
return GET_CHUNK_FLAG(0);
return GET_CHUNK_FLAG(0) | GET_CHUNK_FLAG(15);
}
void ModelBase::SetupMaterialSlots(int32 slotsCount)

View File

@@ -28,6 +28,11 @@ public:
/// </summary>
API_FIELD(ReadOnly) Array<ModelLOD, FixedAllocation<MODEL_MAX_LODS>> LODs;
/// <summary>
/// The generated Sign Distant Field (SDF) for this model (merged all meshes). Use GenerateSDF to update it.
/// </summary>
API_FIELD(ReadOnly) SDFData SDF;
public:
/// <summary>
@@ -200,6 +205,22 @@ public:
API_FUNCTION() bool Save(bool withMeshDataFromGpu = false, const StringView& path = StringView::Empty);
#endif
/// <summary>
/// Generates the Sign Distant Field for this model.
/// </summary>
/// <remarks>Can be called in async in case of SDF generation on a CPU (assuming model is not during rendering).</remarks>
/// <param name="resolutionScale">The SDF texture resolution scale. Use higher values for more precise data but with significant performance and memory overhead.</param>
/// <param name="lodIndex">The index of the LOD to use for the SDF building.</param>
/// <param name="cacheData">If true, the generated SDF texture data will be cached on CPU (in asset chunk storage) to allow saving it later, otherwise it will be runtime for GPU-only. Ignored for virtual assets or in build.</param>
/// <param name="backfacesThreshold">Custom threshold (in range 0-1) for adjusting mesh internals detection based on the percentage of test rays hit triangle backfaces. Use lower value for more dense mesh.</param>
/// <returns>True if failed, otherwise false.</returns>
API_FUNCTION() bool GenerateSDF(float resolutionScale = 1.0f, int32 lodIndex = 6, bool cacheData = true, float backfacesThreshold = 0.6f);
/// <summary>
/// Sets set SDF data (releases the current one).
/// </summary>
API_FUNCTION() void SetSDF(const SDFData& sdf);
private:

View File

@@ -14,7 +14,7 @@
// Chunk 1: LOD0
// Chunk 2: LOD1
// ..
//
// Chunk 15: SDF
#define MODEL_LOD_TO_CHUNK_INDEX(lod) (lod + 1)
class MeshBase;
@@ -24,9 +24,62 @@ class MeshBase;
/// </summary>
API_CLASS(Abstract, NoSpawn) class FLAXENGINE_API ModelBase : public BinaryAsset, public StreamableResource
{
DECLARE_ASSET_HEADER(ModelBase);
protected:
DECLARE_ASSET_HEADER(ModelBase);
public:
/// <summary>
/// The Sign Distant Field (SDF) data for the model.
/// </summary>
API_STRUCT() struct SDFData
{
DECLARE_SCRIPTING_TYPE_MINIMAL(SDFData);
/// <summary>
/// The SDF volume texture (merged all meshes).
/// </summary>
API_FIELD() GPUTexture* Texture = nullptr;
/// <summary>
/// The transformation scale from model local-space to the generated SDF texture space (local-space -> uv).
/// </summary>
API_FIELD() Vector3 LocalToUVWMul;
/// <summary>
/// Amount of world-units per SDF texture voxel.
/// </summary>
API_FIELD() float WorldUnitsPerVoxel;
/// <summary>
/// The transformation offset from model local-space to the generated SDF texture space (local-space -> uv).
/// </summary>
API_FIELD() Vector3 LocalToUVWAdd;
/// <summary>
/// The maximum distance stored in the SDF texture. Used to rescale normalized SDF into world-units (in model local space).
/// </summary>
API_FIELD() float MaxDistance;
/// <summary>
/// The bounding box of the SDF texture in the model local-space.
/// </summary>
API_FIELD() Vector3 LocalBoundsMin;
/// <summary>
/// The SDF texture resolution scale used for building texture.
/// </summary>
API_FIELD() float ResolutionScale = 1.0f;
/// <summary>
/// The bounding box of the SDF texture in the model local-space.
/// </summary>
API_FIELD() Vector3 LocalBoundsMax;
/// <summary>
/// The model LOD index used for the building.
/// </summary>
API_FIELD() int32 LOD = 6;
};
protected:
explicit ModelBase(const SpawnParams& params, const AssetInfo* info, StreamingGroup* group)
: BinaryAsset(params, info)
, StreamableResource(group)
@@ -34,7 +87,6 @@ protected:
}
public:
/// <summary>
/// The minimum screen size to draw this model (the bottom limit). Used to cull small models. Set to 0 to disable this feature.
/// </summary>

View File

@@ -30,13 +30,11 @@
class StreamSkinnedModelLODTask : public ThreadPoolTask
{
private:
WeakAssetReference<SkinnedModel> _asset;
int32 _lodIndex;
FlaxStorage::LockData _dataLock;
public:
/// <summary>
/// Init
/// </summary>
@@ -50,7 +48,6 @@ public:
}
public:
// [ThreadPoolTask]
bool HasReference(Object* resource) const override
{
@@ -58,7 +55,6 @@ public:
}
protected:
// [ThreadPoolTask]
bool Run() override
{
@@ -89,6 +85,7 @@ protected:
// Update residency level
model->_loadedLODs++;
model->ResidencyChanged();
return false;
}
@@ -191,7 +188,7 @@ void SkinnedModel::Draw(RenderContext& renderContext, const SkinnedMesh::DrawInf
if (lodIndex == -1)
{
// Handling model fade-out transition
if (modelFrame == frame && info.DrawState->PrevLOD != -1)
if (modelFrame == frame && info.DrawState->PrevLOD != -1 && !renderContext.View.IsSingleFrame)
{
// Check if start transition
if (info.DrawState->LODTransition == 255)
@@ -220,8 +217,11 @@ void SkinnedModel::Draw(RenderContext& renderContext, const SkinnedMesh::DrawInf
lodIndex += info.LODBias + renderContext.View.ModelLODBias;
lodIndex = ClampLODIndex(lodIndex);
if (renderContext.View.IsSingleFrame)
{
}
// Check if it's the new frame and could update the drawing state (note: model instance could be rendered many times per frame to different viewports)
if (modelFrame == frame)
else if (modelFrame == frame)
{
// Check if start transition
if (info.DrawState->PrevLOD != lodIndex && info.DrawState->LODTransition == 255)
@@ -237,7 +237,7 @@ void SkinnedModel::Draw(RenderContext& renderContext, const SkinnedMesh::DrawInf
info.DrawState->PrevLOD = lodIndex;
}
}
// Check if there was a gap between frames in drawing this model instance
// Check if there was a gap between frames in drawing this model instance
else if (modelFrame < frame || info.DrawState->PrevLOD == -1)
{
// Reset state
@@ -246,7 +246,7 @@ void SkinnedModel::Draw(RenderContext& renderContext, const SkinnedMesh::DrawInf
}
// Draw
if (info.DrawState->PrevLOD == lodIndex)
if (info.DrawState->PrevLOD == lodIndex || renderContext.View.IsSingleFrame)
{
LODs[lodIndex].Draw(renderContext, info, 0.0f);
}
@@ -677,11 +677,8 @@ bool SkinnedModel::Init(const Span<int32>& meshesCountPerLod)
// Setup LODs
for (int32 lodIndex = 0; lodIndex < LODs.Count(); lodIndex++)
{
LODs[lodIndex].Dispose();
}
LODs.Resize(meshesCountPerLod.Length());
_loadedLODs = meshesCountPerLod.Length();
// Setup meshes
for (int32 lodIndex = 0; lodIndex < meshesCountPerLod.Length(); lodIndex++)
@@ -700,6 +697,10 @@ bool SkinnedModel::Init(const Span<int32>& meshesCountPerLod)
}
}
// Update resource residency
_loadedLODs = meshesCountPerLod.Length();
ResidencyChanged();
return false;
}
@@ -828,6 +829,7 @@ Task* SkinnedModel::CreateStreamingTask(int32 residency)
for (int32 i = HighestResidentLODIndex(); i < LODs.Count() - residency; i++)
LODs[i].Unload();
_loadedLODs = residency;
ResidencyChanged();
}
return result;

View File

@@ -18,6 +18,7 @@ public class Content : EngineModule
options.PrivateDependencies.Add("lz4");
options.PrivateDependencies.Add("AudioTool");
options.PrivateDependencies.Add("TextureTool");
options.PrivateDependencies.Add("ModelTool");
options.PrivateDependencies.Add("Particles");
if (options.Target.IsEditor)

View File

@@ -61,13 +61,13 @@ namespace
return &node;
}
ShaderGraphNode<>* AddTextureNode(MaterialLayer* layer, const Guid& textureId)
ShaderGraphNode<>* AddTextureNode(MaterialLayer* layer, const Guid& textureId, bool normalMap = false)
{
if (!textureId.IsValid())
return nullptr;
auto& node = layer->Graph.Nodes.AddOne();
node.ID = layer->Graph.Nodes.Count();
node.Type = GRAPH_NODE_MAKE_TYPE(5, 1);
node.Type = GRAPH_NODE_MAKE_TYPE(5, normalMap ? 4 : 1);
node.Boxes.Resize(7);
node.Boxes[0] = MaterialGraphBox(&node, 0, VariantType::Vector2); // UVs
node.Boxes[6] = MaterialGraphBox(&node, 6, VariantType::Object); // Texture Reference
@@ -178,7 +178,7 @@ CreateAssetResult CreateMaterial::Create(CreateAssetContext& context)
CONNECT(layer->Root->Boxes[static_cast<int32>(MaterialGraphBoxes::Emissive)], emissiveColor->Boxes[0]);
SET_POS(emissiveColor, Vector2(-493.5272f, -2.926111f));
}
auto normalMap = AddTextureNode(layer, options.Normals.Texture);
auto normalMap = AddTextureNode(layer, options.Normals.Texture, true);
if (normalMap)
{
CONNECT(layer->Root->Boxes[static_cast<int32>(MaterialGraphBoxes::Normal)], normalMap->Boxes[1]);

View File

@@ -48,9 +48,9 @@ public:
private:
static CreateAssetResult ImportModel(CreateAssetContext& context, ModelData& modelData);
static CreateAssetResult ImportSkinnedModel(CreateAssetContext& context, ModelData& modelData);
static CreateAssetResult ImportAnimation(CreateAssetContext& context, ModelData& modelData);
static CreateAssetResult ImportModel(CreateAssetContext& context, ModelData& modelData, const Options* options = nullptr);
static CreateAssetResult ImportSkinnedModel(CreateAssetContext& context, ModelData& modelData, const Options* options = nullptr);
static CreateAssetResult ImportAnimation(CreateAssetContext& context, ModelData& modelData, const Options* options = nullptr);
};
#endif

View File

@@ -150,13 +150,13 @@ CreateAssetResult ImportModelFile::Import(CreateAssetContext& context)
switch (options.Type)
{
case ModelTool::ModelType::Model:
result = ImportModel(context, modelData);
result = ImportModel(context, modelData, &options);
break;
case ModelTool::ModelType::SkinnedModel:
result = ImportSkinnedModel(context, modelData);
result = ImportSkinnedModel(context, modelData, &options);
break;
case ModelTool::ModelType::Animation:
result = ImportAnimation(context, modelData);
result = ImportAnimation(context, modelData, &options);
break;
}
if (result != CreateAssetResult::Ok)
@@ -199,7 +199,7 @@ CreateAssetResult ImportModelFile::Create(CreateAssetContext& context)
return ImportModel(context, modelData);
}
CreateAssetResult ImportModelFile::ImportModel(CreateAssetContext& context, ModelData& modelData)
CreateAssetResult ImportModelFile::ImportModel(CreateAssetContext& context, ModelData& modelData, const Options* options)
{
// Base
IMPORT_SETUP(Model, Model::SerializedVersion);
@@ -235,10 +235,22 @@ CreateAssetResult ImportModelFile::ImportModel(CreateAssetContext& context, Mode
context.Data.Header.Chunks[chunkIndex]->Data.Copy(stream.GetHandle(), stream.GetPosition());
}
// Generate SDF
if (options && options->GenerateSDF)
{
stream.SetPosition(0);
if (!ModelTool::GenerateModelSDF(nullptr, &modelData, options->SDFResolution, lodCount - 1, nullptr, &stream, context.TargetAssetPath))
{
if (context.AllocateChunk(15))
return CreateAssetResult::CannotAllocateChunk;
context.Data.Header.Chunks[15]->Data.Copy(stream.GetHandle(), stream.GetPosition());
}
}
return CreateAssetResult::Ok;
}
CreateAssetResult ImportModelFile::ImportSkinnedModel(CreateAssetContext& context, ModelData& modelData)
CreateAssetResult ImportModelFile::ImportSkinnedModel(CreateAssetContext& context, ModelData& modelData, const Options* options)
{
// Base
IMPORT_SETUP(SkinnedModel, SkinnedModel::SerializedVersion);
@@ -277,7 +289,7 @@ CreateAssetResult ImportModelFile::ImportSkinnedModel(CreateAssetContext& contex
return CreateAssetResult::Ok;
}
CreateAssetResult ImportModelFile::ImportAnimation(CreateAssetContext& context, ModelData& modelData)
CreateAssetResult ImportModelFile::ImportAnimation(CreateAssetContext& context, ModelData& modelData, const Options* options)
{
// Base
IMPORT_SETUP(Animation, Animation::SerializedVersion);

View File

@@ -381,10 +381,10 @@ public:
// Insert
ASSERT(pos.FreeSlotIndex != -1);
auto bucket = &_allocation.Get()[pos.FreeSlotIndex];
bucket->Occupy(key);
Bucket& bucket = _allocation.Get()[pos.FreeSlotIndex];
bucket.Occupy(key);
_elementsCount++;
return bucket->Value;
return bucket.Value;
}
/// <summary>
@@ -484,7 +484,7 @@ public:
#endif
void ClearDelete()
{
for (auto i = Begin(); i.IsNotEnd(); ++i)
for (Iterator i = Begin(); i.IsNotEnd(); ++i)
{
if (i->Value)
Delete(i->Value);
@@ -547,22 +547,15 @@ public:
/// Ensures that collection has given capacity.
/// </summary>
/// <param name="minCapacity">The minimum required capacity.</param>
void EnsureCapacity(int32 minCapacity)
/// <param name="preserveContents">True if preserve collection data when changing its size, otherwise collection after resize will be empty.</param>
void EnsureCapacity(int32 minCapacity, bool preserveContents = true)
{
if (_size >= minCapacity)
return;
if (minCapacity < DICTIONARY_DEFAULT_CAPACITY)
minCapacity = DICTIONARY_DEFAULT_CAPACITY;
const int32 capacity = _allocation.CalculateCapacityGrow(_size, minCapacity);
SetCapacity(capacity);
}
/// <summary>
/// Cleanup collection data (changes size to 0 without data preserving).
/// </summary>
FORCE_INLINE void Cleanup()
{
SetCapacity(0, false);
SetCapacity(capacity, preserveContents);
}
/// <summary>
@@ -642,7 +635,7 @@ public:
void Add(const Iterator& i)
{
ASSERT(&i._collection != this && i);
Bucket& bucket = *i;
const Bucket& bucket = *i;
Add(bucket.Key, bucket.Value);
}
@@ -655,11 +648,9 @@ public:
bool Remove(const KeyComparableType& key)
{
if (IsEmpty())
return true;
return false;
FindPositionResult pos;
FindPosition(key, pos);
if (pos.ObjectIndex != -1)
{
_allocation.Get()[pos.ObjectIndex].Delete();
@@ -697,7 +688,7 @@ public:
int32 RemoveValue(const ValueType& value)
{
int32 result = 0;
for (auto i = Begin(); i.IsNotEnd(); ++i)
for (Iterator i = Begin(); i.IsNotEnd(); ++i)
{
if (i->Value == value)
{
@@ -718,16 +709,11 @@ public:
template<typename KeyComparableType>
Iterator Find(const KeyComparableType& key) const
{
if (HasItems())
{
const Bucket* data = _allocation.Get();
for (int32 i = 0; i < _size; i++)
{
if (data[i].IsOccupied() && data[i].Key == key)
return Iterator(*this, i);
}
}
return End();
if (IsEmpty())
return End();
FindPositionResult pos;
FindPosition(key, pos);
return pos.ObjectIndex != -1 ? Iterator(*this, pos.ObjectIndex) : End();
}
/// <summary>
@@ -798,7 +784,7 @@ public:
{
Clear();
SetCapacity(other.Capacity(), false);
for (auto i = other.Begin(); i != other.End(); ++i)
for (Iterator i = other.Begin(); i != other.End(); ++i)
Add(i);
ASSERT(Count() == other.Count());
ASSERT(Capacity() == other.Capacity());
@@ -811,7 +797,7 @@ public:
template<typename ArrayAllocation>
void GetKeys(Array<KeyType, ArrayAllocation>& result) const
{
for (auto i = Begin(); i.IsNotEnd(); ++i)
for (Iterator i = Begin(); i.IsNotEnd(); ++i)
result.Add(i->Key);
}
@@ -822,7 +808,7 @@ public:
template<typename ArrayAllocation>
void GetValues(Array<ValueType, ArrayAllocation>& result) const
{
for (auto i = Begin(); i.IsNotEnd(); ++i)
for (Iterator i = Begin(); i.IsNotEnd(); ++i)
result.Add(i->Value);
}
@@ -897,7 +883,7 @@ protected:
while (checksCount < _size)
{
// Empty bucket
auto& bucket = data[bucketIndex];
const Bucket& bucket = data[bucketIndex];
if (bucket.IsEmpty())
{
// Found place to insert

View File

@@ -2,78 +2,62 @@
#pragma once
#include "Engine/Core/Core.h"
#include "Engine/Core/Math/Math.h"
#include "Engine/Platform/Platform.h"
#include "HashFunctions.h"
#include "Config.h"
#include "Engine/Core/Memory/Memory.h"
#include "Engine/Core/Memory/Allocation.h"
#include "Engine/Core/Collections/HashFunctions.h"
#include "Engine/Core/Collections/Config.h"
/// <summary>
/// Template for unordered set of values (without duplicates with O(1) lookup access).
/// </summary>
/// <typeparam name="T">The type of elements in the set.</typeparam>
template<typename T>
/// <typeparam name="AllocationType">The type of memory allocator.</typeparam>
template<typename T, typename AllocationType = HeapAllocation>
API_CLASS(InBuild) class HashSet
{
friend HashSet;
public:
/// <summary>
/// Describes single portion of space for the item in a hash map
/// Describes single portion of space for the item in a hash map.
/// </summary>
struct Bucket
{
friend HashSet;
public:
enum State : byte
{
Empty,
Deleted,
Occupied,
};
public:
/// <summary>The item.</summary>
T Item;
private:
State _state;
public:
Bucket()
: _state(Empty)
{
}
~Bucket()
{
}
public:
void Free()
{
if (_state == Occupied)
Memory::DestructItem(&Item);
_state = Empty;
}
void Delete()
{
_state = Deleted;
Memory::DestructItem(&Item);
}
void Occupy(const T& item)
template<typename ItemType>
void Occupy(const ItemType& item)
{
Item = item;
Memory::ConstructItems(&Item, &item, 1);
_state = Occupied;
}
public:
FORCE_INLINE bool IsEmpty() const
{
return _state == Empty;
@@ -94,13 +78,15 @@ public:
return _state != Occupied;
}
};
typedef typename AllocationType::template Data<Bucket> AllocationData;
private:
int32 _elementsCount = 0;
int32 _deletedCount = 0;
int32 _tableSize = 0;
Bucket* _table = nullptr;
int32 _size = 0;
AllocationData _allocation;
public:
@@ -117,10 +103,27 @@ public:
/// <param name="capacity">The initial capacity.</param>
HashSet(int32 capacity)
{
ASSERT(capacity >= 0);
SetCapacity(capacity);
}
/// <summary>
/// Initializes a new instance of the <see cref="HashSet"/> class.
/// </summary>
/// <param name="other">The other collection to move.</param>
HashSet(HashSet&& other) noexcept
: _elementsCount(other._elementsCount)
, _deletedCount(other._deletedCount)
, _size(other._size)
{
_elementsCount = other._elementsCount;
_deletedCount = other._deletedCount;
_size = other._size;
other._elementsCount = 0;
other._deletedCount = 0;
other._size = 0;
_allocation.Swap(other._allocation);
}
/// <summary>
/// Initializes a new instance of the <see cref="HashSet"/> class.
/// </summary>
@@ -137,18 +140,39 @@ public:
/// <returns>The reference to this.</returns>
HashSet& operator=(const HashSet& other)
{
// Ensure we're not trying to set to itself
if (this != &other)
Clone(other);
return *this;
}
/// <summary>
/// Moves the data from the other collection.
/// </summary>
/// <param name="other">The other collection to move.</param>
/// <returns>The reference to this.</returns>
HashSet& operator=(HashSet&& other) noexcept
{
if (this != &other)
{
Clear();
_allocation.Free();
_elementsCount = other._elementsCount;
_deletedCount = other._deletedCount;
_size = other._size;
other._elementsCount = 0;
other._deletedCount = 0;
other._size = 0;
_allocation.Swap(other._allocation);
}
return *this;
}
/// <summary>
/// Finalizes an instance of the <see cref="HashSet"/> class.
/// </summary>
~HashSet()
{
Cleanup();
SetCapacity(0, false);
}
public:
@@ -156,7 +180,6 @@ public:
/// <summary>
/// Gets the amount of the elements in the collection.
/// </summary>
/// <returns>The amount of elements in the collection.</returns>
FORCE_INLINE int32 Count() const
{
return _elementsCount;
@@ -165,16 +188,14 @@ public:
/// <summary>
/// Gets the amount of the elements that can be contained by the collection.
/// </summary>
/// <returns>The capacity of the collection.</returns>
FORCE_INLINE int32 Capacity() const
{
return _tableSize;
return _size;
}
/// <summary>
/// Returns true if collection is empty.
/// </summary>
/// <returns>True if is empty, otherwise false.</returns>
FORCE_INLINE bool IsEmpty() const
{
return _elementsCount == 0;
@@ -183,7 +204,6 @@ public:
/// <summary>
/// Returns true if collection has one or more elements.
/// </summary>
/// <returns>True if isn't empty, otherwise false.</returns>
FORCE_INLINE bool HasItems() const
{
return _elementsCount != 0;
@@ -197,9 +217,7 @@ public:
struct Iterator
{
friend HashSet;
private:
HashSet& _collection;
int32 _index;
@@ -223,41 +241,37 @@ public:
{
}
Iterator(Iterator&& i)
: _collection(i._collection)
, _index(i._index)
{
}
public:
/// <summary>
/// Checks if iterator is in the end of the collection
/// </summary>
/// <returns>True if is in the end, otherwise false</returns>
FORCE_INLINE bool IsEnd() const
{
return _index == _collection.Capacity();
}
/// <summary>
/// Checks if iterator is not in the end of the collection
/// </summary>
/// <returns>True if is not in the end, otherwise false</returns>
FORCE_INLINE bool IsNotEnd() const
{
return _index != _collection.Capacity();
}
public:
FORCE_INLINE Bucket& operator*() const
{
return _collection._table[_index];
return _collection._allocation.Get()[_index];
}
FORCE_INLINE Bucket* operator->() const
{
return &_collection._table[_index];
return &_collection._allocation.Get()[_index];
}
FORCE_INLINE explicit operator bool() const
{
return _index >= 0 && _index < _collection._tableSize;
return _index >= 0 && _index < _collection._size;
}
FORCE_INLINE bool operator !() const
@@ -275,17 +289,16 @@ public:
return _index != v._index || &_collection != &v._collection;
}
public:
Iterator& operator++()
{
const int32 capacity = _collection.Capacity();
if (_index != capacity)
{
const Bucket* data = _collection._allocation.Get();
do
{
_index++;
} while (_index != capacity && _collection._table[_index].IsNotOccupied());
} while (_index != capacity && data[_index].IsNotOccupied());
}
return *this;
}
@@ -301,10 +314,11 @@ public:
{
if (_index > 0)
{
const Bucket* data = _collection._allocation.Get();
do
{
_index--;
} while (_index > 0 && _collection._table[_index].IsNotOccupied());
} while (_index > 0 && data[_index].IsNotOccupied());
}
return *this;
}
@@ -324,22 +338,25 @@ public:
/// </summary>
void Clear()
{
if (_table)
if (_elementsCount + _deletedCount != 0)
{
// Free all buckets
// Note: this will not clear allocated objects space!
for (int32 i = 0; i < _tableSize; i++)
_table[i].Free();
Bucket* data = _allocation.Get();
for (int32 i = 0; i < _size; i++)
data[i].Free();
_elementsCount = _deletedCount = 0;
}
}
/// <summary>
/// Clear the collection and delete value objects.
/// Clears the collection and delete value objects.
/// Note: collection must contain pointers to the objects that have public destructor and be allocated using New method.
/// </summary>
#if defined(_MSC_VER)
template<typename = typename TEnableIf<TIsPointer<T>::Value>::Type>
#endif
void ClearDelete()
{
for (auto i = Begin(); i.IsNotEnd(); ++i)
for (Iterator i = Begin(); i.IsNotEnd(); ++i)
{
if (i->Value)
::Delete(i->Value);
@@ -354,86 +371,63 @@ public:
/// <param name="preserveContents">Enable/disable preserving collection contents during resizing</param>
void SetCapacity(int32 capacity, bool preserveContents = true)
{
// Validate input
ASSERT(capacity >= 0);
// Check if capacity won't change
if (capacity == Capacity())
return;
// Cache previous state
auto oldTable = _table;
auto oldTableSize = _tableSize;
// Clear elements counters
const auto oldElementsCount = _elementsCount;
ASSERT(capacity >= 0);
AllocationData oldAllocation;
oldAllocation.Swap(_allocation);
const int32 oldSize = _size;
const int32 oldElementsCount = _elementsCount;
_deletedCount = _elementsCount = 0;
// Check if need to create a new table
if (capacity > 0)
if (capacity != 0 && (capacity & (capacity - 1)) != 0)
{
// Align capacity value
if (Math::IsPowerOfTwo(capacity) == false)
capacity = Math::RoundUpToPowerOf2(capacity);
// Allocate new table
_table = NewArray<Bucket>(capacity);
_tableSize = capacity;
// Check if preserve content
if (oldElementsCount != 0 && preserveContents)
// Align capacity value to the next power of two (if it's not)
capacity++;
capacity |= capacity >> 1;
capacity |= capacity >> 2;
capacity |= capacity >> 4;
capacity |= capacity >> 8;
capacity |= capacity >> 16;
capacity = capacity + 1;
}
if (capacity)
{
_allocation.Allocate(capacity);
Bucket* data = _allocation.Get();
for (int32 i = 0; i < capacity; i++)
data[i]._state = Bucket::Empty;
}
_size = capacity;
Bucket* oldData = oldAllocation.Get();
if (oldElementsCount != 0 && preserveContents)
{
// TODO; move keys and values on realloc
for (int32 i = 0; i < oldSize; i++)
{
// Try to preserve all values in the collection
for (int32 i = 0; i < oldTableSize; i++)
{
if (oldTable[i].IsOccupied())
Add(oldTable[i].Item);
}
if (oldData[i].IsOccupied())
Add(oldData[i].Item);
}
}
else
if (oldElementsCount != 0)
{
// Clear data
_table = nullptr;
_tableSize = 0;
}
ASSERT(preserveContents == false || _elementsCount == oldElementsCount);
// Delete old table
if (oldTable)
{
DeleteArray(oldTable, oldTableSize);
for (int32 i = 0; i < oldSize; i++)
oldData[i].Free();
}
}
/// <summary>
/// Increases collection capacity by given extra size (content will be preserved)
/// Ensures that collection has given capacity.
/// </summary>
/// <param name="extraSize">Extra size to enlarge collection</param>
FORCE_INLINE void IncreaseCapacity(int32 extraSize)
{
ASSERT(extraSize >= 0);
SetCapacity(Capacity() + extraSize);
}
/// <summary>
/// Ensures that collection has given capacity
/// </summary>
/// <param name="minCapacity">Minimum required capacity</param>
void EnsureCapacity(int32 minCapacity)
/// <param name="minCapacity">The minimum required capacity.</param>
/// <param name="preserveContents">True if preserve collection data when changing its size, otherwise collection after resize will be empty.</param>
void EnsureCapacity(int32 minCapacity, bool preserveContents = true)
{
if (Capacity() >= minCapacity)
return;
int32 num = Capacity() == 0 ? DICTIONARY_DEFAULT_CAPACITY : Capacity() * 2;
SetCapacity(Math::Clamp<int32>(num, minCapacity, MAX_int32 - 1410));
}
/// <summary>
/// Cleanup collection data (changes size to 0 without data preserving)
/// </summary>
FORCE_INLINE void Cleanup()
{
SetCapacity(0, false);
if (minCapacity < DICTIONARY_DEFAULT_CAPACITY)
minCapacity = DICTIONARY_DEFAULT_CAPACITY;
const int32 capacity = _allocation.CalculateCapacityGrow(_size, minCapacity);
SetCapacity(capacity, preserveContents);
}
public:
@@ -443,7 +437,8 @@ public:
/// </summary>
/// <param name="item">The element to add to the set.</param>
/// <returns>True if element has been added to the collection, otherwise false if the element is already present.</returns>
bool Add(const T& item)
template<typename ItemType>
bool Add(const ItemType& item)
{
// Ensure to have enough memory for the next item (in case of new element insertion)
EnsureCapacity(_elementsCount + _deletedCount + 1);
@@ -453,12 +448,12 @@ public:
FindPosition(item, pos);
// Check if object has been already added
if (pos.ObjectIndex != INVALID_INDEX)
if (pos.ObjectIndex != -1)
return false;
// Insert
ASSERT(pos.FreeSlotIndex != INVALID_INDEX);
auto bucket = &_table[pos.FreeSlotIndex];
ASSERT(pos.FreeSlotIndex != -1);
Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex];
bucket->Occupy(item);
_elementsCount++;
@@ -472,7 +467,7 @@ public:
void Add(const Iterator& i)
{
ASSERT(&i._collection != this && i);
Bucket& bucket = *i;
const Bucket& bucket = *i;
Add(bucket.Item);
}
@@ -481,22 +476,20 @@ public:
/// </summary>
/// <param name="item">The element to remove.</param>
/// <returns>True if cannot remove item from the collection because cannot find it, otherwise false.</returns>
bool Remove(const T& item)
template<typename ItemType>
bool Remove(const ItemType& item)
{
if (IsEmpty())
return true;
return false;
FindPositionResult pos;
FindPosition(item, pos);
if (pos.ObjectIndex != INVALID_INDEX)
if (pos.ObjectIndex != -1)
{
_table[pos.ObjectIndex].Delete();
_allocation.Get()[pos.ObjectIndex].Delete();
_elementsCount--;
_deletedCount++;
return true;
}
return false;
}
@@ -510,8 +503,8 @@ public:
ASSERT(&i._collection == this);
if (i)
{
ASSERT(_table[i._index].IsOccupied());
_table[i._index].Delete();
ASSERT(_allocation.Get()[i._index].IsOccupied());
_allocation.Get()[i._index].Delete();
_elementsCount--;
_deletedCount++;
return true;
@@ -526,15 +519,14 @@ public:
/// </summary>
/// <param name="item">Item to find</param>
/// <returns>Iterator for the found element or End if cannot find it</returns>
Iterator Find(const T& item) const
template<typename ItemType>
Iterator Find(const ItemType& item) const
{
if (IsEmpty())
return End();
FindPositionResult pos;
FindPosition(item, pos);
return pos.ObjectIndex != INVALID_INDEX ? Iterator(*this, pos.ObjectIndex) : End();
return pos.ObjectIndex != -1 ? Iterator(*this, pos.ObjectIndex) : End();
}
/// <summary>
@@ -542,15 +534,14 @@ public:
/// </summary>
/// <param name="item">The item to locate.</param>
/// <returns>True if value has been found in a collection, otherwise false</returns>
bool Contains(const T& item) const
template<typename ItemType>
bool Contains(const ItemType& item) const
{
if (IsEmpty())
return false;
FindPositionResult pos;
FindPosition(item, pos);
return pos.ObjectIndex != INVALID_INDEX;
return pos.ObjectIndex != -1;
}
public:
@@ -561,41 +552,26 @@ public:
/// <param name="other">Other collection to clone</param>
void Clone(const HashSet& other)
{
// Clear previous data
Clear();
// Update capacity
SetCapacity(other.Capacity(), false);
// Clone items
for (auto i = other.Begin(); i != other.End(); ++i)
for (Iterator i = other.Begin(); i != other.End(); ++i)
Add(i);
// Check
ASSERT(Count() == other.Count());
ASSERT(Capacity() == other.Capacity());
}
public:
/// <summary>
/// Gets iterator for beginning of the collection.
/// </summary>
/// <returns>Iterator for beginning of the collection.</returns>
Iterator Begin() const
{
Iterator i(*this, INVALID_INDEX);
Iterator i(*this, -1);
++i;
return i;
}
/// <summary>
/// Gets iterator for ending of the collection.
/// </summary>
/// <returns>Iterator for ending of the collection.</returns>
Iterator End() const
{
return Iterator(*this, _tableSize);
return Iterator(*this, _size);
}
Iterator begin()
@@ -607,7 +583,7 @@ public:
FORCE_INLINE Iterator end()
{
return Iterator(*this, _tableSize);
return Iterator(*this, _size);
}
const Iterator begin() const
@@ -619,11 +595,14 @@ public:
FORCE_INLINE const Iterator end() const
{
return Iterator(*this, _tableSize);
return Iterator(*this, _size);
}
protected:
/// <summary>
/// The result container of the set item lookup searching.
/// </summary>
struct FindPositionResult
{
int32 ObjectIndex;
@@ -632,42 +611,43 @@ protected:
/// <summary>
/// Returns a pair of positions: 1st where the object is, 2nd where
/// it would go if you wanted to insert it. 1st is INVALID_INDEX
/// if object is not found; 2nd is INVALID_INDEX if it is.
/// it would go if you wanted to insert it. 1st is -1
/// if object is not found; 2nd is -1 if it is.
/// Note: because of deletions where-to-insert is not trivial: it's the
/// first deleted bucket we see, as long as we don't find the item later
/// </summary>
/// <param name="item">The item to find</param>
/// <param name="result">Pair of values: where the object is and where it would go if you wanted to insert it</param>
void FindPosition(const T& item, FindPositionResult& result) const
template<typename ItemType>
void FindPosition(const ItemType& item, FindPositionResult& result) const
{
ASSERT(_table);
const int32 tableSizeMinusOne = _tableSize - 1;
ASSERT(_size);
const int32 tableSizeMinusOne = _size - 1;
int32 bucketIndex = GetHash(item) & tableSizeMinusOne;
int32 insertPos = INVALID_INDEX;
int32 insertPos = -1;
int32 numChecks = 0;
result.FreeSlotIndex = INVALID_INDEX;
while (numChecks < _tableSize)
const Bucket* data = _allocation.Get();
result.FreeSlotIndex = -1;
while (numChecks < _size)
{
// Empty bucket
if (_table[bucketIndex].IsEmpty())
const Bucket& bucket = data[bucketIndex];
if (bucket.IsEmpty())
{
// Found place to insert
result.ObjectIndex = INVALID_INDEX;
result.FreeSlotIndex = insertPos == INVALID_INDEX ? bucketIndex : insertPos;
result.ObjectIndex = -1;
result.FreeSlotIndex = insertPos == -1 ? bucketIndex : insertPos;
return;
}
// Deleted bucket
if (_table[bucketIndex].IsDeleted())
if (bucket.IsDeleted())
{
// Keep searching but mark to insert
if (insertPos == INVALID_INDEX)
if (insertPos == -1)
insertPos = bucketIndex;
}
// Occupied bucket by target item
else if (_table[bucketIndex].Item == item)
else if (bucket.Item == item)
{
// Found item
result.ObjectIndex = bucketIndex;
@@ -675,10 +655,9 @@ protected:
}
numChecks++;
bucketIndex = (bucketIndex + DICTIONARY_PROB_FUNC(_tableSize, numChecks)) & tableSizeMinusOne;
bucketIndex = (bucketIndex + DICTIONARY_PROB_FUNC(_size, numChecks)) & tableSizeMinusOne;
}
result.ObjectIndex = INVALID_INDEX;
result.ObjectIndex = -1;
result.FreeSlotIndex = insertPos;
}
};

View File

@@ -334,7 +334,7 @@ namespace FlaxEditor.Content.Settings
}
// Create new settings asset and link it to the game settings
var path = StringUtils.CombinePaths(Globals.ProjectContentFolder, "Settings", CustomEditors.CustomEditorsUtil.GetPropertyNameUI(typeof(T).Name) + ".json");
var path = StringUtils.CombinePaths(Globals.ProjectContentFolder, "Settings", Utilities.Utils.GetPropertyNameUI(typeof(T).Name) + ".json");
if (Editor.SaveJsonAsset(path, obj))
return true;
asset = FlaxEngine.Content.LoadAsync<JsonAsset>(path);

View File

@@ -11,9 +11,9 @@
/// </summary>
API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API GraphicsSettings : public SettingsBase
{
DECLARE_SCRIPTING_TYPE_MINIMAL(GraphicsSettings);
API_AUTO_SERIALIZATION();
DECLARE_SCRIPTING_TYPE_MINIMAL(GraphicsSettings);
public:
/// <summary>
/// Enables rendering synchronization with the refresh rate of the display device to avoid "tearing" artifacts.
/// </summary>
@@ -62,8 +62,21 @@ public:
API_FIELD(Attributes="EditorOrder(1320), DefaultValue(false), EditorDisplay(\"Quality\", \"Allow CSM Blending\")")
bool AllowCSMBlending = false;
public:
/// <summary>
/// If checked, enables Global SDF rendering. This can be used in materials, shaders, and particles.
/// </summary>
API_FIELD(Attributes="EditorOrder(2000), EditorDisplay(\"Global SDF\")")
bool EnableGlobalSDF = false;
#if USE_EDITOR
/// <summary>
/// If checked, the 'Generate SDF' option will be checked on model import options by default. Use it if your project uses Global SDF (eg. for Global Illumination or particles).
/// </summary>
API_FIELD(Attributes="EditorOrder(2010), EditorDisplay(\"Global SDF\")")
bool GenerateSDFOnModelImport = false;
#endif
public:
/// <summary>
/// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use.
/// </summary>
@@ -71,15 +84,4 @@ public:
// [SettingsBase]
void Apply() override;
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override
{
DESERIALIZE(UseVSync);
DESERIALIZE(AAQuality);
DESERIALIZE(SSRQuality);
DESERIALIZE(SSAOQuality);
DESERIALIZE(VolumetricFogQuality);
DESERIALIZE(ShadowsQuality);
DESERIALIZE(ShadowMapsQuality);
DESERIALIZE(AllowCSMBlending);
}
};

View File

@@ -13,6 +13,26 @@ String BoundingBox::ToString() const
return String::Format(TEXT("{}"), *this);
}
void BoundingBox::GetCorners(Vector3 corners[8]) const
{
corners[0] = Vector3(Minimum.X, Maximum.Y, Maximum.Z);
corners[1] = Vector3(Maximum.X, Maximum.Y, Maximum.Z);
corners[2] = Vector3(Maximum.X, Minimum.Y, Maximum.Z);
corners[3] = Vector3(Minimum.X, Minimum.Y, Maximum.Z);
corners[4] = Vector3(Minimum.X, Maximum.Y, Minimum.Z);
corners[5] = Vector3(Maximum.X, Maximum.Y, Minimum.Z);
corners[6] = Vector3(Maximum.X, Minimum.Y, Minimum.Z);
corners[7] = Vector3(Minimum.X, Minimum.Y, Minimum.Z);
}
BoundingBox BoundingBox::MakeOffsetted(const Vector3& offset) const
{
BoundingBox result;
result.Minimum = Minimum + offset;
result.Maximum = Maximum + offset;
return result;
}
void BoundingBox::FromPoints(const Vector3* points, int32 pointsCount, BoundingBox& result)
{
ASSERT(points && pointsCount > 0);

View File

@@ -76,17 +76,7 @@ public:
/// Gets the eight corners of the bounding box.
/// </summary>
/// <param name="corners">An array of points representing the eight corners of the bounding box.</param>
void GetCorners(Vector3 corners[8]) const
{
corners[0] = Vector3(Minimum.X, Maximum.Y, Maximum.Z);
corners[1] = Vector3(Maximum.X, Maximum.Y, Maximum.Z);
corners[2] = Vector3(Maximum.X, Minimum.Y, Maximum.Z);
corners[3] = Vector3(Minimum.X, Minimum.Y, Maximum.Z);
corners[4] = Vector3(Minimum.X, Maximum.Y, Minimum.Z);
corners[5] = Vector3(Maximum.X, Maximum.Y, Minimum.Z);
corners[6] = Vector3(Maximum.X, Minimum.Y, Minimum.Z);
corners[7] = Vector3(Minimum.X, Minimum.Y, Minimum.Z);
}
void GetCorners(Vector3 corners[8]) const;
/// <summary>
/// Calculates volume of the box.
@@ -200,13 +190,7 @@ public:
/// </summary>
/// <param name="offset">The offset.</param>
/// <returns>The result.</returns>
BoundingBox MakeOffsetted(const Vector3& offset) const
{
BoundingBox result;
result.Minimum = Minimum + offset;
result.Maximum = Maximum + offset;
return result;
}
BoundingBox MakeOffsetted(const Vector3& offset) const;
public:
@@ -421,6 +405,26 @@ public:
{
return CollisionsHelper::BoxContainsSphere(*this, sphere);
}
/// <summary>
/// Determines the distance between a Bounding Box and a point.
/// </summary>
/// <param name="point">The point to test.</param>
/// <returns>The distance between bounding box and a point.</returns>
FORCE_INLINE float Distance(const Vector3& point) const
{
return CollisionsHelper::DistanceBoxPoint(*this, point);
}
/// <summary>
/// Determines the distance between two Bounding Boxed.
/// </summary>
/// <param name="box">The bounding box to test.</param>
/// <returns>The distance between bounding boxes.</returns>
FORCE_INLINE float Distance(const BoundingBox& box) const
{
return CollisionsHelper::DistanceBoxBox(*this, box);
}
};
template<>

View File

@@ -1124,7 +1124,6 @@ bool CollisionsHelper::BoxIntersectsSphere(const BoundingBox& box, const Boundin
Vector3 vector;
Vector3::Clamp(sphere.Center, box.Minimum, box.Maximum, vector);
const float distance = Vector3::DistanceSquared(sphere.Center, vector);
return distance <= sphere.Radius * sphere.Radius;
}
@@ -1438,6 +1437,16 @@ bool CollisionsHelper::LineIntersectsRect(const Vector2& p1, const Vector2& p2,
return (topoverlap < botoverlap) && (!((botoverlap < t) || (topoverlap > b)));*/
}
Vector2 CollisionsHelper::LineHitsBox(const Vector3& lineStart, const Vector3& lineEnd, const Vector3& boxMin, const Vector3& boxMax)
{
const Vector3 invDirection = 1.0f / (lineEnd - lineStart);
const Vector3 enterIntersection = (boxMin - lineStart) * invDirection;
const Vector3 exitIntersection = (boxMax - lineStart) * invDirection;
const Vector3 minIntersections = Vector3::Min(enterIntersection, exitIntersection);
const Vector3 maxIntersections = Vector3::Max(enterIntersection, exitIntersection);
return Vector2(Math::Saturate(minIntersections.MaxValue()), Math::Saturate(maxIntersections.MinValue()));
}
bool CollisionsHelper::IsPointInTriangle(const Vector2& point, const Vector2& a, const Vector2& b, const Vector2& c)
{
const Vector2 an = a - point;

View File

@@ -569,6 +569,12 @@ public:
/// <returns>True if line intersects with the rectangle</returns>
static bool LineIntersectsRect(const Vector2& p1, const Vector2& p2, const Rectangle& rect);
// Hits axis-aligned box (boxMin, boxMax) with a line (lineStart, lineEnd).
// Returns the intersections on the line (x - closest, y - furthest).
// Line hits the box if: intersections.x < intersections.y.
// Hit point is: hitPoint = lineStart + (lineEnd - lineStart) * intersections.x/y.
static Vector2 LineHitsBox(const Vector3& lineStart, const Vector3& lineEnd, const Vector3& boxMin, const Vector3& boxMax);
/// <summary>
/// Determines whether the given 2D point is inside the specified triangle.
/// </summary>

View File

@@ -12,9 +12,9 @@ static_assert(sizeof(Half2) == 4, "Invalid Half2 type size.");
static_assert(sizeof(Half3) == 6, "Invalid Half3 type size.");
static_assert(sizeof(Half4) == 8, "Invalid Half4 type size.");
Half2 Half2::Zero(0, 0);
Half3 Half3::Zero(0, 0, 0);
Half4 Half4::Zero(0, 0, 0, 0);
Half2 Half2::Zero(0.0f, 0.0f);
Half3 Half3::Zero(0.0f, 0.0f, 0.0f);
Half4 Half4::Zero(0.0f, 0.0f, 0.0f, 0.0f);
Half2::Half2(const Vector2& v)
{

View File

@@ -45,7 +45,6 @@ class FLAXENGINE_API Float16Compressor
static const int32 minD = minC - subC - 1;
public:
static Half Compress(const float value)
{
#if USE_SSE_HALF_CONVERSION
@@ -102,14 +101,12 @@ public:
struct FLAXENGINE_API Half2
{
public:
/// <summary>
/// Zero vector
/// </summary>
static Half2 Zero;
public:
/// <summary>
/// Gets or sets the X component of the vector.
/// </summary>
@@ -121,7 +118,6 @@ public:
Half Y;
public:
/// <summary>
/// Default constructor
/// </summary>
@@ -129,6 +125,17 @@ public:
{
}
/// <summary>
/// Init
/// </summary>
/// <param name="x">X component</param>
/// <param name="y">Y component</param>
Half2(Half x, Half y)
: X(x)
, Y(y)
{
}
/// <summary>
/// Init
/// </summary>
@@ -147,7 +154,6 @@ public:
Half2(const Vector2& v);
public:
/// <summary>
/// Convert to Vector2
/// </summary>
@@ -161,14 +167,12 @@ public:
struct FLAXENGINE_API Half3
{
public:
/// <summary>
/// Zero vector
/// </summary>
static Half3 Zero;
public:
/// <summary>
/// Gets or sets the X component of the vector.
/// </summary>
@@ -185,11 +189,17 @@ public:
Half Z;
public:
Half3()
{
}
Half3(Half x, Half y, Half z)
: X(x)
, Y(y)
, Z(z)
{
}
Half3(const float x, const float y, const float z)
{
X = Float16Compressor::Compress(x);
@@ -200,7 +210,6 @@ public:
Half3(const Vector3& v);
public:
Vector3 ToVector3() const;
};
@@ -210,14 +219,12 @@ public:
struct FLAXENGINE_API Half4
{
public:
/// <summary>
/// Zero vector
/// </summary>
static Half4 Zero;
public:
/// <summary>
/// Gets or sets the X component of the vector.
/// </summary>
@@ -239,11 +246,18 @@ public:
Half W;
public:
Half4()
{
}
Half4(Half x, Half y, Half z, Half w)
: X(x)
, Y(y)
, Z(z)
, W(w)
{
}
Half4(const float x, const float y, const float z)
{
X = Float16Compressor::Compress(x);
@@ -265,7 +279,6 @@ public:
explicit Half4(const Rectangle& rect);
public:
Vector2 ToVector2() const;
Vector3 ToVector3() const;
Vector4 ToVector4() const;

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
#include "Matrix.h"
#include "Matrix3x3.h"
#include "Vector2.h"
#include "Quaternion.h"
#include "Transform.h"
@@ -15,6 +16,17 @@ const Matrix Matrix::Identity(
0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f);
Matrix::Matrix(const Matrix3x3& matrix)
{
Platform::MemoryCopy(&M11, &matrix.M11, sizeof(Vector3));
Platform::MemoryCopy(&M21, &matrix.M21, sizeof(Vector3));
Platform::MemoryCopy(&M31, &matrix.M31, sizeof(Vector3));
M14 = 0.0f;
M24 = 0.0f;
M34 = 0.0f;
M44 = 1.0f;
}
String Matrix::ToString() const
{
return String::Format(TEXT("{}"), *this);
@@ -284,8 +296,6 @@ void Matrix::LookAt(const Vector3& eye, const Vector3& target, const Vector3& up
xaxis.Normalize();
Vector3::Cross(zaxis, xaxis, yaxis);
result = Identity;
result.M11 = xaxis.X;
result.M21 = xaxis.Y;
result.M31 = xaxis.Z;
@@ -298,9 +308,14 @@ void Matrix::LookAt(const Vector3& eye, const Vector3& target, const Vector3& up
result.M23 = zaxis.Y;
result.M33 = zaxis.Z;
result.M14 = 0.0f;
result.M24 = 0.0f;
result.M34 = 0.0f;
result.M41 = -Vector3::Dot(xaxis, eye);
result.M42 = -Vector3::Dot(yaxis, eye);
result.M43 = -Vector3::Dot(zaxis, eye);
result.M44 = 1.0f;
}
void Matrix::OrthoOffCenter(float left, float right, float bottom, float top, float zNear, float zFar, Matrix& result)
@@ -587,33 +602,7 @@ void Matrix::Transformation2D(Vector2& scalingCenter, float scalingRotation, con
Matrix Matrix::CreateWorld(const Vector3& position, const Vector3& forward, const Vector3& up)
{
Matrix result;
Vector3 vector3, vector31, vector32;
Vector3::Normalize(forward, vector3);
vector3.Negate();
Vector3::Normalize(Vector3::Cross(up, vector3), vector31);
Vector3::Cross(vector3, vector31, vector32);
result.M11 = vector31.X;
result.M12 = vector31.Y;
result.M13 = vector31.Z;
result.M14 = 0.0f;
result.M21 = vector32.X;
result.M22 = vector32.Y;
result.M23 = vector32.Z;
result.M24 = 0.0f;
result.M31 = vector3.X;
result.M32 = vector3.Y;
result.M33 = vector3.Z;
result.M34 = 0.0f;
result.M41 = position.X;
result.M42 = position.Y;
result.M43 = position.Z;
result.M44 = 1.0f;
CreateWorld(position, forward, up, result);
return result;
}
@@ -649,41 +638,9 @@ void Matrix::CreateWorld(const Vector3& position, const Vector3& forward, const
Matrix Matrix::CreateFromAxisAngle(const Vector3& axis, float angle)
{
Matrix matrix;
const float x = axis.X;
const float y = axis.Y;
const float z = axis.Z;
const float single = Math::Sin(angle);
const float single1 = Math::Cos(angle);
const float single2 = x * x;
const float single3 = y * y;
const float single4 = z * z;
const float single5 = x * y;
const float single6 = x * z;
const float single7 = y * z;
matrix.M11 = single2 + single1 * (1.0f - single2);
matrix.M12 = single5 - single1 * single5 + single * z;
matrix.M13 = single6 - single1 * single6 - single * y;
matrix.M14 = 0.0f;
matrix.M21 = single5 - single1 * single5 - single * z;
matrix.M22 = single3 + single1 * (1.0f - single3);
matrix.M23 = single7 - single1 * single7 + single * x;
matrix.M24 = 0.0f;
matrix.M31 = single6 - single1 * single6 + single * y;
matrix.M32 = single7 - single1 * single7 - single * x;
matrix.M33 = single4 + single1 * (1.0f - single4);
matrix.M34 = 0.0f;
matrix.M41 = 0.0f;
matrix.M42 = 0.0f;
matrix.M43 = 0.0f;
matrix.M44 = 1.0f;
return matrix;
Matrix result;
CreateFromAxisAngle(axis, angle, result);
return result;
}
void Matrix::CreateFromAxisAngle(const Vector3& axis, float angle, Matrix& result)

View File

@@ -150,6 +150,8 @@ public:
Platform::MemoryCopy(Raw, values, sizeof(float) * 16);
}
explicit Matrix(const Matrix3x3& matrix);
public:
String ToString() const;

View File

@@ -1,8 +1,9 @@
// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
#include "Matrix3x3.h"
#include "../Types/String.h"
#include "Matrix.h"
#include "Quaternion.h"
#include "../Types/String.h"
const Matrix3x3 Matrix3x3::Zero(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
const Matrix3x3 Matrix3x3::Identity(
@@ -10,11 +11,37 @@ const Matrix3x3 Matrix3x3::Identity(
0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 1.0f);
Matrix3x3::Matrix3x3(const Matrix& matrix)
{
Platform::MemoryCopy(&M11, &matrix.M11, sizeof(Vector3));
Platform::MemoryCopy(&M21, &matrix.M21, sizeof(Vector3));
Platform::MemoryCopy(&M31, &matrix.M31, sizeof(Vector3));
}
String Matrix3x3::ToString() const
{
return String::Format(TEXT("{}"), *this);
}
void Matrix3x3::NormalizeScale()
{
const float scaleX = 1.0f / Vector3(M11, M21, M31).Length();
const float scaleY = 1.0f / Vector3(M12, M22, M32).Length();
const float scaleZ = 1.0f / Vector3(M13, M23, M33).Length();
M11 *= scaleX;
M21 *= scaleX;
M31 *= scaleX;
M12 *= scaleY;
M22 *= scaleY;
M32 *= scaleY;
M13 *= scaleZ;
M23 *= scaleZ;
M33 *= scaleZ;
}
void Matrix3x3::Invert(const Matrix3x3& value, Matrix3x3& result)
{
const float d11 = value.M22 * value.M33 + value.M23 * -value.M32;

View File

@@ -113,6 +113,12 @@ public:
Platform::MemoryCopy(Raw, values, sizeof(float) * 9);
}
/// <summary>
/// Initializes a new instance of the <see cref="Matrix3x3"/> struct.
/// </summary>
/// <param name="matrix">The 4 by 4 matrix to initialize from with rotation and scale (translation is skipped).</param>
explicit Matrix3x3(const Matrix& matrix);
public:
String ToString() const;
@@ -255,6 +261,11 @@ public:
Transpose(*this, *this);
}
/// <summary>
/// Removes any scaling from the matrix by performing the normalization (each row magnitude is 1).
/// </summary>
void NormalizeScale();
public:
/// <summary>

View File

@@ -8,11 +8,18 @@
OrientedBoundingBox::OrientedBoundingBox(const BoundingBox& bb)
{
const Vector3 center = bb.Minimum + (bb.Maximum - bb.Minimum) / 2.0f;
const Vector3 center = bb.Minimum + (bb.Maximum - bb.Minimum) * 0.5f;
Extents = bb.Maximum - center;
Matrix::Translation(center, Transformation);
}
OrientedBoundingBox::OrientedBoundingBox(const Vector3& extents, const Matrix3x3& rotationScale, const Vector3& translation)
: Extents(extents)
, Transformation(rotationScale)
{
Transformation.SetTranslation(translation);
}
OrientedBoundingBox::OrientedBoundingBox(Vector3 points[], int32 pointCount)
{
ASSERT(points && pointCount > 0);
@@ -104,6 +111,12 @@ void OrientedBoundingBox::GetBoundingBox(BoundingBox& result) const
BoundingBox::FromPoints(corners, 8, result);
}
void OrientedBoundingBox::Transform(const Matrix& matrix)
{
const Matrix tmp = Transformation;
Matrix::Multiply(tmp, matrix, Transformation);
}
ContainmentType OrientedBoundingBox::Contains(const Vector3& point, float* distance) const
{
// Transform the point into the obb coordinates

View File

@@ -40,6 +40,8 @@ public:
Transformation = transformation;
}
OrientedBoundingBox(const Vector3& extents, const Matrix3x3& rotationScale, const Vector3& translation);
// Init
// @param minimum The minimum vertex of the bounding box.
// @param maximum The maximum vertex of the bounding box.
@@ -99,10 +101,7 @@ public:
// Transforms this box using a transformation matrix.
// @param mat The transformation matrix.
void Transform(const Matrix& mat)
{
Transformation *= mat;
}
void Transform(const Matrix& matrix);
// Scales the OBB by scaling its Extents without affecting the Transformation matrix.
// By keeping Transformation matrix scaling-free, the collision detection methods will be more accurate.

View File

@@ -4,6 +4,7 @@
#include "Vector3.h"
#include "Vector4.h"
#include "Matrix.h"
#include "Matrix3x3.h"
#include "Math.h"
#include "../Types/String.h"
@@ -208,6 +209,59 @@ void Quaternion::RotationMatrix(const Matrix& matrix, Quaternion& result)
result.Normalize();
}
void Quaternion::RotationMatrix(const Matrix3x3& matrix, Quaternion& result)
{
float sqrtV;
float half;
const float scale = matrix.M11 + matrix.M22 + matrix.M33;
if (scale > 0.0f)
{
sqrtV = Math::Sqrt(scale + 1.0f);
result.W = sqrtV * 0.5f;
sqrtV = 0.5f / sqrtV;
result.X = (matrix.M23 - matrix.M32) * sqrtV;
result.Y = (matrix.M31 - matrix.M13) * sqrtV;
result.Z = (matrix.M12 - matrix.M21) * sqrtV;
}
else if (matrix.M11 >= matrix.M22 && matrix.M11 >= matrix.M33)
{
sqrtV = Math::Sqrt(1.0f + matrix.M11 - matrix.M22 - matrix.M33);
half = 0.5f / sqrtV;
result = Quaternion(
0.5f * sqrtV,
(matrix.M12 + matrix.M21) * half,
(matrix.M13 + matrix.M31) * half,
(matrix.M23 - matrix.M32) * half);
}
else if (matrix.M22 > matrix.M33)
{
sqrtV = Math::Sqrt(1.0f + matrix.M22 - matrix.M11 - matrix.M33);
half = 0.5f / sqrtV;
result = Quaternion(
(matrix.M21 + matrix.M12) * half,
0.5f * sqrtV,
(matrix.M32 + matrix.M23) * half,
(matrix.M31 - matrix.M13) * half);
}
else
{
sqrtV = Math::Sqrt(1.0f + matrix.M33 - matrix.M11 - matrix.M22);
half = 0.5f / sqrtV;
result = Quaternion(
(matrix.M31 + matrix.M13) * half,
(matrix.M32 + matrix.M23) * half,
0.5f * sqrtV,
(matrix.M12 - matrix.M21) * half);
}
result.Normalize();
}
void Quaternion::LookAt(const Vector3& eye, const Vector3& target, const Vector3& up, Quaternion& result)
{
Matrix matrix;

View File

@@ -10,6 +10,7 @@ struct Vector2;
struct Vector3;
struct Vector4;
struct Matrix;
struct Matrix3x3;
/// <summary>
/// Represents a four dimensional mathematical quaternion. Euler angles are stored in: pitch, yaw, roll order (x, y, z).
@@ -566,6 +567,11 @@ public:
// @param result When the method completes, contains the newly created quaternion
static void RotationMatrix(const Matrix& matrix, Quaternion& result);
// Creates a quaternion given a rotation matrix
// @param matrix The rotation matrix
// @param result When the method completes, contains the newly created quaternion
static void RotationMatrix(const Matrix3x3& matrix, Quaternion& result);
// Creates a left-handed, look-at quaternion
// @param eye The position of the viewer's eye
// @param target The camera look-at target

Some files were not shown because too many files have changed in this diff Show More