Merge remote-tracking branch 'origin/gi' into large-worlds
# Conflicts: # Source/Engine/Core/Math/Vector3.h
This commit is contained in:
@@ -148,5 +148,6 @@ bin/
|
||||
obj/
|
||||
*.vcxproj.filters
|
||||
.vscode/
|
||||
.idea/
|
||||
*.code-workspace
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+2
-2
@@ -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.",
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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>"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -67,7 +67,7 @@ namespace FlaxEditor.GUI
|
||||
{
|
||||
Find(Level.GetScene(i));
|
||||
}
|
||||
SortChildren();
|
||||
SortItems();
|
||||
}
|
||||
|
||||
private void OnItemClicked(Item item)
|
||||
|
||||
@@ -122,7 +122,7 @@ namespace FlaxEditor.GUI
|
||||
if (project.Content != null)
|
||||
FindAssets(project.Content.Folder);
|
||||
}
|
||||
SortChildren();
|
||||
SortItems();
|
||||
}
|
||||
|
||||
private void OnItemClicked(Item item)
|
||||
|
||||
@@ -78,7 +78,7 @@ namespace FlaxEditor.GUI
|
||||
{
|
||||
Find(Level.GetScene(i));
|
||||
}
|
||||
SortChildren();
|
||||
SortItems();
|
||||
}
|
||||
|
||||
private void OnItemClicked(Item item)
|
||||
|
||||
@@ -95,7 +95,7 @@ namespace FlaxEditor.GUI
|
||||
}
|
||||
}
|
||||
}
|
||||
SortChildren();
|
||||
SortItems();
|
||||
}
|
||||
|
||||
private void OnItemClicked(Item item)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -126,7 +126,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
});
|
||||
}
|
||||
cm.ItemClicked += item => AddModule((ushort)item.Tag);
|
||||
cm.SortChildren();
|
||||
cm.SortItems();
|
||||
cm.Show(this, button.BottomLeft);
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -466,7 +466,7 @@ namespace FlaxEditor.Surface
|
||||
cm.AddItem(item);
|
||||
}
|
||||
cm.ItemClicked += OnAddParameterItemClicked;
|
||||
cm.SortChildren();
|
||||
cm.SortItems();
|
||||
cm.Show(button.Parent, button.BottomLeft);
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -262,6 +262,9 @@ namespace FlaxEditor.Windows.Assets
|
||||
base.OnSurfaceEditingStart();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override bool CanEditSurfaceOnAssetLoadError => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override bool SaveToOriginal()
|
||||
{
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -83,7 +83,7 @@ void Animation::ClearCache()
|
||||
|
||||
// Free memory
|
||||
MappingCache.Clear();
|
||||
MappingCache.Cleanup();
|
||||
MappingCache.SetCapacity(0);
|
||||
}
|
||||
|
||||
const Animation::NodeToChannel* Animation::GetMapping(SkinnedModel* obj)
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -150,6 +150,8 @@ public:
|
||||
Platform::MemoryCopy(Raw, values, sizeof(float) * 16);
|
||||
}
|
||||
|
||||
explicit Matrix(const Matrix3x3& matrix);
|
||||
|
||||
public:
|
||||
|
||||
String ToString() const;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user