Merge branch 'development' of https://github.com/o3de/o3de into TerrainMaterialsFix

This commit is contained in:
Sergey Pereslavtsev
2022-02-08 12:49:12 +00:00
155 changed files with 3917 additions and 2638 deletions
@@ -8,33 +8,41 @@
"Name": "UserInterface_Compressed",
"SuppressEngineReduce": true,
"PixelFormat": "R8G8B8A8",
"SourceColor": "Linear",
"DestColor": "Linear"
"SourceColor": "sRGB",
"DestColor": "sRGB"
},
"PlatformsPresets": {
"android": {
"UUID": "{2828FBFE-BDF9-45A7-9370-F93822719CCF}",
"Name": "UserInterface_Compressed",
"SuppressEngineReduce": true,
"PixelFormat": "ASTC_4x4"
"PixelFormat": "ASTC_4x4",
"SourceColor": "sRGB",
"DestColor": "sRGB"
},
"ios": {
"UUID": "{2828FBFE-BDF9-45A7-9370-F93822719CCF}",
"Name": "UserInterface_Compressed",
"SuppressEngineReduce": true,
"PixelFormat": "ASTC_6x6"
"PixelFormat": "ASTC_6x6",
"SourceColor": "sRGB",
"DestColor": "sRGB"
},
"mac": {
"UUID": "{2828FBFE-BDF9-45A7-9370-F93822719CCF}",
"Name": "UserInterface_Compressed",
"SuppressEngineReduce": true,
"PixelFormat": "BC1"
"PixelFormat": "BC1",
"SourceColor": "sRGB",
"DestColor": "sRGB"
},
"provo": {
"UUID": "{2828FBFE-BDF9-45A7-9370-F93822719CCF}",
"Name": "UserInterface_Compressed",
"SuppressEngineReduce": true,
"PixelFormat": "BC1"
"PixelFormat": "BC1",
"SourceColor": "sRGB",
"DestColor": "sRGB"
}
}
}
@@ -8,33 +8,41 @@
"Name": "UserInterface_Lossless",
"SuppressEngineReduce": true,
"PixelFormat": "R8G8B8A8",
"SourceColor": "Linear",
"DestColor": "Linear"
"SourceColor": "sRGB",
"DestColor": "sRGB"
},
"PlatformsPresets": {
"android": {
"UUID": "{83003128-F63E-422B-AEC2-68F0A947225F}",
"Name": "UserInterface_Lossless",
"SuppressEngineReduce": true,
"PixelFormat": "R8G8B8A8"
"PixelFormat": "R8G8B8A8",
"SourceColor": "sRGB",
"DestColor": "sRGB"
},
"ios": {
"UUID": "{83003128-F63E-422B-AEC2-68F0A947225F}",
"Name": "UserInterface_Lossless",
"SuppressEngineReduce": true,
"PixelFormat": "R8G8B8A8"
"PixelFormat": "R8G8B8A8",
"SourceColor": "sRGB",
"DestColor": "sRGB"
},
"mac": {
"UUID": "{83003128-F63E-422B-AEC2-68F0A947225F}",
"Name": "UserInterface_Lossless",
"SuppressEngineReduce": true,
"PixelFormat": "R8G8B8A8"
"PixelFormat": "R8G8B8A8",
"SourceColor": "sRGB",
"DestColor": "sRGB"
},
"provo": {
"UUID": "{83003128-F63E-422B-AEC2-68F0A947225F}",
"Name": "UserInterface_Lossless",
"SuppressEngineReduce": true,
"PixelFormat": "R8G8B8A8"
"PixelFormat": "R8G8B8A8",
"SourceColor": "sRGB",
"DestColor": "sRGB"
}
}
}
@@ -21,6 +21,8 @@
#define mallocAligned(sze) _aligned_malloc(sze, 16)
#define freeAligned(ptr) _aligned_free(ptr)
AZ_PUSH_DISABLE_WARNING_GCC("-Wunused-value")
/* ####################################################################################################################
*/
@@ -1279,3 +1281,5 @@ namespace ImageProcessingAtom
FilterImage(filterIndex, filterOp, blurH, blurV, srcImg, srcMip, dstImg, dstMip, srcRect, dstRect);
}
}
AZ_POP_DISABLE_WARNING_GCC
@@ -1154,7 +1154,7 @@
{
"name": "thickness",
"displayName": " Thickness",
"description": "Normalized global thickness, the maxima between this value (multiplied by thickness map if enabled) and thickness from shadow map (if applicable) will be used as final thickness of pixel",
"description": "In thick transmission mode: Normalized global thickness, the maxima between this value (multiplied by thickness map if enabled) and thickness from shadow map (if applicable) will be used as final thickness of pixel\n\nIn thin transmission mode: This value modulates the distance traversed by light inside an object.",
"type": "float",
"defaultValue": 0.5,
"min": 0.0,
@@ -1223,14 +1223,41 @@
"min": 0.0,
"softMax": 20.0
},
{
"name": "shrinkFactor",
"displayName": " Shrink Factor",
"description": "Shrink (absolute) offset towards the normal opposite direction to ensure correct shadow map projection",
"type": "float",
"defaultValue": 0.005,
"min": 0.0,
"softMax": 0.05
},
{
"name": "transmissionNdLBias",
"displayName": " Angle Bias",
"description": "cosine of angle to extend below (N . L = 0) in scattering through thin objects",
"type": "float",
"defaultValue": 0.1,
"min": -1.0,
"softMax": 1.0
},
{
"name": "distanceAttenuation",
"displayName": " Distance Attenuation",
"description": "Attenuation of the transmission effect, used to hide artifacts due to low-res shadow maps\nFor directional lights: attenuation proportional to the distance from the object to the camera.\nFor other light sources: attenuation proportional to the distance from the object to the light source.",
"type": "float",
"defaultValue": 0.5,
"min": 0.0,
"softMax": 4.0
},
{
"name": "transmissionScale",
"displayName": " Scale",
"description": "Strength of transmission",
"type": "float",
"defaultValue": 3.0,
"defaultValue": 1.0,
"min": 0.0,
"softMax": 20.0
"softMax": 5.0
}
],
"detailLayerGroup": [
@@ -1555,6 +1582,9 @@
"power": "subsurfaceScattering.transmissionPower",
"distortion": "subsurfaceScattering.transmissionDistortion",
"attenuation": "subsurfaceScattering.transmissionAttenuation",
"shrinkFactor": "subsurfaceScattering.shrinkFactor",
"transmissionNdLBias": "subsurfaceScattering.transmissionNdLBias",
"distanceAttenuation": "subsurfaceScattering.distanceAttenuation",
"tintColor": "subsurfaceScattering.transmissionTint",
"thickness": "subsurfaceScattering.thickness",
"enabled": "subsurfaceScattering.enableSubsurfaceScattering",
@@ -93,7 +93,7 @@ ShaderResourceGroup MaterialSrg : SRG_PerMaterial
// Elements of m_transmissionParams:
// Thick object mode: (attenuation coefficient, power, distortion, scale)
// Thin object mode: (float3 scatter distance, scale)
// Thin object mode: (shrinkFactor, transmissionNdLBias, distanceAttenuation, scale)
float4 m_transmissionParams;
// (float3 TintColor, thickness)
@@ -243,6 +243,7 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float
surface.transmission.tint = transmissionTintThickness.rgb;
surface.transmission.thickness = transmissionTintThickness.w;
surface.transmission.transmissionParams = MaterialSrg::m_transmissionParams;
surface.transmission.scatterDistance = MaterialSrg::m_scatterDistance;
// ------- Anisotropy -------
@@ -275,6 +276,17 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float
lightingData.diffuseAmbientOcclusion = GetOcclusionInput(MaterialSrg::m_diffuseOcclusionMap, MaterialSrg::m_sampler, IN.m_uv[MaterialSrg::m_diffuseOcclusionMapUvIndex], MaterialSrg::m_diffuseOcclusionFactor, o_diffuseOcclusion_useTexture);
lightingData.specularOcclusion = GetOcclusionInput(MaterialSrg::m_specularOcclusionMap, MaterialSrg::m_sampler, IN.m_uv[MaterialSrg::m_specularOcclusionMapUvIndex], MaterialSrg::m_specularOcclusionFactor, o_specularOcclusion_useTexture);
// ------- Thin Object Light Transmission -------
// Shrink (absolute) offset towards the normal opposite direction to ensure correct shadow map projection
lightingData.shrinkFactor = surface.transmission.transmissionParams.x;
// Angle offset for subsurface scattering through thin objects
lightingData.transmissionNdLBias = surface.transmission.transmissionParams.y;
// Attenuation applied to hide artifacts due to low-res shadow maps
lightingData.distanceAttenuation = surface.transmission.transmissionParams.z;
// ------- Clearcoat -------
// [GFX TODO][ATOM-14603]: Clean up the double uses of these clear coat flags
@@ -92,23 +92,34 @@ function ProcessEditor(context)
-- Update visibility for transmission...
local transmissionEnabled = TransmissionMode_None ~= context:GetMaterialPropertyValue_enum("subsurfaceScattering.transmissionMode")
local commonTrasmissionVisibility
if(transmissionEnabled) then
local thickTransmissionEnabled = TransmissionMode_ThickObject == context:GetMaterialPropertyValue_enum("subsurfaceScattering.transmissionMode")
local thinTransmissionEnabled = TransmissionMode_ThinObject == context:GetMaterialPropertyValue_enum("subsurfaceScattering.transmissionMode")
local commonTrasmissionVisibility = MaterialPropertyVisibility_Hidden
local thickTransmissionVisibility = MaterialPropertyVisibility_Hidden
local thinTransmissionVisibility = MaterialPropertyVisibility_Hidden
if (thickTransmissionEnabled or thinTransmissionEnabled) then
commonTrasmissionVisibility = MaterialPropertyVisibility_Enabled
else
commonTrasmissionVisibility = MaterialPropertyVisibility_Hidden
if(thickTransmissionEnabled) then
thickTransmissionVisibility = MaterialPropertyVisibility_Enabled
else -- thin transmission enabled
thinTransmissionVisibility = MaterialPropertyVisibility_Enabled
end
end
context:SetMaterialPropertyVisibility("subsurfaceScattering.thickness", commonTrasmissionVisibility)
context:SetMaterialPropertyVisibility("subsurfaceScattering.thicknessMap", commonTrasmissionVisibility)
context:SetMaterialPropertyVisibility("subsurfaceScattering.transmissionTint", commonTrasmissionVisibility)
context:SetMaterialPropertyVisibility("subsurfaceScattering.transmissionPower", commonTrasmissionVisibility)
context:SetMaterialPropertyVisibility("subsurfaceScattering.transmissionDistortion", commonTrasmissionVisibility)
context:SetMaterialPropertyVisibility("subsurfaceScattering.transmissionAttenuation", commonTrasmissionVisibility)
context:SetMaterialPropertyVisibility("subsurfaceScattering.transmissionPower", thickTransmissionVisibility)
context:SetMaterialPropertyVisibility("subsurfaceScattering.transmissionDistortion", thickTransmissionVisibility)
context:SetMaterialPropertyVisibility("subsurfaceScattering.transmissionAttenuation", thickTransmissionVisibility)
context:SetMaterialPropertyVisibility("subsurfaceScattering.shrinkFactor", thinTransmissionVisibility)
context:SetMaterialPropertyVisibility("subsurfaceScattering.transmissionNdLBias", thinTransmissionVisibility)
context:SetMaterialPropertyVisibility("subsurfaceScattering.distanceAttenuation", thinTransmissionVisibility)
context:SetMaterialPropertyVisibility("subsurfaceScattering.transmissionScale", commonTrasmissionVisibility)
UpdateTextureDependentPropertyVisibility(context, transmissionEnabled, "subsurfaceScattering.thicknessMap", "subsurfaceScattering.useThicknessMap", "subsurfaceScattering.thicknessMapUv")
UpdateTextureDependentPropertyVisibility(context, thickTransmissionEnabled or thinTransmissionEnabled, "subsurfaceScattering.thicknessMap", "subsurfaceScattering.useThicknessMap", "subsurfaceScattering.thicknessMapUv")
end
@@ -325,6 +325,7 @@ PbrLightingOutput SkinPS_Common(VSOutput IN)
surface.transmission.tint = transmissionTintThickness.rgb;
surface.transmission.thickness = transmissionTintThickness.w;
surface.transmission.transmissionParams = MaterialSrg::m_transmissionParams;
surface.transmission.scatterDistance = MaterialSrg::m_scatterDistance;
// ------- Lighting Data -------
@@ -341,6 +342,17 @@ PbrLightingOutput SkinPS_Common(VSOutput IN)
lightingData.specularResponse = FresnelSchlickWithRoughness(lightingData.NdotV, surface.specularF0, surface.roughnessLinear);
lightingData.diffuseResponse = 1.0 - lightingData.specularResponse;
// ------- Thin Object Light Transmission -------
// Shrink (absolute) offset towards the normal opposite direction to ensure correct shadow map projection
lightingData.shrinkFactor = surface.transmission.transmissionParams.x;
// Angle offset for subsurface scattering through thin objects
lightingData.transmissionNdLBias = surface.transmission.transmissionParams.y;
// Attenuation applied to hide artifacts due to low-res shadow maps
lightingData.distanceAttenuation = surface.transmission.transmissionParams.z;
// ------- Occlusion -------
lightingData.diffuseAmbientOcclusion = GetOcclusionInput(MaterialSrg::m_diffuseOcclusionMap, MaterialSrg::m_sampler, IN.m_uv[MaterialSrg::m_diffuseOcclusionMapUvIndex], MaterialSrg::m_diffuseOcclusionFactor, o_diffuseOcclusion_useTexture);
@@ -568,7 +568,7 @@
{
"name": "thickness",
"displayName": " Thickness",
"description": "Normalized global thickness, the maxima between this value (multiplied by thickness map if enabled) and thickness from shadow map (if applicable) will be used as final thickness of pixel",
"description": "In thick transmission mode: Normalized global thickness, the maxima between this value (multiplied by thickness map if enabled) and thickness from shadow map (if applicable) will be used as final thickness of pixel\n\nIn thin transmission mode: This value modulates the distance traversed by light inside an object.",
"type": "float",
"defaultValue": 0.5,
"min": 0.0,
@@ -637,14 +637,41 @@
"min": 0.0,
"softMax": 20.0
},
{
"name": "shrinkFactor",
"displayName": " Shrink Factor",
"description": "Shrink (absolute) offset towards the normal opposite direction to ensure correct shadow map projection",
"type": "float",
"defaultValue": 0.005,
"min": 0.0,
"softMax": 0.05
},
{
"name": "transmissionNdLBias",
"displayName": " Angle Bias",
"description": "cosine of angle to extend below (N . L = 0) in scattering through thin objects",
"type": "float",
"defaultValue": 0.1,
"min": -1.0,
"softMax": 1.0
},
{
"name": "distanceAttenuation",
"displayName": " Distance Attenuation",
"description": "Attenuation of the transmission effect, used to hide artifacts due to low-res shadow maps\nFor directional lights: attenuation proportional to the distance from the object to the camera.\nFor other light sources: attenuation proportional to the distance from the object to the light source.",
"type": "float",
"defaultValue": 0.5,
"min": 0.0,
"softMax": 4.0
},
{
"name": "transmissionScale",
"displayName": " Scale",
"description": "Strength of transmission",
"type": "float",
"defaultValue": 3.0,
"defaultValue": 1.0,
"min": 0.0,
"softMax": 20.0
"softMax": 5.0
}
],
"wrinkleLayers": [
@@ -1011,6 +1038,9 @@
"power": "subsurfaceScattering.transmissionPower",
"distortion": "subsurfaceScattering.transmissionDistortion",
"attenuation": "subsurfaceScattering.transmissionAttenuation",
"shrinkFactor": "subsurfaceScattering.shrinkFactor",
"transmissionNdLBias": "subsurfaceScattering.transmissionNdLBias",
"distanceAttenuation": "subsurfaceScattering.distanceAttenuation",
"tintColor": "subsurfaceScattering.transmissionTint",
"thickness": "subsurfaceScattering.thickness",
"enabled": "subsurfaceScattering.enableSubsurfaceScattering",
@@ -22,6 +22,10 @@
#include "MaterialInputs/UvSetCount.azsli"
#include "MaterialInputs/DetailMapsInput.azsli"
// Use human skin profile for thin object transmission (if enabled)
// Remove this line to use a custom profile based on the scatter color
#define USE_HUMAN_SKIN_PROFILE
ShaderResourceGroup MaterialSrg : SRG_PerMaterial
{
// Auto-generate material SRG fields for common inputs
@@ -64,7 +68,7 @@ ShaderResourceGroup MaterialSrg : SRG_PerMaterial
// Elements of m_transmissionParams:
// Thick object mode: (attenuation coefficient, power, distortion, scale)
// Thin object mode: (float3 scatter distance, scale)
// Thin object mode: (shrinkFactor, transmissionNdLBias, distanceAttenuation, scale)
float4 m_transmissionParams;
// (float3 TintColor, thickness)
@@ -16,24 +16,28 @@
#include <Atom/Features/PBR/LightingOptions.azsli>
// Analytical integation (approximation) of diffusion profile over radius, could be replaced by other pre integrated kernels
// such as sum of Gaussian
// such as sum of Gaussian (see T(s))
float3 TransmissionKernel(float t, float3 s)
{
float3 exponent = s * t;
return 0.25 * (1.0 / exp(exponent) + 3.0 / exp(exponent / 3.0));
}
float ThinObjectFalloff(const float3 surfaceNormal, const float3 dirToLight)
// [specific profile for human skin]
// Analytical integation (approximation) of diffusion profile over radius, could be precomputed in a LUT
// From d'Eon and Luebke (https://developer.nvidia.com/gpugems/gpugems3/part-iii-rendering/chapter-14-advanced-techniques-realistic-real-time-skin, section 14.4.7)
float3 T(float s)
{
const float ndl = saturate(dot(-surfaceNormal, dirToLight));
// ndl works decently well but it can produce a harsh discontinuity in the area just before
// the shadow starts appearing on objects like cylinder and tubes.
// Smoothing out ndl does a decent enough job of removing this artifact.
return smoothstep(0, 1, ndl * ndl);
// dipoles and multipoles are approximated with sums of a small number of Gaussians with variable weights and variances
return float3(0.233, 0.455, 0.649) * exp(-s*s/0.0064) +
float3(0.1, 0.336, 0.344) * exp(-s*s/0.0484) +
float3(0.118, 0.198, 0.0) * exp(-s*s/0.187) +
float3(0.113, 0.007, 0.007) * exp(-s*s/0.567) +
float3(0.358, 0.004, 0.0) * exp(-s*s/1.99) +
float3(0.078, 0.0, 0.0) * exp(-s*s/7.41);
}
float3 GetBackLighting(Surface surface, LightingData lightingData, float3 lightIntensity, float3 dirToLight, float shadowRatio)
float3 GetBackLighting(Surface surface, LightingData lightingData, float3 lightIntensity, float3 dirToLight, float transmissionDistance, float attenuationDistance)
{
float3 result = float3(0.0, 0.0, 0.0);
@@ -52,7 +56,7 @@ float3 GetBackLighting(Surface surface, LightingData lightingData, float3 lightI
// https://colinbarrebrisebois.com/2011/03/07/gdc-2011-approximating-translucency-for-a-fast-cheap-and-convincing-subsurface-scattering-look/
{
thickness = max(shadowRatio, surface.transmission.thickness);
thickness = max(transmissionDistance, surface.transmission.thickness);
float transmittance = pow( saturate( dot( lightingData.dirToCamera, -normalize( dirToLight + surface.normal * transmissionParams.z ) ) ), transmissionParams.y ) * transmissionParams.w;
float lamberAttenuation = exp(-thickness * transmissionParams.x) * saturate(1.0 - thickness);
result = transmittance * lamberAttenuation * lightIntensity;
@@ -60,18 +64,39 @@ float3 GetBackLighting(Surface surface, LightingData lightingData, float3 lightI
break;
case TransmissionMode::ThinObject:
// Thin object mode, using thin-film assumption proposed by Jimenez J. et al, 2010, "Real-Time Realistic Skin Translucency"
// Thin object mode, based on Jimenez J. et al, 2010, "Real-Time Realistic Skin Translucency"
// http://www.iryoku.com/translucency/downloads/Real-Time-Realistic-Skin-Translucency.pdf
float litRatio = 1.0 - shadowRatio;
if (litRatio)
{
const float thickness = surface.transmission.thickness * transmissionParams.w;
const float3 invScattering = rcp(transmissionParams.xyz);
const float falloff = ThinObjectFalloff(surface.normal, dirToLight);
result = TransmissionKernel(thickness, invScattering) * falloff * lightIntensity * litRatio;
}
// transmissionDistance < 0.0f means shadows are not enabled --> avoid unnecessary computation
if (transmissionDistance < 0.0f)
{
break;
}
// Irradiance arround surface point.
// Albedo at front (surface point) is used to approximate irradiance at the back of the object (observation 4 in [Jimenez J. et al, 2010])
// Increase angle of influence to smooth transition regions
float3 E = surface.albedo * saturate(lightingData.transmissionNdLBias + dot(-surface.normal, dirToLight));
// Transmission distance modulated by hardcoded constant C and the thickness exposed parameter (in this case modulating the distance traversed by the light inside the object)
const float C = 300.0f;
float s = transmissionDistance * C * surface.transmission.thickness;
// Use scattering color to weight thin object transmission color
const float3 invScattering = rcp(max(surface.transmission.scatterDistance, float(0.00001)));
#ifndef USE_HUMAN_SKIN_PROFILE
// Generic profile based on scatter color
result = TransmissionKernel(s, invScattering) * lightIntensity * E * transmissionParams.w;
#else // USE_HUMAN_SKIN_PROFILE
// Profile specific to human skin
result = T(s) * lightIntensity * E * transmissionParams.w;
#endif
// Distance attenuation applied to hide artifacts due to low-res projected areas onto shadowmaps (might need some work in the future)
result /= max(1.0, attenuationDistance * attenuationDistance * lightingData.distanceAttenuation);
}
break;
}
@@ -28,6 +28,15 @@ class LightingData
// Direction light shadow coordinates
float3 shadowCoords[ViewSrg::MaxCascadeCount];
// (N . L) to accept below (N . L = 0) in scattering through thin objects
float transmissionNdLBias;
// Shrink (absolute) offset towards the normal opposite direction to ensure correct shadow map projection
float shrinkFactor;
// Attenuation applied to hide artifacts due to low-res shadow maps
float distanceAttenuation;
// Normalized direction from surface to camera
float3 dirToCamera;
@@ -135,8 +135,15 @@ void ApplyCapsuleLight(ViewSrg::CapsuleLight light, Surface surface, inout Light
float3 closestIntersectionPoint = startPoint + closestT * startToEnd;
float3 posToLight = closestIntersectionPoint - surface.position;
// Tranmission contribution
lightingData.translucentBackLighting += GetBackLighting(surface, lightingData, lightIntensity, normalize(posToLight), 0.0);
// Transmission contribution
// We cannot compute the actual transmission distance so we want to:
// - If transmission mode is thick object -> use transmission thickness parameter instead
// - If transmission mode is thin object -> ignore back lighting
// To detect and apply this behavior in the GetBackLighting function, we need to use a negative transmissionDistance
const float transmissionDistance = -1.0f;
// If the transmissionDistance is ignored then the attenuation distance (only used on thin objects) does not have any influence
const float attenuationDistance = 0.0f;
lightingData.translucentBackLighting += GetBackLighting(surface, lightingData, lightIntensity, normalize(posToLight), transmissionDistance, attenuationDistance);
// Calculate the offset from the nearest point on the reflection vector to the nearest point on the capsule light
float3 posToClosestPointAlongReflection = dot(posToLight, reflectionDir) * reflectionDir;
@@ -18,7 +18,13 @@ void ApplyDirectionalLights(Surface surface, inout LightingData lightingData)
// Shadowed check
const uint shadowIndex = ViewSrg::m_shadowIndexDirectionalLight;
float litRatio = 1.0f;
float backShadowRatio = 0.0f;
float camToSurfDist = distance(ViewSrg::m_worldPosition, surface.position);
// Distance travelled by the light inside the object. If not redefined to a non-negative value, it will take the following behavior:
// - If transmission mode is thick object -> use transmission thickness parameter instead
// - If transmission mode is thin object -> ignore back lighting
float transmissionDistance = -1.0f;
if (o_enableShadows && shadowIndex < SceneSrg::m_directionalLightCount)
{
litRatio = DirectionalLightShadow::GetVisibility(
@@ -30,7 +36,19 @@ void ApplyDirectionalLights(Surface surface, inout LightingData lightingData)
#if ENABLE_TRANSMISSION
if (o_transmission_mode == TransmissionMode::ThickObject)
{
backShadowRatio = DirectionalLightShadow::GetThickness(shadowIndex, lightingData.shadowCoords);
transmissionDistance = DirectionalLightShadow::GetThickness(shadowIndex, lightingData.shadowCoords);
}
else if (o_transmission_mode == TransmissionMode::ThinObject)
{
// Fetch and use shrinked positions for thin object transmission to ensure they fall onto the object when querying
DirectionalLightShadow::GetShadowCoords(
shadowIndex,
surface.position - lightingData.shrinkFactor * surface.vertexNormal,
surface.normal,
lightingData.shadowCoords);
// the depth from the shadow map
transmissionDistance = DirectionalLightShadow::GetThickness(shadowIndex, lightingData.shadowCoords);
}
#endif
}
@@ -52,23 +70,20 @@ void ApplyDirectionalLights(Surface surface, inout LightingData lightingData)
// [GFX TODO][ATOM-2012] care of multiple directional light
// Currently shadow check is done only for index == shadowIndex.
float currentLitRatio = 1.0f;
float currentBackShadowRatio = 1.0f;
if (o_enableShadows)
float currentTransmissionDistance = -1.0f;
if (o_enableShadows && index == shadowIndex)
{
currentLitRatio = (index == shadowIndex) ? litRatio : 1.;
currentBackShadowRatio = 1.0 - currentLitRatio;
#if ENABLE_TRANSMISSION
if (o_transmission_mode == TransmissionMode::ThickObject)
{
currentBackShadowRatio = (index == shadowIndex) ? backShadowRatio : 0.;
}
#endif
// Add contribution only if current directional light is the active one for shadows
currentLitRatio = litRatio;
currentTransmissionDistance = transmissionDistance;
}
// Transmission contribution
lightingData.translucentBackLighting += GetBackLighting(surface, lightingData, light.m_rgbIntensityLux, dirToLight, currentTransmissionDistance, camToSurfDist);
lightingData.diffuseLighting += GetDiffuseLighting(surface, lightingData, light.m_rgbIntensityLux, dirToLight) * currentLitRatio;
lightingData.specularLighting += GetSpecularLighting(surface, lightingData, light.m_rgbIntensityLux, dirToLight) * currentLitRatio;
lightingData.translucentBackLighting += GetBackLighting(surface, lightingData, light.m_rgbIntensityLux, dirToLight, currentBackShadowRatio);
}
// Add debug coloring for directional light shadow
@@ -76,9 +76,12 @@ void ApplyDiskLight(ViewSrg::DiskLight light, Surface surface, inout LightingDat
// shadow
float litRatio = 1.0;
// How much is back face shadowed, it's set to the reverse of litRatio to share the same default value with thickness, which should be 0 if no shadow map available
float backShadowRatio = 0.0;
// Distance travelled by the light inside the object. If not redefined to a non-negative value, it will take the following behavior:
// - If transmission mode is thick object -> use transmission thickness parameter instead
// - If transmission mode is thin object -> ignore back lighting
float transmissionDistance = -1.0f;
if (o_enableShadows)
{
litRatio = ProjectedShadow::GetVisibility(
@@ -88,14 +91,16 @@ void ApplyDiskLight(ViewSrg::DiskLight light, Surface surface, inout LightingDat
-dirToConeTip,
surface.vertexNormal);
// Use backShadowRatio to carry thickness from shadow map for thick mode
backShadowRatio = 1.0 - litRatio;
// o_transmission_mode == NONE is not taken into account because GetBackLighting already ignores this case
#if ENABLE_TRANSMISSION
if (o_transmission_mode == TransmissionMode::ThickObject)
{
backShadowRatio = ProjectedShadow::GetThickness(
light.m_shadowIndex,
surface.position);
transmissionDistance = ProjectedShadow::GetThickness(light.m_shadowIndex, surface.position);
}
else if (o_transmission_mode == TransmissionMode::ThinObject)
{
transmissionDistance = ProjectedShadow::GetThickness(light.m_shadowIndex, surface.position - lightingData.shrinkFactor * surface.vertexNormal);
}
#endif
}
@@ -115,8 +120,8 @@ void ApplyDiskLight(ViewSrg::DiskLight light, Surface surface, inout LightingDat
lightingData.diffuseLighting += GetDiffuseLighting(surface, lightingData, lightIntensity, posToLightDir) * litRatio;
// Transmission contribution
lightingData.translucentBackLighting += GetBackLighting(surface, lightingData, lightIntensity, posToLightDir, backShadowRatio);
lightingData.translucentBackLighting += GetBackLighting(surface, lightingData, lightIntensity, posToLightDir, transmissionDistance, distanceToLight2);
// Adjust the light direction for specular based on disk size
// Calculate the reflection off the normal from the view direction
@@ -46,6 +46,16 @@ void AddSampleContribution(
float3 intensityRgb = float3(intensity, intensity, intensity);
diffuseAcc += GetDiffuseLighting(surface, lightingData, intensityRgb, posToLightSampleDir);
translucentAcc += GetBackLighting(surface, lightingData, intensityRgb, posToLightSampleDir, 0.0);
// Transmission contribution
// We cannot compute the actual transmission distance so we want to:
// - If transmission mode is thick object -> use transmission thickness parameter instead
// - If transmission mode is thin object -> ignore back lighting
// To detect and apply this behavior in the GetBackLighting function, we need to use a negative transmissionDistance
const float transmissionDistance = -1.0f;
// If the transmissionDistance is ignored then the attenuation distance (only used on thin objects) does not have any influence
const float attenuationDistance = 0.0f;
translucentAcc += GetBackLighting(surface, lightingData, intensityRgb, posToLightSampleDir, transmissionDistance, attenuationDistance);
specularAcc += GetSpecularLighting(surface, lightingData, intensityRgb, posToLightSampleDir);
}
@@ -67,6 +67,7 @@ uint ComputeShadowIndex(const ViewSrg::PointLight light, const Surface surface)
void ApplyPointLight(ViewSrg::PointLight light, Surface surface, inout LightingData lightingData)
{
float3 posToLight = light.m_position - surface.position;
float posToLightDist = length(posToLight);
float d2 = dot(posToLight, posToLight); // light distance squared
float falloff = d2 * light.m_invAttenuationRadiusSquared;
@@ -84,8 +85,11 @@ void ApplyPointLight(ViewSrg::PointLight light, Surface surface, inout LightingD
// shadow
float litRatio = 1.0;
// How much is back face shadowed, it's set to the reverse of litRatio to share the same default value with thickness, which should be 0 if no shadow map available
float backShadowRatio = 0.0;
// Distance travelled by the light inside the object. If not redefined to a non-negative value, it will take the following behavior:
// - If transmission mode is thick object -> use transmission thickness parameter instead
// - If transmission mode is thin object -> ignore back lighting
float transmissionDistance = -1.0f;
if (o_enableShadows)
{
const float3 lightDir = normalize(light.m_position - surface.position);
@@ -96,16 +100,17 @@ void ApplyPointLight(ViewSrg::PointLight light, Surface surface, inout LightingD
surface.position,
lightDir,
surface.vertexNormal);
// Use backShadowRatio to carry thickness from shadow map for thick mode
backShadowRatio = 1.0 - litRatio;
#if ENABLE_TRANSMISSION
// o_transmission_mode == NONE is not taken into account because GetBackLighting already ignores this case
if (o_transmission_mode == TransmissionMode::ThickObject)
{
backShadowRatio = ProjectedShadow::GetThickness(
shadowIndex,
surface.position);
}
transmissionDistance = ProjectedShadow::GetThickness(shadowIndex, surface.position);
}
else if (o_transmission_mode == TransmissionMode::ThinObject)
{
transmissionDistance = ProjectedShadow::GetThickness(shadowIndex, surface.position - lightingData.shrinkFactor * surface.vertexNormal);
}
#endif
}
@@ -113,7 +118,7 @@ void ApplyPointLight(ViewSrg::PointLight light, Surface surface, inout LightingD
lightingData.diffuseLighting += GetDiffuseLighting(surface, lightingData, lightIntensity, normalize(posToLight)) * litRatio;
// Transmission contribution
lightingData.translucentBackLighting += GetBackLighting(surface, lightingData, lightIntensity, normalize(posToLight), backShadowRatio);
lightingData.translucentBackLighting += GetBackLighting(surface, lightingData, lightIntensity, normalize(posToLight), transmissionDistance, posToLightDist);
// Adjust the light direcion for specular based on bulb size
@@ -148,13 +148,21 @@ void ApplyQuadLight(ViewSrg::QuadLight light, Surface surface, inout LightingDat
GetDiffuseLighting(surface, lightingData, intensity, dirToLightCenter)
);
// Transmission contribution
// We cannot compute the actual transmission distance so we want to:
// - If transmission mode is thick object -> use transmission thickness parameter instead
// - If transmission mode is thin object -> ignore back lighting
// To detect and apply this behavior in the GetBackLighting function, we need to use a negative transmissionDistance
const float transmissionDistance = -1.0f;
// If the transmissionDistance is ignored then the attenuation distance (only used on thin objects) does not have any influence
const float attenuationDistance = 0.0f;
lightingData.translucentBackLighting +=
(
GetBackLighting(surface, lightingData, intensity, p0, 0.0) +
GetBackLighting(surface, lightingData, intensity, p1, 0.0) +
GetBackLighting(surface, lightingData, intensity, p2, 0.0) +
GetBackLighting(surface, lightingData, intensity, p3, 0.0) +
GetBackLighting(surface, lightingData, intensity, dirToLightCenter, 0.0)
GetBackLighting(surface, lightingData, intensity, p0, transmissionDistance, attenuationDistance) +
GetBackLighting(surface, lightingData, intensity, p1, transmissionDistance, attenuationDistance) +
GetBackLighting(surface, lightingData, intensity, p2, transmissionDistance, attenuationDistance) +
GetBackLighting(surface, lightingData, intensity, p3, transmissionDistance, attenuationDistance) +
GetBackLighting(surface, lightingData, intensity, dirToLightCenter, transmissionDistance, attenuationDistance)
);
// Calculate specular by choosing a single representative point on the light's surface based on the reflection ray
@@ -14,7 +14,8 @@ class TransmissionSurfaceData
{
float3 tint;
float thickness; //!< pre baked local thickness, used for transmission
float4 transmissionParams; //!< parameters: thick mode->(attenuation coefficient, power, distortion, scale), thin mode: (float3 scatter distance, scale)
float4 transmissionParams; //!< parameters: thick mode->(attenuation coefficient, power, distortion, scale), thin mode: (shrinkFactor, transmissionNdLBias, distanceAttenuation, scale)
float3 scatterDistance; //!< scatter distance (same as in MaterialSrg) >
void InitializeToZero();
};
@@ -24,6 +25,7 @@ void TransmissionSurfaceData::InitializeToZero()
tint = float3(0.0f, 0.0f, 0.0f);
thickness = 0.0f;
transmissionParams = float4(0.0f, 0.0f, 0.0f, 0.0f);
scatterDistance = float3(0.0f, 0.0f, 0.0f);
}
#endif
@@ -161,7 +161,10 @@ float DirectionalLightShadow::GetThickness(uint lightIndex, float3 shadowCoords[
shadowCoord.y >= 0. && shadowCoord.y * size < size - PixelMargin && shadowCoord.z < (1. - DepthMargin))
{
const float depthBufferValue = shadowmap.Sample(PassSrg::LinearSampler, float3(shadowCoord.xy, indexOfCascade)).r;
const float deltaDepth = abs(shadowCoord.z - depthBufferValue);
// Normalized thickness (avoid negative values given by precision errors or shrinking offsets)
const float deltaDepth = max(shadowCoord.z - depthBufferValue,0.0);
const float viewSpaceThickness = ViewSrg::m_directionalLightShadows[lightIndex].m_far_minus_near * deltaDepth;
return viewSpaceThickness;
}
@@ -274,8 +274,9 @@ float ProjectedShadow::GetThickness()
float3(atlasPosition.xy * invAtlasSize, atlasPosition.z),
/*LOD=*/0
).r;
const float viewSpaceThickness = abs(UnprojectDepth(m_shadowIndex, m_shadowPosition.z) - UnprojectDepth(m_shadowIndex, depthValue));
// Denormalized thickness (avoid negative values given by precision errors or shrinking offsets)
const float viewSpaceThickness = max(UnprojectDepth(m_shadowIndex, depthValue) - UnprojectDepth(m_shadowIndex, m_shadowPosition.z), 0.0);
return viewSpaceThickness;
}
@@ -26,6 +26,9 @@ namespace AZ
->Field("m_power", &SubsurfaceTransmissionParameterFunctor::m_power)
->Field("m_distortion", &SubsurfaceTransmissionParameterFunctor::m_distortion)
->Field("m_attenuation", &SubsurfaceTransmissionParameterFunctor::m_attenuation)
->Field("m_shrinkFactor", &SubsurfaceTransmissionParameterFunctor::m_shrinkFactor)
->Field("m_transmissionNdLBias", &SubsurfaceTransmissionParameterFunctor::m_transmissionNdLBias)
->Field("m_distanceAttenuation", &SubsurfaceTransmissionParameterFunctor::m_distanceAttenuation)
->Field("m_tintColor", &SubsurfaceTransmissionParameterFunctor::m_tintColor)
->Field("m_thickness", &SubsurfaceTransmissionParameterFunctor::m_thickness)
->Field("m_enabled", &SubsurfaceTransmissionParameterFunctor::m_enabled)
@@ -50,6 +53,9 @@ namespace AZ
auto power = context.GetMaterialPropertyValue<float>(m_power);
auto distortion = context.GetMaterialPropertyValue<float>(m_distortion);
auto attenuation = context.GetMaterialPropertyValue<float>(m_attenuation);
auto shrinkFactor = context.GetMaterialPropertyValue<float>(m_shrinkFactor);
auto transmissionNdLBias = context.GetMaterialPropertyValue<float>(m_transmissionNdLBias);
auto distanceAttenuation = context.GetMaterialPropertyValue<float>(m_distanceAttenuation);
auto tintColor = context.GetMaterialPropertyValue<Color>(m_tintColor);
auto thickness = context.GetMaterialPropertyValue<float>(m_thickness);
auto scatterDistanceColor = context.GetMaterialPropertyValue<Color>(m_scatterDistanceColor);
@@ -67,7 +73,9 @@ namespace AZ
}
else
{
transmissionParams.Set(scatterDistance);
transmissionParams.SetX(shrinkFactor);
transmissionParams.SetY(transmissionNdLBias);
transmissionParams.SetZ(distanceAttenuation);
transmissionParams.SetW(scale);
}
@@ -37,6 +37,9 @@ namespace AZ
RPI::MaterialPropertyIndex m_power;
RPI::MaterialPropertyIndex m_distortion;
RPI::MaterialPropertyIndex m_attenuation;
RPI::MaterialPropertyIndex m_shrinkFactor;
RPI::MaterialPropertyIndex m_transmissionNdLBias;
RPI::MaterialPropertyIndex m_distanceAttenuation;
RPI::MaterialPropertyIndex m_tintColor;
RPI::MaterialPropertyIndex m_thickness;
RPI::MaterialPropertyIndex m_enabled;
@@ -25,6 +25,9 @@ namespace AZ
->Field("power", &SubsurfaceTransmissionParameterFunctorSourceData::m_power)
->Field("distortion", &SubsurfaceTransmissionParameterFunctorSourceData::m_distortion)
->Field("attenuation", &SubsurfaceTransmissionParameterFunctorSourceData::m_attenuation)
->Field("shrinkFactor", &SubsurfaceTransmissionParameterFunctorSourceData::m_shrinkFactor)
->Field("transmissionNdLBias", &SubsurfaceTransmissionParameterFunctorSourceData::m_transmissionNdLBias)
->Field("distanceAttenuation", &SubsurfaceTransmissionParameterFunctorSourceData::m_distanceAttenuation)
->Field("tintColor", &SubsurfaceTransmissionParameterFunctorSourceData::m_tintColor)
->Field("thickness", &SubsurfaceTransmissionParameterFunctorSourceData::m_thickness)
->Field("enabled", &SubsurfaceTransmissionParameterFunctorSourceData::m_enabled)
@@ -48,6 +51,9 @@ namespace AZ
functor->m_power = context.FindMaterialPropertyIndex(Name{ m_power });
functor->m_distortion = context.FindMaterialPropertyIndex(Name{ m_distortion });
functor->m_attenuation = context.FindMaterialPropertyIndex(Name{ m_attenuation });
functor->m_shrinkFactor = context.FindMaterialPropertyIndex(Name{ m_shrinkFactor });
functor->m_transmissionNdLBias = context.FindMaterialPropertyIndex(Name{ m_transmissionNdLBias });
functor->m_distanceAttenuation = context.FindMaterialPropertyIndex(Name{ m_distanceAttenuation });
functor->m_tintColor = context.FindMaterialPropertyIndex(Name{ m_tintColor });
functor->m_thickness = context.FindMaterialPropertyIndex(Name{ m_thickness });
functor->m_enabled = context.FindMaterialPropertyIndex(Name{ m_enabled });
@@ -56,7 +62,8 @@ namespace AZ
if (functor->m_mode.IsNull() || functor->m_scale.IsNull() || functor->m_power.IsNull() || functor->m_distortion.IsNull() ||
functor->m_tintColor.IsNull() || functor->m_thickness.IsNull() || functor->m_enabled.IsNull() || functor->m_attenuation.IsNull() || functor->m_scatterDistanceColor.IsNull() ||
functor->m_scatterDistanceIntensity.IsNull())
functor->m_scatterDistanceIntensity.IsNull() || functor->m_shrinkFactor.IsNull() || functor->m_transmissionNdLBias.IsNull() ||
functor->m_distanceAttenuation.IsNull())
{
return Failure();
}
@@ -66,6 +73,9 @@ namespace AZ
AddMaterialPropertyDependency(functor, functor->m_power);
AddMaterialPropertyDependency(functor, functor->m_distortion);
AddMaterialPropertyDependency(functor, functor->m_attenuation);
AddMaterialPropertyDependency(functor, functor->m_shrinkFactor);
AddMaterialPropertyDependency(functor, functor->m_transmissionNdLBias);
AddMaterialPropertyDependency(functor, functor->m_distanceAttenuation);
AddMaterialPropertyDependency(functor, functor->m_tintColor);
AddMaterialPropertyDependency(functor, functor->m_thickness);
AddMaterialPropertyDependency(functor, functor->m_enabled);
@@ -36,6 +36,9 @@ namespace AZ
AZStd::string m_power; //!< material property for thick transmission power
AZStd::string m_distortion; //!< material property for thick transmission distortion towards surface normal
AZStd::string m_attenuation; //!< material property for thick transmission volume absorption
AZStd::string m_shrinkFactor; //!< material property for thin transmission position shrink factor towards surface normal
AZStd::string m_transmissionNdLBias; //!< material property for thin transmission bias of the NdL value
AZStd::string m_distanceAttenuation; //!< material property for thin transmission attenuation with distance
AZStd::string m_tintColor; //!< material property for transmission tint
AZStd::string m_thickness; //!< material property for normalized object thickness
AZStd::string m_enabled; //!< material property for subsurface scattering feature switch (enabled or disabled)
@@ -0,0 +1,65 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzToolsFramework/AssetBrowser/AssetBrowserBus.h>
#include <AzToolsFramework/SourceControl/SourceControlAPI.h>
class QWidget;
class QMenu;
class QAction;
namespace AzToolsFramework
{
namespace AssetBrowser
{
class AssetBrowserEntry;
} // namespace AssetBrowser
} // namespace AzToolsFramework
namespace AtomToolsFramework
{
//! Provides common asset browser interactions, source control integration, and functionality to add custom menu actions
class AtomToolsAssetBrowserInteractions : public AzToolsFramework::AssetBrowser::AssetBrowserInteractionNotificationBus::Handler
{
public:
AZ_CLASS_ALLOCATOR(AtomToolsAssetBrowserInteractions, AZ::SystemAllocator, 0);
AtomToolsAssetBrowserInteractions();
~AtomToolsAssetBrowserInteractions();
using AssetBrowserEntryVector = AZStd::vector<AzToolsFramework::AssetBrowser::AssetBrowserEntry*>;
using FilterCallback = AZStd::function<bool(const AssetBrowserEntryVector&)>;
using ActionCallback = AZStd::function<void(QWidget* caller, QMenu* menu, const AssetBrowserEntryVector&)>;
//! Add a filter and handler for custom context menu entries that will be added to the top of the context menu
void RegisterContextMenuActions(const FilterCallback& filterCallback, const ActionCallback& actionCallback);
private:
//! AssetBrowserInteractionNotificationBus::Handler overrides...
void AddContextMenuActions(QWidget* caller, QMenu* menu, const AssetBrowserEntryVector& entries) override;
void AddContextMenuActionsForSourceEntries(
QWidget* caller, QMenu* menu, const AzToolsFramework::AssetBrowser::AssetBrowserEntry* entry);
void AddContextMenuActionsForFolderEntries(
QWidget* caller, QMenu* menu, const AzToolsFramework::AssetBrowser::AssetBrowserEntry* entry);
void AddContextMenuActionsForAllEntries(
QWidget* caller, QMenu* menu, const AzToolsFramework::AssetBrowser::AssetBrowserEntry* entry);
void AddContextMenuActionsForSourceControl(
QWidget* caller, QMenu* menu, const AzToolsFramework::AssetBrowser::AssetBrowserEntry* entry);
void UpdateContextMenuActionsForSourceControl(bool success, AzToolsFramework::SourceControlFileInfo info);
QWidget* m_caller = {};
QAction* m_addAction = {};
QAction* m_checkOutAction = {};
QAction* m_undoCheckOutAction = {};
QAction* m_getLatestAction = {};
AZStd::vector<AZStd::pair<FilterCallback, ActionCallback>> m_contextMenuCallbacks;
};
} // namespace AtomToolsFramework
@@ -7,44 +7,47 @@
*/
#include <Atom/RPI.Edit/Common/AssetUtils.h>
#include <Atom/RPI.Edit/Material/MaterialSourceData.h>
#include <Atom/RPI.Edit/Material/MaterialTypeSourceData.h>
#include <Atom/RPI.Reflect/Material/MaterialAsset.h>
#include <AtomToolsFramework/Document/AtomToolsDocumentSystemRequestBus.h>
#include <AtomToolsFramework/AssetBrowser/AtomToolsAssetBrowserInteractions.h>
#include <AtomToolsFramework/Util/Util.h>
#include <AzCore/Utils/Utils.h>
#include <AzCore/std/string/wildcard.h>
#include <AzQtComponents/Utilities/DesktopUtilities.h>
#include <AzToolsFramework/API/EditorPythonRunnerRequestsBus.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserBus.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserEntry.h>
#include <AzToolsFramework/AssetBrowser/AssetSelectionModel.h>
#include <AzToolsFramework/Thumbnails/SourceControlThumbnail.h>
#include <Window/CreateMaterialDialog/CreateMaterialDialog.h>
#include <Window/MaterialEditorBrowserInteractions.h>
#include <QApplication>
#include <QClipboard>
#include <QDesktopServices>
#include <QDir>
#include <QFileDialog>
#include <QFileInfo>
#include <QInputDialog>
#include <QMenu>
#include <QMessageBox>
namespace MaterialEditor
namespace AtomToolsFramework
{
MaterialEditorBrowserInteractions::MaterialEditorBrowserInteractions()
AtomToolsAssetBrowserInteractions::AtomToolsAssetBrowserInteractions()
{
using namespace AzToolsFramework::AssetBrowser;
AssetBrowserInteractionNotificationBus::Handler::BusConnect();
}
MaterialEditorBrowserInteractions::~MaterialEditorBrowserInteractions()
AtomToolsAssetBrowserInteractions::~AtomToolsAssetBrowserInteractions()
{
AssetBrowserInteractionNotificationBus::Handler::BusDisconnect();
}
void MaterialEditorBrowserInteractions::AddContextMenuActions(QWidget* caller, QMenu* menu, const AZStd::vector<AzToolsFramework::AssetBrowser::AssetBrowserEntry*>& entries)
void AtomToolsAssetBrowserInteractions::RegisterContextMenuActions(
const FilterCallback& filterCallback, const ActionCallback& actionCallback)
{
m_contextMenuCallbacks.emplace_back(filterCallback, actionCallback);
}
void AtomToolsAssetBrowserInteractions::AddContextMenuActions(
QWidget* caller, QMenu* menu, const AssetBrowserEntryVector& entries)
{
AssetBrowserEntry* entry = entries.empty() ? nullptr : entries.front();
if (!entry)
@@ -53,79 +56,33 @@ namespace MaterialEditor
}
m_caller = caller;
QObject::connect(m_caller, &QObject::destroyed, [this]()
{
m_caller = nullptr;
});
QObject::connect(m_caller, &QObject::destroyed, [this]() { m_caller = nullptr; });
AddGenericContextMenuActions(caller, menu, entry);
// Add all of the custom context menu entries first
for (const auto& contextMenuCallbackPair : m_contextMenuCallbacks)
{
if (contextMenuCallbackPair.first(entries))
{
contextMenuCallbackPair.second(caller, menu, entries);
}
}
if (entry->GetEntryType() == AssetBrowserEntry::AssetEntryType::Source)
{
const auto source = azalias_cast<const SourceAssetBrowserEntry*>(entry);
if (AzFramework::StringFunc::Path::IsExtension(entry->GetFullPath().c_str(), AZ::RPI::MaterialSourceData::Extension))
{
AddContextMenuActionsForMaterialSource(caller, menu, source);
}
else if (AzFramework::StringFunc::Path::IsExtension(entry->GetFullPath().c_str(), AZ::RPI::MaterialTypeSourceData::Extension))
{
AddContextMenuActionsForMaterialTypeSource(caller, menu, source);
}
else
{
AddContextMenuActionsForOtherSource(caller, menu, source);
}
AddContextMenuActionsForSourceEntries(caller, menu, entry);
}
else if (entry->GetEntryType() == AssetBrowserEntry::AssetEntryType::Folder)
{
const auto folder = azalias_cast<const FolderAssetBrowserEntry*>(entry);
AddContextMenuActionsForFolder(caller, menu, folder);
AddContextMenuActionsForFolderEntries(caller, menu, entry);
}
}
void MaterialEditorBrowserInteractions::AddGenericContextMenuActions([[maybe_unused]] QWidget* caller, QMenu* menu, const AzToolsFramework::AssetBrowser::AssetBrowserEntry* entry)
{
menu->addAction(QObject::tr("Copy Name To Clipboard"), [=]()
{
QApplication::clipboard()->setText(entry->GetName().c_str());
});
menu->addAction(QObject::tr("Copy Path To Clipboard"), [=]()
{
QApplication::clipboard()->setText(entry->GetFullPath().c_str());
});
AddContextMenuActionsForAllEntries(caller, menu, entry);
AddContextMenuActionsForSourceControl(caller, menu, entry);
}
void MaterialEditorBrowserInteractions::AddContextMenuActionsForMaterialTypeSource(QWidget* caller, QMenu* menu, const AzToolsFramework::AssetBrowser::SourceAssetBrowserEntry* entry)
void AtomToolsAssetBrowserInteractions::AddContextMenuActionsForSourceEntries(
[[maybe_unused]] QWidget* caller, QMenu* menu, const AzToolsFramework::AssetBrowser::AssetBrowserEntry* entry)
{
menu->addAction(AzQtComponents::fileBrowserActionName(), [entry]()
{
AzQtComponents::ShowFileOnDesktop(entry->GetFullPath().c_str());
});
menu->addSeparator();
menu->addAction("Create Material...", [entry]()
{
const QString defaultPath = AtomToolsFramework::GetUniqueFileInfo(
QString(AZ::Utils::GetProjectPath().c_str()) +
AZ_CORRECT_FILESYSTEM_SEPARATOR + "Assets" +
AZ_CORRECT_FILESYSTEM_SEPARATOR + "untitled." +
AZ::RPI::MaterialSourceData::Extension).absoluteFilePath();
AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Broadcast(&AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Events::CreateDocumentFromFile,
entry->GetFullPath(), AtomToolsFramework::GetSaveFileInfo(defaultPath).absoluteFilePath().toUtf8().constData());
});
AddPerforceMenuActions(caller, menu, entry);
}
void MaterialEditorBrowserInteractions::AddContextMenuActionsForOtherSource(QWidget* caller, QMenu* menu, const AzToolsFramework::AssetBrowser::SourceAssetBrowserEntry* entry)
{
menu->addAction("Open", [entry]()
{
QDesktopServices::openUrl(QUrl::fromLocalFile(entry->GetFullPath().c_str()));
});
menu->addAction("Duplicate...", [entry]()
{
const QFileInfo duplicateFileInfo(AtomToolsFramework::GetDuplicationFileInfo(entry->GetFullPath().c_str()));
@@ -142,79 +99,26 @@ namespace MaterialEditor
}
});
menu->addAction(AzQtComponents::fileBrowserActionName(), [entry]()
{
AzQtComponents::ShowFileOnDesktop(entry->GetFullPath().c_str());
});
AddPerforceMenuActions(caller, menu, entry);
}
void MaterialEditorBrowserInteractions::AddContextMenuActionsForMaterialSource(QWidget* caller, QMenu* menu, const AzToolsFramework::AssetBrowser::SourceAssetBrowserEntry* entry)
{
menu->addAction("Open", [entry]()
{
AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Broadcast(&AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Events::OpenDocument, entry->GetFullPath());
});
menu->addAction("Duplicate...", [entry]()
{
const QFileInfo duplicateFileInfo(AtomToolsFramework::GetDuplicationFileInfo(entry->GetFullPath().c_str()));
if (!duplicateFileInfo.absoluteFilePath().isEmpty())
menu->addAction("Run Python on File...", [caller, entry]()
{
const QString script = QFileDialog::getOpenFileName(
caller, QObject::tr("Run Script"), QString(AZ::Utils::GetProjectPath().c_str()), QString("*.py"));
if (!script.isEmpty())
{
if (QFile::copy(entry->GetFullPath().c_str(), duplicateFileInfo.absoluteFilePath()))
{
QFile::setPermissions(duplicateFileInfo.absoluteFilePath(), QFile::ReadOther | QFile::WriteOther);
// Auto add file to source control
AzToolsFramework::SourceControlCommandBus::Broadcast(&AzToolsFramework::SourceControlCommandBus::Events::RequestEdit,
duplicateFileInfo.absoluteFilePath().toUtf8().constData(), true, [](bool, const AzToolsFramework::SourceControlFileInfo&) {});
}
AZStd::vector<AZStd::string_view> pythonArgs{ entry->GetFullPath() };
AzToolsFramework::EditorPythonRunnerRequestBus::Broadcast(
&AzToolsFramework::EditorPythonRunnerRequestBus::Events::ExecuteByFilenameWithArgs, script.toUtf8().constData(),
pythonArgs);
}
});
menu->addAction(AzQtComponents::fileBrowserActionName(), [entry]()
{
AzQtComponents::ShowFileOnDesktop(entry->GetFullPath().c_str());
});
menu->addSeparator();
menu->addAction("Create Child Material...", [entry]()
{
const QString defaultPath = AtomToolsFramework::GetUniqueFileInfo(
QString(AZ::Utils::GetProjectPath().c_str()) +
AZ_CORRECT_FILESYSTEM_SEPARATOR + "Assets" +
AZ_CORRECT_FILESYSTEM_SEPARATOR + "untitled." +
AZ::RPI::MaterialSourceData::Extension).absoluteFilePath();
AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Broadcast(&AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Events::CreateDocumentFromFile,
entry->GetFullPath(), AtomToolsFramework::GetSaveFileInfo(defaultPath).absoluteFilePath().toUtf8().constData());
});
menu->addSeparator();
QAction* openParentAction = menu->addAction("Open Parent Material", [entry]()
{
AZ_UNUSED(entry);
// ToDo
});
openParentAction->setEnabled(false);
AddPerforceMenuActions(caller, menu, entry);
}
void MaterialEditorBrowserInteractions::AddContextMenuActionsForFolder(QWidget* caller, QMenu* menu, const AzToolsFramework::AssetBrowser::FolderAssetBrowserEntry* entry)
void AtomToolsAssetBrowserInteractions::AddContextMenuActionsForFolderEntries(
QWidget* caller, QMenu* menu, const AzToolsFramework::AssetBrowser::AssetBrowserEntry* entry)
{
menu->addAction(AzQtComponents::fileBrowserActionName(), [entry]()
menu->addAction(QObject::tr("Create new sub folder..."), [caller, entry]()
{
AzQtComponents::ShowFileOnDesktop(entry->GetFullPath().c_str());
});
QAction* createFolderAction = menu->addAction(QObject::tr("Create new sub folder..."));
QObject::connect(createFolderAction, &QAction::triggered, caller, [caller, entry]()
{
bool ok;
bool ok = false;
QString newFolderName = QInputDialog::getText(caller, "Enter new folder name", "name:", QLineEdit::Normal, "NewFolder", &ok);
if (ok)
{
@@ -242,27 +146,29 @@ namespace MaterialEditor
}
}
});
}
void AtomToolsAssetBrowserInteractions::AddContextMenuActionsForAllEntries(
[[maybe_unused]] QWidget* caller, QMenu* menu, const AzToolsFramework::AssetBrowser::AssetBrowserEntry* entry)
{
menu->addAction(AzQtComponents::fileBrowserActionName(), [entry]()
{
AzQtComponents::ShowFileOnDesktop(entry->GetFullPath().c_str());
});
menu->addSeparator();
QAction* createMaterialAction = menu->addAction(QObject::tr("Create Material..."));
QObject::connect(createMaterialAction, &QAction::triggered, caller, [caller, entry]()
menu->addAction(QObject::tr("Copy Name To Clipboard"), [=]()
{
CreateMaterialDialog createDialog(entry->GetFullPath().c_str(), caller);
createDialog.adjustSize();
if (createDialog.exec() == QDialog::Accepted &&
!createDialog.m_materialFileInfo.absoluteFilePath().isEmpty() &&
!createDialog.m_materialTypeFileInfo.absoluteFilePath().isEmpty())
{
AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Broadcast(&AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Events::CreateDocumentFromFile,
createDialog.m_materialTypeFileInfo.absoluteFilePath().toUtf8().constData(),
createDialog.m_materialFileInfo.absoluteFilePath().toUtf8().constData());
}
QApplication::clipboard()->setText(entry->GetName().c_str());
});
menu->addAction(QObject::tr("Copy Path To Clipboard"), [=]()
{
QApplication::clipboard()->setText(entry->GetFullPath().c_str());
});
}
void MaterialEditorBrowserInteractions::AddPerforceMenuActions([[maybe_unused]] QWidget* caller, QMenu* menu, const AzToolsFramework::AssetBrowser::AssetBrowserEntry* entry)
void AtomToolsAssetBrowserInteractions::AddContextMenuActionsForSourceControl(
[[maybe_unused]] QWidget* caller, QMenu* menu, const AzToolsFramework::AssetBrowser::AssetBrowserEntry* entry)
{
using namespace AzToolsFramework;
@@ -282,7 +188,7 @@ namespace MaterialEditor
QMenu::connect(sourceControlMenu, &QMenu::aboutToShow, [this, path]()
{
SourceControlCommandBus::Broadcast(&SourceControlCommandBus::Events::GetFileInfo, path.c_str(),
[this](bool success, const SourceControlFileInfo& info) { UpdateSourceControlActions(success, info); });
[this](bool success, const SourceControlFileInfo& info) { UpdateContextMenuActionsForSourceControl(success, info); });
});
// add get latest action
@@ -344,7 +250,7 @@ namespace MaterialEditor
}
}
void MaterialEditorBrowserInteractions::UpdateSourceControlActions(bool success, AzToolsFramework::SourceControlFileInfo info)
void AtomToolsAssetBrowserInteractions::UpdateContextMenuActionsForSourceControl(bool success, AzToolsFramework::SourceControlFileInfo info)
{
if (!success && m_caller)
{
@@ -367,4 +273,4 @@ namespace MaterialEditor
m_undoCheckOutAction->setEnabled(info.IsManaged() && !info.IsReadOnly());
}
}
} // namespace MaterialEditor
} // namespace AtomToolsFramework
@@ -9,6 +9,7 @@
set(FILES
Include/AtomToolsFramework/Application/AtomToolsApplication.h
Include/AtomToolsFramework/AssetBrowser/AtomToolsAssetBrowser.h
Include/AtomToolsFramework/AssetBrowser/AtomToolsAssetBrowserInteractions.h
Include/AtomToolsFramework/AssetGridDialog/AssetGridDialog.h
Include/AtomToolsFramework/Communication/LocalServer.h
Include/AtomToolsFramework/Communication/LocalSocket.h
@@ -44,6 +45,7 @@ set(FILES
Source/AssetBrowser/AtomToolsAssetBrowser.cpp
Source/AssetBrowser/AtomToolsAssetBrowser.qrc
Source/AssetBrowser/AtomToolsAssetBrowser.ui
Source/AssetBrowser/AtomToolsAssetBrowserInteractions.cpp
Source/AssetGridDialog/AssetGridDialog.cpp
Source/AssetGridDialog/AssetGridDialog.ui
Source/Communication/LocalServer.cpp
@@ -1,47 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <Document/MaterialDocumentSettings.h>
namespace MaterialEditor
{
void MaterialDocumentSettings::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<MaterialDocumentSettings, AZ::UserSettings>()
->Version(1)
->Field("defaultMaterialTypeName", &MaterialDocumentSettings::m_defaultMaterialTypeName)
;
if (auto editContext = serializeContext->GetEditContext())
{
editContext->Class<MaterialDocumentSettings>(
"MaterialDocumentSettings", "")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(AZ::Edit::UIHandlers::Default, &MaterialDocumentSettings::m_defaultMaterialTypeName, "Default Material Type Name", "")
;
}
}
if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->Class<MaterialDocumentSettings>("MaterialDocumentSettings")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(AZ::Script::Attributes::Category, "Editor")
->Attribute(AZ::Script::Attributes::Module, "materialeditor")
->Constructor()
->Constructor<const MaterialDocumentSettings&>()
->Property("defaultMaterialTypeName", BehaviorValueProperty(&MaterialDocumentSettings::m_defaultMaterialTypeName))
;
}
}
} // namespace MaterialEditor
@@ -1,30 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzCore/Memory/Memory.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/RTTI/ReflectContext.h>
#include <AzCore/UserSettings/UserSettings.h>
#endif
namespace MaterialEditor
{
struct MaterialDocumentSettings
: public AZ::UserSettings
{
AZ_RTTI(MaterialDocumentSettings, "{12E8461F-65AD-4AD2-8A1D-82C3B1183522}", AZ::UserSettings);
AZ_CLASS_ALLOCATOR(MaterialDocumentSettings, AZ::SystemAllocator, 0);
static void Reflect(AZ::ReflectContext* context);
AZStd::string m_defaultMaterialTypeName = "StandardPBR";
};
} // namespace MaterialEditor
@@ -6,21 +6,32 @@
*
*/
#include <Atom/RPI.Edit/Material/MaterialSourceData.h>
#include <Atom/RPI.Edit/Material/MaterialTypeSourceData.h>
#include <Atom/RPI.Reflect/Material/MaterialAsset.h>
#include <AtomToolsFramework/Document/AtomToolsDocumentSystemRequestBus.h>
#include <AtomToolsFramework/Util/Util.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzCore/Utils/Utils.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserEntry.h>
#include <Document/MaterialDocument.h>
#include <Document/MaterialDocumentRequestBus.h>
#include <Document/MaterialDocumentSettings.h>
#include <MaterialEditorApplication.h>
#include <MaterialEditor_Traits_Platform.h>
#include <Viewport/MaterialViewportModule.h>
#include <Window/CreateMaterialDialog/CreateMaterialDialog.h>
#include <Window/MaterialEditorWindow.h>
#include <Window/MaterialEditorWindowSettings.h>
#include <QDesktopServices>
#include <QDialog>
#include <QMenu>
#include <QUrl>
void InitMaterialEditorResources()
{
// Must register qt resources from other modules
@@ -56,7 +67,6 @@ namespace MaterialEditor
void MaterialEditorApplication::Reflect(AZ::ReflectContext* context)
{
Base::Reflect(context);
MaterialDocumentSettings::Reflect(context);
MaterialEditorWindowSettings::Reflect(context);
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
@@ -112,8 +122,77 @@ namespace MaterialEditor
void MaterialEditorApplication::CreateMainWindow()
{
m_materialEditorBrowserInteractions.reset(aznew MaterialEditorBrowserInteractions);
m_window.reset(aznew MaterialEditorWindow);
m_assetBrowserInteractions.reset(aznew AtomToolsFramework::AtomToolsAssetBrowserInteractions);
m_assetBrowserInteractions->RegisterContextMenuActions(
[](const AtomToolsFramework::AtomToolsAssetBrowserInteractions::AssetBrowserEntryVector& entries)
{
return entries.front()->GetEntryType() == AzToolsFramework::AssetBrowser::AssetBrowserEntry::AssetEntryType::Source;
},
[]([[maybe_unused]] QWidget* caller, QMenu* menu, const AtomToolsFramework::AtomToolsAssetBrowserInteractions::AssetBrowserEntryVector& entries)
{
const bool isMaterial = AzFramework::StringFunc::Path::IsExtension(
entries.front()->GetFullPath().c_str(), AZ::RPI::MaterialSourceData::Extension);
const bool isMaterialType = AzFramework::StringFunc::Path::IsExtension(
entries.front()->GetFullPath().c_str(), AZ::RPI::MaterialTypeSourceData::Extension);
if (isMaterial || isMaterialType)
{
menu->addAction(QObject::tr("Open"), [entries]()
{
AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Broadcast(
&AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Events::OpenDocument,
entries.front()->GetFullPath());
});
const QString createActionName =
isMaterialType ? QObject::tr("Create Material...") : QObject::tr("Create Child Material...");
menu->addAction(createActionName, [entries]()
{
const QString defaultPath = AtomToolsFramework::GetUniqueFileInfo(
QString(AZ::Utils::GetProjectPath().c_str()) +
AZ_CORRECT_FILESYSTEM_SEPARATOR + "Assets" +
AZ_CORRECT_FILESYSTEM_SEPARATOR + "untitled." +
AZ::RPI::MaterialSourceData::Extension).absoluteFilePath();
AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Broadcast(
&AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Events::CreateDocumentFromFile,
entries.front()->GetFullPath(),
AtomToolsFramework::GetSaveFileInfo(defaultPath).absoluteFilePath().toUtf8().constData());
});
}
else
{
menu->addAction(QObject::tr("Open"), [entries]()
{
QDesktopServices::openUrl(QUrl::fromLocalFile(entries.front()->GetFullPath().c_str()));
});
}
});
m_assetBrowserInteractions->RegisterContextMenuActions(
[](const AtomToolsFramework::AtomToolsAssetBrowserInteractions::AssetBrowserEntryVector& entries)
{
return entries.front()->GetEntryType() == AzToolsFramework::AssetBrowser::AssetBrowserEntry::AssetEntryType::Folder;
},
[](QWidget* caller, QMenu* menu, const AtomToolsFramework::AtomToolsAssetBrowserInteractions::AssetBrowserEntryVector& entries)
{
menu->addAction(QObject::tr("Create Material..."), [caller, entries]()
{
CreateMaterialDialog createDialog(entries.front()->GetFullPath().c_str(), caller);
createDialog.adjustSize();
if (createDialog.exec() == QDialog::Accepted &&
!createDialog.m_materialFileInfo.absoluteFilePath().isEmpty() &&
!createDialog.m_materialTypeFileInfo.absoluteFilePath().isEmpty())
{
AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Broadcast(
&AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Events::CreateDocumentFromFile,
createDialog.m_materialTypeFileInfo.absoluteFilePath().toUtf8().constData(),
createDialog.m_materialFileInfo.absoluteFilePath().toUtf8().constData());
}
});
});
}
void MaterialEditorApplication::DestroyMainWindow()
@@ -8,10 +8,10 @@
#pragma once
#include <AtomToolsFramework/AssetBrowser/AtomToolsAssetBrowserInteractions.h>
#include <AtomToolsFramework/Document/AtomToolsDocumentApplication.h>
#include <AtomToolsFramework/Window/AtomToolsMainWindowFactoryRequestBus.h>
#include <AzToolsFramework/API/EditorWindowRequestBus.h>
#include <Window/MaterialEditorBrowserInteractions.h>
#include <Window/MaterialEditorWindow.h>
namespace MaterialEditor
@@ -49,6 +49,6 @@ namespace MaterialEditor
QWidget* GetAppMainWindow() override;
AZStd::unique_ptr<MaterialEditorWindow> m_window;
AZStd::unique_ptr<MaterialEditorBrowserInteractions> m_materialEditorBrowserInteractions;
AZStd::unique_ptr<AtomToolsFramework::AtomToolsAssetBrowserInteractions> m_assetBrowserInteractions;
};
} // namespace MaterialEditor
@@ -14,7 +14,6 @@
#include <AzFramework/Application/Application.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzQtComponents/Components/Widgets/FileDialog.h>
#include <Document/MaterialDocumentSettings.h>
#include <Window/CreateMaterialDialog/CreateMaterialDialog.h>
namespace MaterialEditor
@@ -65,11 +64,10 @@ namespace MaterialEditor
m_ui->m_materialTypeComboBox->model()->sort(0, Qt::AscendingOrder);
// Select the default material type from settings
auto settings =
AZ::UserSettings::CreateFind<MaterialDocumentSettings>(AZ::Crc32("MaterialDocumentSettings"), AZ::UserSettings::CT_GLOBAL);
const AZStd::string defaultMaterialType = AtomToolsFramework::GetSettingOrDefault<AZStd::string>(
"/O3DE/Atom/MaterialEditor/CreateMaterialDialog/DefaultMaterialType", "StandardPBR");
const int index = m_ui->m_materialTypeComboBox->findText(settings->m_defaultMaterialTypeName.c_str());
const int index = m_ui->m_materialTypeComboBox->findText(defaultMaterialType.c_str());
if (index >= 0)
{
m_ui->m_materialTypeComboBox->setCurrentIndex(index);
@@ -1,58 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzToolsFramework/AssetBrowser/AssetBrowserBus.h>
#include <AzToolsFramework/SourceControl/SourceControlAPI.h>
class QWidget;
class QMenu;
class QAction;
namespace AzToolsFramework
{
namespace AssetBrowser
{
class AssetBrowserEntry;
class SourceAssetBrowserEntry;
class FolderAssetBrowserEntry;
}
}
namespace MaterialEditor
{
class MaterialEditorBrowserInteractions
: public AzToolsFramework::AssetBrowser::AssetBrowserInteractionNotificationBus::Handler
{
public:
AZ_CLASS_ALLOCATOR(MaterialEditorBrowserInteractions, AZ::SystemAllocator, 0);
MaterialEditorBrowserInteractions();
~MaterialEditorBrowserInteractions();
private:
//! AssetBrowserInteractionNotificationBus::Handler overrides...
void AddContextMenuActions(QWidget* caller, QMenu* menu, const AZStd::vector<AzToolsFramework::AssetBrowser::AssetBrowserEntry*>& entries) override;
void AddGenericContextMenuActions(QWidget* caller, QMenu* menu, const AzToolsFramework::AssetBrowser::AssetBrowserEntry* entry);
void AddContextMenuActionsForOtherSource(QWidget* caller, QMenu* menu, const AzToolsFramework::AssetBrowser::SourceAssetBrowserEntry* entry);
void AddContextMenuActionsForMaterialSource(QWidget* caller, QMenu* menu, const AzToolsFramework::AssetBrowser::SourceAssetBrowserEntry* entry);
void AddContextMenuActionsForMaterialTypeSource(QWidget* caller, QMenu* menu, const AzToolsFramework::AssetBrowser::SourceAssetBrowserEntry* entry);
void AddContextMenuActionsForFolder(QWidget* caller, QMenu* menu, const AzToolsFramework::AssetBrowser::FolderAssetBrowserEntry* entry);
void AddPerforceMenuActions(QWidget* caller, QMenu* menu, const AzToolsFramework::AssetBrowser::AssetBrowserEntry* entry);
void UpdateSourceControlActions(bool success, AzToolsFramework::SourceControlFileInfo info);
QWidget* m_caller = nullptr;
QAction* m_addAction = nullptr;
QAction* m_checkOutAction = nullptr;
QAction* m_undoCheckOutAction = nullptr;
QAction* m_getLatestAction = nullptr;
};
} // namespace MaterialEditor
@@ -146,7 +146,7 @@ namespace MaterialEditor
bool MaterialEditorWindow::GetCreateDocumentParams(AZStd::string& openPath, AZStd::string& savePath)
{
CreateMaterialDialog createDialog(this);
CreateMaterialDialog createDialog(openPath.c_str(), this);
createDialog.adjustSize();
if (createDialog.exec() == QDialog::Accepted &&
@@ -14,8 +14,6 @@ namespace MaterialEditor
SettingsWidget::SettingsWidget(QWidget* parent)
: AtomToolsFramework::InspectorWidget(parent)
{
m_documentSettings =
AZ::UserSettings::CreateFind<MaterialDocumentSettings>(AZ_CRC_CE("MaterialDocumentSettings"), AZ::UserSettings::CT_GLOBAL);
m_documentSystemSettings = AZ::UserSettings::CreateFind<AtomToolsFramework::AtomToolsDocumentSystemSettings>(
AZ_CRC_CE("AtomToolsDocumentSystemSettings"), AZ::UserSettings::CT_GLOBAL);
}
@@ -29,23 +27,9 @@ namespace MaterialEditor
{
AddGroupsBegin();
AddDocumentSystemSettingsGroup();
AddDocumentSettingsGroup();
AddGroupsEnd();
}
void SettingsWidget::AddDocumentSettingsGroup()
{
const AZStd::string groupName = "documentSettings";
const AZStd::string groupDisplayName = "Document Settings";
const AZStd::string groupDescription = "Document Settings";
const AZ::Crc32 saveStateKey(AZStd::string::format("SettingsWidget::DocumentSettingsGroup"));
AddGroup(
groupName, groupDisplayName, groupDescription,
new AtomToolsFramework::InspectorPropertyGroupWidget(
m_documentSettings.get(), nullptr, m_documentSettings->TYPEINFO_Uuid(), this, this, saveStateKey));
}
void SettingsWidget::AddDocumentSystemSettingsGroup()
{
const AZStd::string groupName = "documentSystemSettings";
@@ -56,7 +40,7 @@ namespace MaterialEditor
AddGroup(
groupName, groupDisplayName, groupDescription,
new AtomToolsFramework::InspectorPropertyGroupWidget(
m_documentSystemSettings.get(), nullptr, m_documentSystemSettings->TYPEINFO_Uuid(), this, this, saveStateKey));
m_documentSystemSettings.get(), nullptr, m_documentSystemSettings->TYPEINFO_Uuid(), nullptr, this, saveStateKey));
}
void SettingsWidget::Reset()
@@ -64,21 +48,6 @@ namespace MaterialEditor
AtomToolsFramework::InspectorRequestBus::Handler::BusDisconnect();
AtomToolsFramework::InspectorWidget::Reset();
}
void SettingsWidget::BeforePropertyModified(AzToolsFramework::InstanceDataNode* pNode)
{
AZ_UNUSED(pNode);
}
void SettingsWidget::AfterPropertyModified(AzToolsFramework::InstanceDataNode* pNode)
{
AZ_UNUSED(pNode);
}
void SettingsWidget::SetPropertyEditingComplete(AzToolsFramework::InstanceDataNode* pNode)
{
AZ_UNUSED(pNode);
}
} // namespace MaterialEditor
//#include <Window/SettingsWidget/moc_SettingsWidget.cpp>
@@ -12,7 +12,6 @@
#include <AtomToolsFramework/Document/AtomToolsDocumentSystemSettings.h>
#include <AtomToolsFramework/Inspector/InspectorWidget.h>
#include <AzToolsFramework/UI/PropertyEditor/PropertyEditorAPI_Internals.h>
#include <Document/MaterialDocumentSettings.h>
#endif
namespace MaterialEditor
@@ -20,7 +19,6 @@ namespace MaterialEditor
//! Provides controls for viewing and editing settings.
class SettingsWidget
: public AtomToolsFramework::InspectorWidget
, private AzToolsFramework::IPropertyEditorNotify
{
Q_OBJECT
public:
@@ -32,22 +30,11 @@ namespace MaterialEditor
void Populate();
private:
void AddDocumentSettingsGroup();
void AddDocumentSystemSettingsGroup();
// AtomToolsFramework::InspectorRequestBus::Handler overrides...
void Reset() override;
// AzToolsFramework::IPropertyEditorNotify overrides...
void BeforePropertyModified(AzToolsFramework::InstanceDataNode* pNode) override;
void AfterPropertyModified(AzToolsFramework::InstanceDataNode* pNode) override;
void SetPropertyEditingActive([[maybe_unused]] AzToolsFramework::InstanceDataNode* pNode) override {}
void SetPropertyEditingComplete(AzToolsFramework::InstanceDataNode* pNode) override;
void SealUndoStack() override {}
void RequestPropertyContextMenu(AzToolsFramework::InstanceDataNode*, const QPoint&) override {}
void PropertySelectionChanged(AzToolsFramework::InstanceDataNode*, bool) override {}
AZStd::intrusive_ptr<MaterialDocumentSettings> m_documentSettings;
AZStd::intrusive_ptr<AtomToolsFramework::AtomToolsDocumentSystemSettings> m_documentSystemSettings;
};
} // namespace MaterialEditor
@@ -12,10 +12,8 @@ set(FILES
Source/MaterialEditorApplication.h
Source/Document/MaterialDocumentRequestBus.h
Source/Document/MaterialDocumentSettings.h
Source/Document/MaterialDocument.cpp
Source/Document/MaterialDocument.h
Source/Document/MaterialDocumentSettings.cpp
Source/Viewport/MaterialViewportModule.h
Source/Viewport/MaterialViewportModule.cpp
@@ -49,8 +47,6 @@ set(FILES
Source/Viewport/MaterialViewportWidget.ui
Source/Window/MaterialEditorWindowSettings.h
Source/Window/MaterialEditorBrowserInteractions.h
Source/Window/MaterialEditorBrowserInteractions.cpp
Source/Window/MaterialEditorWindow.h
Source/Window/MaterialEditorWindow.cpp
Source/Window/MaterialEditorWindowSettings.cpp
@@ -12,12 +12,16 @@
#include <Atom/RPI.Public/Material/Material.h>
#include <Atom/RPI.Reflect/Asset/AssetUtils.h>
#include <AtomToolsFramework/Document/AtomToolsDocumentSystemRequestBus.h>
#include <AtomToolsFramework/Util/Util.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzCore/Utils/Utils.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <AzToolsFramework/API/EditorPythonRunnerRequestsBus.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserEntry.h>
#include <AzToolsFramework/UI/UICore/QWidgetSavedState.h>
#include <Document/ShaderManagementConsoleDocument.h>
#include <Document/ShaderManagementConsoleDocumentRequestBus.h>
@@ -25,11 +29,13 @@
#include <ShaderManagementConsoleRequestBus.h>
#include <ShaderManagementConsole_Traits_Platform.h>
AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT
#include <QDesktopServices>
#include <QDialog>
#include <QFile>
#include <QFileDialog>
#include <QMenu>
#include <QMessageBox>
AZ_POP_DISABLE_WARNING
#include <QUrl>
void InitShaderManagementConsoleResources()
{
@@ -135,9 +141,46 @@ namespace ShaderManagementConsole
void ShaderManagementConsoleApplication::CreateMainWindow()
{
m_assetBrowserInteractions.reset(aznew ShaderManagementConsoleBrowserInteractions);
m_window.reset(aznew ShaderManagementConsoleWindow);
m_window->show();
m_assetBrowserInteractions.reset(aznew AtomToolsFramework::AtomToolsAssetBrowserInteractions);
m_assetBrowserInteractions->RegisterContextMenuActions(
[](const AtomToolsFramework::AtomToolsAssetBrowserInteractions::AssetBrowserEntryVector& entries)
{
return entries.front()->GetEntryType() == AzToolsFramework::AssetBrowser::AssetBrowserEntry::AssetEntryType::Source;
},
[]([[maybe_unused]] QWidget* caller, QMenu* menu, const AtomToolsFramework::AtomToolsAssetBrowserInteractions::AssetBrowserEntryVector& entries)
{
if (AzFramework::StringFunc::Path::IsExtension(
entries.front()->GetFullPath().c_str(), AZ::RPI::ShaderSourceData::Extension))
{
menu->addAction("Generate Shader Variant List", [entries]()
{
const QString script =
"@engroot@/Gems/Atom/Tools/ShaderManagementConsole/Scripts/GenerateShaderVariantListForMaterials.py";
AZStd::vector<AZStd::string_view> pythonArgs{ entries.front()->GetFullPath() };
AzToolsFramework::EditorPythonRunnerRequestBus::Broadcast(
&AzToolsFramework::EditorPythonRunnerRequestBus::Events::ExecuteByFilenameWithArgs, script.toUtf8().constData(),
pythonArgs);
});
}
else if (AzFramework::StringFunc::Path::IsExtension(
entries.front()->GetFullPath().c_str(), AZ::RPI::ShaderVariantListSourceData::Extension))
{
menu->addAction(QObject::tr("Open"), [entries]()
{
AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Broadcast(
&AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Events::OpenDocument,
entries.front()->GetFullPath());
});
}
else
{
menu->addAction(QObject::tr("Open"), [entries]()
{
QDesktopServices::openUrl(QUrl::fromLocalFile(entries.front()->GetFullPath().c_str()));
});
}
});
}
void ShaderManagementConsoleApplication::DestroyMainWindow()
@@ -9,11 +9,11 @@
#pragma once
#include <Atom/RPI.Reflect/Material/MaterialAsset.h>
#include <AtomToolsFramework/AssetBrowser/AtomToolsAssetBrowserInteractions.h>
#include <AtomToolsFramework/Document/AtomToolsDocumentApplication.h>
#include <AtomToolsFramework/Window/AtomToolsMainWindowFactoryRequestBus.h>
#include <AzToolsFramework/API/EditorWindowRequestBus.h>
#include <ShaderManagementConsoleRequestBus.h>
#include <Window/ShaderManagementConsoleBrowserInteractions.h>
#include <Window/ShaderManagementConsoleWindow.h>
namespace ShaderManagementConsole
@@ -55,6 +55,6 @@ namespace ShaderManagementConsole
private:
AZStd::unique_ptr<ShaderManagementConsoleWindow> m_window;
AZStd::unique_ptr<ShaderManagementConsoleBrowserInteractions> m_assetBrowserInteractions;
AZStd::unique_ptr<AtomToolsFramework::AtomToolsAssetBrowserInteractions> m_assetBrowserInteractions;
};
} // namespace ShaderManagementConsole
@@ -1,273 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <Atom/RPI.Edit/Shader/ShaderVariantListSourceData.h>
#include <AtomToolsFramework/Document/AtomToolsDocumentSystemRequestBus.h>
#include <AtomToolsFramework/Util/Util.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzQtComponents/Utilities/DesktopUtilities.h>
#include <AzToolsFramework/API/EditorPythonRunnerRequestsBus.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserBus.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserEntry.h>
#include <AzToolsFramework/AssetBrowser/AssetSelectionModel.h>
#include <AzToolsFramework/Thumbnails/SourceControlThumbnail.h>
#include <Window/ShaderManagementConsoleBrowserInteractions.h>
#include <QApplication>
#include <QDesktopServices>
#include <QFileDialog>
#include <QInputDialog>
#include <QMenu>
#include <QMessageBox>
namespace ShaderManagementConsole
{
ShaderManagementConsoleBrowserInteractions::ShaderManagementConsoleBrowserInteractions()
{
using namespace AzToolsFramework::AssetBrowser;
AssetBrowserInteractionNotificationBus::Handler::BusConnect();
}
ShaderManagementConsoleBrowserInteractions::~ShaderManagementConsoleBrowserInteractions()
{
AssetBrowserInteractionNotificationBus::Handler::BusDisconnect();
}
AzToolsFramework::AssetBrowser::SourceFileDetails ShaderManagementConsoleBrowserInteractions::GetSourceFileDetails([[maybe_unused]] const char* fullSourceFileName)
{
return AzToolsFramework::AssetBrowser::SourceFileDetails();
}
void ShaderManagementConsoleBrowserInteractions::AddContextMenuActions(QWidget* caller, QMenu* menu, const AZStd::vector<AzToolsFramework::AssetBrowser::AssetBrowserEntry*>& entries)
{
AssetBrowserEntry* entry = entries.empty() ? nullptr : entries.front();
if (!entry)
{
return;
}
m_caller = caller;
QObject::connect(m_caller, &QObject::destroyed, [this]()
{
m_caller = nullptr;
});
if (entry->GetEntryType() == AssetBrowserEntry::AssetEntryType::Source)
{
const auto source = azalias_cast<const SourceAssetBrowserEntry*>(entry);
AddContextMenuActionsForOtherSource(caller, menu, source);
}
else if (entry->GetEntryType() == AssetBrowserEntry::AssetEntryType::Folder)
{
const auto folder = azalias_cast<const FolderAssetBrowserEntry*>(entry);
AddContextMenuActionsForFolder(caller, menu, folder);
}
}
void ShaderManagementConsoleBrowserInteractions::AddContextMenuActionsForOtherSource(QWidget* caller, QMenu* menu, const AzToolsFramework::AssetBrowser::SourceAssetBrowserEntry* entry)
{
menu->addAction("Open", [entry]()
{
if (AzFramework::StringFunc::Path::IsExtension(entry->GetFullPath().c_str(), AZ::RPI::ShaderVariantListSourceData::Extension))
{
AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Broadcast(&AtomToolsFramework::AtomToolsDocumentSystemRequestBus::Events::OpenDocument, entry->GetFullPath().c_str());
}
else
{
QDesktopServices::openUrl(QUrl::fromLocalFile(entry->GetFullPath().c_str()));
}
});
menu->addAction("Duplicate...", [entry]()
{
const QFileInfo duplicateFileInfo(AtomToolsFramework::GetDuplicationFileInfo(entry->GetFullPath().c_str()));
if (!duplicateFileInfo.absoluteFilePath().isEmpty())
{
if (QFile::copy(entry->GetFullPath().c_str(), duplicateFileInfo.absoluteFilePath()))
{
QFile::setPermissions(duplicateFileInfo.absoluteFilePath(), QFile::ReadOther | QFile::WriteOther);
// Auto add file to source control
AzToolsFramework::SourceControlCommandBus::Broadcast(&AzToolsFramework::SourceControlCommandBus::Events::RequestEdit,
duplicateFileInfo.absoluteFilePath().toUtf8().constData(), true, [](bool, const AzToolsFramework::SourceControlFileInfo&) {});
}
}
});
menu->addAction(AzQtComponents::fileBrowserActionName(), [entry]()
{
AzQtComponents::ShowFileOnDesktop(entry->GetFullPath().c_str());
});
menu->addSeparator();
menu->addAction("Generate Shader Variant List", [entry]() {
const QString script = "@engroot@/Gems/Atom/Tools/ShaderManagementConsole/Scripts/GenerateShaderVariantListForMaterials.py";
AZStd::vector<AZStd::string_view> pythonArgs{ entry->GetFullPath() };
AzToolsFramework::EditorPythonRunnerRequestBus::Broadcast(&AzToolsFramework::EditorPythonRunnerRequestBus::Events::ExecuteByFilenameWithArgs, script.toUtf8().constData(), pythonArgs);
});
menu->addAction("Run Python on Asset...", [entry]()
{
const QString script = QFileDialog::getOpenFileName(nullptr, "Run Script", QString(), QString("*.py"));
if (!script.isEmpty())
{
AZStd::vector<AZStd::string_view> pythonArgs { entry->GetFullPath() };
AzToolsFramework::EditorPythonRunnerRequestBus::Broadcast(&AzToolsFramework::EditorPythonRunnerRequestBus::Events::ExecuteByFilenameWithArgs, script.toUtf8().constData(), pythonArgs);
}
});
AddPerforceMenuActions(caller, menu, entry);
}
void ShaderManagementConsoleBrowserInteractions::AddContextMenuActionsForFolder(QWidget* caller, QMenu* menu, const AzToolsFramework::AssetBrowser::FolderAssetBrowserEntry* entry)
{
menu->addAction(AzQtComponents::fileBrowserActionName(), [entry]()
{
AzQtComponents::ShowFileOnDesktop(entry->GetFullPath().c_str());
});
QAction* createFolderAction = menu->addAction(QObject::tr("Create new sub folder..."));
QObject::connect(createFolderAction, &QAction::triggered, caller, [caller, entry]()
{
bool ok;
QString newFolderName = QInputDialog::getText(caller, "Enter new folder name", "name:", QLineEdit::Normal, "NewFolder", &ok);
if (ok)
{
if (newFolderName.isEmpty())
{
QMessageBox msgBox(QMessageBox::Icon::Critical, "Error", "Folder name can't be empty", QMessageBox::Ok, caller);
msgBox.exec();
}
else
{
AZStd::string newFolderPath;
AzFramework::StringFunc::Path::Join(entry->GetFullPath().c_str(), newFolderName.toUtf8().constData(), newFolderPath);
QDir dir(newFolderPath.c_str());
if (dir.exists())
{
QMessageBox::critical(caller, "Error", "Folder with this name already exists");
return;
}
auto result = dir.mkdir(newFolderPath.c_str());
if (!result)
{
AZ_Error("ShaderManagementConsoleBrowser", false, "Failed to make new folder");
return;
}
}
}
});
}
void ShaderManagementConsoleBrowserInteractions::AddPerforceMenuActions([[maybe_unused]] QWidget* caller, QMenu* menu, const AzToolsFramework::AssetBrowser::AssetBrowserEntry* entry)
{
using namespace AzToolsFramework;
bool isActive = false;
SourceControlConnectionRequestBus::BroadcastResult(isActive, &SourceControlConnectionRequests::IsActive);
if (isActive)
{
menu->addSeparator();
AZStd::string path = entry->GetFullPath();
AzFramework::StringFunc::Path::Normalize(path);
QMenu* sourceControlMenu = menu->addMenu("Source Control");
// Update the enabled state of source control menu actions only if menu is shown
QMenu::connect(sourceControlMenu, &QMenu::aboutToShow, [this, path]()
{
SourceControlCommandBus::Broadcast(&SourceControlCommandBus::Events::GetFileInfo, path.c_str(),
[this](bool success, const SourceControlFileInfo& info) { UpdateSourceControlActions(success, info); });
});
// add get latest action
m_getLatestAction = sourceControlMenu->addAction("Get Latest", [path]()
{
SourceControlCommandBus::Broadcast(&SourceControlCommandBus::Events::RequestLatest, path.c_str(),
[](bool, const SourceControlFileInfo&) {});
});
QObject::connect(m_getLatestAction, &QObject::destroyed, [this]()
{
m_getLatestAction = nullptr;
});
m_getLatestAction->setEnabled(false);
// add add action
m_addAction = sourceControlMenu->addAction("Add", [path]()
{
SourceControlCommandBus::Broadcast(&SourceControlCommandBus::Events::RequestEdit, path.c_str(), true,
[path](bool, const SourceControlFileInfo&)
{
SourceControlThumbnailRequestBus::Broadcast(&SourceControlThumbnailRequests::FileStatusChanged, path.c_str());
});
});
QObject::connect(m_addAction, &QObject::destroyed, [this]()
{
m_addAction = nullptr;
});
m_addAction->setEnabled(false);
// add checkout action
m_checkOutAction = sourceControlMenu->addAction("Check Out", [path]()
{
SourceControlCommandBus::Broadcast(&SourceControlCommandBus::Events::RequestEdit, path.c_str(), true,
[path](bool, const SourceControlFileInfo&)
{
SourceControlThumbnailRequestBus::Broadcast(&SourceControlThumbnailRequests::FileStatusChanged, path.c_str());
});
});
QObject::connect(m_checkOutAction, &QObject::destroyed, [this]()
{
m_checkOutAction = nullptr;
});
m_checkOutAction->setEnabled(false);
// add undo checkout action
m_undoCheckOutAction = sourceControlMenu->addAction("Undo Check Out", [path]()
{
SourceControlCommandBus::Broadcast(&SourceControlCommandBus::Events::RequestRevert, path.c_str(),
[path](bool, const SourceControlFileInfo&)
{
SourceControlThumbnailRequestBus::Broadcast(&SourceControlThumbnailRequests::FileStatusChanged, path.c_str());
});
});
QObject::connect(m_undoCheckOutAction, &QObject::destroyed, [this]()
{
m_undoCheckOutAction = nullptr;
});
m_undoCheckOutAction->setEnabled(false);
}
}
void ShaderManagementConsoleBrowserInteractions::UpdateSourceControlActions(bool success, AzToolsFramework::SourceControlFileInfo info)
{
if (!success && m_caller)
{
QMessageBox::critical(m_caller, "Error", "Source control operation failed.");
}
if (m_getLatestAction)
{
m_getLatestAction->setEnabled(info.IsManaged() && info.HasFlag(AzToolsFramework::SCF_OutOfDate));
}
if (m_addAction)
{
m_addAction->setEnabled(!info.IsManaged());
}
if (m_checkOutAction)
{
m_checkOutAction->setEnabled(info.IsManaged() && info.IsReadOnly() && !info.IsLockedByOther());
}
if (m_undoCheckOutAction)
{
m_undoCheckOutAction->setEnabled(info.IsManaged() && !info.IsReadOnly());
}
}
} // namespace ShaderManagementConsole
@@ -1,58 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzToolsFramework/AssetBrowser/AssetBrowserBus.h>
#include <AzToolsFramework/SourceControl/SourceControlAPI.h>
class QWidget;
class QMenu;
class QAction;
namespace AzToolsFramework
{
namespace AssetBrowser
{
class AssetBrowserEntry;
class SourceAssetBrowserEntry;
class FolderAssetBrowserEntry;
}
}
namespace ShaderManagementConsole
{
class ShaderManagementConsoleBrowserInteractions
: public AzToolsFramework::AssetBrowser::AssetBrowserInteractionNotificationBus::Handler
{
public:
AZ_CLASS_ALLOCATOR(ShaderManagementConsoleBrowserInteractions, AZ::SystemAllocator, 0);
ShaderManagementConsoleBrowserInteractions();
~ShaderManagementConsoleBrowserInteractions();
private:
////////////////////////////////////////////////////////////////////////
// AssetBrowserInteractionNotificationBus::Handler implementation
AzToolsFramework::AssetBrowser::SourceFileDetails GetSourceFileDetails(const char* fullSourceFileName) override;
void AddContextMenuActions(QWidget* caller, QMenu* menu, const AZStd::vector<AzToolsFramework::AssetBrowser::AssetBrowserEntry*>& entries) override;
////////////////////////////////////////////////////////////////////////
void AddContextMenuActionsForOtherSource(QWidget* caller, QMenu* menu, const AzToolsFramework::AssetBrowser::SourceAssetBrowserEntry* entry);
void AddContextMenuActionsForFolder(QWidget* caller, QMenu* menu, const AzToolsFramework::AssetBrowser::FolderAssetBrowserEntry* entry);
void AddPerforceMenuActions(QWidget* caller, QMenu* menu, const AzToolsFramework::AssetBrowser::AssetBrowserEntry* entry);
void UpdateSourceControlActions(bool success, AzToolsFramework::SourceControlFileInfo info);
QWidget* m_caller = nullptr;
QAction* m_addAction = nullptr;
QAction* m_checkOutAction = nullptr;
QAction* m_undoCheckOutAction = nullptr;
QAction* m_getLatestAction = nullptr;
};
} // namespace ShaderManagementConsole
@@ -11,8 +11,6 @@ set(FILES
Source/Document/ShaderManagementConsoleDocument.cpp
Source/Document/ShaderManagementConsoleDocument.h
Source/Window/ShaderManagementConsoleBrowserInteractions.h
Source/Window/ShaderManagementConsoleBrowserInteractions.cpp
Source/Window/ShaderManagementConsoleWindow.h
Source/Window/ShaderManagementConsoleWindow.cpp
Source/Window/ShaderManagementConsole.qrc
@@ -177,10 +177,19 @@ namespace EMStudio
m_rotateManipulators.ConfigureView(
AzToolsFramework::RotationManipulatorRadius(), AzFramework::ViewportColors::XAxisColor, AzFramework::ViewportColors::YAxisColor,
AzFramework::ViewportColors::ZAxisColor);
m_rotateManipulators.InstallLeftMouseDownCallback(
[this]([[maybe_unused]]const AzToolsFramework::AngularManipulator::Action& action)
{
const AZ::EntityId entityId = m_animViewportWidget->GetAnimViewportRenderer()->GetEntityId();
AZ::TransformBus::EventResult(m_mouseDownStartTransform, entityId, &AZ::TransformBus::Events::GetLocalTM);
});
m_rotateManipulators.InstallMouseMoveCallback(
[this](const AzToolsFramework::AngularManipulator::Action& action)
{
OnManipulatorRotated(action.LocalOrientation());
const AZ::EntityId entityId = m_animViewportWidget->GetAnimViewportRenderer()->GetEntityId();
AZ::TransformBus::Event(entityId, &AZ::TransformBus::Events::SetLocalRotationQuaternion,
m_mouseDownStartTransform.GetRotation() * action.m_current.m_delta);
});
// Setup the scale manipulator
@@ -189,10 +198,21 @@ namespace EMStudio
m_scaleManipulators.ConfigureView(
AzToolsFramework::LinearManipulatorAxisLength(), AzFramework::ViewportColors::XAxisColor,
AzFramework::ViewportColors::YAxisColor, AzFramework::ViewportColors::ZAxisColor);
m_scaleManipulators.InstallAxisLeftMouseDownCallback(
[this]([[maybe_unused]] const AzToolsFramework::LinearManipulator::Action& action)
{
const AZ::EntityId entityId = m_animViewportWidget->GetAnimViewportRenderer()->GetEntityId();
AZ::TransformBus::EventResult(m_mouseDownStartTransform, entityId, &AZ::TransformBus::Events::GetLocalTM);
});
m_scaleManipulators.InstallAxisMouseMoveCallback(
[this](const AzToolsFramework::LinearManipulator::Action& action)
{
OnManipulatorScaled(action.LocalScale(), action.LocalScaleOffset());
// Since we are compulting a uniform scale, the delta scale should be the none-zero value from one of the three axis.
const float deltaScale = action.m_current.m_localPositionOffset.GetMaxElement()
+ action.m_current.m_localPositionOffset.GetMinElement();
const AZ::EntityId entityId = m_animViewportWidget->GetAnimViewportRenderer()->GetEntityId();
AZ::TransformBus::Event(entityId, &AZ::TransformBus::Events::SetLocalUniformScale,
m_mouseDownStartTransform.GetUniformScale() + deltaScale);
});
}
@@ -259,33 +279,6 @@ namespace EMStudio
AZ::TransformBus::Event(entityId, &AZ::TransformBus::Events::SetLocalTranslation, position);
}
void AtomRenderPlugin::OnManipulatorRotated(const AZ::Quaternion& rotation)
{
const AZ::EntityId entityId = m_animViewportWidget->GetAnimViewportRenderer()->GetEntityId();
AZ::TransformBus::Event(entityId, &AZ::TransformBus::Events::SetLocalRotationQuaternion, rotation);
}
void AtomRenderPlugin::OnManipulatorScaled(
const AZ::Vector3& scale, const AZ::Vector3& scaleOffset)
{
// Use the scaleOffset to determine which axis to use on the uniform scale.
float localScale = 1.0f;
if (scaleOffset.GetX() != 0.0f)
{
localScale = scale.GetX();
}
else if (scaleOffset.GetY() != 0.0f)
{
localScale = scale.GetY();
}
else if (scaleOffset.GetZ() != 0.0f)
{
localScale = scale.GetZ();
}
const AZ::EntityId entityId = m_animViewportWidget->GetAnimViewportRenderer()->GetEntityId();
AZ::TransformBus::Event(entityId, &AZ::TransformBus::Events::SetLocalUniformScale, localScale);
}
void AtomRenderPlugin::LoadRenderOptions()
{
AZStd::string renderOptionsFilename(GetManager()->GetAppDataFolder());
@@ -70,8 +70,6 @@ namespace EMStudio
void SetupManipulators();
void OnManipulatorMoved(const AZ::Vector3& position);
void OnManipulatorRotated(const AZ::Quaternion& rotation);
void OnManipulatorScaled(const AZ::Vector3& scale, const AZ::Vector3& scaleOffset);
QWidget* m_innerWidget = nullptr;
AnimViewportWidget* m_animViewportWidget = nullptr;
@@ -82,6 +80,7 @@ namespace EMStudio
AzToolsFramework::RotationManipulators m_rotateManipulators;
AzToolsFramework::ScaleManipulators m_scaleManipulators;
AZStd::shared_ptr<AzToolsFramework::ManipulatorManager> m_manipulatorManager;
AZ::Transform m_mouseDownStartTransform;
MCORE_DEFINECOMMANDCALLBACK(ImportActorCallback);
MCORE_DEFINECOMMANDCALLBACK(RemoveActorCallback);
+20 -21
View File
@@ -282,8 +282,8 @@ namespace AMD
TressFXTFXFileHeader header = {};
// read the header
EI_Seek(ioObject, 0); // make sure the stream pos is at the beginning.
EI_Read((void*)&header, sizeof(TressFXTFXFileHeader), ioObject);
[[maybe_unused]] auto eiSeekResult = EI_Seek(ioObject, 0); // make sure the stream pos is at the beginning.
[[maybe_unused]] auto eiReadResult = EI_Read((void*)&header, sizeof(TressFXTFXFileHeader), ioObject);
// If the tfx version is lower than the current major version, exit.
if (header.version < AMD_TRESSFX_VERSION_MAJOR)
@@ -317,8 +317,8 @@ namespace AMD
m_positions.resize(m_numTotalVertices); // size of m_positions = number of total vertices * sizeo of each position vector.
// Read position data from the io stream.
EI_Seek(ioObject, header.offsetVertexPosition);
EI_Read((void*)m_positions.data(), numStrandsInFile * m_numVerticesPerStrand * sizeof(AMD::float4), ioObject); // note that the position data in io stream contains only guide hairs. If we call GenerateFollowHairs
eiSeekResult = EI_Seek(ioObject, header.offsetVertexPosition);
eiReadResult = EI_Read((void*)m_positions.data(), numStrandsInFile * m_numVerticesPerStrand * sizeof(AMD::float4), ioObject); // note that the position data in io stream contains only guide hairs. If we call GenerateFollowHairs
// to generate follow hairs, m_positions will be re-allocated.
// We need to make up some strands to fill up the buffer because the number of strands from stream is not necessarily multile of thread size.
@@ -335,11 +335,11 @@ namespace AMD
}
// Read strand UVs
EI_Seek(ioObject, header.offsetStrandUV);
eiSeekResult = EI_Seek(ioObject, header.offsetStrandUV);
m_strandUV.resize(m_numTotalStrands); // If we call GenerateFollowHairs to generate follow hairs,
// m_strandUV will be re-allocated.
EI_Read((void*)m_strandUV.data(), numStrandsInFile * sizeof(AMD::float2), ioObject);
eiReadResult = EI_Read((void*)m_strandUV.data(), numStrandsInFile * sizeof(AMD::float2), ioObject);
// Fill up the last empty space
AMD::int32 indexLastStrand = (numStrandsInFile - 1);
@@ -683,21 +683,21 @@ namespace AMD
void TressFXAsset::GetBonesNames(FILE* ioObject, std::vector<std::string>& boneNames)
{
AMD::int32 numOfBones = 0;
EI_Seek(ioObject, 0);
EI_Read((void*)&numOfBones, sizeof(AMD::int32), ioObject);
[[maybe_unused]] auto eiSeekResult = EI_Seek(ioObject, 0);
[[maybe_unused]] auto eiReadResult = EI_Read((void*)&numOfBones, sizeof(AMD::int32), ioObject);
// boneNames.reserve(numOfBones);
boneNames.resize(numOfBones);
for (int i = 0; i < numOfBones; i++)
{
int boneIndex;
EI_Read((char*)&boneIndex, sizeof(AMD::int32), ioObject);
eiReadResult = EI_Read((char*)&boneIndex, sizeof(AMD::int32), ioObject);
AMD::int32 charLen = 0;
EI_Read((char*)&charLen, sizeof(AMD::int32), ioObject); // character length includes null termination already.
eiReadResult = EI_Read((char*)&charLen, sizeof(AMD::int32), ioObject); // character length includes null termination already.
char boneName[128];
EI_Read(boneName, sizeof(char) * charLen, ioObject);
eiReadResult = EI_Read(boneName, sizeof(char) * charLen, ioObject);
boneName[charLen] = '\0'; // adding 0 termination to be on the safe side.
boneNames[i] = std::string(boneName);
}
@@ -730,8 +730,8 @@ namespace AMD
m_boneSkinningData.resize(0);
AMD::int32 numOfBones = 0;
EI_Seek(ioObject, 0);
EI_Read((void*)&numOfBones, sizeof(AMD::int32), ioObject);
[[maybe_unused]] auto eiSeekResult = EI_Seek(ioObject, 0);
[[maybe_unused]] auto eiReadResult = EI_Read((void*)&numOfBones, sizeof(AMD::int32), ioObject);
if (skeletonBoneIndices.size() != numOfBones)
{
@@ -742,18 +742,18 @@ namespace AMD
for (int i = 0; i < numOfBones; i++)
{
int boneIndex;
EI_Read((char*)&boneIndex, sizeof(AMD::int32), ioObject);
eiReadResult = EI_Read((char*)&boneIndex, sizeof(AMD::int32), ioObject);
AMD::int32 charLen = 0;
EI_Read((char*)&charLen, sizeof(AMD::int32), ioObject); // character length includes null termination already.
eiReadResult = EI_Read((char*)&charLen, sizeof(AMD::int32), ioObject); // character length includes null termination already.
char boneName[128];
EI_Read(boneName, sizeof(char) * charLen, ioObject);
eiReadResult = EI_Read(boneName, sizeof(char) * charLen, ioObject);
}
// Reading the number of strands
AMD::int32 numOfStrandsInStream = 0;
EI_Read((char*)&numOfStrandsInStream, sizeof(AMD::int32), ioObject);
eiReadResult = EI_Read((char*)&numOfStrandsInStream, sizeof(AMD::int32), ioObject);
//If the number of strands from the input stream (tfxbone) is bigger than what we already know from tfx, something is wrong.
if (m_numGuideStrands < numOfStrandsInStream)
@@ -765,15 +765,15 @@ namespace AMD
for (int i = 0; i < numOfStrandsInStream; ++i)
{
AMD::int32 index = 0; // Well, we don't really use this here.
EI_Read((char*)&index, sizeof(AMD::int32), ioObject);
eiReadResult = EI_Read((char*)&index, sizeof(AMD::int32), ioObject);
for (AMD::int32 j = 0; j < TRESSFX_MAX_INFLUENTIAL_BONE_COUNT; ++j)
{
AMD::int32 boneIndex;
EI_Read((char*)&boneIndex, sizeof(AMD::int32), ioObject);
eiReadResult = EI_Read((char*)&boneIndex, sizeof(AMD::int32), ioObject);
assert(boneIndex >= 0);
skinData.boneIndex[j] = (float)skeletonBoneIndices[boneIndex]; // Change the joint index to be what the engine wants
EI_Read((char*)&skinData.weight[j], sizeof(AMD::real32), ioObject);
eiReadResult = EI_Read((char*)&skinData.weight[j], sizeof(AMD::real32), ioObject);
}
#if defined(AZ_ENABLE_TRACING)
@@ -987,4 +987,3 @@ namespace AMD
return true;
}
} // namespace AMD
@@ -251,7 +251,7 @@ namespace GradientSignal
const float value = m_gradientSampler.GetValue(sampleParams);
if (value >= m_configuration.m_thresholdMin && value <= m_configuration.m_thresholdMax)
{
weights.AddSurfaceWeightsIfGreater(m_configuration.m_modifierTags, value);
weights.AddSurfaceTagWeights(m_configuration.m_modifierTags, value);
}
}
});
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f974fa29f3542311ee6a4b6fbb92047186c3124b3bd6f88b728646bf9d686c45
size 39099
+44 -52
View File
@@ -99,57 +99,49 @@ endif()
################################################################################
# See if globally, tests are supported
if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
# We globally support tests, see if we support tests on this platform for MotionMatching.Static
if(PAL_TRAIT_MOTIONMATCHING_TEST_SUPPORTED)
# We support MotionMatching.Tests on this platform, add MotionMatching.Tests target which depends on MotionMatching.Static
ly_add_target(
NAME MotionMatching.Tests ${PAL_TRAIT_TEST_TARGET_TYPE}
NAMESPACE Gem
FILES_CMAKE
motionmatching_files.cmake
motionmatching_tests_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
Tests
Source
BUILD_DEPENDENCIES
PRIVATE
AZ::AzTest
AZ::AzFramework
Gem::EMotionFX.Tests.Static
Gem::MotionMatching.Static
)
ly_add_target(
NAME MotionMatching.Tests ${PAL_TRAIT_TEST_TARGET_TYPE}
NAMESPACE Gem
FILES_CMAKE
motionmatching_files.cmake
motionmatching_tests_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
Tests
Source
BUILD_DEPENDENCIES
PRIVATE
AZ::AzTest
AZ::AzFramework
Gem::EMotionFX.Tests.Static
Gem::MotionMatching.Static
)
# Add MotionMatching.Tests to googletest
ly_add_googletest(
NAME Gem::MotionMatching.Tests
)
endif()
# If we are a host platform we want to add tools test like editor tests here
if(PAL_TRAIT_BUILD_HOST_TOOLS)
# We are a host platform, see if Editor tests are supported on this platform
if(PAL_TRAIT_MOTIONMATCHING_EDITOR_TEST_SUPPORTED)
# We support MotionMatching.Editor.Tests on this platform, add MotionMatching.Editor.Tests target which depends on MotionMatching.Editor
ly_add_target(
NAME MotionMatching.Editor.Tests ${PAL_TRAIT_TEST_TARGET_TYPE}
NAMESPACE Gem
FILES_CMAKE
motionmatching_editor_tests_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
Tests
Source
BUILD_DEPENDENCIES
PRIVATE
AZ::AzTest
Gem::MotionMatching.Editor
)
# Add MotionMatching.Editor.Tests to googletest
ly_add_googletest(
NAME Gem::MotionMatching.Editor.Tests
)
endif()
endif()
# Add MotionMatching.Tests to googletest
ly_add_googletest(
NAME Gem::MotionMatching.Tests
)
endif()
# If we are a host platform we want to add tools test like editor tests here
if(PAL_TRAIT_BUILD_HOST_TOOLS)
ly_add_target(
NAME MotionMatching.Editor.Tests ${PAL_TRAIT_TEST_TARGET_TYPE}
NAMESPACE Gem
FILES_CMAKE
motionmatching_editor_tests_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
Tests
Source
BUILD_DEPENDENCIES
PRIVATE
AZ::AzTest
Gem::MotionMatching.Editor
)
# Add MotionMatching.Editor.Tests to googletest
ly_add_googletest(
NAME Gem::MotionMatching.Editor.Tests
)
endif()
@@ -11,8 +11,20 @@
#include <AzCore/EBus/EBus.h>
#include <AzCore/Interface/Interface.h>
#include <AzFramework/Entity/EntityDebugDisplayBus.h>
namespace EMotionFX::MotionMatching
{
class DebugDrawRequests
: public AZ::EBusTraits
{
public:
AZ_RTTI(DebugDrawRequests, "{7BBA4249-EC00-445C-8A0C-4472841049C3}");
virtual void DebugDraw(AzFramework::DebugDisplayRequests& debugDisplay) = 0;
};
using DebugDrawRequestBus = AZ::EBus<DebugDrawRequests>;
class MotionMatchingRequests
{
public:
@@ -297,8 +297,6 @@ namespace EMotionFX::MotionMatching
ImGuiMonitorRequestBus::Broadcast(&ImGuiMonitorRequests::PushPerformanceHistogramValue, "Output", m_outputTimeInMs);
#endif
}
instance->DebugDraw();
}
AZ::Crc32 BlendTreeMotionMatchNode::GetTrajectoryPathSettingsVisibility() const
@@ -16,10 +16,11 @@ namespace EMotionFX::MotionMatching
ImGuiMonitor::ImGuiMonitor()
{
m_performanceStats.m_name = "Performance Statistics";
m_performanceStats.SetName("Performance Statistics");
m_performanceStats.SetHistogramBinCount(500);
m_featureCosts.m_name = "Feature Costs";
m_featureCosts.m_histogramContainerCount = 100;
m_featureCosts.SetName("Feature Costs");
m_featureCosts.SetHistogramBinCount(100);
ImGui::ImGuiUpdateListenerBus::Handler::BusConnect();
ImGuiMonitorRequestBus::Handler::BusConnect();
@@ -40,18 +41,40 @@ namespace EMotionFX::MotionMatching
if (ImGui::Begin("Motion Matching"))
{
if (ImGui::CollapsingHeader("Motion Database", ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_Framed))
{
if (ImGui::BeginTable("MDB", 2))
{
ImGui::TableNextColumn(); ImGui::Text("Memory Usage: %.2f MB", m_frameDatabaseInfo.m_memoryUsedInBytes / 1024.0f / 1024.0f);
ImGui::TableNextColumn(); ImGui::Text("Motion Data: %.0f minutes", m_frameDatabaseInfo.m_motionDataInSeconds / 60.0f);
ImGui::TableNextColumn(); ImGui::Text("Num Frames: %zu", m_frameDatabaseInfo.m_numFrames);
ImGui::TableNextColumn(); ImGui::Text("Num Motions: %zu", m_frameDatabaseInfo.m_numMotions);
ImGui::EndTable();
}
}
if (ImGui::CollapsingHeader("Feature Matrix", ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_Framed))
{
ImGui::Text("Memory Usage: %.2f MB", m_featureMatrixMemoryUsageInBytes / 1024.0f / 1024.0f);
ImGui::Text("Num Frames: %zu", m_featureMatrixNumFrames);
ImGui::Text("Num Feature Components: %zu", m_featureMatrixNumComponents);
if (ImGui::BeginTable("FM", 2))
{
ImGui::TableNextColumn(); ImGui::Text("Memory Usage: %.2f MB", m_featurMatrixInfo.m_memoryUsedInBytes / 1024.0f / 1024.0f);
ImGui::TableNextColumn();
ImGui::TableNextColumn(); ImGui::Text("Num Frames: %zu", m_featurMatrixInfo.m_numFrames);
ImGui::TableNextColumn(); ImGui::Text("Num Feature Components: %zu", m_featurMatrixInfo.m_numDimensions);
ImGui::EndTable();
}
}
if (ImGui::CollapsingHeader("Kd-Tree", ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_Framed))
{
ImGui::Text("Memory Usage: %.2f MB", m_kdTreeMemoryUsageInBytes / 1024.0f / 1024.0f);
ImGui::Text("Num Nodes: %zu", m_kdTreeNumNodes);
ImGui::Text("Num Dimensions: %zu", m_kdTreeNumDimensions);
if (ImGui::BeginTable("KDT", 2))
{
ImGui::TableNextColumn(); ImGui::Text("Memory Usage: %.2f MB", m_kdTreeInfo.m_memoryUsedInBytes / 1024.0f / 1024.0f);
ImGui::TableNextColumn();
ImGui::TableNextColumn(); ImGui::Text("Num Nodes: %zu", m_kdTreeInfo.m_numNodes);
ImGui::TableNextColumn(); ImGui::Text("Num Dimensions: %zu", m_kdTreeInfo.m_numDimensions);
ImGui::EndTable();
}
}
m_performanceStats.OnImGuiUpdate();
@@ -63,8 +86,8 @@ namespace EMotionFX::MotionMatching
{
if (ImGui::BeginMenu("Motion Matching"))
{
ImGui::MenuItem(m_performanceStats.m_name.c_str(), "", &m_performanceStats.m_show);
ImGui::MenuItem(m_featureCosts.m_name.c_str(), "", &m_featureCosts.m_show);
ImGui::MenuItem(m_performanceStats.GetName(), "", &m_performanceStats.m_show);
ImGui::MenuItem(m_featureCosts.GetName(), "", &m_featureCosts.m_show);
ImGui::EndMenu();
}
}
@@ -78,67 +101,6 @@ namespace EMotionFX::MotionMatching
{
m_featureCosts.PushHistogramValue(costName, value, color);
}
void ImGuiMonitor::HistogramGroup::PushHistogramValue(const char* valueName, float value, const AZ::Color& color)
{
auto iterator = m_histogramIndexByName.find(valueName);
if (iterator != m_histogramIndexByName.end())
{
ImGui::LYImGuiUtils::HistogramContainer& histogramContiner = m_histograms[iterator->second];
histogramContiner.PushValue(value);
histogramContiner.SetBarLineColor(ImColor(color.GetR(), color.GetG(), color.GetB(), color.GetA()));
}
else
{
ImGui::LYImGuiUtils::HistogramContainer newHistogram;
newHistogram.Init(/*histogramName=*/valueName,
/*containerCount=*/m_histogramContainerCount,
/*viewType=*/ImGui::LYImGuiUtils::HistogramContainer::ViewType::Histogram,
/*displayOverlays=*/true,
/*min=*/0.0f,
/*max=*/0.0f);
newHistogram.SetMoveDirection(ImGui::LYImGuiUtils::HistogramContainer::PushRightMoveLeft);
newHistogram.PushValue(value);
m_histogramIndexByName[valueName] = m_histograms.size();
m_histograms.push_back(newHistogram);
}
}
void ImGuiMonitor::HistogramGroup::OnImGuiUpdate()
{
if (!m_show)
{
return;
}
if (ImGui::CollapsingHeader(m_name.c_str(), ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_Framed))
{
for (auto& histogram : m_histograms)
{
ImGui::BeginGroup();
{
histogram.Draw(ImGui::GetColumnWidth() - 70, s_histogramHeight);
ImGui::SameLine();
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0,0,0,255));
{
const ImColor color = histogram.GetBarLineColor();
ImGui::PushStyleColor(ImGuiCol_Button, color.Value);
{
const AZStd::string valueString = AZStd::string::format("%.2f", histogram.GetLastValue());
ImGui::Button(valueString.c_str());
}
ImGui::PopStyleColor();
}
ImGui::PopStyleColor();
}
ImGui::EndGroup();
}
}
}
} // namespace EMotionFX::MotionMatching
#endif // IMGUI_ENABLED
+9 -33
View File
@@ -20,7 +20,7 @@
#include <imgui/imgui.h>
#include <ImGuiBus.h>
#include <ImGuiMonitorBus.h>
#include <LYImGuiUtils/HistogramContainer.h>
#include <LYImGuiUtils/HistogramGroup.h>
namespace EMotionFX::MotionMatching
{
@@ -43,41 +43,17 @@ namespace EMotionFX::MotionMatching
void PushPerformanceHistogramValue(const char* performanceMetricName, float value) override;
void PushCostHistogramValue(const char* costName, float value, const AZ::Color& color) override;
void SetFeatureMatrixMemoryUsage(size_t sizeInBytes) override { m_featureMatrixMemoryUsageInBytes = sizeInBytes; }
void SetFeatureMatrixNumFrames(size_t numFrames) override { m_featureMatrixNumFrames = numFrames; }
void SetFeatureMatrixNumComponents(size_t numFeatureComponents) override { m_featureMatrixNumComponents = numFeatureComponents; }
void SetKdTreeMemoryUsage(size_t sizeInBytes) override { m_kdTreeMemoryUsageInBytes = sizeInBytes; }
void SetKdTreeNumNodes(size_t numNodes) override { m_kdTreeNumNodes = numNodes; }
void SetKdTreeNumDimensions(size_t numDimensions) override { m_kdTreeNumDimensions = numDimensions; }
void SetFrameDatabaseInfo(const ImGuiMonitorRequests::FrameDatabaseInfo& info) override { m_frameDatabaseInfo = info; }
void SetFeatureMatrixInfo(const ImGuiMonitorRequests::FeatureMatrixInfo& info) override { m_featurMatrixInfo = info; }
void SetKdTreeInfo(const ImGuiMonitorRequests::KdTreeInfo& info) override { m_kdTreeInfo = info; }
private:
//! Named and sub-divided group containing several histograms.
struct HistogramGroup
{
void OnImGuiUpdate();
void PushHistogramValue(const char* valueName, float value, const AZ::Color& color);
ImGui::LYImGuiUtils::HistogramGroup m_performanceStats;
ImGui::LYImGuiUtils::HistogramGroup m_featureCosts;
bool m_show = true;
AZStd::string m_name;
using HistogramIndexByNames = AZStd::unordered_map<const char*, size_t>;
HistogramIndexByNames m_histogramIndexByName;
AZStd::vector<ImGui::LYImGuiUtils::HistogramContainer> m_histograms;
int m_histogramContainerCount = 500;
static constexpr float s_histogramHeight = 95.0f;
};
HistogramGroup m_performanceStats;
HistogramGroup m_featureCosts;
size_t m_featureMatrixMemoryUsageInBytes = 0;
size_t m_featureMatrixNumFrames = 0;
size_t m_featureMatrixNumComponents = 0;
size_t m_kdTreeMemoryUsageInBytes = 0;
size_t m_kdTreeNumNodes = 0;
size_t m_kdTreeNumDimensions = 0;
ImGuiMonitorRequests::FrameDatabaseInfo m_frameDatabaseInfo;
ImGuiMonitorRequests::FeatureMatrixInfo m_featurMatrixInfo;
ImGuiMonitorRequests::KdTreeInfo m_kdTreeInfo;
};
} // namespace EMotionFX::MotionMatching
@@ -25,13 +25,30 @@ namespace EMotionFX::MotionMatching
virtual void PushPerformanceHistogramValue(const char* performanceMetricName, float value) = 0;
virtual void PushCostHistogramValue(const char* costName, float value, const AZ::Color& color) = 0;
virtual void SetFeatureMatrixMemoryUsage(size_t sizeInBytes) = 0;
virtual void SetFeatureMatrixNumFrames(size_t numFrames) = 0;
virtual void SetFeatureMatrixNumComponents(size_t numFeatureComponents) = 0;
struct FrameDatabaseInfo
{
size_t m_memoryUsedInBytes = 0;
size_t m_numFrames;
size_t m_numMotions;
float m_motionDataInSeconds;
};
virtual void SetFrameDatabaseInfo(const FrameDatabaseInfo& info) = 0;
virtual void SetKdTreeMemoryUsage(size_t sizeInBytes) = 0;
virtual void SetKdTreeNumNodes(size_t numNodes) = 0;
virtual void SetKdTreeNumDimensions(size_t numDimensions) = 0;
struct FeatureMatrixInfo
{
size_t m_memoryUsedInBytes = 0;
size_t m_numFrames = 0;
size_t m_numDimensions = 0;
};
virtual void SetFeatureMatrixInfo(const FeatureMatrixInfo& info) = 0;
struct KdTreeInfo
{
size_t m_memoryUsedInBytes = 0;
size_t m_numNodes = 0;
size_t m_numDimensions = 0;
};
virtual void SetKdTreeInfo(const KdTreeInfo& info) = 0;
};
using ImGuiMonitorRequestBus = AZ::EBus<ImGuiMonitorRequests>;
} // namespace EMotionFX::MotionMatching
@@ -28,14 +28,14 @@
#include <EMotionFX/Source/TransformData.h>
#include <PoseDataJointVelocities.h>
#include <EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/RenderPlugin/ViewportPluginBus.h>
namespace EMotionFX::MotionMatching
{
AZ_CLASS_ALLOCATOR_IMPL(MotionMatchingInstance, MotionMatchAllocator, 0)
MotionMatchingInstance::~MotionMatchingInstance()
{
DebugDrawRequestBus::Handler::BusDisconnect();
if (m_motionInstance)
{
GetMotionInstancePool().Free(m_motionInstance);
@@ -58,6 +58,8 @@ namespace EMotionFX::MotionMatching
AZ_Assert(settings.m_actorInstance, "The actor instance cannot be a nullptr.");
AZ_Assert(settings.m_data, "The motion match data cannot be nullptr.");
DebugDrawRequestBus::Handler::BusConnect();
// Update the cached pointer to the trajectory feature.
const FeatureSchema& featureSchema = settings.m_data->GetFeatureSchema();
for (Feature* feature : featureSchema.GetFeatures())
@@ -69,29 +71,6 @@ namespace EMotionFX::MotionMatching
}
}
// Debug display initialization.
const auto AddDebugDisplay = [=](AZ::s32 debugDisplayId)
{
if (debugDisplayId == -1)
{
return;
}
AzFramework::DebugDisplayRequestBus::BusPtr debugDisplayBus;
AzFramework::DebugDisplayRequestBus::Bind(debugDisplayBus, debugDisplayId);
AzFramework::DebugDisplayRequests* debugDisplay = AzFramework::DebugDisplayRequestBus::FindFirstHandler(debugDisplayBus);
if (debugDisplay)
{
m_debugDisplays.emplace_back(debugDisplay);
}
};
// Draw the debug visualizations to the Animation Editor as well as the LY Editor viewport.
AZ::s32 animationEditorViewportId = -1;
EMStudio::ViewportPluginRequestBus::BroadcastResult(animationEditorViewportId, &EMStudio::ViewportPluginRequestBus::Events::GetViewportId);
AddDebugDisplay(animationEditorViewportId);
AddDebugDisplay(AzFramework::g_defaultSceneEntityDebugDisplayId);
m_actorInstance = settings.m_actorInstance;
m_data = settings.m_data;
if (settings.m_data->GetFrameDatabase().GetNumFrames() == 0)
@@ -123,30 +102,17 @@ namespace EMotionFX::MotionMatching
m_queryFeatureValues.resize(numValuesInKdTree);
// Initialize the trajectory history.
size_t rootJointIndex = m_actorInstance->GetActor()->GetMotionExtractionNodeIndex();
if (rootJointIndex == InvalidIndex32)
if (m_cachedTrajectoryFeature)
{
rootJointIndex = 0;
}
m_trajectoryHistory.Init(*m_actorInstance->GetTransformData()->GetCurrentPose(),
rootJointIndex,
m_cachedTrajectoryFeature->GetFacingAxisDir(),
m_trajectorySecsToTrack);
}
void MotionMatchingInstance::DebugDraw()
{
if (m_data && !m_debugDisplays.empty())
{
for (AzFramework::DebugDisplayRequests* debugDisplay : m_debugDisplays)
size_t rootJointIndex = m_actorInstance->GetActor()->GetMotionExtractionNodeIndex();
if (rootJointIndex == InvalidIndex32)
{
if (debugDisplay)
{
const AZ::u32 prevState = debugDisplay->GetState();
DebugDraw(*debugDisplay);
debugDisplay->SetState(prevState);
}
rootJointIndex = 0;
}
m_trajectoryHistory.Init(*m_actorInstance->GetTransformData()->GetCurrentPose(),
rootJointIndex,
m_cachedTrajectoryFeature->GetFacingAxisDir(),
m_trajectorySecsToTrack);
}
}
@@ -306,7 +272,7 @@ namespace EMotionFX::MotionMatching
{
AZ_PROFILE_SCOPE(Animation, "MotionMatchingInstance::Update");
if (!m_data)
if (!m_data || !m_motionInstance)
{
return;
}
@@ -322,7 +288,7 @@ namespace EMotionFX::MotionMatching
// Update the time. After this there is no sample for the updated time in the history as we're about to prepare this with the current update.
m_trajectoryHistory.Update(timePassedInSeconds);
// Register the current actor instance position to the history data of the spline.
// Update the trajectory query control points.
m_trajectoryQuery.Update(m_actorInstance,
m_cachedTrajectoryFeature,
m_trajectoryHistory,
@@ -371,8 +337,7 @@ namespace EMotionFX::MotionMatching
SamplePose(m_motionInstance->GetMotion(), m_queryPose, newMotionTime);
// Copy over the motion extraction joint transform from the current pose to the newly sampled pose.
// When sampling a motion, the motion extraction joint is in animation space, while we need the query pose to be in
// world space.
// When sampling a motion, the motion extraction joint is in animation space, while we need the query pose to be in world space.
// Note: This does not yet take the extraction delta from the current tick into account.
if (m_actorInstance->GetActor()->GetMotionExtractionNode())
{
@@ -433,16 +398,17 @@ namespace EMotionFX::MotionMatching
// ImGui monitor
{
#ifdef IMGUI_ENABLED
const KdTree& kdTree = m_data->GetKdTree();
ImGuiMonitorRequestBus::Broadcast(&ImGuiMonitorRequests::SetKdTreeMemoryUsage, kdTree.CalcMemoryUsageInBytes());
ImGuiMonitorRequestBus::Broadcast(&ImGuiMonitorRequests::SetKdTreeNumNodes, kdTree.GetNumNodes());
ImGuiMonitorRequestBus::Broadcast(&ImGuiMonitorRequests::SetKdTreeNumDimensions, kdTree.GetNumDimensions());
// TODO: add memory usage for frame database
const FrameDatabase& frameDatabase = m_data->GetFrameDatabase();
ImGuiMonitorRequests::FrameDatabaseInfo frameDatabaseInfo{frameDatabase.CalcMemoryUsageInBytes(), frameDatabase.GetNumFrames(), frameDatabase.GetNumUsedMotions(), frameDatabase.GetNumFrames() / (float)frameDatabase.GetSampleRate()};
ImGuiMonitorRequestBus::Broadcast(&ImGuiMonitorRequests::SetFrameDatabaseInfo, frameDatabaseInfo);
const KdTree& kdTree = m_data->GetKdTree();
ImGuiMonitorRequests::KdTreeInfo kdTreeInfo{kdTree.CalcMemoryUsageInBytes(), kdTree.GetNumNodes(), kdTree.GetNumDimensions()};
ImGuiMonitorRequestBus::Broadcast(&ImGuiMonitorRequests::SetKdTreeInfo, kdTreeInfo);
const FeatureMatrix& featureMatrix = m_data->GetFeatureMatrix();
ImGuiMonitorRequestBus::Broadcast(&ImGuiMonitorRequests::SetFeatureMatrixMemoryUsage, featureMatrix.CalcMemoryUsageInBytes());
ImGuiMonitorRequestBus::Broadcast(&ImGuiMonitorRequests::SetFeatureMatrixNumFrames, featureMatrix.rows());
ImGuiMonitorRequestBus::Broadcast(&ImGuiMonitorRequests::SetFeatureMatrixNumComponents, featureMatrix.cols());
ImGuiMonitorRequests::FeatureMatrixInfo featureMatrixInfo{featureMatrix.CalcMemoryUsageInBytes(), static_cast<size_t>(featureMatrix.rows()), static_cast<size_t>(featureMatrix.cols())};
ImGuiMonitorRequestBus::Broadcast(&ImGuiMonitorRequests::SetFeatureMatrixInfo, featureMatrixInfo);
#endif
}
}
@@ -19,6 +19,8 @@
#include <TrajectoryHistory.h>
#include <TrajectoryQuery.h>
#include <MotionMatching/MotionMatchingBus.h>
namespace AZ
{
class ReflectContext;
@@ -34,12 +36,17 @@ namespace EMotionFX::MotionMatching
{
class MotionMatchingData;
//! The instance is where everything comes together. It stores the trajectory history, the trajectory query along with the query vector, knows about the
//! last lowest cost frame frame index and stores the time of the animation that the instance is currently playing. It is responsible for motion extraction,
//! blending towards a new frame in the motion capture database in case the algorithm found a better matching frame and executes the actual search.
class EMFX_API MotionMatchingInstance
: public DebugDrawRequestBus::Handler
{
public:
AZ_RTTI(MotionMatchingInstance, "{1ED03AD8-0FB2-431B-AF01-02F7E930EB73}")
AZ_CLASS_ALLOCATOR_DECL
MotionMatchingInstance() = default;
virtual ~MotionMatchingInstance();
struct EMFX_API InitSettings
@@ -49,8 +56,8 @@ namespace EMotionFX::MotionMatching
};
void Init(const InitSettings& settings);
void DebugDraw();
void DebugDraw(AzFramework::DebugDisplayRequests& debugDisplay);
// DebugDrawRequestBus::Handler overrides
void DebugDraw(AzFramework::DebugDisplayRequests& debugDisplay) override;
void Update(float timePassedInSeconds, const AZ::Vector3& targetPos, const AZ::Vector3& targetFacingDir, TrajectoryQuery::EMode mode, float pathRadius, float pathSpeed);
void PostUpdate(float timeDelta);
@@ -64,10 +71,8 @@ namespace EMotionFX::MotionMatching
void SetLowestCostSearchFrequency(float frequency) { m_lowestCostSearchFrequency = frequency; }
float GetNewMotionTime() const { return m_newMotionTime; }
/**
* Get the cached trajectory feature.
* The trajectory feature is searched in the feature schema used in the current instance at init time.
*/
//! Get the cached trajectory feature.
//! The trajectory feature is searched in the feature schema used in the current instance at init time.
FeatureTrajectory* GetTrajectoryFeature() const { return m_cachedTrajectoryFeature; }
const TrajectoryQuery& GetTrajectoryQuery() const { return m_trajectoryQuery; }
const TrajectoryHistory& GetTrajectoryHistory() const { return m_trajectoryHistory; }
@@ -90,10 +95,10 @@ namespace EMotionFX::MotionMatching
Transform m_motionExtractionDelta = Transform::CreateIdentity();
/// Buffers used for the broad-phase KD-tree search.
AZStd::vector<float> m_queryFeatureValues; /** The input query features to be compared to every entry/row in the feature matrix with the motion matching search. */
AZStd::vector<size_t> m_nearestFrames; /** Stores the nearest matching frames / search result from the KD-tree. */
AZStd::vector<float> m_queryFeatureValues; //< The input query features to be compared to every entry/row in the feature matrix with the motion matching search.
AZStd::vector<size_t> m_nearestFrames; //< Stores the nearest matching frames / search result from the KD-tree.
FeatureTrajectory* m_cachedTrajectoryFeature = nullptr; /** Cached pointer to the trajectory feature in the feature schema. */
FeatureTrajectory* m_cachedTrajectoryFeature = nullptr; //< Cached pointer to the trajectory feature in the feature schema.
TrajectoryQuery m_trajectoryQuery;
TrajectoryHistory m_trajectoryHistory;
static constexpr float m_trajectorySecsToTrack = 5.0f;
@@ -105,7 +110,7 @@ namespace EMotionFX::MotionMatching
bool m_blending = false;
float m_blendWeight = 1.0f;
float m_blendProgressTime = 0.0f; // How long are we already blending? In seconds.
float m_blendProgressTime = 0.0f; //< How long are we already blending? In seconds.
/// Buffers used for FindLowestCostFrameIndex().
AZStd::vector<float> m_tempCosts;
@@ -16,6 +16,8 @@
#include <Integration/EMotionFXBus.h>
#include <EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/RenderPlugin/ViewportPluginBus.h>
#include <BlendTreeMotionMatchNode.h>
#include <Feature.h>
#include <FeaturePosition.h>
@@ -39,7 +41,7 @@ namespace EMotionFX::MotionMatching
{
ec->Class<MotionMatchingSystemComponent>("MotionMatching", "[Description of functionality provided by this System Component]")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("System"))
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("System"))
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
;
}
@@ -69,9 +71,9 @@ namespace EMotionFX::MotionMatching
incompatible.push_back(AZ_CRC_CE("MotionMatchingService"));
}
void MotionMatchingSystemComponent::GetRequiredServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& required)
void MotionMatchingSystemComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
{
required.push_back(AZ_CRC("EMotionFXAnimationService", 0x3f8a6369));
required.push_back(AZ_CRC_CE("EMotionFXAnimationService"));
}
void MotionMatchingSystemComponent::GetDependentServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& dependent)
@@ -122,7 +124,33 @@ namespace EMotionFX::MotionMatching
MotionMatchingRequestBus::Handler::BusDisconnect();
}
void MotionMatchingSystemComponent::DebugDraw(AZ::s32 debugDisplayId)
{
AZ_PROFILE_SCOPE(Animation, "MotionMatchingSystemComponent::DebugDraw");
if (debugDisplayId == -1)
{
return;
}
AzFramework::DebugDisplayRequestBus::BusPtr debugDisplayBus;
AzFramework::DebugDisplayRequestBus::Bind(debugDisplayBus, debugDisplayId);
AzFramework::DebugDisplayRequests* debugDisplay = AzFramework::DebugDisplayRequestBus::FindFirstHandler(debugDisplayBus);
if (debugDisplay)
{
const AZ::u32 prevState = debugDisplay->GetState();
EMotionFX::MotionMatching::DebugDrawRequestBus::Broadcast(&EMotionFX::MotionMatching::DebugDrawRequests::DebugDraw, *debugDisplay);
debugDisplay->SetState(prevState);
}
}
void MotionMatchingSystemComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
{
// Draw the debug visualizations to the Animation Editor as well as the LY Editor viewport.
AZ::s32 animationEditorViewportId = -1;
EMStudio::ViewportPluginRequestBus::BroadcastResult(animationEditorViewportId, &EMStudio::ViewportPluginRequestBus::Events::GetViewportId);
DebugDraw(animationEditorViewportId);
DebugDraw(AzFramework::g_defaultSceneEntityDebugDisplayId);
}
} // namespace EMotionFX::MotionMatching
@@ -33,8 +33,7 @@ namespace EMotionFX::MotionMatching
~MotionMatchingSystemComponent();
protected:
////////////////////////////////////////////////////////////////////////
// MotionMatchingRequestBus interface implementation
void DebugDraw(AZ::s32 debugDisplayId);
////////////////////////////////////////////////////////////////////////
// AZ::Component interface implementation
@@ -45,6 +44,10 @@ namespace EMotionFX::MotionMatching
////////////////////////////////////////////////////////////////////////
// AZTickBus interface implementation
int GetTickOrder() override
{
return AZ::TICK_PRE_RENDER;
}
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
////////////////////////////////////////////////////////////////////////
};
@@ -24,9 +24,17 @@ namespace SurfaceData
using SurfaceTagVector = AZStd::vector<SurfaceTag>;
//! SurfaceTagWeights stores a collection of surface tags and weights.
//! A surface tag can only appear once in the collection. Attempting to add it multiple times will always preserve the
//! highest weight value.
class SurfaceTagWeights
{
public:
//! The maximum number of surface weights that we can store.
//! For performance reasons, we want to limit this so that we can preallocate the max size in advance.
//! The current number is chosen to be higher than expected needs, but small enough to avoid being excessively wasteful.
//! (Dynamic structures would end up taking more memory than what we're preallocating)
static inline constexpr size_t MaxSurfaceWeights = 16;
SurfaceTagWeights() = default;
//! Construct a collection of SurfaceTagWeights from the given SurfaceTagWeightList.
@@ -45,42 +53,68 @@ namespace SurfaceData
//! @param weight - The weight to assign to each tag.
void AssignSurfaceTagWeights(const SurfaceTagVector& tags, float weight);
//! Add a surface tag weight to this collection.
//! @param tag - The surface tag.
//! @param weight - The surface tag weight.
void AddSurfaceTagWeight(const AZ::Crc32 tag, const float weight);
//! Replace the surface tag weight with the new one if it's higher, or add it if the tag isn't found.
//! Add a surface tag weight to this collection. If the tag already exists, the higher weight will be preserved.
//! (This method is intentionally inlined for its performance impact)
//! @param tag - The surface tag.
//! @param weight - The surface tag weight.
void AddSurfaceWeightIfGreater(const AZ::Crc32 tag, const float weight)
void AddSurfaceTagWeight(const AZ::Crc32 tag, const float weight)
{
const auto maskItr = m_weights.find(tag);
const float previousValue = maskItr != m_weights.end() ? maskItr->second : 0.0f;
m_weights[tag] = AZ::GetMax(weight, previousValue);
}
//! Replace the surface tag weight with the new one if it's higher, or add it if the tag isn't found.
//! (This method is intentionally inlined for its performance impact)
//! @param tags - The surface tags to replace/add.
//! @param weight - The surface tag weight to use for each tag.
void AddSurfaceWeightsIfGreater(const SurfaceTagVector& tags, const float weight)
{
for (const auto& tag : tags)
for (auto weightItr = m_weights.begin(); weightItr != m_weights.end(); ++weightItr)
{
AddSurfaceWeightIfGreater(tag, weight);
// Since we need to scan for duplicate surface types, store the entries sorted by surface type so that we can
// early-out once we pass the location for the entry instead of always searching every entry.
if (weightItr->m_surfaceType > tag)
{
if (m_weights.size() != MaxSurfaceWeights)
{
// We didn't find the surface type, so add the new entry in sorted order.
m_weights.insert(weightItr, { tag, weight });
}
else
{
AZ_Assert(false, "SurfaceTagWeights has reached max capacity, it cannot add a new tag / weight.");
}
return;
}
else if (weightItr->m_surfaceType == tag)
{
// We found the surface type, so just keep the higher of the two weights.
weightItr->m_weight = AZ::GetMax(weight, weightItr->m_weight);
return;
}
}
// We didn't find the surface weight, and the sort order for it is at the end, so add it to the back of the list.
if (m_weights.size() != MaxSurfaceWeights)
{
m_weights.emplace_back(tag, weight);
}
else
{
AZ_Assert(false, "SurfaceTagWeights has reached max capacity, it cannot add a new tag / weight.");
}
}
//! Replace the surface tag weight with the new one if it's higher, or add it if the tag isn't found.
//! Add surface tags and weights to this collection. If a tag already exists, the higher weight will be preserved.
//! (This method is intentionally inlined for its performance impact)
//! @param tags - The surface tags to replace/add.
//! @param weight - The surface tag weight to use for each tag.
void AddSurfaceTagWeights(const SurfaceTagVector& tags, const float weight)
{
for (const auto& tag : tags)
{
AddSurfaceTagWeight(tag, weight);
}
}
//! Add surface tags and weights to this collection. If a tag already exists, the higher weight will be preserved.
//! (This method is intentionally inlined for its performance impact)
//! @param weights - The surface tags and weights to replace/add.
void AddSurfaceWeightsIfGreater(const SurfaceTagWeights& weights)
void AddSurfaceTagWeights(const SurfaceTagWeights& weights)
{
for (const auto& [tag, weight] : weights.m_weights)
{
AddSurfaceWeightIfGreater(tag, weight);
AddSurfaceTagWeight(tag, weight);
}
}
@@ -125,7 +159,7 @@ namespace SurfaceData
//! Check to see if the collection contains the given tag.
//! @param sampleTag - The tag to look for.
//! @return True if the tag is found, false if it isn't.
bool HasMatchingTag(const AZ::Crc32& sampleTag) const;
bool HasMatchingTag(AZ::Crc32 sampleTag) const;
//! Check to see if the collection contains the given tag with the given weight range.
//! The range check is inclusive on both sides of the range: [weightMin, weightMax]
@@ -133,7 +167,7 @@ namespace SurfaceData
//! @param weightMin - The minimum weight for this tag.
//! @param weightMax - The maximum weight for this tag.
//! @return True if the tag is found, false if it isn't.
bool HasMatchingTag(const AZ::Crc32& sampleTag, float weightMin, float weightMax) const;
bool HasMatchingTag(AZ::Crc32 sampleTag, float weightMin, float weightMax) const;
//! Check to see if the collection contains any of the given tags.
//! @param sampleTags - The tags to look for.
@@ -149,7 +183,12 @@ namespace SurfaceData
bool HasAnyMatchingTags(const SurfaceTagVector& sampleTags, float weightMin, float weightMax) const;
private:
AZStd::unordered_map<AZ::Crc32, float> m_weights;
//! Search for the given tag entry.
//! @param tag - The tag to search for.
//! @return The pointer to the tag that's found, or end() if it wasn't found.
const AzFramework::SurfaceData::SurfaceTagWeight* FindTag(AZ::Crc32 tag) const;
AZStd::fixed_vector<AzFramework::SurfaceData::SurfaceTagWeight, MaxSurfaceWeights> m_weights;
};
//! SurfacePointList stores a collection of surface point data, which consists of positions, normals, and surface tag weights.
@@ -259,7 +259,7 @@ namespace SurfaceData
if (DoRayTrace(position, queryPointOnly, hitPosition, hitNormal))
{
// If the query point collides with the volume, add all our modifier tags with a weight of 1.0f.
weights.AddSurfaceWeightsIfGreater(m_configuration.m_modifierTags, 1.0f);
weights.AddSurfaceTagWeights(m_configuration.m_modifierTags, 1.0f);
}
}
});
@@ -179,7 +179,7 @@ namespace SurfaceData
if (m_shapeBounds.Contains(position) && shape->IsPointInside(position))
{
// If the point is inside our shape, add all our modifier tags with a weight of 1.0f.
weights.AddSurfaceWeightsIfGreater(m_configuration.m_modifierTags, 1.0f);
weights.AddSurfaceTagWeights(m_configuration.m_modifierTags, 1.0f);
}
});
});
@@ -14,28 +14,21 @@ namespace SurfaceData
void SurfaceTagWeights::AssignSurfaceTagWeights(const AzFramework::SurfaceData::SurfaceTagWeightList& weights)
{
m_weights.clear();
m_weights.reserve(weights.size());
for (auto& weight : weights)
{
m_weights.emplace(weight.m_surfaceType, weight.m_weight);
AddSurfaceTagWeight(weight.m_surfaceType, weight.m_weight);
}
}
void SurfaceTagWeights::AssignSurfaceTagWeights(const SurfaceTagVector& tags, float weight)
{
m_weights.clear();
m_weights.reserve(tags.size());
for (auto& tag : tags)
{
m_weights[tag] = weight;
AddSurfaceTagWeight(tag.operator AZ::Crc32(), weight);
}
}
void SurfaceTagWeights::AddSurfaceTagWeight(const AZ::Crc32 tag, const float value)
{
m_weights[tag] = value;
}
void SurfaceTagWeights::Clear()
{
m_weights.clear();
@@ -52,7 +45,7 @@ namespace SurfaceData
weights.reserve(m_weights.size());
for (auto& weight : m_weights)
{
weights.emplace_back(weight.first, weight.second);
weights.emplace_back(weight);
}
return weights;
}
@@ -65,17 +58,8 @@ namespace SurfaceData
return false;
}
for (auto& weight : m_weights)
{
auto rhsWeight = rhs.m_weights.find(weight.first);
if ((rhsWeight == rhs.m_weights.end()) || (rhsWeight->second != weight.second))
{
return false;
}
}
// All the entries matched, and the lists are the same size, so they're equal.
return true;
// The lists are stored in sorted order, so we can compare every entry in order for equivalence.
return (m_weights == rhs.m_weights);
}
bool SurfaceTagWeights::SurfaceWeightsAreEqual(const AzFramework::SurfaceData::SurfaceTagWeightList& compareWeights) const
@@ -92,7 +76,7 @@ namespace SurfaceData
compareWeights.begin(), compareWeights.end(),
[weight](const AzFramework::SurfaceData::SurfaceTagWeight& compareWeight) -> bool
{
return (weight.first == compareWeight.m_surfaceType) && (weight.second == compareWeight.m_weight);
return (weight == compareWeight);
});
// If we didn't find a match, they're not equal.
@@ -119,9 +103,9 @@ namespace SurfaceData
bool SurfaceTagWeights::HasValidTags() const
{
for (const auto& sourceTag : m_weights)
for (const auto& weight : m_weights)
{
if (sourceTag.first != Constants::s_unassignedTagCrc)
if (weight.m_surfaceType != Constants::s_unassignedTagCrc)
{
return true;
}
@@ -129,9 +113,9 @@ namespace SurfaceData
return false;
}
bool SurfaceTagWeights::HasMatchingTag(const AZ::Crc32& sampleTag) const
bool SurfaceTagWeights::HasMatchingTag(AZ::Crc32 sampleTag) const
{
return m_weights.find(sampleTag) != m_weights.end();
return FindTag(sampleTag) != m_weights.end();
}
bool SurfaceTagWeights::HasAnyMatchingTags(const SurfaceTagVector& sampleTags) const
@@ -147,10 +131,10 @@ namespace SurfaceData
return false;
}
bool SurfaceTagWeights::HasMatchingTag(const AZ::Crc32& sampleTag, float weightMin, float weightMax) const
bool SurfaceTagWeights::HasMatchingTag(AZ::Crc32 sampleTag, float weightMin, float weightMax) const
{
auto maskItr = m_weights.find(sampleTag);
return maskItr != m_weights.end() && weightMin <= maskItr->second && weightMax >= maskItr->second;
auto weightEntry = FindTag(sampleTag);
return weightEntry != m_weights.end() && weightMin <= weightEntry->m_weight && weightMax >= weightEntry->m_weight;
}
bool SurfaceTagWeights::HasAnyMatchingTags(const SurfaceTagVector& sampleTags, float weightMin, float weightMax) const
@@ -166,7 +150,25 @@ namespace SurfaceData
return false;
}
const AzFramework::SurfaceData::SurfaceTagWeight* SurfaceTagWeights::FindTag(AZ::Crc32 tag) const
{
for (auto weightItr = m_weights.begin(); weightItr != m_weights.end(); ++weightItr)
{
if (weightItr->m_surfaceType == tag)
{
// Found the tag, return a pointer to the entry.
return weightItr;
}
else if (weightItr->m_surfaceType > tag)
{
// Our list is stored in sorted order by surfaceType, so early-out if our values get too high.
break;
}
}
// The tag wasn't found, so return end().
return m_weights.end();
}
SurfacePointList::SurfacePointList(AZStd::initializer_list<const AzFramework::SurfaceData::SurfacePoint> surfacePoints)
@@ -192,7 +194,7 @@ namespace SurfaceData
if (m_surfacePositionList[index].IsClose(position) && m_surfaceNormalList[index].IsClose(normal))
{
// consolidate points with similar attributes by adding masks/weights to the similar point instead of adding a new one.
m_surfaceWeightsList[index].AddSurfaceWeightsIfGreater(masks);
m_surfaceWeightsList[index].AddSurfaceTagWeights(masks);
return;
}
else if (m_surfacePositionList[index].GetZ() < position.GetZ())
@@ -12,6 +12,7 @@
#include <AzCore/Component/Entity.h>
#include <AzCore/Debug/Profiler.h>
#include <AzCore/Memory/PoolAllocator.h>
#include <AzCore/Math/Random.h>
#include <AzCore/Math/Vector2.h>
#include <AzCore/std/containers/array.h>
#include <AzCore/std/containers/span.h>
@@ -270,6 +271,88 @@ namespace UnitTest
->Arg( 2048 )
->Unit(::benchmark::kMillisecond);
BENCHMARK_DEFINE_F(SurfaceDataBenchmark, BM_AddSurfaceTagWeight)(benchmark::State& state)
{
AZ_PROFILE_FUNCTION(Entity);
AZ::Crc32 tags[SurfaceData::SurfaceTagWeights::MaxSurfaceWeights];
AZ::SimpleLcgRandom randomGenerator(1234567);
// Declare this outside the loop so that we aren't benchmarking creation and destruction.
SurfaceData::SurfaceTagWeights weights;
bool clearEachTime = state.range(0) > 0;
// Create a list of randomly-generated tag values.
for (auto& tag : tags)
{
tag = randomGenerator.GetRandom();
}
for (auto _ : state)
{
// We'll benchmark this two ways:
// 1. We clear each time, which means each AddSurfaceWeightIfGreater call will search the whole list then add.
// 2. We don't clear, which means that after the first run, AddSurfaceWeightIfGreater will always try to replace values.
if (clearEachTime)
{
weights.Clear();
}
// For each tag, try to add it with a random weight.
for (auto& tag : tags)
{
weights.AddSurfaceTagWeight(tag, randomGenerator.GetRandomFloat());
}
}
}
BENCHMARK_REGISTER_F(SurfaceDataBenchmark, BM_AddSurfaceTagWeight)
->Arg(false)
->Arg(true)
->ArgName("ClearEachTime");
BENCHMARK_DEFINE_F(SurfaceDataBenchmark, BM_HasAnyMatchingTags_NoMatches)(benchmark::State& state)
{
AZ_PROFILE_FUNCTION(Entity);
AZ::Crc32 tags[SurfaceData::SurfaceTagWeights::MaxSurfaceWeights];
AZ::SimpleLcgRandom randomGenerator(1234567);
// Declare this outside the loop so that we aren't benchmarking creation and destruction.
SurfaceData::SurfaceTagWeights weights;
// Create a list of randomly-generated tag values.
for (auto& tag : tags)
{
// Specifically always set the last bit so that we can create comparison tags that won't match.
tag = randomGenerator.GetRandom() | 0x01;
// Add the tag to our weights list with a random weight.
weights.AddSurfaceTagWeight(tag, randomGenerator.GetRandomFloat());
}
// Create a set of similar comparison tags that won't match. We still want a random distribution of values though,
// because the SurfaceTagWeights might behave differently with ordered lists.
SurfaceData::SurfaceTagVector comparisonTags;
for (auto& tag : tags)
{
comparisonTags.emplace_back(tag ^ 0x01);
}
for (auto _ : state)
{
// Test to see if any of our tags match.
// All of comparison tags should get compared against all of the added tags.
bool result = weights.HasAnyMatchingTags(comparisonTags);
benchmark::DoNotOptimize(result);
}
}
BENCHMARK_REGISTER_F(SurfaceDataBenchmark, BM_HasAnyMatchingTags_NoMatches);
#endif
}
@@ -169,7 +169,7 @@ class MockSurfaceProvider
if (surfacePoints != m_GetSurfacePoints.end())
{
weights.AddSurfaceWeightsIfGreater(m_tags, 1.0f);
weights.AddSurfaceTagWeights(m_tags, 1.0f);
}
});
}
@@ -1137,7 +1137,7 @@ namespace Vegetation
claimPoint.m_position = position;
claimPoint.m_normal = normal;
claimPoint.m_masks = masks;
sectorInfo.m_baseContext.m_masks.AddSurfaceWeightsIfGreater(masks);
sectorInfo.m_baseContext.m_masks.AddSurfaceTagWeights(masks);
return true;
});
}