Merge remote-tracking branch 'origin/gi' into large-worlds
# Conflicts: # Source/Engine/Core/Math/Vector3.h
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
BIN
Content/Editor/Primitives/Capsule.flax
(Stored with Git LFS)
BIN
Content/Editor/Primitives/Capsule.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Editor/Primitives/Cone.flax
(Stored with Git LFS)
BIN
Content/Editor/Primitives/Cone.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Editor/Primitives/Cube.flax
(Stored with Git LFS)
BIN
Content/Editor/Primitives/Cube.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Editor/Primitives/Cylinder.flax
(Stored with Git LFS)
BIN
Content/Editor/Primitives/Cylinder.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Editor/Primitives/Plane.flax
(Stored with Git LFS)
BIN
Content/Editor/Primitives/Plane.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Editor/Primitives/Sphere.flax
(Stored with Git LFS)
BIN
Content/Editor/Primitives/Sphere.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Engine/Models/Quad.flax
(Stored with Git LFS)
BIN
Content/Engine/Models/Quad.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Engine/Models/Sphere.flax
(Stored with Git LFS)
BIN
Content/Engine/Models/Sphere.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Shaders/GI/GlobalSurfaceAtlas.flax
(Stored with Git LFS)
Normal file
BIN
Content/Shaders/GI/GlobalSurfaceAtlas.flax
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Content/Shaders/GlobalSignDistanceField.flax
(Stored with Git LFS)
Normal file
BIN
Content/Shaders/GlobalSignDistanceField.flax
(Stored with Git LFS)
Normal file
Binary file not shown.
@@ -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