diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..236fa6d67 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,34 @@ +# How to contribute to the FlaxEngine + +For any questions, suggestions or help join our discord! + + + +Want to see whats planned for Flax? + +Go check out our [Trello](https://trello.com/b/NQjLXRCP/flax-roadmap). + +## **Found a bug?** + +* Avoid opening any new issues without having checked if your problem has already been reported. If there are no currently open issues that fit your problem's description, feel free to [add it](https://github.com/FlaxEngine/FlaxEngine/issues/new). + +* When writing an issue make sure to include a clear title and description as well as having filled out all the necessary information, depending on the severity of the issue also include the necessary log files and minidump. + +* Try to following the given template when writing a new issue if possible. + +## **Want to contribute?** + +* Fork the FlaxEngine, create a new branch and push your changes there. Then, create a pull request. + +* When creating a PR for fixing an issue/bug make sure to describe as to what led to the fix for better understanding, for small and obvious fixes this is not really needed. + However make sure to mention the relevant issue where it was first reported if possible. + +* For feature PR's the first thing you should evaluate is the value of your contribution, as in, what would it bring to this engine? Is it really required? + If its a small change you could preferably suggest it to us on our discord, else feel free to open up a PR for it. + +* Ensure when creating a PR that your contribution is well explained with a adequate description and title. + +* Generally, good code quality is expected, make sure your contribution works as intended and is appropriately commented where necessary. + + +Thank you for taking interest in contributing to Flax! diff --git a/Content/Editor/Camera/M_Camera.flax b/Content/Editor/Camera/M_Camera.flax index b5473caa3..94890c96c 100644 --- a/Content/Editor/Camera/M_Camera.flax +++ b/Content/Editor/Camera/M_Camera.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b8b77651be81af931c1e63c1cb411b9f2764b4b0be4a7fa51e2c3c043a063435 -size 38085 +oid sha256:7b4752ca9986784f54afbad450edacdb3f87ed93d6e904c76ba40bb467e58d25 +size 30027 diff --git a/Content/Editor/CubeTexturePreviewMaterial.flax b/Content/Editor/CubeTexturePreviewMaterial.flax index 90bc9f368..9825995fb 100644 --- a/Content/Editor/CubeTexturePreviewMaterial.flax +++ b/Content/Editor/CubeTexturePreviewMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:959f9d4006dc11b0ecbd57cbb406213b2519a388de0d798bff69d61c2042c89b -size 39859 +oid sha256:6740a5d3ecc88c95f296dc0b77d8e177fcf3cc303dbaf9d1602aaf7008eb8b65 +size 31584 diff --git a/Content/Editor/DefaultFontMaterial.flax b/Content/Editor/DefaultFontMaterial.flax index a0f703a85..f464ad768 100644 --- a/Content/Editor/DefaultFontMaterial.flax +++ b/Content/Editor/DefaultFontMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e424d2436f4973a0ecef28c241e222e7e5f992a2ae017e943f4ba033608b3bd7 -size 38383 +oid sha256:c9c2e7cc6393d6f2079d4d064daa0a2663ec84c25be7778b73da22e9c66c226e +size 30108 diff --git a/Content/Editor/Gizmo/FoliageBrushMaterial.flax b/Content/Editor/Gizmo/FoliageBrushMaterial.flax index d8cc3fb6a..af6e065a2 100644 --- a/Content/Editor/Gizmo/FoliageBrushMaterial.flax +++ b/Content/Editor/Gizmo/FoliageBrushMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e3b239a3122fe40c386c3161576cf74c94248d92a3d501b18ed2d658f18742a2 -size 41087 +oid sha256:15e7562cb1f6b58f9030fbd66477b131b577f9d7c08f0d6b67078f02d05e6015 +size 34541 diff --git a/Content/Editor/Gizmo/Material.flax b/Content/Editor/Gizmo/Material.flax index b4917f502..64145ecec 100644 --- a/Content/Editor/Gizmo/Material.flax +++ b/Content/Editor/Gizmo/Material.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:85435f0342a96eeebad705692df74ec19871e28bc35a3d3046f8f63dee4d9334 -size 35945 +oid sha256:26a9333914f7ddf750e8d33be03c739bd3cc4fb455ebecbe83018b63ab8277b2 +size 31067 diff --git a/Content/Editor/Gizmo/MaterialWire.flax b/Content/Editor/Gizmo/MaterialWire.flax index 888846500..4e85879a0 100644 --- a/Content/Editor/Gizmo/MaterialWire.flax +++ b/Content/Editor/Gizmo/MaterialWire.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9ab58f495cdd9fee4c81332a291bbee4f1adc327d7d14b5158d4360aac1ef8fa -size 35158 +oid sha256:405fdb8843f7836807a0efc40564c0ab46eab62072d04b6e209d140e4bedab66 +size 30280 diff --git a/Content/Editor/Gizmo/SelectionOutlineMaterial.flax b/Content/Editor/Gizmo/SelectionOutlineMaterial.flax index 48a87ba88..f3b1474d9 100644 --- a/Content/Editor/Gizmo/SelectionOutlineMaterial.flax +++ b/Content/Editor/Gizmo/SelectionOutlineMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f578d4b9e0dbd295de0674cf46cfc5d2894db225fd2c3cfc443c6f2e9c7167d3 -size 15941 +oid sha256:74ab675aa70e0bfa43e7eedf8b813c8e0a1c792fa59289ef50cfe2cf2505b900 +size 15863 diff --git a/Content/Editor/Gizmo/VertexColorsPreviewMaterial.flax b/Content/Editor/Gizmo/VertexColorsPreviewMaterial.flax index e42b74129..574773030 100644 --- a/Content/Editor/Gizmo/VertexColorsPreviewMaterial.flax +++ b/Content/Editor/Gizmo/VertexColorsPreviewMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c2abe350d858eeb42158b6c5fa8b0875a332bc003038fe846bf63aa3a9117577 -size 39316 +oid sha256:4e79a6d2c67d7dab5a6ef26e433c21db3533c54605627baf16d17cd33e998bb3 +size 31042 diff --git a/Content/Editor/Highlight Material.flax b/Content/Editor/Highlight Material.flax index 9f1de8fcd..6fb6aa10d 100644 --- a/Content/Editor/Highlight Material.flax +++ b/Content/Editor/Highlight Material.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0eba0cb1115fb6510ed32159a8fa5a81bbfabfd0e6ec00f4d5ccbfa0426fad97 -size 35356 +oid sha256:f1a371e03bfc916068cbc2863f3c92dcab3ac0ba3a4839ac0efa5e10e43807b2 +size 28875 diff --git a/Content/Editor/Icons/IconsMaterial.flax b/Content/Editor/Icons/IconsMaterial.flax index 91bba0296..2cf1d5cb7 100644 --- a/Content/Editor/Icons/IconsMaterial.flax +++ b/Content/Editor/Icons/IconsMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:51bceae1877ea1859c8757a1e21abade8cf21eda8857d3204bf4b0239fc66d18 -size 35289 +oid sha256:df1097033d79b531c8f09c2cb95bbb85a8190c6cd0649b77f05dd617410a0b8e +size 28807 diff --git a/Content/Editor/IesProfilePreviewMaterial.flax b/Content/Editor/IesProfilePreviewMaterial.flax index f9e249e81..5466742ea 100644 --- a/Content/Editor/IesProfilePreviewMaterial.flax +++ b/Content/Editor/IesProfilePreviewMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:946c1c4723c46a55c2d47a67009810a58c6e790b0af27137507e2c5cb9f3d64e -size 18669 +oid sha256:b02d31b97be388836c675b7d5814fc5f46623446c9ffd7cff837901694141ad8 +size 18415 diff --git a/Content/Editor/MaterialTemplates/Decal.shader b/Content/Editor/MaterialTemplates/Decal.shader index e0253efd6..452937955 100644 --- a/Content/Editor/MaterialTemplates/Decal.shader +++ b/Content/Editor/MaterialTemplates/Decal.shader @@ -108,15 +108,14 @@ float4 GetVertexColor(MaterialInput input) return 1; } +@8 + // Get material properties function (for pixel shader) Material GetMaterialPS(MaterialInput input) { @4 } -// Fix line for errors/warnings for shader code from template -#line 1000 - // Input macro specified by the material: DECAL_BLEND_MODE #define DECAL_BLEND_MODE_TRANSLUCENT 0 @@ -211,3 +210,5 @@ void PS_Decal( #error "Invalid decal blending mode" #endif } + +@9 diff --git a/Content/Editor/MaterialTemplates/Deformable.shader b/Content/Editor/MaterialTemplates/Deformable.shader new file mode 100644 index 000000000..044f7184c --- /dev/null +++ b/Content/Editor/MaterialTemplates/Deformable.shader @@ -0,0 +1,377 @@ +// File generated by Flax Materials Editor +// Version: @0 + +#define MATERIAL 1 +@3 +#include "./Flax/Common.hlsl" +#include "./Flax/MaterialCommon.hlsl" +#include "./Flax/GBufferCommon.hlsl" +@7 +// Primary constant buffer (with additional material parameters) +META_CB_BEGIN(0, Data) +float4x4 ViewProjectionMatrix; +float4x4 WorldMatrix; +float4x4 LocalMatrix; +float4x4 ViewMatrix; +float3 ViewPos; +float ViewFar; +float3 ViewDir; +float TimeParam; +float4 ViewInfo; +float4 ScreenSize; +float3 Dummy0; +float WorldDeterminantSign; +float MeshMinZ; +float Segment; +float ChunksPerSegment; +float PerInstanceRandom; +float4 TemporalAAJitter; +float3 GeometrySize; +float MeshMaxZ; +@1META_CB_END + +// Shader resources +@2 +// The spline deformation buffer (stored as 4x3, 3 float4 behind each other) +Buffer SplineDeformation : register(t0); + +// Geometry data passed though the graphics rendering stages up to the pixel shader +struct GeometryData +{ + float3 WorldPosition : TEXCOORD0; + float2 TexCoord : TEXCOORD1; +#if USE_VERTEX_COLOR + half4 VertexColor : COLOR; +#endif + float3 WorldNormal : TEXCOORD2; + float4 WorldTangent : TEXCOORD3; +}; + +// Interpolants passed from the vertex shader +struct VertexOutput +{ + float4 Position : SV_Position; + GeometryData Geometry; +#if USE_CUSTOM_VERTEX_INTERPOLATORS + float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; +#endif +#if USE_TESSELLATION + float TessellationMultiplier : TESS; +#endif +}; + +// Interpolants passed to the pixel shader +struct PixelInput +{ + float4 Position : SV_Position; + GeometryData Geometry; +#if USE_CUSTOM_VERTEX_INTERPOLATORS + float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; +#endif + bool IsFrontFace : SV_IsFrontFace; +}; + +// Material properties generation input +struct MaterialInput +{ + float3 WorldPosition; + float TwoSidedSign; + float2 TexCoord; +#if USE_VERTEX_COLOR + half4 VertexColor; +#endif + float3x3 TBN; + float4 SvPosition; + float3 PreSkinnedPosition; + float3 PreSkinnedNormal; +#if USE_CUSTOM_VERTEX_INTERPOLATORS + float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT]; +#endif +}; + +// Extracts geometry data to the material input +MaterialInput GetGeometryMaterialInput(GeometryData geometry) +{ + MaterialInput output = (MaterialInput)0; + output.WorldPosition = geometry.WorldPosition; + output.TexCoord = geometry.TexCoord; +#if USE_VERTEX_COLOR + output.VertexColor = geometry.VertexColor; +#endif + output.TBN = CalcTangentBasis(geometry.WorldNormal, geometry.WorldTangent); + return output; +} + +#if USE_TESSELLATION + +// Interpolates the geometry positions data only (used by the tessallation when generating vertices) +#define InterpolateGeometryPositions(output, p0, w0, p1, w1, p2, w2, offset) output.WorldPosition = p0.WorldPosition * w0 + p1.WorldPosition * w1 + p2.WorldPosition * w2 + offset + +// Offsets the geometry positions data only (used by the tessallation when generating vertices) +#define OffsetGeometryPositions(geometry, offset) geometry.WorldPosition += offset + +// Applies the Phong tessallation to the geometry positions (used by the tessallation when doing Phong tess) +#define ApplyGeometryPositionsPhongTess(geometry, p0, p1, p2, U, V, W) \ + float3 posProjectedU = TessalationProjectOntoPlane(p0.WorldNormal, p0.WorldPosition, geometry.WorldPosition); \ + float3 posProjectedV = TessalationProjectOntoPlane(p1.WorldNormal, p1.WorldPosition, geometry.WorldPosition); \ + float3 posProjectedW = TessalationProjectOntoPlane(p2.WorldNormal, p2.WorldPosition, geometry.WorldPosition); \ + geometry.WorldPosition = U * posProjectedU + V * posProjectedV + W * posProjectedW + +// Interpolates the geometry data except positions (used by the tessallation when generating vertices) +GeometryData InterpolateGeometry(GeometryData p0, float w0, GeometryData p1, float w1, GeometryData p2, float w2) +{ + GeometryData output = (GeometryData)0; + output.TexCoord = p0.TexCoord * w0 + p1.TexCoord * w1 + p2.TexCoord * w2; +#if USE_VERTEX_COLOR + output.VertexColor = p0.VertexColor * w0 + p1.VertexColor * w1 + p2.VertexColor * w2; +#endif + output.WorldNormal = p0.WorldNormal * w0 + p1.WorldNormal * w1 + p2.WorldNormal * w2; + output.WorldNormal = normalize(output.WorldNormal); + output.WorldTangent = p0.WorldTangent * w0 + p1.WorldTangent * w1 + p2.WorldTangent * w2; + output.WorldTangent.xyz = normalize(output.WorldTangent.xyz); + return output; +} + +#endif + +MaterialInput GetMaterialInput(PixelInput input) +{ + MaterialInput output = GetGeometryMaterialInput(input.Geometry); + output.TwoSidedSign = WorldDeterminantSign * (input.IsFrontFace ? 1.0 : -1.0); + output.SvPosition = input.Position; +#if USE_CUSTOM_VERTEX_INTERPOLATORS + output.CustomVSToPS = input.CustomVSToPS; +#endif + return output; +} + +// Removes the scale vector from the local to world transformation matrix +float3x3 RemoveScaleFromLocalToWorld(float3x3 localToWorld) +{ + // Extract per axis scales from localToWorld transform + float scaleX = length(localToWorld[0]); + float scaleY = length(localToWorld[1]); + float scaleZ = length(localToWorld[2]); + float3 invScale = float3( + scaleX > 0.00001f ? 1.0f / scaleX : 0.0f, + scaleY > 0.00001f ? 1.0f / scaleY : 0.0f, + scaleZ > 0.00001f ? 1.0f / scaleZ : 0.0f); + localToWorld[0] *= invScale.x; + localToWorld[1] *= invScale.y; + localToWorld[2] *= invScale.z; + return localToWorld; +} + +// Transforms a vector from tangent space to world space +float3 TransformTangentVectorToWorld(MaterialInput input, float3 tangentVector) +{ + return mul(tangentVector, input.TBN); +} + +// Transforms a vector from world space to tangent space +float3 TransformWorldVectorToTangent(MaterialInput input, float3 worldVector) +{ + return mul(input.TBN, worldVector); +} + +// Transforms a vector from world space to view space +float3 TransformWorldVectorToView(MaterialInput input, float3 worldVector) +{ + return mul(worldVector, (float3x3)ViewMatrix); +} + +// Transforms a vector from view space to world space +float3 TransformViewVectorToWorld(MaterialInput input, float3 viewVector) +{ + return mul((float3x3)ViewMatrix, viewVector); +} + +// Transforms a vector from local space to world space +float3 TransformLocalVectorToWorld(MaterialInput input, float3 localVector) +{ + float3x3 localToWorld = (float3x3)WorldMatrix; + //localToWorld = RemoveScaleFromLocalToWorld(localToWorld); + return mul(localVector, localToWorld); +} + +// Transforms a vector from local space to world space +float3 TransformWorldVectorToLocal(MaterialInput input, float3 worldVector) +{ + float3x3 localToWorld = (float3x3)WorldMatrix; + //localToWorld = RemoveScaleFromLocalToWorld(localToWorld); + return mul(localToWorld, worldVector); +} + +// Gets the current object position +float3 GetObjectPosition(MaterialInput input) +{ + return WorldMatrix[3].xyz; +} + +// Gets the current object size +float3 GetObjectSize(MaterialInput input) +{ + float4x4 world = WorldMatrix; + return GeometrySize * float3(world._m00, world._m11, world._m22); +} + +// Get the current object random value +float GetPerInstanceRandom(MaterialInput input) +{ + return PerInstanceRandom; +} + +// Get the current object LOD transition dither factor +float GetLODDitherFactor(MaterialInput input) +{ + return 0; +} + +// Gets the interpolated vertex color (in linear space) +float4 GetVertexColor(MaterialInput input) +{ +#if USE_VERTEX_COLOR + return input.VertexColor; +#else + return 1; +#endif +} + +float3 SampleLightmap(Material material, MaterialInput materialInput) +{ + return 0; +} + +@8 + +// Get material properties function (for vertex shader) +Material GetMaterialVS(MaterialInput input) +{ +@5 +} + +// Get material properties function (for domain shader) +Material GetMaterialDS(MaterialInput input) +{ +@6 +} + +// Get material properties function (for pixel shader) +Material GetMaterialPS(MaterialInput input) +{ +@4 +} + +// Calculates the transform matrix from mesh tangent space to local space +float3x3 CalcTangentToLocal(ModelInput input) +{ + float bitangentSign = input.Tangent.w ? -1.0f : +1.0f; + float3 normal = input.Normal.xyz * 2.0 - 1.0; + float3 tangent = input.Tangent.xyz * 2.0 - 1.0; + float3 bitangent = cross(normal, tangent) * bitangentSign; + return float3x3(tangent, bitangent, normal); +} + +// Vertex Shader function for GBuffer Pass and Depth Pass (with full vertex data) +META_VS(true, FEATURE_LEVEL_ES2) +META_VS_IN_ELEMENT(POSITION, 0, R32G32B32_FLOAT, 0, 0, PER_VERTEX, 0, true) +META_VS_IN_ELEMENT(TEXCOORD, 0, R16G16_FLOAT, 1, 0, PER_VERTEX, 0, true) +META_VS_IN_ELEMENT(NORMAL, 0, R10G10B10A2_UNORM, 1, ALIGN, PER_VERTEX, 0, true) +META_VS_IN_ELEMENT(TANGENT, 0, R10G10B10A2_UNORM, 1, ALIGN, PER_VERTEX, 0, true) +META_VS_IN_ELEMENT(TEXCOORD, 1, R16G16_FLOAT, 1, ALIGN, PER_VERTEX, 0, true) +META_VS_IN_ELEMENT(COLOR, 0, R8G8B8A8_UNORM, 2, 0, PER_VERTEX, 0, USE_VERTEX_COLOR) +VertexOutput VS_SplineModel(ModelInput input) +{ + VertexOutput output; + + // Apply local transformation of the geometry before deformation + float3 position = mul(float4(input.Position, 1), LocalMatrix).xyz; + float4x4 world = LocalMatrix; + + // Apply spline curve deformation + float splineAlpha = saturate((position.z - MeshMinZ) / (MeshMaxZ - MeshMinZ)); + int splineIndex = (int)((Segment + splineAlpha) * ChunksPerSegment); + position.z = splineAlpha; + float3x4 splineMatrix = float3x4(SplineDeformation[splineIndex * 3], SplineDeformation[splineIndex * 3 + 1], SplineDeformation[splineIndex * 3 + 2]); + position = mul(splineMatrix, float4(position, 1)); + float4x3 splineMatrixT = transpose(splineMatrix); + world = mul(world, float4x4(float4(splineMatrixT[0], 0), float4(splineMatrixT[1], 0), float4(splineMatrixT[2], 0), float4(splineMatrixT[3], 1))); + + // Compute world space vertex position + output.Geometry.WorldPosition = mul(float4(position, 1), WorldMatrix).xyz; + world = mul(world, WorldMatrix); + + // Compute clip space position + output.Position = mul(float4(output.Geometry.WorldPosition, 1), ViewProjectionMatrix); + + // Pass vertex attributes + output.Geometry.TexCoord = input.TexCoord; +#if USE_VERTEX_COLOR + output.Geometry.VertexColor = input.Color; +#endif + + // Calculate tanget space to world space transformation matrix for unit vectors + float3x3 tangentToLocal = CalcTangentToLocal(input); + float3x3 localToWorld = RemoveScaleFromLocalToWorld((float3x3)world); + float3x3 tangentToWorld = mul(tangentToLocal, localToWorld); + output.Geometry.WorldNormal = tangentToWorld[2]; + output.Geometry.WorldTangent.xyz = tangentToWorld[0]; + output.Geometry.WorldTangent.w = input.Tangent.w ? -1.0f : +1.0f; + + // Get material input params if need to evaluate any material property +#if USE_POSITION_OFFSET || USE_TESSELLATION || USE_CUSTOM_VERTEX_INTERPOLATORS + MaterialInput materialInput = GetGeometryMaterialInput(output.Geometry); + materialInput.TwoSidedSign = WorldDeterminantSign; + materialInput.SvPosition = output.Position; + materialInput.PreSkinnedPosition = input.Position.xyz; + materialInput.PreSkinnedNormal = tangentToLocal[2].xyz; + Material material = GetMaterialVS(materialInput); +#endif + + // Apply world position offset per-vertex +#if USE_POSITION_OFFSET + output.Geometry.WorldPosition += material.PositionOffset; + output.Position = mul(float4(output.Geometry.WorldPosition, 1), ViewProjectionMatrix); +#endif + + // Get tessalation multiplier (per vertex) +#if USE_TESSELLATION + output.TessellationMultiplier = material.TessellationMultiplier; +#endif + + // Copy interpolants for other shader stages +#if USE_CUSTOM_VERTEX_INTERPOLATORS + output.CustomVSToPS = material.CustomVSToPS; +#endif + + return output; +} + +#if USE_DITHERED_LOD_TRANSITION + +void ClipLODTransition(PixelInput input) +{ +} + +#endif + +// Pixel Shader function for Depth Pass +META_PS(true, FEATURE_LEVEL_ES2) +void PS_Depth(PixelInput input) +{ +#if MATERIAL_MASKED || MATERIAL_BLEND != MATERIAL_BLEND_OPAQUE + // Get material parameters + MaterialInput materialInput = GetMaterialInput(input); + Material material = GetMaterialPS(materialInput); + + // Perform per pixel clipping +#if MATERIAL_MASKED + clip(material.Mask - MATERIAL_MASK_THRESHOLD); +#endif +#if MATERIAL_BLEND != MATERIAL_BLEND_OPAQUE + clip(material.Opacity - MATERIAL_OPACITY_THRESHOLD); +#endif +#endif +} + +@9 diff --git a/Content/Editor/MaterialTemplates/Features/DeferredShading.hlsl b/Content/Editor/MaterialTemplates/Features/DeferredShading.hlsl new file mode 100644 index 000000000..d3a7a2518 --- /dev/null +++ b/Content/Editor/MaterialTemplates/Features/DeferredShading.hlsl @@ -0,0 +1,84 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +@0// Deferred Shading: Defines +@1// Deferred Shading: Includes +@2// Deferred Shading: Constants +@3// Deferred Shading: Resources +@4// Deferred Shading: Utilities +@5// Deferred Shading: Shaders + +// Pixel Shader function for GBuffer Pass +META_PS(true, FEATURE_LEVEL_ES2) +META_PERMUTATION_1(USE_LIGHTMAP=0) +META_PERMUTATION_1(USE_LIGHTMAP=1) +void PS_GBuffer( + in PixelInput input + ,out float4 Light : SV_Target0 +#if MATERIAL_BLEND == MATERIAL_BLEND_OPAQUE + // GBuffer + ,out float4 RT0 : SV_Target1 + ,out float4 RT1 : SV_Target2 + ,out float4 RT2 : SV_Target3 +#if USE_GBUFFER_CUSTOM_DATA + ,out float4 RT3 : SV_Target4 +#endif +#endif + ) +{ + Light = 0; + +#if USE_DITHERED_LOD_TRANSITION + // LOD masking + ClipLODTransition(input); +#endif + + // Get material parameters + MaterialInput materialInput = GetMaterialInput(input); + Material material = GetMaterialPS(materialInput); + + // Masking +#if MATERIAL_MASKED + clip(material.Mask - MATERIAL_MASK_THRESHOLD); +#endif + +#if USE_LIGHTMAP + float3 diffuseColor = GetDiffuseColor(material.Color, material.Metalness); + float3 specularColor = GetSpecularColor(material.Color, material.Specular, material.Metalness); + + // Sample lightmap + float3 diffuseIndirectLighting = SampleLightmap(material, materialInput); + + // Apply static indirect light + Light.rgb = diffuseColor * diffuseIndirectLighting * AOMultiBounce(material.AO, diffuseColor); +#endif + +#if MATERIAL_BLEND == MATERIAL_BLEND_OPAQUE + + // Pack material properties to GBuffer + RT0 = float4(material.Color, material.AO); + RT1 = float4(material.WorldNormal * 0.5 + 0.5, MATERIAL_SHADING_MODEL * (1.0 / 3.0)); + RT2 = float4(material.Roughness, material.Metalness, material.Specular, 0); + + // Custom data +#if USE_GBUFFER_CUSTOM_DATA +#if MATERIAL_SHADING_MODEL == SHADING_MODEL_SUBSURFACE + RT3 = float4(material.SubsurfaceColor, material.Opacity); +#elif MATERIAL_SHADING_MODEL == SHADING_MODEL_FOLIAGE + RT3 = float4(material.SubsurfaceColor, material.Opacity); +#else + RT3 = float4(0, 0, 0, 0); +#endif +#endif + + // Add light emission +#if USE_EMISSIVE + Light.rgb += material.Emissive; +#endif + +#else + + // Handle blending as faked forward pass (use Light buffer and skip GBuffer modification) + Light = float4(material.Emissive, material.Opacity); + +#endif +} diff --git a/Content/Editor/MaterialTemplates/Features/Distortion.hlsl b/Content/Editor/MaterialTemplates/Features/Distortion.hlsl new file mode 100644 index 000000000..c74014cca --- /dev/null +++ b/Content/Editor/MaterialTemplates/Features/Distortion.hlsl @@ -0,0 +1,50 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +@0// Distortion: Defines +@1// Distortion: Includes +@2// Distortion: Constants +@3// Distortion: Resources +@4// Distortion: Utilities +@5// Distortion: Shaders +#if USE_DISTORTION + +// Pixel Shader function for Distortion Pass +META_PS(USE_DISTORTION, FEATURE_LEVEL_ES2) +float4 PS_Distortion(PixelInput input) : SV_Target0 +{ +#if USE_DITHERED_LOD_TRANSITION + // LOD masking + ClipLODTransition(input); +#endif + + // Get material parameters + MaterialInput materialInput = GetMaterialInput(input); + Material material = GetMaterialPS(materialInput); + + // Masking +#if MATERIAL_MASKED + clip(material.Mask - MATERIAL_MASK_THRESHOLD); +#endif + + float3 viewNormal = normalize(TransformWorldVectorToView(materialInput, material.WorldNormal)); + float airIOR = 1.0f; +#if USE_PIXEL_NORMAL_OFFSET_REFRACTION + float3 viewVertexNormal = TransformWorldVectorToView(materialInput, TransformTangentVectorToWorld(materialInput, float3(0, 0, 1))); + float2 distortion = (viewVertexNormal.xy - viewNormal.xy) * (material.Refraction - airIOR); +#else + float2 distortion = viewNormal.xy * (material.Refraction - airIOR); +#endif + + // Clip if the distortion distance (squared) is too small to be noticed + clip(dot(distortion, distortion) - 0.00001); + + // Scale up for better precision in low/subtle refractions at the expense of artefacts at higher refraction + distortion *= 4.0f; + + // Use separate storage for positive and negative offsets + float2 addOffset = max(distortion, 0); + float2 subOffset = abs(min(distortion, 0)); + return float4(addOffset.x, addOffset.y, subOffset.x, subOffset.y); +} + +#endif diff --git a/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl b/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl new file mode 100644 index 000000000..c21fe37d2 --- /dev/null +++ b/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl @@ -0,0 +1,124 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +@0// Forward Shading: Defines +#define MAX_LOCAL_LIGHTS 4 +@1// Forward Shading: Includes +#include "./Flax/LightingCommon.hlsl" +#if USE_REFLECTIONS +#include "./Flax/ReflectionsCommon.hlsl" +#endif +#include "./Flax/Lighting.hlsl" +#include "./Flax/ShadowsSampling.hlsl" +#include "./Flax/ExponentialHeightFog.hlsl" +@2// Forward Shading: Constants +LightData DirectionalLight; +LightShadowData DirectionalLightShadow; +LightData SkyLight; +ProbeData EnvironmentProbe; +ExponentialHeightFogData ExponentialHeightFog; +float3 Dummy2; +uint LocalLightsCount; +LightData LocalLights[MAX_LOCAL_LIGHTS]; +@3// Forward Shading: Resources +TextureCube EnvProbe : register(t__SRV__); +TextureCube SkyLightTexture : register(t__SRV__); +Texture2DArray DirectionalLightShadowMap : register(t__SRV__); +@4// Forward Shading: Utilities +DECLARE_LIGHTSHADOWDATA_ACCESS(DirectionalLightShadow); +@5// Forward Shading: Shaders + +// Pixel Shader function for Forward Pass +META_PS(USE_FORWARD, FEATURE_LEVEL_ES2) +float4 PS_Forward(PixelInput input) : SV_Target0 +{ + float4 output = 0; + +#if USE_DITHERED_LOD_TRANSITION + // LOD masking + ClipLODTransition(input); +#endif + + // Get material parameters + MaterialInput materialInput = GetMaterialInput(input); + Material material = GetMaterialPS(materialInput); + + // Masking +#if MATERIAL_MASKED + clip(material.Mask - MATERIAL_MASK_THRESHOLD); +#endif + + // Add emissive light + output = float4(material.Emissive, material.Opacity); + +#if MATERIAL_SHADING_MODEL != SHADING_MODEL_UNLIT + + // Setup GBuffer data as proxy for lighting + GBufferSample gBuffer; + gBuffer.Normal = material.WorldNormal; + gBuffer.Roughness = material.Roughness; + gBuffer.Metalness = material.Metalness; + gBuffer.Color = material.Color; + gBuffer.Specular = material.Specular; + gBuffer.AO = material.AO; + gBuffer.ViewPos = mul(float4(materialInput.WorldPosition, 1), ViewMatrix).xyz; +#if MATERIAL_SHADING_MODEL == SHADING_MODEL_SUBSURFACE + gBuffer.CustomData = float4(material.SubsurfaceColor, material.Opacity); +#elif MATERIAL_SHADING_MODEL == SHADING_MODEL_FOLIAGE + gBuffer.CustomData = float4(material.SubsurfaceColor, material.Opacity); +#else + gBuffer.CustomData = float4(0, 0, 0, 0); +#endif + gBuffer.WorldPos = materialInput.WorldPosition; + gBuffer.ShadingModel = MATERIAL_SHADING_MODEL; + + // Calculate lighting from a single directional light + float4 shadowMask = 1.0f; + if (DirectionalLight.CastShadows > 0) + { + LightShadowData directionalLightShadowData = GetDirectionalLightShadowData(); + shadowMask.r = SampleShadow(DirectionalLight, directionalLightShadowData, DirectionalLightShadowMap, gBuffer, shadowMask.g); + } + float4 light = GetLighting(ViewPos, DirectionalLight, gBuffer, shadowMask, false, false); + + // Calculate lighting from sky light + light += GetSkyLightLighting(SkyLight, gBuffer, SkyLightTexture); + + // Calculate lighting from local lights + LOOP + for (uint localLightIndex = 0; localLightIndex < LocalLightsCount; localLightIndex++) + { + const LightData localLight = LocalLights[localLightIndex]; + bool isSpotLight = localLight.SpotAngles.x > -2.0f; + shadowMask = 1.0f; + light += GetLighting(ViewPos, localLight, gBuffer, shadowMask, true, isSpotLight); + } + +#if USE_REFLECTIONS + // Calculate reflections + light.rgb += GetEnvProbeLighting(ViewPos, EnvProbe, EnvironmentProbe, gBuffer) * light.a; +#endif + + // Add lighting (apply ambient occlusion) + output.rgb += light.rgb * gBuffer.AO; + +#if USE_FOG + // Calculate exponential height fog + float4 fog = GetExponentialHeightFog(ExponentialHeightFog, materialInput.WorldPosition, ViewPos, 0); + + // Apply fog to the output color +#if MATERIAL_BLEND == MATERIAL_BLEND_OPAQUE + output = float4(output.rgb * fog.a + fog.rgb, output.a); +#elif MATERIAL_BLEND == MATERIAL_BLEND_TRANSPARENT + output = float4(output.rgb * fog.a + fog.rgb, output.a); +#elif MATERIAL_BLEND == MATERIAL_BLEND_ADDITIVE + output = float4(output.rgb * fog.a + fog.rgb, output.a * fog.a); +#elif MATERIAL_BLEND == MATERIAL_BLEND_MULTIPLY + output = float4(lerp(float3(1, 1, 1), output.rgb, fog.aaa * fog.aaa), output.a); +#endif + +#endif + +#endif + + return output; +} diff --git a/Content/Editor/MaterialTemplates/Features/Lightmap.hlsl b/Content/Editor/MaterialTemplates/Features/Lightmap.hlsl new file mode 100644 index 000000000..f4583fa7f --- /dev/null +++ b/Content/Editor/MaterialTemplates/Features/Lightmap.hlsl @@ -0,0 +1,54 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +@0// Lightmap: Defines +#define CAN_USE_LIGHTMAP 1 +@1// Lightmap: Includes +@2// Lightmap: Constants +float4 LightmapArea; +@3// Lightmap: Resources +#if USE_LIGHTMAP +// Irradiance and directionality prebaked lightmaps +Texture2D Lightmap0 : register(t__SRV__); +Texture2D Lightmap1 : register(t__SRV__); +Texture2D Lightmap2 : register(t__SRV__); +#endif +@4// Lightmap: Utilities +#if USE_LIGHTMAP + +// Evaluates the H-Basis coefficients in the tangent space normal direction +float3 GetHBasisIrradiance(float3 n, float3 h0, float3 h1, float3 h2, float3 h3) +{ + // Band 0 + float3 color = h0 * (1.0f / sqrt(2.0f * PI)); + + // Band 1 + color += h1 * -sqrt(1.5f / PI) * n.y; + color += h2 * sqrt(1.5f / PI) * (2 * n.z - 1.0f); + color += h3 * -sqrt(1.5f / PI) * n.x; + + return color; +} + +float3 SampleLightmap(Material material, MaterialInput materialInput) +{ + // Sample lightmaps + float4 lightmap0 = Lightmap0.Sample(SamplerLinearClamp, materialInput.LightmapUV); + float4 lightmap1 = Lightmap1.Sample(SamplerLinearClamp, materialInput.LightmapUV); + float4 lightmap2 = Lightmap2.Sample(SamplerLinearClamp, materialInput.LightmapUV); + + // Unpack H-basis + float3 h0 = float3(lightmap0.x, lightmap1.x, lightmap2.x); + float3 h1 = float3(lightmap0.y, lightmap1.y, lightmap2.y); + float3 h2 = float3(lightmap0.z, lightmap1.z, lightmap2.z); + float3 h3 = float3(lightmap0.w, lightmap1.w, lightmap2.w); + + // Sample baked diffuse irradiance from the H-basis coefficients + float3 normal = material.TangentNormal; +#if MATERIAL_SHADING_MODEL == SHADING_MODEL_FOLIAGE + normal *= material.TangentNormal; +#endif + return GetHBasisIrradiance(normal, h0, h1, h2, h3) / PI; +} + +#endif +@5// Lightmap: Shaders diff --git a/Content/Editor/MaterialTemplates/Features/MotionVectors.hlsl b/Content/Editor/MaterialTemplates/Features/MotionVectors.hlsl new file mode 100644 index 000000000..992eb6805 --- /dev/null +++ b/Content/Editor/MaterialTemplates/Features/MotionVectors.hlsl @@ -0,0 +1,44 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +@0// Motion Vectors: Defines +@1// Motion Vectors: Includes +@2// Motion Vectors: Constants +@3// Motion Vectors: Resources +@4// Motion Vectors: Utilities +@5// Motion Vectors: Shaders + +// Pixel Shader function for Motion Vectors Pass +META_PS(true, FEATURE_LEVEL_ES2) +float4 PS_MotionVectors(PixelInput input) : SV_Target0 +{ +#if USE_DITHERED_LOD_TRANSITION + // LOD masking + ClipLODTransition(input); +#endif + +#if MATERIAL_MASKED + // Perform per pixel clipping if material requries it + MaterialInput materialInput = GetMaterialInput(input); + Material material = GetMaterialPS(materialInput); + clip(material.Mask - MATERIAL_MASK_THRESHOLD); +#endif + + // Calculate this and previosu frame pixel locations in clip space + float4 prevClipPos = mul(float4(input.Geometry.PrevWorldPosition, 1), PrevViewProjectionMatrix); + float4 curClipPos = mul(float4(input.Geometry.WorldPosition, 1), ViewProjectionMatrix); + float2 prevHPos = prevClipPos.xy / prevClipPos.w; + float2 curHPos = curClipPos.xy / curClipPos.w; + + // Revert temporal jitter offset + prevHPos -= TemporalAAJitter.zw; + curHPos -= TemporalAAJitter.xy; + + // Clip Space -> UV Space + float2 vPosPrev = prevHPos.xy * 0.5f + 0.5f; + float2 vPosCur = curHPos.xy * 0.5f + 0.5f; + vPosPrev.y = 1.0 - vPosPrev.y; + vPosCur.y = 1.0 - vPosCur.y; + + // Calculate per-pixel motion vector + return float4(vPosCur - vPosPrev, 0, 1); +} diff --git a/Content/Editor/MaterialTemplates/Features/Tessellation.hlsl b/Content/Editor/MaterialTemplates/Features/Tessellation.hlsl new file mode 100644 index 000000000..e2e0b54ea --- /dev/null +++ b/Content/Editor/MaterialTemplates/Features/Tessellation.hlsl @@ -0,0 +1,184 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +@0// Tessellation: Defines +#define TessalationProjectOntoPlane(planeNormal, planePosition, pointToProject) pointToProject - dot(pointToProject - planePosition, planeNormal) * planeNormal +@1// Tessellation: Includes +@2// Tessellation: Constants +@3// Tessellation: Resources +@4// Tessellation: Utilities +@5// Tessellation: Shaders +#if USE_TESSELLATION + +// Interpolants passed from the hull shader to the domain shader +struct TessalationHSToDS +{ + float4 Position : SV_Position; + GeometryData Geometry; +#if USE_CUSTOM_VERTEX_INTERPOLATORS + float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; +#endif + float TessellationMultiplier : TESS; +}; + +// Interpolants passed from the domain shader and to the pixel shader +struct TessalationDSToPS +{ + float4 Position : SV_Position; + GeometryData Geometry; +#if USE_CUSTOM_VERTEX_INTERPOLATORS + float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; +#endif +}; + +MaterialInput GetMaterialInput(TessalationDSToPS input) +{ + MaterialInput output = GetGeometryMaterialInput(input.Geometry); + output.SvPosition = input.Position; + output.TwoSidedSign = WorldDeterminantSign; +#if USE_CUSTOM_VERTEX_INTERPOLATORS + output.CustomVSToPS = input.CustomVSToPS; +#endif + return output; +} + +struct TessalationPatch +{ + float EdgeTessFactor[3] : SV_TessFactor; + float InsideTessFactor : SV_InsideTessFactor; +#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PN + float3 B210 : POSITION4; + float3 B120 : POSITION5; + float3 B021 : POSITION6; + float3 B012 : POSITION7; + float3 B102 : POSITION8; + float3 B201 : POSITION9; + float3 B111 : CENTER; +#endif +}; + +TessalationPatch HS_PatchConstant(InputPatch input) +{ + TessalationPatch output; + + // Average tess factors along edges, and pick an edge tess factor for the interior tessellation + float4 tessellationMultipliers; + tessellationMultipliers.x = 0.5f * (input[1].TessellationMultiplier + input[2].TessellationMultiplier); + tessellationMultipliers.y = 0.5f * (input[2].TessellationMultiplier + input[0].TessellationMultiplier); + tessellationMultipliers.z = 0.5f * (input[0].TessellationMultiplier + input[1].TessellationMultiplier); + tessellationMultipliers.w = 0.333f * (input[0].TessellationMultiplier + input[1].TessellationMultiplier + input[2].TessellationMultiplier); + tessellationMultipliers = clamp(tessellationMultipliers, 1, MAX_TESSELLATION_FACTOR); + + output.EdgeTessFactor[0] = tessellationMultipliers.x; // 1->2 edge + output.EdgeTessFactor[1] = tessellationMultipliers.y; // 2->0 edge + output.EdgeTessFactor[2] = tessellationMultipliers.z; // 0->1 edge + output.InsideTessFactor = tessellationMultipliers.w; + +#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PN + // Calculate PN Triangle control points + // Reference: [Vlachos 2001] + float3 p1 = input[0].Geometry.WorldPosition; + float3 p2 = input[1].Geometry.WorldPosition; + float3 p3 = input[2].Geometry.WorldPosition; + float3 n1 = input[0].Geometry.WorldNormal; + float3 n2 = input[1].Geometry.WorldNormal; + float3 n3 = input[2].Geometry.WorldNormal; + output.B210 = (2.0f * p1 + p2 - dot((p2 - p1), n1) * n1) / 3.0f; + output.B120 = (2.0f * p2 + p1 - dot((p1 - p2), n2) * n2) / 3.0f; + output.B021 = (2.0f * p2 + p3 - dot((p3 - p2), n2) * n2) / 3.0f; + output.B012 = (2.0f * p3 + p2 - dot((p2 - p3), n3) * n3) / 3.0f; + output.B102 = (2.0f * p3 + p1 - dot((p1 - p3), n3) * n3) / 3.0f; + output.B201 = (2.0f * p1 + p3 - dot((p3 - p1), n1) * n1) / 3.0f; + float3 e = (output.B210 + output.B120 + output.B021 + output.B012 + output.B102 + output.B201) / 6.0f; + float3 v = (p1 + p2 + p3) / 3.0f; + output.B111 = e + ((e - v) / 2.0f); +#endif + + return output; +} + +META_HS(USE_TESSELLATION, FEATURE_LEVEL_SM5) +META_HS_PATCH(TESSELLATION_IN_CONTROL_POINTS) +[domain("tri")] +[partitioning("fractional_odd")] +[outputtopology("triangle_cw")] +[maxtessfactor(MAX_TESSELLATION_FACTOR)] +[outputcontrolpoints(3)] +[patchconstantfunc("HS_PatchConstant")] +TessalationHSToDS HS(InputPatch input, uint ControlPointID : SV_OutputControlPointID) +{ + TessalationHSToDS output; + + // Pass through shader +#define COPY(thing) output.thing = input[ControlPointID].thing; + COPY(Position); + COPY(Geometry); + COPY(TessellationMultiplier); +#if USE_CUSTOM_VERTEX_INTERPOLATORS + COPY(CustomVSToPS); +#endif +#undef COPY + + return output; +} + +META_DS(USE_TESSELLATION, FEATURE_LEVEL_SM5) +[domain("tri")] +TessalationDSToPS DS(TessalationPatch constantData, float3 barycentricCoords : SV_DomainLocation, const OutputPatch input) +{ + TessalationDSToPS output; + + // Get the barycentric coords + float U = barycentricCoords.x; + float V = barycentricCoords.y; + float W = barycentricCoords.z; + + // Interpolate patch attributes to generated vertices + output.Geometry = InterpolateGeometry(input[0].Geometry, U, input[1].Geometry, V, input[2].Geometry, W); +#define INTERPOLATE(thing) output.thing = U * input[0].thing + V * input[1].thing + W * input[2].thing + INTERPOLATE(Position); +#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PN + // Interpolate using barycentric coordinates and PN Triangle control points + float UU = U * U; + float VV = V * V; + float WW = W * W; + float UU3 = UU * 3.0f; + float VV3 = VV * 3.0f; + float WW3 = WW * 3.0f; + float3 offset = + constantData.B210 * UU3 * V + + constantData.B120 * VV3 * U + + constantData.B021 * VV3 * W + + constantData.B012 * WW3 * V + + constantData.B102 * WW3 * U + + constantData.B201 * UU3 * W + + constantData.B111 * 6.0f * W * U * V; + InterpolateGeometryPositions(output.Geometry, input[0].Geometry, UU * U, input[1].Geometry, VV * V, input[2].Geometry, WW * W, offset); +#else + InterpolateGeometryPositions(output.Geometry, input[0].Geometry, U, input[1].Geometry, V, input[2].Geometry, W, float3(0, 0, 0)); +#endif +#if USE_CUSTOM_VERTEX_INTERPOLATORS + UNROLL + for (int i = 0; i < CUSTOM_VERTEX_INTERPOLATORS_COUNT; i++) + INTERPOLATE(CustomVSToPS[i]); +#endif +#undef INTERPOLATE + +#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PHONG + // Orthogonal projection in the tangent planes with interpolation + ApplyGeometryPositionsPhongTess(output.Geometry, input[0].Geometry, input[1].Geometry, input[2].Geometry, U, V, W); +#endif + + // Perform displacement mapping +#if USE_DISPLACEMENT + MaterialInput materialInput = GetMaterialInput(output); + Material material = GetMaterialDS(materialInput); + OffsetGeometryPositions(output.Geometry, material.WorldDisplacement); +#endif + + // Recalculate the clip space position + output.Position = mul(float4(output.Geometry.WorldPosition, 1), ViewProjectionMatrix); + + return output; +} + +#endif diff --git a/Content/Editor/MaterialTemplates/GUI.shader b/Content/Editor/MaterialTemplates/GUI.shader index 1457a0bc3..7eced6390 100644 --- a/Content/Editor/MaterialTemplates/GUI.shader +++ b/Content/Editor/MaterialTemplates/GUI.shader @@ -3,7 +3,6 @@ #define MATERIAL 1 @3 - #include "./Flax/Common.hlsl" #include "./Flax/MaterialCommon.hlsl" #include "./Flax/GBufferCommon.hlsl" @@ -22,7 +21,7 @@ float4 ViewInfo; float4 ScreenSize; @1META_CB_END -// Material shader resources +// Shader resources @2 // Interpolants passed from the vertex shader struct VertexOutput @@ -185,6 +184,8 @@ float4 GetVertexColor(MaterialInput input) #endif } +@8 + // Get material properties function (for vertex shader) Material GetMaterialVS(MaterialInput input) { @@ -197,9 +198,6 @@ Material GetMaterialPS(MaterialInput input) @4 } -// Fix line for errors/warnings for shader code from template -#line 1000 - // Vertex Shader function for GUI materials rendering META_VS(true, FEATURE_LEVEL_ES2) META_VS_IN_ELEMENT(POSITION, 0, R32G32_FLOAT, 0, ALIGN, PER_VERTEX, 0, true) @@ -257,3 +255,5 @@ float4 PS_GUI(PixelInput input) : SV_Target0 return float4(material.Emissive, material.Opacity); } + +@9 diff --git a/Content/Editor/MaterialTemplates/Particle.shader b/Content/Editor/MaterialTemplates/Particle.shader index 5fd161645..726ad225a 100644 --- a/Content/Editor/MaterialTemplates/Particle.shader +++ b/Content/Editor/MaterialTemplates/Particle.shader @@ -2,19 +2,13 @@ // Version: @0 #define MATERIAL 1 -#define MAX_LOCAL_LIGHTS 4 @3 +// Ribbons don't use sorted indices so overlap the segment distances buffer on the slot +#define HAS_SORTED_INDICES (!defined(_VS_Ribbon)) #include "./Flax/Common.hlsl" #include "./Flax/MaterialCommon.hlsl" #include "./Flax/GBufferCommon.hlsl" -#include "./Flax/LightingCommon.hlsl" -#if USE_REFLECTIONS -#include "./Flax/ReflectionsCommon.hlsl" -#endif -#include "./Flax/Lighting.hlsl" -#include "./Flax/ShadowsSampling.hlsl" -#include "./Flax/ExponentialHeightFog.hlsl" #include "./Flax/Matrix.hlsl" @7 struct SpriteInput @@ -55,44 +49,19 @@ uint RibbonSegmentCount; float4x4 WorldMatrixInverseTransposed; @1META_CB_END -// Secondary constantant buffer (for lighting) -META_CB_BEGIN(1, LightingData) -LightData DirectionalLight; -LightShadowData DirectionalLightShadow; -LightData SkyLight; -ProbeData EnvironmentProbe; -ExponentialHeightFogData ExponentialHeightFog; -float3 Dummy1; -uint LocalLightsCount; -LightData LocalLights[MAX_LOCAL_LIGHTS]; -META_CB_END - -DECLARE_LIGHTSHADOWDATA_ACCESS(DirectionalLightShadow); - // Particles attributes buffer ByteAddressBuffer ParticlesData : register(t0); -// Ribbons don't use sorted indices so overlap the segment distances buffer on the slot -#define HAS_SORTED_INDICES (!defined(_VS_Ribbon)) - #if HAS_SORTED_INDICES - // Sorted particles indices Buffer SortedIndices : register(t1); - #else - // Ribbon particles segments distances buffer Buffer SegmentDistances : register(t1); - #endif // Shader resources -TextureCube EnvProbe : register(t2); -TextureCube SkyLightTexture : register(t3); -Texture2DArray DirectionalLightShadowMap : register(t4); @2 - // Interpolants passed from the vertex shader struct VertexOutput { @@ -172,14 +141,11 @@ MaterialInput GetMaterialInput(PixelInput input) } // Gets the local to world transform matrix (supports instancing) -float4x4 GetInstanceTransform(ModelInput input) -{ - return WorldMatrix; -} -float4x4 GetInstanceTransform(MaterialInput input) -{ - return WorldMatrix; -} +#if USE_INSTANCING +#define GetInstanceTransform(input) float4x4(float4(input.InstanceTransform1.xyz, 0.0f), float4(input.InstanceTransform2.xyz, 0.0f), float4(input.InstanceTransform3.xyz, 0.0f), float4(input.InstanceOrigin.xyz, 1.0f)) +#else +#define GetInstanceTransform(input) WorldMatrix; +#endif // Removes the scale vector from the local to world transformation matrix (supports instancing) float3x3 RemoveScaleFromLocalToWorld(float3x3 localToWorld) @@ -312,6 +278,8 @@ float3 TransformParticleVector(float3 input) return mul(float4(input, 0.0f), WorldMatrixInverseTransposed).xyz; } +@8 + // Get material properties function (for vertex shader) Material GetMaterialVS(MaterialInput input) { @@ -330,9 +298,6 @@ Material GetMaterialPS(MaterialInput input) @4 } -// Fix line for errors/warnings for shader code from template -#line 1000 - // Calculates the transform matrix from mesh tangent space to local space half3x3 CalcTangentToLocal(ModelInput input) { @@ -712,142 +677,9 @@ VertexOutput VS_Ribbon(uint vertexIndex : SV_VertexID) return output; } -// Pixel Shader function for Forward Pass -META_PS(USE_FORWARD, FEATURE_LEVEL_ES2) -float4 PS_Forward(PixelInput input) : SV_Target0 -{ - float4 output = 0; - - // Get material parameters - MaterialInput materialInput = GetMaterialInput(input); - Material material = GetMaterialPS(materialInput); - - // Masking -#if MATERIAL_MASKED - clip(material.Mask - MATERIAL_MASK_THRESHOLD); -#endif - - // Add emissive light - output = float4(material.Emissive, material.Opacity); - -#if MATERIAL_SHADING_MODEL != SHADING_MODEL_UNLIT - - // Setup GBuffer data as proxy for lighting - GBufferSample gBuffer; - gBuffer.Normal = material.WorldNormal; - gBuffer.Roughness = material.Roughness; - gBuffer.Metalness = material.Metalness; - gBuffer.Color = material.Color; - gBuffer.Specular = material.Specular; - gBuffer.AO = material.AO; - gBuffer.ViewPos = mul(float4(materialInput.WorldPosition, 1), ViewMatrix).xyz; -#if MATERIAL_SHADING_MODEL == SHADING_MODEL_SUBSURFACE - gBuffer.CustomData = float4(material.SubsurfaceColor, material.Opacity); -#elif MATERIAL_SHADING_MODEL == SHADING_MODEL_FOLIAGE - gBuffer.CustomData = float4(material.SubsurfaceColor, material.Opacity); -#else - gBuffer.CustomData = float4(0, 0, 0, 0); -#endif - gBuffer.WorldPos = materialInput.WorldPosition; - gBuffer.ShadingModel = MATERIAL_SHADING_MODEL; - - // Calculate lighting from a single directional light - float4 shadowMask = 1.0f; - if (DirectionalLight.CastShadows > 0) - { - LightShadowData directionalLightShadowData = GetDirectionalLightShadowData(); - shadowMask.r = SampleShadow(DirectionalLight, directionalLightShadowData, DirectionalLightShadowMap, gBuffer, shadowMask.g); - } - float4 light = GetLighting(ViewPos, DirectionalLight, gBuffer, shadowMask, false, false); - - // Calculate lighting from sky light - light += GetSkyLightLighting(SkyLight, gBuffer, SkyLightTexture); - - // Calculate lighting from local lights - LOOP - for (uint localLightIndex = 0; localLightIndex < LocalLightsCount; localLightIndex++) - { - const LightData localLight = LocalLights[localLightIndex]; - bool isSpotLight = localLight.SpotAngles.x > -2.0f; - shadowMask = 1.0f; - light += GetLighting(ViewPos, localLight, gBuffer, shadowMask, true, isSpotLight); - } - -#if USE_REFLECTIONS - // Calculate reflections - light.rgb += GetEnvProbeLighting(ViewPos, EnvProbe, EnvironmentProbe, gBuffer) * light.a; -#endif - - // Add lighting (apply ambient occlusion) - output.rgb += light.rgb * gBuffer.AO; - -#if USE_FOG - // Calculate exponential height fog - float4 fog = GetExponentialHeightFog(ExponentialHeightFog, materialInput.WorldPosition, ViewPos, 0); - - // Apply fog to the output color -#if MATERIAL_BLEND == MATERIAL_BLEND_OPAQUE - output = float4(output.rgb * fog.a + fog.rgb, output.a); -#elif MATERIAL_BLEND == MATERIAL_BLEND_TRANSPARENT - output = float4(output.rgb * fog.a + fog.rgb, output.a); -#elif MATERIAL_BLEND == MATERIAL_BLEND_ADDITIVE - output = float4(output.rgb * fog.a + fog.rgb, output.a * fog.a); -#elif MATERIAL_BLEND == MATERIAL_BLEND_MULTIPLY - output = float4(lerp(float3(1, 1, 1), output.rgb, fog.aaa * fog.aaa), output.a); -#endif - -#endif - -#endif - - return output; -} - -#if USE_DISTORTION - -// Pixel Shader function for Distortion Pass -META_PS(USE_DISTORTION, FEATURE_LEVEL_ES2) -float4 PS_Distortion(PixelInput input) : SV_Target0 -{ - // Get material parameters - MaterialInput materialInput = GetMaterialInput(input); - Material material = GetMaterialPS(materialInput); - - // Masking -#if MATERIAL_MASKED - clip(material.Mask - MATERIAL_MASK_THRESHOLD); -#endif - - float3 viewNormal = normalize(TransformWorldVectorToView(materialInput, material.WorldNormal)); - float airIOR = 1.0f; -#if USE_PIXEL_NORMAL_OFFSET_REFRACTION - float3 viewVertexNormal = TransformWorldVectorToView(materialInput, TransformTangentVectorToWorld(materialInput, float3(0, 0, 1))); - float2 distortion = (viewVertexNormal.xy - viewNormal.xy) * (material.Refraction - airIOR); -#else - float2 distortion = viewNormal.xy * (material.Refraction - airIOR); -#endif - - // Clip if the distortion distance (squared) is too small to be noticed - clip(dot(distortion, distortion) - 0.00001); - - // Scale up for better precision in low/subtle refractions at the expense of artefacts at higher refraction - distortion *= 4.0f; - - // Use separate storage for positive and negative offsets - float2 addOffset = max(distortion, 0); - float2 subOffset = abs(min(distortion, 0)); - return float4(addOffset.x, addOffset.y, subOffset.x, subOffset.y); -} - -#endif - // Pixel Shader function for Depth Pass META_PS(true, FEATURE_LEVEL_ES2) -void PS_Depth(PixelInput input -#if GLSL - , out float4 OutColor : SV_Target0 -#endif - ) +void PS_Depth(PixelInput input) { // Get material parameters MaterialInput materialInput = GetMaterialInput(input); @@ -860,8 +692,6 @@ void PS_Depth(PixelInput input #if MATERIAL_BLEND == MATERIAL_BLEND_TRANSPARENT clip(material.Opacity - MATERIAL_OPACITY_THRESHOLD); #endif - -#if GLSL - OutColor = 0; -#endif } + +@9 diff --git a/Content/Editor/MaterialTemplates/PostProcess.shader b/Content/Editor/MaterialTemplates/PostProcess.shader index dad9795af..f20bdc610 100644 --- a/Content/Editor/MaterialTemplates/PostProcess.shader +++ b/Content/Editor/MaterialTemplates/PostProcess.shader @@ -20,7 +20,7 @@ float4 ScreenSize; float4 TemporalAAJitter; @1META_CB_END -// Material shader resources +// Shader resources @2 // Interpolants passed to the pixel shader struct PixelInput @@ -128,15 +128,14 @@ float4 GetVertexColor(MaterialInput input) return 1; } +@8 + // Get material properties function (for pixel shader) Material GetMaterialPS(MaterialInput input) { @4 } -// Fix line for errors/warnings for shader code from template -#line 1000 - // Pixel Shader function for PostFx materials rendering META_PS(true, FEATURE_LEVEL_ES2) float4 PS_PostFx(PixelInput input) : SV_Target0 @@ -147,3 +146,5 @@ float4 PS_PostFx(PixelInput input) : SV_Target0 return float4(material.Emissive, material.Opacity); } + +@9 diff --git a/Content/Editor/MaterialTemplates/Surface.shader b/Content/Editor/MaterialTemplates/Surface.shader new file mode 100644 index 000000000..4eee27691 --- /dev/null +++ b/Content/Editor/MaterialTemplates/Surface.shader @@ -0,0 +1,620 @@ +// File generated by Flax Materials Editor +// Version: @0 + +#define MATERIAL 1 +@3 +#include "./Flax/Common.hlsl" +#include "./Flax/MaterialCommon.hlsl" +#include "./Flax/GBufferCommon.hlsl" +@7 +// Primary constant buffer (with additional material parameters) +META_CB_BEGIN(0, Data) +float4x4 ViewProjectionMatrix; +float4x4 WorldMatrix; +float4x4 ViewMatrix; +float4x4 PrevViewProjectionMatrix; +float4x4 PrevWorldMatrix; +float3 ViewPos; +float ViewFar; +float3 ViewDir; +float TimeParam; +float4 ViewInfo; +float4 ScreenSize; +float3 WorldInvScale; +float WorldDeterminantSign; +float2 Dummy0; +float LODDitherFactor; +float PerInstanceRandom; +float4 TemporalAAJitter; +float3 GeometrySize; +float Dummy1; +@1META_CB_END + +// Shader resources +@2 +// Geometry data passed though the graphics rendering stages up to the pixel shader +struct GeometryData +{ + float3 WorldPosition : TEXCOORD0; + float2 TexCoord : TEXCOORD1; + float2 LightmapUV : TEXCOORD2; +#if USE_VERTEX_COLOR + half4 VertexColor : COLOR; +#endif + float3 WorldNormal : TEXCOORD3; + float4 WorldTangent : TEXCOORD4; + float3 InstanceOrigin : TEXCOORD5; + float2 InstanceParams : TEXCOORD6; // x-PerInstanceRandom, y-LODDitherFactor + float3 PrevWorldPosition : TEXCOORD7; +}; + +// Interpolants passed from the vertex shader +struct VertexOutput +{ + float4 Position : SV_Position; + GeometryData Geometry; +#if USE_CUSTOM_VERTEX_INTERPOLATORS + float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; +#endif +#if USE_TESSELLATION + float TessellationMultiplier : TESS; +#endif +}; + +// Interpolants passed to the pixel shader +struct PixelInput +{ + float4 Position : SV_Position; + GeometryData Geometry; +#if USE_CUSTOM_VERTEX_INTERPOLATORS + float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; +#endif + bool IsFrontFace : SV_IsFrontFace; +}; + +// Material properties generation input +struct MaterialInput +{ + float3 WorldPosition; + float TwoSidedSign; + float2 TexCoord; +#if USE_LIGHTMAP + float2 LightmapUV; +#endif +#if USE_VERTEX_COLOR + half4 VertexColor; +#endif + float3x3 TBN; + float4 SvPosition; + float3 PreSkinnedPosition; + float3 PreSkinnedNormal; + float3 InstanceOrigin; + float2 InstanceParams; +#if USE_INSTANCING + float3 InstanceTransform1; + float3 InstanceTransform2; + float3 InstanceTransform3; +#endif +#if USE_CUSTOM_VERTEX_INTERPOLATORS + float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT]; +#endif +}; + +// Extracts geometry data to the material input +MaterialInput GetGeometryMaterialInput(GeometryData geometry) +{ + MaterialInput output = (MaterialInput)0; + output.WorldPosition = geometry.WorldPosition; + output.TexCoord = geometry.TexCoord; +#if USE_LIGHTMAP + output.LightmapUV = geometry.LightmapUV; +#endif +#if USE_VERTEX_COLOR + output.VertexColor = geometry.VertexColor; +#endif + output.TBN = CalcTangentBasis(geometry.WorldNormal, geometry.WorldTangent); + output.InstanceOrigin = geometry.InstanceOrigin; + output.InstanceParams = geometry.InstanceParams; + return output; +} + +#if USE_TESSELLATION + +// Interpolates the geometry positions data only (used by the tessallation when generating vertices) +#define InterpolateGeometryPositions(output, p0, w0, p1, w1, p2, w2, offset) output.WorldPosition = p0.WorldPosition * w0 + p1.WorldPosition * w1 + p2.WorldPosition * w2 + offset; output.PrevWorldPosition = p0.PrevWorldPosition * w0 + p1.PrevWorldPosition * w1 + p2.PrevWorldPosition * w2 + offset + +// Offsets the geometry positions data only (used by the tessallation when generating vertices) +#define OffsetGeometryPositions(geometry, offset) geometry.WorldPosition += offset; geometry.PrevWorldPosition += offset + +// Applies the Phong tessallation to the geometry positions (used by the tessallation when doing Phong tess) +#define ApplyGeometryPositionsPhongTess(geometry, p0, p1, p2, U, V, W) \ + float3 posProjectedU = TessalationProjectOntoPlane(p0.WorldNormal, p0.WorldPosition, geometry.WorldPosition); \ + float3 posProjectedV = TessalationProjectOntoPlane(p1.WorldNormal, p1.WorldPosition, geometry.WorldPosition); \ + float3 posProjectedW = TessalationProjectOntoPlane(p2.WorldNormal, p2.WorldPosition, geometry.WorldPosition); \ + geometry.WorldPosition = U * posProjectedU + V * posProjectedV + W * posProjectedW; \ + posProjectedU = TessalationProjectOntoPlane(p0.WorldNormal, p0.PrevWorldPosition, geometry.PrevWorldPosition); \ + posProjectedV = TessalationProjectOntoPlane(p1.WorldNormal, p1.PrevWorldPosition, geometry.PrevWorldPosition); \ + posProjectedW = TessalationProjectOntoPlane(p2.WorldNormal, p2.PrevWorldPosition, geometry.PrevWorldPosition); \ + geometry.PrevWorldPosition = U * posProjectedU + V * posProjectedV + W * posProjectedW + +// Interpolates the geometry data except positions (used by the tessallation when generating vertices) +GeometryData InterpolateGeometry(GeometryData p0, float w0, GeometryData p1, float w1, GeometryData p2, float w2) +{ + GeometryData output = (GeometryData)0; + output.TexCoord = p0.TexCoord * w0 + p1.TexCoord * w1 + p2.TexCoord * w2; +#if USE_LIGHTMAP + output.LightmapUV = p0.LightmapUV * w0 + p1.LightmapUV * w1 + p2.LightmapUV * w2; +#endif +#if USE_VERTEX_COLOR + output.VertexColor = p0.VertexColor * w0 + p1.VertexColor * w1 + p2.VertexColor * w2; +#endif + output.WorldNormal = p0.WorldNormal * w0 + p1.WorldNormal * w1 + p2.WorldNormal * w2; + output.WorldNormal = normalize(output.WorldNormal); + output.WorldTangent = p0.WorldTangent * w0 + p1.WorldTangent * w1 + p2.WorldTangent * w2; + output.WorldTangent.xyz = normalize(output.WorldTangent.xyz); + output.InstanceOrigin = p0.InstanceOrigin; + output.InstanceParams = p0.InstanceParams; + return output; +} + +#endif + +MaterialInput GetMaterialInput(PixelInput input) +{ + MaterialInput output = GetGeometryMaterialInput(input.Geometry); + output.TwoSidedSign = WorldDeterminantSign * (input.IsFrontFace ? 1.0 : -1.0); + output.SvPosition = input.Position; +#if USE_CUSTOM_VERTEX_INTERPOLATORS + output.CustomVSToPS = input.CustomVSToPS; +#endif + return output; +} + +// Gets the local to world transform matrix (supports instancing) +#if USE_INSTANCING +#define GetInstanceTransform(input) float4x4(float4(input.InstanceTransform1.xyz, 0.0f), float4(input.InstanceTransform2.xyz, 0.0f), float4(input.InstanceTransform3.xyz, 0.0f), float4(input.InstanceOrigin.xyz, 1.0f)) +#else +#define GetInstanceTransform(input) WorldMatrix; +#endif + +// Removes the scale vector from the local to world transformation matrix (supports instancing) +float3x3 RemoveScaleFromLocalToWorld(float3x3 localToWorld) +{ +#if USE_INSTANCING + // Extract per axis scales from localToWorld transform + float scaleX = length(localToWorld[0]); + float scaleY = length(localToWorld[1]); + float scaleZ = length(localToWorld[2]); + float3 invScale = float3( + scaleX > 0.00001f ? 1.0f / scaleX : 0.0f, + scaleY > 0.00001f ? 1.0f / scaleY : 0.0f, + scaleZ > 0.00001f ? 1.0f / scaleZ : 0.0f); +#else + float3 invScale = WorldInvScale; +#endif + localToWorld[0] *= invScale.x; + localToWorld[1] *= invScale.y; + localToWorld[2] *= invScale.z; + return localToWorld; +} + +// Transforms a vector from tangent space to world space +float3 TransformTangentVectorToWorld(MaterialInput input, float3 tangentVector) +{ + return mul(tangentVector, input.TBN); +} + +// Transforms a vector from world space to tangent space +float3 TransformWorldVectorToTangent(MaterialInput input, float3 worldVector) +{ + return mul(input.TBN, worldVector); +} + +// Transforms a vector from world space to view space +float3 TransformWorldVectorToView(MaterialInput input, float3 worldVector) +{ + return mul(worldVector, (float3x3)ViewMatrix); +} + +// Transforms a vector from view space to world space +float3 TransformViewVectorToWorld(MaterialInput input, float3 viewVector) +{ + return mul((float3x3)ViewMatrix, viewVector); +} + +// Transforms a vector from local space to world space +float3 TransformLocalVectorToWorld(MaterialInput input, float3 localVector) +{ + float3x3 localToWorld = (float3x3)GetInstanceTransform(input); + //localToWorld = RemoveScaleFromLocalToWorld(localToWorld); + return mul(localVector, localToWorld); +} + +// Transforms a vector from local space to world space +float3 TransformWorldVectorToLocal(MaterialInput input, float3 worldVector) +{ + float3x3 localToWorld = (float3x3)GetInstanceTransform(input); + //localToWorld = RemoveScaleFromLocalToWorld(localToWorld); + return mul(localToWorld, worldVector); +} + +// Gets the current object position (supports instancing) +float3 GetObjectPosition(MaterialInput input) +{ + return input.InstanceOrigin.xyz; +} + +// Gets the current object size (supports instancing) +float3 GetObjectSize(MaterialInput input) +{ + float4x4 world = GetInstanceTransform(input); + return GeometrySize * float3(world._m00, world._m11, world._m22); +} + +// Get the current object random value (supports instancing) +float GetPerInstanceRandom(MaterialInput input) +{ + return input.InstanceParams.x; +} + +// Get the current object LOD transition dither factor (supports instancing) +float GetLODDitherFactor(MaterialInput input) +{ +#if USE_DITHERED_LOD_TRANSITION + return input.InstanceParams.y; +#else + return 0; +#endif +} + +// Gets the interpolated vertex color (in linear space) +float4 GetVertexColor(MaterialInput input) +{ +#if USE_VERTEX_COLOR + return input.VertexColor; +#else + return 1; +#endif +} + +@8 + +// Get material properties function (for vertex shader) +Material GetMaterialVS(MaterialInput input) +{ +@5 +} + +// Get material properties function (for domain shader) +Material GetMaterialDS(MaterialInput input) +{ +@6 +} + +// Get material properties function (for pixel shader) +Material GetMaterialPS(MaterialInput input) +{ +@4 +} + +// Calculates the transform matrix from mesh tangent space to local space +float3x3 CalcTangentToLocal(ModelInput input) +{ + float bitangentSign = input.Tangent.w ? -1.0f : +1.0f; + float3 normal = input.Normal.xyz * 2.0 - 1.0; + float3 tangent = input.Tangent.xyz * 2.0 - 1.0; + float3 bitangent = cross(normal, tangent) * bitangentSign; + return float3x3(tangent, bitangent, normal); +} + +float3x3 CalcTangentToWorld(float4x4 world, float3x3 tangentToLocal) +{ + float3x3 localToWorld = RemoveScaleFromLocalToWorld((float3x3)world); + return mul(tangentToLocal, localToWorld); +} + +// Vertex Shader function for GBuffer Pass and Depth Pass (with full vertex data) +META_VS(true, FEATURE_LEVEL_ES2) +META_PERMUTATION_1(USE_INSTANCING=0) +META_PERMUTATION_1(USE_INSTANCING=1) +META_VS_IN_ELEMENT(POSITION, 0, R32G32B32_FLOAT, 0, 0, PER_VERTEX, 0, true) +META_VS_IN_ELEMENT(TEXCOORD, 0, R16G16_FLOAT, 1, 0, PER_VERTEX, 0, true) +META_VS_IN_ELEMENT(NORMAL, 0, R10G10B10A2_UNORM, 1, ALIGN, PER_VERTEX, 0, true) +META_VS_IN_ELEMENT(TANGENT, 0, R10G10B10A2_UNORM, 1, ALIGN, PER_VERTEX, 0, true) +META_VS_IN_ELEMENT(TEXCOORD, 1, R16G16_FLOAT, 1, ALIGN, PER_VERTEX, 0, true) +META_VS_IN_ELEMENT(COLOR, 0, R8G8B8A8_UNORM, 2, 0, PER_VERTEX, 0, USE_VERTEX_COLOR) +META_VS_IN_ELEMENT(ATTRIBUTE,0, R32G32B32A32_FLOAT,3, 0, PER_INSTANCE, 1, USE_INSTANCING) +META_VS_IN_ELEMENT(ATTRIBUTE,1, R32G32B32A32_FLOAT,3, ALIGN, PER_INSTANCE, 1, USE_INSTANCING) +META_VS_IN_ELEMENT(ATTRIBUTE,2, R32G32B32_FLOAT, 3, ALIGN, PER_INSTANCE, 1, USE_INSTANCING) +META_VS_IN_ELEMENT(ATTRIBUTE,3, R32G32B32_FLOAT, 3, ALIGN, PER_INSTANCE, 1, USE_INSTANCING) +META_VS_IN_ELEMENT(ATTRIBUTE,4, R16G16B16A16_FLOAT,3, ALIGN, PER_INSTANCE, 1, USE_INSTANCING) +VertexOutput VS(ModelInput input) +{ + VertexOutput output; + + // Compute world space vertex position + float4x4 world = GetInstanceTransform(input); + output.Geometry.WorldPosition = mul(float4(input.Position.xyz, 1), world).xyz; + output.Geometry.PrevWorldPosition = mul(float4(input.Position.xyz, 1), PrevWorldMatrix).xyz; + + // Compute clip space position + output.Position = mul(float4(output.Geometry.WorldPosition, 1), ViewProjectionMatrix); + + // Pass vertex attributes + output.Geometry.TexCoord = input.TexCoord; +#if USE_VERTEX_COLOR + output.Geometry.VertexColor = input.Color; +#endif + output.Geometry.InstanceOrigin = world[3].xyz; +#if USE_INSTANCING + output.Geometry.LightmapUV = input.LightmapUV * input.InstanceLightmapArea.zw + input.InstanceLightmapArea.xy; + output.Geometry.InstanceParams = float2(input.InstanceOrigin.w, input.InstanceTransform1.w); +#else +#if CAN_USE_LIGHTMAP + output.Geometry.LightmapUV = input.LightmapUV * LightmapArea.zw + LightmapArea.xy; +#else + output.Geometry.LightmapUV = input.LightmapUV; +#endif + output.Geometry.InstanceParams = float2(PerInstanceRandom, LODDitherFactor); +#endif + + // Calculate tanget space to world space transformation matrix for unit vectors + float3x3 tangentToLocal = CalcTangentToLocal(input); + float3x3 tangentToWorld = CalcTangentToWorld(world, tangentToLocal); + output.Geometry.WorldNormal = tangentToWorld[2]; + output.Geometry.WorldTangent.xyz = tangentToWorld[0]; + output.Geometry.WorldTangent.w = input.Tangent.w ? -1.0f : +1.0f; + + // Get material input params if need to evaluate any material property +#if USE_POSITION_OFFSET || USE_TESSELLATION || USE_CUSTOM_VERTEX_INTERPOLATORS + MaterialInput materialInput = GetGeometryMaterialInput(output.Geometry); + materialInput.TwoSidedSign = WorldDeterminantSign; + materialInput.SvPosition = output.Position; + materialInput.PreSkinnedPosition = input.Position.xyz; + materialInput.PreSkinnedNormal = tangentToLocal[2].xyz; +#if USE_INSTANCING + materialInput.InstanceTransform1 = input.InstanceTransform1.xyz; + materialInput.InstanceTransform2 = input.InstanceTransform2.xyz; + materialInput.InstanceTransform3 = input.InstanceTransform3.xyz; +#endif + Material material = GetMaterialVS(materialInput); +#endif + + // Apply world position offset per-vertex +#if USE_POSITION_OFFSET + output.Geometry.WorldPosition += material.PositionOffset; + output.Position = mul(float4(output.Geometry.WorldPosition, 1), ViewProjectionMatrix); +#endif + + // Get tessalation multiplier (per vertex) +#if USE_TESSELLATION + output.TessellationMultiplier = material.TessellationMultiplier; +#endif + + // Copy interpolants for other shader stages +#if USE_CUSTOM_VERTEX_INTERPOLATORS + output.CustomVSToPS = material.CustomVSToPS; +#endif + + return output; +} + +// Vertex Shader function for Depth Pass +META_VS(true, FEATURE_LEVEL_ES2) +META_PERMUTATION_1(USE_INSTANCING=0) +META_PERMUTATION_1(USE_INSTANCING=1) +META_VS_IN_ELEMENT(POSITION, 0, R32G32B32_FLOAT, 0, 0, PER_VERTEX, 0, true) +META_VS_IN_ELEMENT(ATTRIBUTE,0, R32G32B32A32_FLOAT,3, 0, PER_INSTANCE, 1, USE_INSTANCING) +META_VS_IN_ELEMENT(ATTRIBUTE,1, R32G32B32A32_FLOAT,3, ALIGN, PER_INSTANCE, 1, USE_INSTANCING) +META_VS_IN_ELEMENT(ATTRIBUTE,2, R32G32B32_FLOAT, 3, ALIGN, PER_INSTANCE, 1, USE_INSTANCING) +META_VS_IN_ELEMENT(ATTRIBUTE,3, R32G32B32_FLOAT, 3, ALIGN, PER_INSTANCE, 1, USE_INSTANCING) +META_VS_IN_ELEMENT(ATTRIBUTE,4, R16G16B16A16_FLOAT,3, ALIGN, PER_INSTANCE, 1, USE_INSTANCING) +float4 VS_Depth(ModelInput_PosOnly input) : SV_Position +{ + float4x4 world = GetInstanceTransform(input); + float3 worldPosition = mul(float4(input.Position.xyz, 1), world).xyz; + float4 position = mul(float4(worldPosition, 1), ViewProjectionMatrix); + return position; +} + +#if USE_SKINNING + +// The skeletal bones matrix buffer (stored as 4x3, 3 float4 behind each other) +Buffer BoneMatrices : register(t0); + +#if PER_BONE_MOTION_BLUR + +// The skeletal bones matrix buffer from the previous frame +Buffer PrevBoneMatrices : register(t1); + +float3x4 GetPrevBoneMatrix(int index) +{ + float4 a = PrevBoneMatrices[index * 3]; + float4 b = PrevBoneMatrices[index * 3 + 1]; + float4 c = PrevBoneMatrices[index * 3 + 2]; + return float3x4(a, b, c); +} + +float3 SkinPrevPosition(ModelInput_Skinned input) +{ + float4 position = float4(input.Position.xyz, 1); + float3x4 boneMatrix = input.BlendWeights.x * GetPrevBoneMatrix(input.BlendIndices.x); + boneMatrix += input.BlendWeights.y * GetPrevBoneMatrix(input.BlendIndices.y); + boneMatrix += input.BlendWeights.z * GetPrevBoneMatrix(input.BlendIndices.z); + boneMatrix += input.BlendWeights.w * GetPrevBoneMatrix(input.BlendIndices.w); + return mul(boneMatrix, position); +} + +#endif + +// Cached skinning data to avoid multiple calculation +struct SkinningData +{ + float3x4 BlendMatrix; +}; + +// Calculates the transposed transform matrix for the given bone index +float3x4 GetBoneMatrix(int index) +{ + float4 a = BoneMatrices[index * 3]; + float4 b = BoneMatrices[index * 3 + 1]; + float4 c = BoneMatrices[index * 3 + 2]; + return float3x4(a, b, c); +} + +// Calculates the transposed transform matrix for the given vertex (uses blending) +float3x4 GetBoneMatrix(ModelInput_Skinned input) +{ + float3x4 boneMatrix = input.BlendWeights.x * GetBoneMatrix(input.BlendIndices.x); + boneMatrix += input.BlendWeights.y * GetBoneMatrix(input.BlendIndices.y); + boneMatrix += input.BlendWeights.z * GetBoneMatrix(input.BlendIndices.z); + boneMatrix += input.BlendWeights.w * GetBoneMatrix(input.BlendIndices.w); + return boneMatrix; +} + +// Transforms the vertex position by weighted sum of the skinning matrices +float3 SkinPosition(ModelInput_Skinned input, SkinningData data) +{ + return mul(data.BlendMatrix, float4(input.Position.xyz, 1)); +} + +// Transforms the vertex position by weighted sum of the skinning matrices +float3x3 SkinTangents(ModelInput_Skinned input, SkinningData data) +{ + // Unpack vertex tangent frame + float bitangentSign = input.Tangent.w ? -1.0f : +1.0f; + float3 normal = input.Normal.xyz * 2.0 - 1.0; + float3 tangent = input.Tangent.xyz * 2.0 - 1.0; + + // Apply skinning + tangent = mul(data.BlendMatrix, float4(tangent, 0)); + normal = mul(data.BlendMatrix, float4(normal, 0)); + + float3 bitangent = cross(normal, tangent) * bitangentSign; + return float3x3(tangent, bitangent, normal); +} + +// Vertex Shader function for GBuffers/Depth Pass (skinned mesh rendering) +META_VS(true, FEATURE_LEVEL_ES2) +META_PERMUTATION_1(USE_SKINNING=1) +META_PERMUTATION_2(USE_SKINNING=1, PER_BONE_MOTION_BLUR=1) +META_VS_IN_ELEMENT(POSITION, 0, R32G32B32_FLOAT, 0, 0, PER_VERTEX, 0, true) +META_VS_IN_ELEMENT(TEXCOORD, 0, R16G16_FLOAT, 0, ALIGN, PER_VERTEX, 0, true) +META_VS_IN_ELEMENT(NORMAL, 0, R10G10B10A2_UNORM, 0, ALIGN, PER_VERTEX, 0, true) +META_VS_IN_ELEMENT(TANGENT, 0, R10G10B10A2_UNORM, 0, ALIGN, PER_VERTEX, 0, true) +META_VS_IN_ELEMENT(BLENDINDICES, 0, R8G8B8A8_UINT, 0, ALIGN, PER_VERTEX, 0, true) +META_VS_IN_ELEMENT(BLENDWEIGHT, 0, R16G16B16A16_FLOAT,0, ALIGN, PER_VERTEX, 0, true) +VertexOutput VS_Skinned(ModelInput_Skinned input) +{ + VertexOutput output; + + // Perform skinning + SkinningData data; + data.BlendMatrix = GetBoneMatrix(input); + float3 position = SkinPosition(input, data); + float3x3 tangentToLocal = SkinTangents(input, data); + + // Compute world space vertex position + float4x4 world = GetInstanceTransform(input); + output.Geometry.WorldPosition = mul(float4(position, 1), world).xyz; +#if PER_BONE_MOTION_BLUR + float3 prevPosition = SkinPrevPosition(input); + output.Geometry.PrevWorldPosition = mul(float4(prevPosition, 1), PrevWorldMatrix).xyz; +#else + output.Geometry.PrevWorldPosition = mul(float4(position, 1), PrevWorldMatrix).xyz; +#endif + + // Compute clip space position + output.Position = mul(float4(output.Geometry.WorldPosition, 1), ViewProjectionMatrix); + + // Pass vertex attributes + output.Geometry.TexCoord = input.TexCoord; +#if USE_VERTEX_COLOR + output.Geometry.VertexColor = float4(0, 0, 0, 1); +#endif + output.Geometry.LightmapUV = float2(0, 0); + output.Geometry.InstanceOrigin = world[3].xyz; +#if USE_INSTANCING + output.Geometry.InstanceParams = float2(input.InstanceOrigin.w, input.InstanceTransform1.w); +#else + output.Geometry.InstanceParams = float2(PerInstanceRandom, LODDitherFactor); +#endif + + // Calculate tanget space to world space transformation matrix for unit vectors + float3x3 tangentToWorld = CalcTangentToWorld(world, tangentToLocal); + output.Geometry.WorldNormal = tangentToWorld[2]; + output.Geometry.WorldTangent.xyz = tangentToWorld[0]; + output.Geometry.WorldTangent.w = input.Tangent.w ? -1.0f : +1.0f; + + // Get material input params if need to evaluate any material property +#if USE_POSITION_OFFSET || USE_TESSELLATION || USE_CUSTOM_VERTEX_INTERPOLATORS + MaterialInput materialInput = GetGeometryMaterialInput(output.Geometry); + materialInput.TwoSidedSign = WorldDeterminantSign; + materialInput.SvPosition = output.Position; + materialInput.PreSkinnedPosition = input.Position.xyz; + materialInput.PreSkinnedNormal = tangentToLocal[2].xyz; + Material material = GetMaterialVS(materialInput); +#endif + + // Apply world position offset per-vertex +#if USE_POSITION_OFFSET + output.Geometry.WorldPosition += material.PositionOffset; + output.Position = mul(float4(output.Geometry.WorldPosition, 1), ViewProjectionMatrix); +#endif + + // Get tessalation multiplier (per vertex) +#if USE_TESSELLATION + output.TessellationMultiplier = material.TessellationMultiplier; +#endif + + // Copy interpolants for other shader stages +#if USE_CUSTOM_VERTEX_INTERPOLATORS + output.CustomVSToPS = material.CustomVSToPS; +#endif + + return output; +} + +#endif + +#if USE_DITHERED_LOD_TRANSITION + +void ClipLODTransition(PixelInput input) +{ + float ditherFactor = input.InstanceParams.y; + if (abs(ditherFactor) > 0.001) + { + float randGrid = cos(dot(floor(input.Position.xy), float2(347.83452793, 3343.28371863))); + float randGridFrac = frac(randGrid * 1000.0); + half mask = (ditherFactor < 0.0) ? (ditherFactor + 1.0 > randGridFrac) : (ditherFactor < randGridFrac); + clip(mask - 0.001); + } +} + +#endif + +// Pixel Shader function for Depth Pass +META_PS(true, FEATURE_LEVEL_ES2) +void PS_Depth(PixelInput input) +{ +#if USE_DITHERED_LOD_TRANSITION + // LOD masking + ClipLODTransition(input); +#endif + +#if MATERIAL_MASKED || MATERIAL_BLEND != MATERIAL_BLEND_OPAQUE + // Get material parameters + MaterialInput materialInput = GetMaterialInput(input); + Material material = GetMaterialPS(materialInput); + + // Perform per pixel clipping +#if MATERIAL_MASKED + clip(material.Mask - MATERIAL_MASK_THRESHOLD); +#endif +#if MATERIAL_BLEND != MATERIAL_BLEND_OPAQUE + clip(material.Opacity - MATERIAL_OPACITY_THRESHOLD); +#endif +#endif +} + +@9 diff --git a/Content/Editor/MaterialTemplates/SurfaceDeferred.shader b/Content/Editor/MaterialTemplates/SurfaceDeferred.shader deleted file mode 100644 index 6f393464b..000000000 --- a/Content/Editor/MaterialTemplates/SurfaceDeferred.shader +++ /dev/null @@ -1,1083 +0,0 @@ -// File generated by Flax Materials Editor -// Version: @0 - -#define MATERIAL 1 -@3 - -#include "./Flax/Common.hlsl" -#include "./Flax/MaterialCommon.hlsl" -#include "./Flax/GBufferCommon.hlsl" -@7 -// Primary constant buffer (with additional material parameters) -META_CB_BEGIN(0, Data) -float4x4 ViewProjectionMatrix; -float4x4 WorldMatrix; -float4x4 ViewMatrix; -float4x4 PrevViewProjectionMatrix; -float4x4 PrevWorldMatrix; -float3 ViewPos; -float ViewFar; -float3 ViewDir; -float TimeParam; -float4 ViewInfo; -float4 ScreenSize; -float4 LightmapArea; -float3 WorldInvScale; -float WorldDeterminantSign; -float2 Dummy0; -float LODDitherFactor; -float PerInstanceRandom; -float4 TemporalAAJitter; -float3 GeometrySize; -float Dummy1; -@1META_CB_END - -#if CAN_USE_LIGHTMAP - -// Irradiance and directionality prebaked lightmaps -Texture2D Lightmap0 : register(t0); -Texture2D Lightmap1 : register(t1); -Texture2D Lightmap2 : register(t2); - -#endif - -// Material shader resources -@2 - -// Interpolants passed from the vertex shader -struct VertexOutput -{ - float4 Position : SV_Position; - float3 WorldPosition : TEXCOORD0; - float2 TexCoord : TEXCOORD1; - float2 LightmapUV : TEXCOORD2; -#if USE_VERTEX_COLOR - half4 VertexColor : COLOR; -#endif - float3 WorldNormal : TEXCOORD3; - float4 WorldTangent : TEXCOORD4; -#if USE_CUSTOM_VERTEX_INTERPOLATORS - float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; -#endif - float3 InstanceOrigin : TEXCOORD6; - float2 InstanceParams : TEXCOORD7; // x-PerInstanceRandom, y-LODDitherFactor -#if IS_MOTION_VECTORS_PASS - float3 PrevWorldPosition : TEXCOORD8; -#endif -#if USE_TESSELLATION - float TessellationMultiplier : TESS; -#endif -}; - -// Interpolants passed to the pixel shader -struct PixelInput -{ - float4 Position : SV_Position; - float3 WorldPosition : TEXCOORD0; - float2 TexCoord : TEXCOORD1; - float2 LightmapUV : TEXCOORD2; -#if USE_VERTEX_COLOR - half4 VertexColor : COLOR; -#endif - float3 WorldNormal : TEXCOORD3; - float4 WorldTangent : TEXCOORD4; -#if USE_CUSTOM_VERTEX_INTERPOLATORS - float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; -#endif - float3 InstanceOrigin : TEXCOORD6; - float2 InstanceParams : TEXCOORD7; // x-PerInstanceRandom, y-LODDitherFactor -#if IS_MOTION_VECTORS_PASS - float3 PrevWorldPosition : TEXCOORD8; -#endif - bool IsFrontFace : SV_IsFrontFace; -}; - -// Material properties generation input -struct MaterialInput -{ - float3 WorldPosition; - float TwoSidedSign; - float2 TexCoord; -#if USE_LIGHTMAP - float2 LightmapUV; -#endif -#if USE_VERTEX_COLOR - half4 VertexColor; -#endif - float3x3 TBN; - float4 SvPosition; - float3 PreSkinnedPosition; - float3 PreSkinnedNormal; - float3 InstanceOrigin; - float2 InstanceParams; -#if USE_INSTANCING - float3 InstanceTransform1; - float3 InstanceTransform2; - float3 InstanceTransform3; -#endif -#if USE_CUSTOM_VERTEX_INTERPOLATORS - float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT]; -#endif -}; - -float3x3 CalcTangentBasis(float3 normal, float4 tangent) -{ - float3 bitangent = cross(normal, tangent.xyz) * tangent.w; - return float3x3(tangent.xyz, bitangent, normal); -} - -MaterialInput GetMaterialInput(ModelInput input, VertexOutput output, float3 localNormal) -{ - MaterialInput result = (MaterialInput)0; - result.WorldPosition = output.WorldPosition; - result.TexCoord = output.TexCoord; -#if USE_LIGHTMAP - result.LightmapUV = output.LightmapUV; -#endif -#if USE_VERTEX_COLOR - result.VertexColor = output.VertexColor; -#endif - result.TBN = CalcTangentBasis(output.WorldNormal, output.WorldTangent); - result.TwoSidedSign = WorldDeterminantSign; - result.SvPosition = output.Position; - result.PreSkinnedPosition = input.Position.xyz; - result.PreSkinnedNormal = localNormal; -#if USE_INSTANCING - result.InstanceOrigin = input.InstanceOrigin.xyz; - result.InstanceParams = float2(input.InstanceOrigin.w, input.InstanceTransform1.w); - result.InstanceTransform1 = input.InstanceTransform1.xyz; - result.InstanceTransform2 = input.InstanceTransform2.xyz; - result.InstanceTransform3 = input.InstanceTransform3.xyz; -#else - result.InstanceOrigin = WorldMatrix[3].xyz; - result.InstanceParams = float2(PerInstanceRandom, LODDitherFactor); -#endif - return result; -} - -MaterialInput GetMaterialInput(VertexOutput output, float3 localPosition, float3 localNormal) -{ - MaterialInput result = (MaterialInput)0; - result.WorldPosition = output.WorldPosition; - result.TexCoord = output.TexCoord; -#if USE_LIGHTMAP - result.LightmapUV = output.LightmapUV; -#endif -#if USE_VERTEX_COLOR - result.VertexColor = output.VertexColor; -#endif - result.TBN = CalcTangentBasis(output.WorldNormal, output.WorldTangent); - result.TwoSidedSign = WorldDeterminantSign; - result.InstanceOrigin = WorldMatrix[3].xyz; - result.InstanceParams = float2(PerInstanceRandom, LODDitherFactor); - result.SvPosition = output.Position; - result.PreSkinnedPosition = localPosition; - result.PreSkinnedNormal = localNormal; - return result; -} - -MaterialInput GetMaterialInput(PixelInput input) -{ - MaterialInput result = (MaterialInput)0; - result.WorldPosition = input.WorldPosition; - result.TexCoord = input.TexCoord; -#if USE_LIGHTMAP - result.LightmapUV = input.LightmapUV; -#endif -#if USE_VERTEX_COLOR - result.VertexColor = input.VertexColor; -#endif - result.TBN = CalcTangentBasis(input.WorldNormal, input.WorldTangent); - result.TwoSidedSign = WorldDeterminantSign * (input.IsFrontFace ? 1.0 : -1.0); - result.InstanceOrigin = input.InstanceOrigin; - result.InstanceParams = input.InstanceParams; - result.SvPosition = input.Position; -#if USE_CUSTOM_VERTEX_INTERPOLATORS - result.CustomVSToPS = input.CustomVSToPS; -#endif - return result; -} - -#if USE_INSTANCING -#define INSTANCE_TRANS_WORLD float4x4(float4(input.InstanceTransform1.xyz, 0.0f), float4(input.InstanceTransform2.xyz, 0.0f), float4(input.InstanceTransform3.xyz, 0.0f), float4(input.InstanceOrigin.xyz, 1.0f)) -#else -#define INSTANCE_TRANS_WORLD WorldMatrix -#endif - -// Gets the local to world transform matrix (supports instancing) -float4x4 GetInstanceTransform(ModelInput input) -{ - return INSTANCE_TRANS_WORLD; -} -float4x4 GetInstanceTransform(ModelInput_Skinned input) -{ - return INSTANCE_TRANS_WORLD; -} -float4x4 GetInstanceTransform(ModelInput_PosOnly input) -{ - return INSTANCE_TRANS_WORLD; -} -float4x4 GetInstanceTransform(MaterialInput input) -{ - return INSTANCE_TRANS_WORLD; -} - -// Removes the scale vector from the local to world transformation matrix (supports instancing) -float3x3 RemoveScaleFromLocalToWorld(float3x3 localToWorld) -{ -#if USE_INSTANCING - // Extract per axis scales from localToWorld transform - float scaleX = length(localToWorld[0]); - float scaleY = length(localToWorld[1]); - float scaleZ = length(localToWorld[2]); - float3 invScale = float3( - scaleX > 0.00001f ? 1.0f / scaleX : 0.0f, - scaleY > 0.00001f ? 1.0f / scaleY : 0.0f, - scaleZ > 0.00001f ? 1.0f / scaleZ : 0.0f); -#else - float3 invScale = WorldInvScale; -#endif - localToWorld[0] *= invScale.x; - localToWorld[1] *= invScale.y; - localToWorld[2] *= invScale.z; - return localToWorld; -} - -// Transforms a vector from tangent space to world space -float3 TransformTangentVectorToWorld(MaterialInput input, float3 tangentVector) -{ - return mul(tangentVector, input.TBN); -} - -// Transforms a vector from world space to tangent space -float3 TransformWorldVectorToTangent(MaterialInput input, float3 worldVector) -{ - return mul(input.TBN, worldVector); -} - -// Transforms a vector from world space to view space -float3 TransformWorldVectorToView(MaterialInput input, float3 worldVector) -{ - return mul(worldVector, (float3x3)ViewMatrix); -} - -// Transforms a vector from view space to world space -float3 TransformViewVectorToWorld(MaterialInput input, float3 viewVector) -{ - return mul((float3x3)ViewMatrix, viewVector); -} - -// Transforms a vector from local space to world space -float3 TransformLocalVectorToWorld(MaterialInput input, float3 localVector) -{ - float3x3 localToWorld = (float3x3)GetInstanceTransform(input); - //localToWorld = RemoveScaleFromLocalToWorld(localToWorld); - return mul(localVector, localToWorld); -} - -// Transforms a vector from local space to world space -float3 TransformWorldVectorToLocal(MaterialInput input, float3 worldVector) -{ - float3x3 localToWorld = (float3x3)GetInstanceTransform(input); - //localToWorld = RemoveScaleFromLocalToWorld(localToWorld); - return mul(localToWorld, worldVector); -} - -// Gets the current object position (supports instancing) -float3 GetObjectPosition(MaterialInput input) -{ - return input.InstanceOrigin.xyz; -} - -// Gets the current object size (supports instancing) -float3 GetObjectSize(MaterialInput input) -{ - float4x4 world = GetInstanceTransform(input); - return GeometrySize * float3(world._m00, world._m11, world._m22); -} - -// Get the current object random value (supports instancing) -float GetPerInstanceRandom(MaterialInput input) -{ - return input.InstanceParams.x; -} - -// Get the current object LOD transition dither factor (supports instancing) -float GetLODDitherFactor(MaterialInput input) -{ -#if USE_DITHERED_LOD_TRANSITION - return input.InstanceParams.y; -#else - return 0; -#endif -} - -// Gets the interpolated vertex color (in linear space) -float4 GetVertexColor(MaterialInput input) -{ -#if USE_VERTEX_COLOR - return input.VertexColor; -#else - return 1; -#endif -} - -// Get material properties function (for vertex shader) -Material GetMaterialVS(MaterialInput input) -{ -@5 -} - -// Get material properties function (for domain shader) -Material GetMaterialDS(MaterialInput input) -{ -@6 -} - -// Get material properties function (for pixel shader) -Material GetMaterialPS(MaterialInput input) -{ -@4 -} - -// Fix line for errors/warnings for shader code from template -#line 1000 - -// Calculates the transform matrix from mesh tangent space to local space -float3x3 CalcTangentToLocal(ModelInput input) -{ - float bitangentSign = input.Tangent.w ? -1.0f : +1.0f; - float3 normal = input.Normal.xyz * 2.0 - 1.0; - float3 tangent = input.Tangent.xyz * 2.0 - 1.0; - float3 bitangent = cross(normal, tangent) * bitangentSign; - return float3x3(tangent, bitangent, normal); -} - -float3x3 CalcTangentToWorld(float4x4 world, float3x3 tangentToLocal) -{ - float3x3 localToWorld = RemoveScaleFromLocalToWorld((float3x3)world); - return mul(tangentToLocal, localToWorld); -} - -// Vertex Shader function for GBuffer Pass and Depth Pass (with full vertex data) -META_VS(IS_SURFACE, FEATURE_LEVEL_ES2) -META_PERMUTATION_1(USE_INSTANCING=0) -META_PERMUTATION_1(USE_INSTANCING=1) -META_PERMUTATION_2(USE_INSTANCING=0, IS_MOTION_VECTORS_PASS=1) -META_VS_IN_ELEMENT(POSITION, 0, R32G32B32_FLOAT, 0, 0, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(TEXCOORD, 0, R16G16_FLOAT, 1, 0, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(NORMAL, 0, R10G10B10A2_UNORM, 1, ALIGN, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(TANGENT, 0, R10G10B10A2_UNORM, 1, ALIGN, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(TEXCOORD, 1, R16G16_FLOAT, 1, ALIGN, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(COLOR, 0, R8G8B8A8_UNORM, 2, 0, PER_VERTEX, 0, USE_VERTEX_COLOR) -META_VS_IN_ELEMENT(ATTRIBUTE,0, R32G32B32A32_FLOAT,3, 0, PER_INSTANCE, 1, USE_INSTANCING) -META_VS_IN_ELEMENT(ATTRIBUTE,1, R32G32B32A32_FLOAT,3, ALIGN, PER_INSTANCE, 1, USE_INSTANCING) -META_VS_IN_ELEMENT(ATTRIBUTE,2, R32G32B32_FLOAT, 3, ALIGN, PER_INSTANCE, 1, USE_INSTANCING) -META_VS_IN_ELEMENT(ATTRIBUTE,3, R32G32B32_FLOAT, 3, ALIGN, PER_INSTANCE, 1, USE_INSTANCING) -META_VS_IN_ELEMENT(ATTRIBUTE,4, R16G16B16A16_FLOAT,3, ALIGN, PER_INSTANCE, 1, USE_INSTANCING) -VertexOutput VS(ModelInput input) -{ - VertexOutput output; - - // Compute world space vertex position - float4x4 world = GetInstanceTransform(input); - output.WorldPosition = mul(float4(input.Position.xyz, 1), world).xyz; -#if IS_MOTION_VECTORS_PASS - output.PrevWorldPosition = mul(float4(input.Position.xyz, 1), PrevWorldMatrix).xyz; -#endif - - // Compute clip space position - output.Position = mul(float4(output.WorldPosition.xyz, 1), ViewProjectionMatrix); - - // Pass vertex attributes - output.TexCoord = input.TexCoord; -#if USE_VERTEX_COLOR - output.VertexColor = input.Color; -#endif - output.InstanceOrigin = world[3].xyz; -#if USE_INSTANCING - output.LightmapUV = input.LightmapUV * input.InstanceLightmapArea.zw + input.InstanceLightmapArea.xy; - output.InstanceParams = float2(input.InstanceOrigin.w, input.InstanceTransform1.w); -#else - output.LightmapUV = input.LightmapUV * LightmapArea.zw + LightmapArea.xy; - output.InstanceParams = float2(PerInstanceRandom, LODDitherFactor); -#endif - - // Calculate tanget space to world space transformation matrix for unit vectors - float3x3 tangentToLocal = CalcTangentToLocal(input); - float3x3 tangentToWorld = CalcTangentToWorld(world, tangentToLocal); - output.WorldNormal = tangentToWorld[2]; - output.WorldTangent.xyz = tangentToWorld[0]; - output.WorldTangent.w = input.Tangent.w ? -1.0f : +1.0f; - - // Get material input params if need to evaluate any material property -#if USE_POSITION_OFFSET || USE_TESSELLATION || USE_CUSTOM_VERTEX_INTERPOLATORS - MaterialInput materialInput = GetMaterialInput(input, output, tangentToLocal[2].xyz); - Material material = GetMaterialVS(materialInput); -#endif - - // Apply world position offset per-vertex -#if USE_POSITION_OFFSET - output.WorldPosition += material.PositionOffset; - output.Position = mul(float4(output.WorldPosition.xyz, 1), ViewProjectionMatrix); -#endif - - // Get tessalation multiplier (per vertex) -#if USE_TESSELLATION - output.TessellationMultiplier = material.TessellationMultiplier; -#endif - - // Copy interpolants for other shader stages -#if USE_CUSTOM_VERTEX_INTERPOLATORS - output.CustomVSToPS = material.CustomVSToPS; -#endif - - return output; -} - -// Vertex Shader function for Depth Pass -META_VS(IS_SURFACE, FEATURE_LEVEL_ES2) -META_PERMUTATION_1(USE_INSTANCING=0) -META_PERMUTATION_1(USE_INSTANCING=1) -META_VS_IN_ELEMENT(POSITION, 0, R32G32B32_FLOAT, 0, 0, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(ATTRIBUTE,0, R32G32B32A32_FLOAT,3, 0, PER_INSTANCE, 1, USE_INSTANCING) -META_VS_IN_ELEMENT(ATTRIBUTE,1, R32G32B32A32_FLOAT,3, ALIGN, PER_INSTANCE, 1, USE_INSTANCING) -META_VS_IN_ELEMENT(ATTRIBUTE,2, R32G32B32_FLOAT, 3, ALIGN, PER_INSTANCE, 1, USE_INSTANCING) -META_VS_IN_ELEMENT(ATTRIBUTE,3, R32G32B32_FLOAT, 3, ALIGN, PER_INSTANCE, 1, USE_INSTANCING) -META_VS_IN_ELEMENT(ATTRIBUTE,4, R16G16B16A16_FLOAT,3, ALIGN, PER_INSTANCE, 1, USE_INSTANCING) -float4 VS_Depth(ModelInput_PosOnly input) : SV_Position -{ - float4x4 world = GetInstanceTransform(input); - float3 worldPosition = mul(float4(input.Position.xyz, 1), world).xyz; - float4 position = mul(float4(worldPosition.xyz, 1), ViewProjectionMatrix); - return position; -} - -#if USE_SKINNING - -// The skeletal bones matrix buffer (stored as 4x3, 3 float4 behind each other) -Buffer BoneMatrices : register(t0); - -#if PER_BONE_MOTION_BLUR - -// The skeletal bones matrix buffer from the previous frame -Buffer PrevBoneMatrices : register(t1); - -float3x4 GetPrevBoneMatrix(int index) -{ - float4 a = PrevBoneMatrices[index * 3]; - float4 b = PrevBoneMatrices[index * 3 + 1]; - float4 c = PrevBoneMatrices[index * 3 + 2]; - return float3x4(a, b, c); -} - -float3 SkinPrevPosition(ModelInput_Skinned input) -{ - float4 position = float4(input.Position.xyz, 1); - float3x4 boneMatrix = input.BlendWeights.x * GetPrevBoneMatrix(input.BlendIndices.x); - boneMatrix += input.BlendWeights.y * GetPrevBoneMatrix(input.BlendIndices.y); - boneMatrix += input.BlendWeights.z * GetPrevBoneMatrix(input.BlendIndices.z); - boneMatrix += input.BlendWeights.w * GetPrevBoneMatrix(input.BlendIndices.w); - return mul(boneMatrix, position); -} - -#endif - -// Cached skinning data to avoid multiple calculation -struct SkinningData -{ - float3x4 BlendMatrix; -}; - -// Calculates the transposed transform matrix for the given bone index -float3x4 GetBoneMatrix(int index) -{ - float4 a = BoneMatrices[index * 3]; - float4 b = BoneMatrices[index * 3 + 1]; - float4 c = BoneMatrices[index * 3 + 2]; - return float3x4(a, b, c); -} - -// Calculates the transposed transform matrix for the given vertex (uses blending) -float3x4 GetBoneMatrix(ModelInput_Skinned input) -{ - float3x4 boneMatrix = input.BlendWeights.x * GetBoneMatrix(input.BlendIndices.x); - boneMatrix += input.BlendWeights.y * GetBoneMatrix(input.BlendIndices.y); - boneMatrix += input.BlendWeights.z * GetBoneMatrix(input.BlendIndices.z); - boneMatrix += input.BlendWeights.w * GetBoneMatrix(input.BlendIndices.w); - return boneMatrix; -} - -// Transforms the vertex position by weighted sum of the skinning matrices -float3 SkinPosition(ModelInput_Skinned input, SkinningData data) -{ - float4 position = float4(input.Position.xyz, 1); - return mul(data.BlendMatrix, position); -} - -// Transforms the vertex position by weighted sum of the skinning matrices -float3x3 SkinTangents(ModelInput_Skinned input, SkinningData data) -{ - // Unpack vertex tangent frame - float bitangentSign = input.Tangent.w ? -1.0f : +1.0f; - float3 normal = input.Normal.xyz * 2.0 - 1.0; - float3 tangent = input.Tangent.xyz * 2.0 - 1.0; - - // Apply skinning - tangent = mul(data.BlendMatrix, float4(tangent, 0)); - normal = mul(data.BlendMatrix, float4(normal, 0)); - - float3 bitangent = cross(normal, tangent) * bitangentSign; - return float3x3(tangent, bitangent, normal); -} - -// Vertex Shader function for GBuffers/Depth Pass (skinned mesh rendering) -META_VS(IS_SURFACE, FEATURE_LEVEL_ES2) -META_PERMUTATION_1(USE_SKINNING=1) -META_PERMUTATION_2(USE_SKINNING=1, IS_MOTION_VECTORS_PASS=1) -META_PERMUTATION_3(USE_SKINNING=1, IS_MOTION_VECTORS_PASS=1, PER_BONE_MOTION_BLUR=1) -META_VS_IN_ELEMENT(POSITION, 0, R32G32B32_FLOAT, 0, 0, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(TEXCOORD, 0, R16G16_FLOAT, 0, ALIGN, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(NORMAL, 0, R10G10B10A2_UNORM, 0, ALIGN, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(TANGENT, 0, R10G10B10A2_UNORM, 0, ALIGN, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(BLENDINDICES, 0, R8G8B8A8_UINT, 0, ALIGN, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(BLENDWEIGHT, 0, R16G16B16A16_FLOAT,0, ALIGN, PER_VERTEX, 0, true) -VertexOutput VS_Skinned(ModelInput_Skinned input) -{ - VertexOutput output; - - // Perform skinning - SkinningData data; - data.BlendMatrix = GetBoneMatrix(input); - float3 position = SkinPosition(input, data); - float3x3 tangentToLocal = SkinTangents(input, data); - - // Compute world space vertex position - float4x4 world = GetInstanceTransform(input); - output.WorldPosition = mul(float4(position, 1), world).xyz; -#if IS_MOTION_VECTORS_PASS -#if PER_BONE_MOTION_BLUR - float3 prevPosition = SkinPrevPosition(input); - output.PrevWorldPosition = mul(float4(prevPosition, 1), PrevWorldMatrix).xyz; -#else - output.PrevWorldPosition = mul(float4(position, 1), PrevWorldMatrix).xyz; -#endif -#endif - - // Compute clip space position - output.Position = mul(float4(output.WorldPosition.xyz, 1), ViewProjectionMatrix); - - // Pass vertex attributes - output.TexCoord = input.TexCoord; -#if USE_VERTEX_COLOR - output.VertexColor = float4(0, 0, 0, 1); -#endif - output.LightmapUV = float2(0, 0); - output.InstanceOrigin = world[3].xyz; -#if USE_INSTANCING - output.InstanceParams = float2(input.InstanceOrigin.w, input.InstanceTransform1.w); -#else - output.InstanceParams = float2(PerInstanceRandom, LODDitherFactor); -#endif - - // Calculate tanget space to world space transformation matrix for unit vectors - float3x3 tangentToWorld = CalcTangentToWorld(world, tangentToLocal); - output.WorldNormal = tangentToWorld[2]; - output.WorldTangent.xyz = tangentToWorld[0]; - output.WorldTangent.w = input.Tangent.w ? -1.0f : +1.0f; - - // Get material input params if need to evaluate any material property -#if USE_POSITION_OFFSET || USE_TESSELLATION || USE_CUSTOM_VERTEX_INTERPOLATORS - MaterialInput materialInput = GetMaterialInput(output, input.Position.xyz, tangentToLocal[2].xyz); - Material material = GetMaterialVS(materialInput); -#endif - - // Apply world position offset per-vertex -#if USE_POSITION_OFFSET - output.WorldPosition += material.PositionOffset; - output.Position = mul(float4(output.WorldPosition.xyz, 1), ViewProjectionMatrix); -#endif - - // Get tessalation multiplier (per vertex) -#if USE_TESSELLATION - output.TessellationMultiplier = material.TessellationMultiplier; -#endif - - // Copy interpolants for other shader stages -#if USE_CUSTOM_VERTEX_INTERPOLATORS - output.CustomVSToPS = material.CustomVSToPS; -#endif - - return output; -} - -#endif - -#if USE_TESSELLATION - -// Interpolants passed from the hull shader to the domain shader -struct TessalationHSToDS -{ - float4 Position : SV_Position; - float3 WorldPosition : TEXCOORD0; - float2 TexCoord : TEXCOORD1; - float2 LightmapUV : TEXCOORD2; -#if USE_VERTEX_COLOR - half4 VertexColor : COLOR; -#endif - float3 WorldNormal : TEXCOORD3; - float4 WorldTangent : TEXCOORD4; -#if USE_CUSTOM_VERTEX_INTERPOLATORS - float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; -#endif - float3 InstanceOrigin : TEXCOORD6; - float2 InstanceParams : TEXCOORD7; -#if IS_MOTION_VECTORS_PASS - float3 PrevWorldPosition : TEXCOORD8; -#endif - float TessellationMultiplier : TESS; -}; - -// Interpolants passed from the domain shader and to the pixel shader -struct TessalationDSToPS -{ - float4 Position : SV_Position; - float3 WorldPosition : TEXCOORD0; - float2 TexCoord : TEXCOORD1; - float2 LightmapUV : TEXCOORD2; -#if USE_VERTEX_COLOR - half4 VertexColor : COLOR; -#endif - float3 WorldNormal : TEXCOORD3; - float4 WorldTangent : TEXCOORD4; -#if USE_CUSTOM_VERTEX_INTERPOLATORS - float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; -#endif - float3 InstanceOrigin : TEXCOORD6; - float2 InstanceParams : TEXCOORD7; -#if IS_MOTION_VECTORS_PASS - float3 PrevWorldPosition : TEXCOORD8; -#endif -}; - -MaterialInput GetMaterialInput(TessalationDSToPS input) -{ - MaterialInput result = (MaterialInput)0; - result.WorldPosition = input.WorldPosition; - result.TexCoord = input.TexCoord; -#if USE_LIGHTMAP - result.LightmapUV = input.LightmapUV; -#endif -#if USE_VERTEX_COLOR - result.VertexColor = input.VertexColor; -#endif - result.TBN = CalcTangentBasis(input.WorldNormal, input.WorldTangent); - result.TwoSidedSign = WorldDeterminantSign; - result.InstanceOrigin = input.InstanceOrigin; - result.InstanceParams = input.InstanceParams; - result.SvPosition = input.Position; -#if USE_CUSTOM_VERTEX_INTERPOLATORS - result.CustomVSToPS = input.CustomVSToPS; -#endif - return result; -} - -struct TessalationPatch -{ - float EdgeTessFactor[3] : SV_TessFactor; - float InsideTessFactor : SV_InsideTessFactor; -#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PN - float3 B210 : POSITION4; - float3 B120 : POSITION5; - float3 B021 : POSITION6; - float3 B012 : POSITION7; - float3 B102 : POSITION8; - float3 B201 : POSITION9; - float3 B111 : CENTER; -#endif -}; - -TessalationPatch HS_PatchConstant(InputPatch input) -{ - TessalationPatch output; - - // Average tess factors along edges, and pick an edge tess factor for the interior tessellation - float4 tessellationMultipliers; - tessellationMultipliers.x = 0.5f * (input[1].TessellationMultiplier + input[2].TessellationMultiplier); - tessellationMultipliers.y = 0.5f * (input[2].TessellationMultiplier + input[0].TessellationMultiplier); - tessellationMultipliers.z = 0.5f * (input[0].TessellationMultiplier + input[1].TessellationMultiplier); - tessellationMultipliers.w = 0.333f * (input[0].TessellationMultiplier + input[1].TessellationMultiplier + input[2].TessellationMultiplier); - tessellationMultipliers = clamp(tessellationMultipliers, 1, MAX_TESSELLATION_FACTOR); - - output.EdgeTessFactor[0] = tessellationMultipliers.x; // 1->2 edge - output.EdgeTessFactor[1] = tessellationMultipliers.y; // 2->0 edge - output.EdgeTessFactor[2] = tessellationMultipliers.z; // 0->1 edge - output.InsideTessFactor = tessellationMultipliers.w; - -#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PN - // Calculate PN-Triangle coefficients - // Refer to Vlachos 2001 for the original formula - float3 p1 = input[0].WorldPosition; - float3 p2 = input[1].WorldPosition; - float3 p3 = input[2].WorldPosition; - float3 n1 = input[0].WorldNormal; - float3 n2 = input[1].WorldNormal; - float3 n3 = input[2].WorldNormal; - - // Calculate control points - output.B210 = (2.0f * p1 + p2 - dot((p2 - p1), n1) * n1) / 3.0f; - output.B120 = (2.0f * p2 + p1 - dot((p1 - p2), n2) * n2) / 3.0f; - output.B021 = (2.0f * p2 + p3 - dot((p3 - p2), n2) * n2) / 3.0f; - output.B012 = (2.0f * p3 + p2 - dot((p2 - p3), n3) * n3) / 3.0f; - output.B102 = (2.0f * p3 + p1 - dot((p1 - p3), n3) * n3) / 3.0f; - output.B201 = (2.0f * p1 + p3 - dot((p3 - p1), n1) * n1) / 3.0f; - float3 e = (output.B210 + output.B120 + output.B021 + - output.B012 + output.B102 + output.B201) / 6.0f; - float3 v = (p1 + p2 + p3) / 3.0f; - output.B111 = e + ((e - v) / 2.0f); -#endif - - return output; -} - -META_HS(USE_TESSELLATION, FEATURE_LEVEL_SM5) -META_PERMUTATION_1(IS_MOTION_VECTORS_PASS=0) -META_PERMUTATION_1(IS_MOTION_VECTORS_PASS=1) -META_HS_PATCH(TESSELLATION_IN_CONTROL_POINTS) -[domain("tri")] -[partitioning("fractional_odd")] -[outputtopology("triangle_cw")] -[maxtessfactor(MAX_TESSELLATION_FACTOR)] -[outputcontrolpoints(3)] -[patchconstantfunc("HS_PatchConstant")] -TessalationHSToDS HS(InputPatch input, uint ControlPointID : SV_OutputControlPointID) -{ - TessalationHSToDS output; - - // Pass through shader -#define COPY(thing) output.thing = input[ControlPointID].thing; - COPY(Position); - COPY(WorldPosition); - COPY(TexCoord); - COPY(LightmapUV); -#if USE_VERTEX_COLOR - COPY(VertexColor); -#endif - COPY(WorldNormal); - COPY(WorldTangent); - COPY(InstanceOrigin); - COPY(InstanceParams); -#if IS_MOTION_VECTORS_PASS - COPY(PrevWorldPosition); -#endif - COPY(TessellationMultiplier); -#if USE_CUSTOM_VERTEX_INTERPOLATORS - COPY(CustomVSToPS); -#endif -#undef COPY - - return output; -} - -#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PHONG - -// Orthogonal projection on to plane -float3 ProjectOntoPlane(float3 planeNormal, float3 planePoint, float3 pointToProject) -{ - return pointToProject - dot(pointToProject-planePoint, planeNormal) * planeNormal; -} - -#endif - -META_DS(USE_TESSELLATION, FEATURE_LEVEL_SM5) -META_PERMUTATION_1(IS_MOTION_VECTORS_PASS=0) -META_PERMUTATION_1(IS_MOTION_VECTORS_PASS=1) -[domain("tri")] -TessalationDSToPS DS(TessalationPatch constantData, float3 barycentricCoords : SV_DomainLocation, const OutputPatch input) -{ - TessalationDSToPS output; - - // Get the barycentric coords - float U = barycentricCoords.x; - float V = barycentricCoords.y; - float W = barycentricCoords.z; - - // Interpolate patch attributes to generated vertices -#define INTERPOLATE(thing) output.thing = U * input[0].thing + V * input[1].thing + W * input[2].thing -#define COPY(thing) output.thing = input[0].thing - INTERPOLATE(Position); -#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PN - float UU = U * U; - float VV = V * V; - float WW = W * W; - float UU3 = UU * 3.0f; - float VV3 = VV * 3.0f; - float WW3 = WW * 3.0f; - - // Interpolate using barycentric coordinates and PN Triangle control points - output.WorldPosition = - input[0].WorldPosition * UU * U + - input[1].WorldPosition * VV * V + - input[2].WorldPosition * WW * W + - constantData.B210 * UU3 * V + - constantData.B120 * VV3 * U + - constantData.B021 * VV3 * W + - constantData.B012 * WW3 * V + - constantData.B102 * WW3 * U + - constantData.B201 * UU3 * W + - constantData.B111 * 6.0f * W * U * V; -#if IS_MOTION_VECTORS_PASS - output.PrevWorldPosition = - input[0].PrevWorldPosition * UU * U + - input[1].PrevWorldPosition * VV * V + - input[2].PrevWorldPosition * WW * W + - constantData.B210 * UU3 * V + - constantData.B120 * VV3 * U + - constantData.B021 * VV3 * W + - constantData.B012 * WW3 * V + - constantData.B102 * WW3 * U + - constantData.B201 * UU3 * W + - constantData.B111 * 6.0f * W * U * V; -#endif -#else - INTERPOLATE(WorldPosition); -#if IS_MOTION_VECTORS_PASS - INTERPOLATE(PrevWorldPosition); -#endif -#endif - INTERPOLATE(TexCoord); - INTERPOLATE(LightmapUV); -#if USE_VERTEX_COLOR - INTERPOLATE(VertexColor); -#endif - INTERPOLATE(WorldNormal); - INTERPOLATE(WorldTangent); - COPY(InstanceOrigin); - COPY(InstanceParams); -#if USE_CUSTOM_VERTEX_INTERPOLATORS - UNROLL - for (int i = 0; i < CUSTOM_VERTEX_INTERPOLATORS_COUNT; i++) - { - INTERPOLATE(CustomVSToPS[i]); - } -#endif -#undef INTERPOLATE -#undef COPY - - // Interpolating tangents can unnormalize it, so normalize it - output.WorldNormal = normalize(output.WorldNormal); - output.WorldTangent.xyz = normalize(output.WorldTangent.xyz); - -#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PHONG - // Orthogonal projection in the tangent planes - float3 posProjectedU = ProjectOntoPlane(input[0].WorldNormal, input[0].WorldPosition, output.WorldPosition); - float3 posProjectedV = ProjectOntoPlane(input[1].WorldNormal, input[1].WorldPosition, output.WorldPosition); - float3 posProjectedW = ProjectOntoPlane(input[2].WorldNormal, input[2].WorldPosition, output.WorldPosition); - - // Interpolate the projected points - output.WorldPosition = U * posProjectedU + V * posProjectedV + W * posProjectedW; -#if IS_MOTION_VECTORS_PASS - posProjectedU = ProjectOntoPlane(input[0].WorldNormal, input[0].PrevWorldPosition, output.PrevWorldPosition); - posProjectedV = ProjectOntoPlane(input[1].WorldNormal, input[1].PrevWorldPosition, output.PrevWorldPosition); - posProjectedW = ProjectOntoPlane(input[2].WorldNormal, input[2].PrevWorldPosition, output.PrevWorldPosition); - output.PrevWorldPosition = U * posProjectedU + V * posProjectedV + W * posProjectedW; -#endif -#endif - - // Perform displacement mapping -#if USE_DISPLACEMENT - MaterialInput materialInput = GetMaterialInput(output); - Material material = GetMaterialDS(materialInput); - output.WorldPosition += material.WorldDisplacement; -#if IS_MOTION_VECTORS_PASS - output.PrevWorldPosition += material.WorldDisplacement; -#endif -#endif - - // Recalculate the clip space position - output.Position = mul(float4(output.WorldPosition, 1), ViewProjectionMatrix); - - return output; -} - -#endif - -#if USE_LIGHTMAP - -float3 SampleLightmap(Material material, MaterialInput materialInput) -{ - // Sample lightmaps - float4 lightmap0 = Lightmap0.Sample(SamplerLinearClamp, materialInput.LightmapUV); - float4 lightmap1 = Lightmap1.Sample(SamplerLinearClamp, materialInput.LightmapUV); - float4 lightmap2 = Lightmap2.Sample(SamplerLinearClamp, materialInput.LightmapUV); - - // Unpack H-basis - float3 h0 = float3(lightmap0.x, lightmap1.x, lightmap2.x); - float3 h1 = float3(lightmap0.y, lightmap1.y, lightmap2.y); - float3 h2 = float3(lightmap0.z, lightmap1.z, lightmap2.z); - float3 h3 = float3(lightmap0.w, lightmap1.w, lightmap2.w); - - // Sample baked diffuse irradiance from the H-basis coefficients - float3 normal = material.TangentNormal; -#if MATERIAL_SHADING_MODEL == SHADING_MODEL_FOLIAGE - normal *= material.TangentNormal; -#endif - return GetHBasisIrradiance(normal, h0, h1, h2, h3) / PI; -} - -#endif - -#if USE_DITHERED_LOD_TRANSITION - -void ClipLODTransition(PixelInput input) -{ - float ditherFactor = input.InstanceParams.y; - if (abs(ditherFactor) > 0.001) - { - float randGrid = cos(dot(floor(input.Position.xy), float2(347.83452793, 3343.28371863))); - float randGridFrac = frac(randGrid * 1000.0); - half mask = (ditherFactor < 0.0) ? (ditherFactor + 1.0 > randGridFrac) : (ditherFactor < randGridFrac); - clip(mask - 0.001); - } -} - -#else - -void ClipLODTransition(PixelInput input) -{ -} - -#endif - -// Pixel Shader function for GBuffer Pass -META_PS(true, FEATURE_LEVEL_ES2) -META_PERMUTATION_1(USE_LIGHTMAP=0) -META_PERMUTATION_1(USE_LIGHTMAP=1) -void PS_GBuffer( - in PixelInput input - ,out float4 Light : SV_Target0 -#if MATERIAL_DOMAIN == MATERIAL_DOMAIN_SURFACE - // GBuffer - ,out float4 RT0 : SV_Target1 - ,out float4 RT1 : SV_Target2 - ,out float4 RT2 : SV_Target3 -#if USE_GBUFFER_CUSTOM_DATA - ,out float4 RT3 : SV_Target4 -#endif -#endif - ) -{ - Light = 0; - - // LOD masking - ClipLODTransition(input); - - // Get material parameters - MaterialInput materialInput = GetMaterialInput(input); - Material material = GetMaterialPS(materialInput); - -#if MATERIAL_DOMAIN == MATERIAL_DOMAIN_SURFACE - - // Masking -#if MATERIAL_MASKED - clip(material.Mask - MATERIAL_MASK_THRESHOLD); -#endif - -#if USE_LIGHTMAP - - float3 diffuseColor = GetDiffuseColor(material.Color, material.Metalness); - float3 specularColor = GetSpecularColor(material.Color, material.Specular, material.Metalness); - - // Sample lightmap - float3 diffuseIndirectLighting = SampleLightmap(material, materialInput); - - // Apply static indirect light - Light.rgb = diffuseColor * diffuseIndirectLighting * AOMultiBounce(material.AO, diffuseColor); - -#endif - - // Pack material properties to GBuffer - RT0 = float4(material.Color, material.AO); - RT1 = float4(material.WorldNormal * 0.5 + 0.5, MATERIAL_SHADING_MODEL * (1.0 / 3.0)); - RT2 = float4(material.Roughness, material.Metalness, material.Specular, 0); - - // Custom data -#if USE_GBUFFER_CUSTOM_DATA -#if MATERIAL_SHADING_MODEL == SHADING_MODEL_SUBSURFACE - RT3 = float4(material.SubsurfaceColor, material.Opacity); -#elif MATERIAL_SHADING_MODEL == SHADING_MODEL_FOLIAGE - RT3 = float4(material.SubsurfaceColor, material.Opacity); -#else - RT3 = float4(0, 0, 0, 0); -#endif -#endif - -#endif - - // Add light emission -#if USE_EMISSIVE - Light.rgb += material.Emissive; -#endif -} - -// Pixel Shader function for Depth Pass -META_PS(IS_SURFACE, FEATURE_LEVEL_ES2) -void PS_Depth(PixelInput input -#if GLSL - , out float4 OutColor : SV_Target0 -#endif - ) -{ - // LOD masking - ClipLODTransition(input); - -#if MATERIAL_MASKED - // Perform per pixel clipping if material requries it - MaterialInput materialInput = GetMaterialInput(input); - Material material = GetMaterialPS(materialInput); - clip(material.Mask - MATERIAL_MASK_THRESHOLD); -#endif - -#if GLSL - OutColor = 0; -#endif -} - -// Pixel Shader function for Motion Vectors Pass -META_PS(true, FEATURE_LEVEL_ES2) -META_PERMUTATION_1(IS_MOTION_VECTORS_PASS=1) -float4 PS_MotionVectors(PixelInput input) : SV_Target0 -{ -#if IS_MOTION_VECTORS_PASS - // LOD masking - ClipLODTransition(input); - -#if MATERIAL_MASKED - // Perform per pixel clipping if material requries it - MaterialInput materialInput = GetMaterialInput(input); - Material material = GetMaterialPS(materialInput); - clip(material.Mask - MATERIAL_MASK_THRESHOLD); -#endif - - // Calculate this and previosu frame pixel locations in clip space - float4 prevClipPos = mul(float4(input.PrevWorldPosition, 1), PrevViewProjectionMatrix); - float4 curClipPos = mul(float4(input.WorldPosition, 1), ViewProjectionMatrix); - float2 prevHPos = prevClipPos.xy / prevClipPos.w; - float2 curHPos = curClipPos.xy / curClipPos.w; - - // Revert temporal jitter offset - prevHPos -= TemporalAAJitter.zw; - curHPos -= TemporalAAJitter.xy; - - // Clip Space -> UV Space - float2 vPosPrev = prevHPos.xy * 0.5f + 0.5f; - float2 vPosCur = curHPos.xy * 0.5f + 0.5f; - vPosPrev.y = 1.0 - vPosPrev.y; - vPosCur.y = 1.0 - vPosCur.y; - - // Calculate per-pixel motion vector - return float4(vPosCur - vPosPrev, 0, 1); -#else - return float4(0, 0, 0, 1); -#endif -} diff --git a/Content/Editor/MaterialTemplates/SurfaceForward.shader b/Content/Editor/MaterialTemplates/SurfaceForward.shader deleted file mode 100644 index 7a97e5e5a..000000000 --- a/Content/Editor/MaterialTemplates/SurfaceForward.shader +++ /dev/null @@ -1,1011 +0,0 @@ -// File generated by Flax Materials Editor -// Version: @0 - -#define MATERIAL 1 -#define MAX_LOCAL_LIGHTS 4 -@3 - -#include "./Flax/Common.hlsl" -#include "./Flax/MaterialCommon.hlsl" -#include "./Flax/GBufferCommon.hlsl" -#include "./Flax/LightingCommon.hlsl" -#if USE_REFLECTIONS -#include "./Flax/ReflectionsCommon.hlsl" -#endif -#include "./Flax/Lighting.hlsl" -#include "./Flax/ShadowsSampling.hlsl" -#include "./Flax/ExponentialHeightFog.hlsl" -@7 -// Primary constant buffer (with additional material parameters) -META_CB_BEGIN(0, Data) -float4x4 ViewProjectionMatrix; -float4x4 WorldMatrix; -float4x4 ViewMatrix; -float4x4 PrevViewProjectionMatrix; -float4x4 PrevWorldMatrix; -float3 ViewPos; -float ViewFar; -float3 ViewDir; -float TimeParam; -float4 ViewInfo; -float4 ScreenSize; -float4 LightmapArea; -float3 WorldInvScale; -float WorldDeterminantSign; -float2 Dummy0; -float LODDitherFactor; -float PerInstanceRandom; -float3 GeometrySize; -float Dummy1; -@1META_CB_END - -// Secondary constantant buffer (for lighting) -META_CB_BEGIN(1, LightingData) -LightData DirectionalLight; -LightShadowData DirectionalLightShadow; -LightData SkyLight; -ProbeData EnvironmentProbe; -ExponentialHeightFogData ExponentialHeightFog; -float3 Dummy2; -uint LocalLightsCount; -LightData LocalLights[MAX_LOCAL_LIGHTS]; -META_CB_END - -DECLARE_LIGHTSHADOWDATA_ACCESS(DirectionalLightShadow); - -// Shader resources -TextureCube EnvProbe : register(t0); -TextureCube SkyLightTexture : register(t1); -Texture2DArray DirectionalLightShadowMap : register(t2); -@2 - -// Interpolants passed from the vertex shader -struct VertexOutput -{ - float4 Position : SV_Position; - float3 WorldPosition : TEXCOORD0; - float2 TexCoord : TEXCOORD1; - float2 LightmapUV : TEXCOORD2; -#if USE_VERTEX_COLOR - half4 VertexColor : COLOR; -#endif - float3x3 TBN : TEXCOORD3; -#if USE_CUSTOM_VERTEX_INTERPOLATORS - float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; -#endif - float3 InstanceOrigin : TEXCOORD6; - float2 InstanceParams : TEXCOORD7; // x-PerInstanceRandom, y-LODDitherFactor -#if USE_TESSELLATION - float TessellationMultiplier : TESS; -#endif -}; - -// Interpolants passed to the pixel shader -struct PixelInput -{ - float4 Position : SV_Position; - float3 WorldPosition : TEXCOORD0; - float2 TexCoord : TEXCOORD1; - float2 LightmapUV : TEXCOORD2; -#if USE_VERTEX_COLOR - half4 VertexColor : COLOR; -#endif - float3x3 TBN : TEXCOORD3; -#if USE_CUSTOM_VERTEX_INTERPOLATORS - float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; -#endif - float3 InstanceOrigin : TEXCOORD6; - float2 InstanceParams : TEXCOORD7; // x-PerInstanceRandom, y-LODDitherFactor - bool IsFrontFace : SV_IsFrontFace; -}; - -// Material properties generation input -struct MaterialInput -{ - float3 WorldPosition; - float TwoSidedSign; - float2 TexCoord; -#if USE_LIGHTMAP - float2 LightmapUV; -#endif -#if USE_VERTEX_COLOR - half4 VertexColor; -#endif - float3x3 TBN; - float4 SvPosition; - float3 PreSkinnedPosition; - float3 PreSkinnedNormal; - float3 InstanceOrigin; - float2 InstanceParams; -#if USE_INSTANCING - float3 InstanceTransform1; - float3 InstanceTransform2; - float3 InstanceTransform3; -#endif -#if USE_CUSTOM_VERTEX_INTERPOLATORS - float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT]; -#endif -}; - -MaterialInput GetMaterialInput(ModelInput input, VertexOutput output, float3 localNormal) -{ - MaterialInput result = (MaterialInput)0; - result.WorldPosition = output.WorldPosition; - result.TexCoord = output.TexCoord; -#if USE_LIGHTMAP - result.LightmapUV = output.LightmapUV; -#endif -#if USE_VERTEX_COLOR - result.VertexColor = output.VertexColor; -#endif - result.TBN = output.TBN; - result.TwoSidedSign = WorldDeterminantSign; - result.SvPosition = output.Position; - result.PreSkinnedPosition = input.Position.xyz; - result.PreSkinnedNormal = localNormal; -#if USE_INSTANCING - result.InstanceOrigin = input.InstanceOrigin.xyz; - result.InstanceParams = float2(input.InstanceOrigin.w, input.InstanceTransform1.w); - result.InstanceTransform1 = input.InstanceTransform1.xyz; - result.InstanceTransform2 = input.InstanceTransform2.xyz; - result.InstanceTransform3 = input.InstanceTransform3.xyz; -#else - result.InstanceOrigin = WorldMatrix[3].xyz; - result.InstanceParams = float2(PerInstanceRandom, LODDitherFactor); -#endif - return result; -} - -MaterialInput GetMaterialInput(VertexOutput output, float3 localPosition, float3 localNormal) -{ - MaterialInput result = (MaterialInput)0; - result.WorldPosition = output.WorldPosition; - result.TexCoord = output.TexCoord; -#if USE_LIGHTMAP - result.LightmapUV = output.LightmapUV; -#endif -#if USE_VERTEX_COLOR - result.VertexColor = output.VertexColor; -#endif - result.TBN = output.TBN; - result.TwoSidedSign = WorldDeterminantSign; - result.InstanceOrigin = WorldMatrix[3].xyz; - result.InstanceParams = float2(PerInstanceRandom, LODDitherFactor); - result.SvPosition = output.Position; - result.PreSkinnedPosition = localPosition; - result.PreSkinnedNormal = localNormal; - return result; -} - -MaterialInput GetMaterialInput(PixelInput input) -{ - MaterialInput result = (MaterialInput)0; - result.WorldPosition = input.WorldPosition; - result.TexCoord = input.TexCoord; -#if USE_LIGHTMAP - result.LightmapUV = input.LightmapUV; -#endif -#if USE_VERTEX_COLOR - result.VertexColor = input.VertexColor; -#endif - result.TBN = input.TBN; - result.TwoSidedSign = WorldDeterminantSign * (input.IsFrontFace ? 1.0 : -1.0); - result.InstanceOrigin = input.InstanceOrigin; - result.InstanceParams = input.InstanceParams; - result.SvPosition = input.Position; -#if USE_CUSTOM_VERTEX_INTERPOLATORS - result.CustomVSToPS = input.CustomVSToPS; -#endif - return result; -} - -#if USE_INSTANCING -#define INSTANCE_TRANS_WORLD float4x4(float4(input.InstanceTransform1.xyz, 0.0f), float4(input.InstanceTransform2.xyz, 0.0f), float4(input.InstanceTransform3.xyz, 0.0f), float4(input.InstanceOrigin.xyz, 1.0f)) -#else -#define INSTANCE_TRANS_WORLD WorldMatrix -#endif - -// Gets the local to world transform matrix (supports instancing) -float4x4 GetInstanceTransform(ModelInput input) -{ - return INSTANCE_TRANS_WORLD; -} -float4x4 GetInstanceTransform(ModelInput_Skinned input) -{ - return INSTANCE_TRANS_WORLD; -} -float4x4 GetInstanceTransform(MaterialInput input) -{ - return INSTANCE_TRANS_WORLD; -} - -// Removes the scale vector from the local to world transformation matrix (supports instancing) -float3x3 RemoveScaleFromLocalToWorld(float3x3 localToWorld) -{ -#if USE_INSTANCING - // Extract per axis scales from localToWorld transform - float scaleX = length(localToWorld[0]); - float scaleY = length(localToWorld[1]); - float scaleZ = length(localToWorld[2]); - float3 invScale = float3( - scaleX > 0.00001f ? 1.0f / scaleX : 0.0f, - scaleY > 0.00001f ? 1.0f / scaleY : 0.0f, - scaleZ > 0.00001f ? 1.0f / scaleZ : 0.0f); -#else - float3 invScale = WorldInvScale; -#endif - localToWorld[0] *= invScale.x; - localToWorld[1] *= invScale.y; - localToWorld[2] *= invScale.z; - return localToWorld; -} - -// Transforms a vector from tangent space to world space -float3 TransformTangentVectorToWorld(MaterialInput input, float3 tangentVector) -{ - return mul(tangentVector, input.TBN); -} - -// Transforms a vector from world space to tangent space -float3 TransformWorldVectorToTangent(MaterialInput input, float3 worldVector) -{ - return mul(input.TBN, worldVector); -} - -// Transforms a vector from world space to view space -float3 TransformWorldVectorToView(MaterialInput input, float3 worldVector) -{ - return mul(worldVector, (float3x3)ViewMatrix); -} - -// Transforms a vector from view space to world space -float3 TransformViewVectorToWorld(MaterialInput input, float3 viewVector) -{ - return mul((float3x3)ViewMatrix, viewVector); -} - -// Transforms a vector from local space to world space -float3 TransformLocalVectorToWorld(MaterialInput input, float3 localVector) -{ - float3x3 localToWorld = (float3x3)GetInstanceTransform(input); - //localToWorld = RemoveScaleFromLocalToWorld(localToWorld); - return mul(localVector, localToWorld); -} - -// Transforms a vector from local space to world space -float3 TransformWorldVectorToLocal(MaterialInput input, float3 worldVector) -{ - float3x3 localToWorld = (float3x3)GetInstanceTransform(input); - //localToWorld = RemoveScaleFromLocalToWorld(localToWorld); - return mul(localToWorld, worldVector); -} - -// Gets the current object position (supports instancing) -float3 GetObjectPosition(MaterialInput input) -{ - return input.InstanceOrigin.xyz; -} - -// Gets the current object size (supports instancing) -float3 GetObjectSize(MaterialInput input) -{ - float4x4 world = GetInstanceTransform(input); - return GeometrySize * float3(world._m00, world._m11, world._m22); -} - -// Get the current object random value (supports instancing) -float GetPerInstanceRandom(MaterialInput input) -{ - return input.InstanceParams.x; -} - -// Get the current object LOD transition dither factor (supports instancing) -float GetLODDitherFactor(MaterialInput input) -{ -#if USE_DITHERED_LOD_TRANSITION - return input.InstanceParams.y; -#else - return 0; -#endif -} - -// Gets the interpolated vertex color (in linear space) -float4 GetVertexColor(MaterialInput input) -{ -#if USE_VERTEX_COLOR - return input.VertexColor; -#else - return 1; -#endif -} - -// Get material properties function (for vertex shader) -Material GetMaterialVS(MaterialInput input) -{ -@5 -} - -// Get material properties function (for domain shader) -Material GetMaterialDS(MaterialInput input) -{ -@6 -} - -// Get material properties function (for pixel shader) -Material GetMaterialPS(MaterialInput input) -{ -@4 -} - -// Fix line for errors/warnings for shader code from template -#line 1000 - -// Calculates the transform matrix from mesh tangent space to local space -half3x3 CalcTangentToLocal(ModelInput input) -{ - float bitangentSign = input.Tangent.w ? -1.0f : +1.0f; - float3 normal = input.Normal.xyz * 2.0 - 1.0; - float3 tangent = input.Tangent.xyz * 2.0 - 1.0; - float3 bitangent = cross(normal, tangent) * bitangentSign; - return float3x3(tangent, bitangent, normal); -} - -half3x3 CalcTangentToWorld(in float4x4 world, in half3x3 tangentToLocal) -{ - half3x3 localToWorld = RemoveScaleFromLocalToWorld((float3x3)world); - return mul(tangentToLocal, localToWorld); -} - -// Vertex Shader function for Forward/Depth Pass -META_VS(IS_SURFACE, FEATURE_LEVEL_ES2) -META_PERMUTATION_1(USE_INSTANCING=0) -META_PERMUTATION_1(USE_INSTANCING=1) -META_VS_IN_ELEMENT(POSITION, 0, R32G32B32_FLOAT, 0, 0, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(TEXCOORD, 0, R16G16_FLOAT, 1, 0, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(NORMAL, 0, R10G10B10A2_UNORM, 1, ALIGN, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(TANGENT, 0, R10G10B10A2_UNORM, 1, ALIGN, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(TEXCOORD, 1, R16G16_FLOAT, 1, ALIGN, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(COLOR, 0, R8G8B8A8_UNORM, 2, 0, PER_VERTEX, 0, USE_VERTEX_COLOR) -META_VS_IN_ELEMENT(ATTRIBUTE,0, R32G32B32A32_FLOAT,3, 0, PER_INSTANCE, 1, USE_INSTANCING) -META_VS_IN_ELEMENT(ATTRIBUTE,1, R32G32B32A32_FLOAT,3, ALIGN, PER_INSTANCE, 1, USE_INSTANCING) -META_VS_IN_ELEMENT(ATTRIBUTE,2, R32G32B32_FLOAT, 3, ALIGN, PER_INSTANCE, 1, USE_INSTANCING) -META_VS_IN_ELEMENT(ATTRIBUTE,3, R32G32B32_FLOAT, 3, ALIGN, PER_INSTANCE, 1, USE_INSTANCING) -META_VS_IN_ELEMENT(ATTRIBUTE,4, R16G16B16A16_FLOAT,3, ALIGN, PER_INSTANCE, 1, USE_INSTANCING) -VertexOutput VS(ModelInput input) -{ - VertexOutput output; - - // Compute world space vertex position - float4x4 world = GetInstanceTransform(input); - output.WorldPosition = mul(float4(input.Position.xyz, 1), world).xyz; - - // Compute clip space position - output.Position = mul(float4(output.WorldPosition.xyz, 1), ViewProjectionMatrix); - - // Pass vertex attributes - output.TexCoord = input.TexCoord; -#if USE_VERTEX_COLOR - output.VertexColor = input.Color; -#endif -#if USE_INSTANCING - output.LightmapUV = input.LightmapUV * input.InstanceLightmapArea.zw + input.InstanceLightmapArea.xy; - output.InstanceOrigin = world[3].xyz; - output.InstanceParams = float2(input.InstanceOrigin.w, input.InstanceTransform1.w); -#else - output.LightmapUV = input.LightmapUV * LightmapArea.zw + LightmapArea.xy; - output.InstanceOrigin = WorldMatrix[3].xyz; - output.InstanceParams = float2(PerInstanceRandom, LODDitherFactor); -#endif - - // Calculate tanget space to world space transformation matrix for unit vectors - half3x3 tangentToLocal = CalcTangentToLocal(input); - half3x3 tangentToWorld = CalcTangentToWorld(world, tangentToLocal); - output.TBN = tangentToWorld; - - // Get material input params if need to evaluate any material property -#if USE_POSITION_OFFSET || USE_TESSELLATION || USE_CUSTOM_VERTEX_INTERPOLATORS - MaterialInput materialInput = GetMaterialInput(input, output, tangentToLocal[2].xyz); - Material material = GetMaterialVS(materialInput); -#endif - - // Apply world position offset per-vertex -#if USE_POSITION_OFFSET - output.WorldPosition += material.PositionOffset; - output.Position = mul(float4(output.WorldPosition.xyz, 1), ViewProjectionMatrix); -#endif - - // Get tessalation multiplier (per vertex) -#if USE_TESSELLATION - output.TessellationMultiplier = material.TessellationMultiplier; -#endif - - // Copy interpolants for other shader stages -#if USE_CUSTOM_VERTEX_INTERPOLATORS - output.CustomVSToPS = material.CustomVSToPS; -#endif - - return output; -} - -#if USE_SKINNING - -// The skeletal bones matrix buffer (stored as 4x3, 3 float4 behind each other) -Buffer BoneMatrices : register(t0); - -#if PER_BONE_MOTION_BLUR - -// The skeletal bones matrix buffer from the previous frame -Buffer PrevBoneMatrices : register(t1); - -float3x4 GetPrevBoneMatrix(int index) -{ - float4 a = PrevBoneMatrices[index * 3]; - float4 b = PrevBoneMatrices[index * 3 + 1]; - float4 c = PrevBoneMatrices[index * 3 + 2]; - return float3x4(a, b, c); -} - -float3 SkinPrevPosition(ModelInput_Skinned input) -{ - float4 position = float4(input.Position.xyz, 1); - float3x4 boneMatrix = input.BlendWeights.x * GetPrevBoneMatrix(input.BlendIndices.x); - boneMatrix += input.BlendWeights.y * GetPrevBoneMatrix(input.BlendIndices.y); - boneMatrix += input.BlendWeights.z * GetPrevBoneMatrix(input.BlendIndices.z); - boneMatrix += input.BlendWeights.w * GetPrevBoneMatrix(input.BlendIndices.w); - return mul(boneMatrix, position); -} - -#endif - -// Cached skinning data to avoid multiple calculation -struct SkinningData -{ - float3x4 BlendMatrix; -}; - -// Calculates the transposed transform matrix for the given bone index -float3x4 GetBoneMatrix(int index) -{ - float4 a = BoneMatrices[index * 3]; - float4 b = BoneMatrices[index * 3 + 1]; - float4 c = BoneMatrices[index * 3 + 2]; - return float3x4(a, b, c); -} - -// Calculates the transposed transform matrix for the given vertex (uses blending) -float3x4 GetBoneMatrix(ModelInput_Skinned input) -{ - float3x4 boneMatrix = input.BlendWeights.x * GetBoneMatrix(input.BlendIndices.x); - boneMatrix += input.BlendWeights.y * GetBoneMatrix(input.BlendIndices.y); - boneMatrix += input.BlendWeights.z * GetBoneMatrix(input.BlendIndices.z); - boneMatrix += input.BlendWeights.w * GetBoneMatrix(input.BlendIndices.w); - return boneMatrix; -} - -// Transforms the vertex position by weighted sum of the skinning matrices -float3 SkinPosition(ModelInput_Skinned input, SkinningData data) -{ - float4 position = float4(input.Position.xyz, 1); - return mul(data.BlendMatrix, position); -} - -// Transforms the vertex position by weighted sum of the skinning matrices -half3x3 SkinTangents(ModelInput_Skinned input, SkinningData data) -{ - // Unpack vertex tangent frame - float bitangentSign = input.Tangent.w ? -1.0f : +1.0f; - float3 normal = input.Normal.xyz * 2.0 - 1.0; - float3 tangent = input.Tangent.xyz * 2.0 - 1.0; - - // Apply skinning - tangent = mul(data.BlendMatrix, float4(tangent, 0)); - normal = mul(data.BlendMatrix, float4(normal, 0)); - - float3 bitangent = cross(normal, tangent) * bitangentSign; - return half3x3(tangent, bitangent, normal); -} - -// Vertex Shader function for Forward/Depth Pass (skinned mesh rendering) -META_VS(IS_SURFACE, FEATURE_LEVEL_ES2) -META_PERMUTATION_1(USE_SKINNING=1) -META_VS_IN_ELEMENT(POSITION, 0, R32G32B32_FLOAT, 0, 0, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(TEXCOORD, 0, R16G16_FLOAT, 0, ALIGN, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(NORMAL, 0, R10G10B10A2_UNORM, 0, ALIGN, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(TANGENT, 0, R10G10B10A2_UNORM, 0, ALIGN, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(BLENDINDICES, 0, R8G8B8A8_UINT, 0, ALIGN, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(BLENDWEIGHT, 0, R16G16B16A16_FLOAT,0, ALIGN, PER_VERTEX, 0, true) -VertexOutput VS_Skinned(ModelInput_Skinned input) -{ - VertexOutput output; - - // Perform skinning - SkinningData data; - data.BlendMatrix = GetBoneMatrix(input); - float3 position = SkinPosition(input, data); - half3x3 tangentToLocal = SkinTangents(input, data); - - // Compute world space vertex position - float4x4 world = GetInstanceTransform(input); - output.WorldPosition = mul(float4(position, 1), world).xyz; - - // Compute clip space position - output.Position = mul(float4(output.WorldPosition.xyz, 1), ViewProjectionMatrix); - - // Pass vertex attributes - output.TexCoord = input.TexCoord; -#if USE_VERTEX_COLOR - output.VertexColor = float4(0, 0, 0, 1); -#endif - output.LightmapUV = float2(0, 0); -#if USE_INSTANCING - output.InstanceOrigin = world[3].xyz; - output.InstanceParams = float2(input.InstanceOrigin.w, input.InstanceTransform1.w); -#else - output.InstanceOrigin = WorldMatrix[3].xyz; - output.InstanceParams = float2(PerInstanceRandom, LODDitherFactor); -#endif - - // Calculate tanget space to world space transformation matrix for unit vectors - half3x3 tangentToWorld = CalcTangentToWorld(world, tangentToLocal); - output.TBN = tangentToWorld; - - // Get material input params if need to evaluate any material property -#if USE_POSITION_OFFSET || USE_TESSELLATION || USE_CUSTOM_VERTEX_INTERPOLATORS - MaterialInput materialInput = GetMaterialInput(output, input.Position.xyz, tangentToLocal[2].xyz); - Material material = GetMaterialVS(materialInput); -#endif - - // Apply world position offset per-vertex -#if USE_POSITION_OFFSET - output.WorldPosition += material.PositionOffset; - output.Position = mul(float4(output.WorldPosition.xyz, 1), ViewProjectionMatrix); -#endif - - // Get tessalation multiplier (per vertex) -#if USE_TESSELLATION - output.TessellationMultiplier = material.TessellationMultiplier; -#endif - - // Copy interpolants for other shader stages -#if USE_CUSTOM_VERTEX_INTERPOLATORS - output.CustomVSToPS = material.CustomVSToPS; -#endif - - return output; -} - -#endif - -#if USE_TESSELLATION - -// Interpolants passed from the hull shader to the domain shader -struct TessalationHSToDS -{ - float4 Position : SV_Position; - float3 WorldPosition : TEXCOORD0; - float2 TexCoord : TEXCOORD1; - float2 LightmapUV : TEXCOORD2; -#if USE_VERTEX_COLOR - half4 VertexColor : COLOR; -#endif - float3x3 TBN : TEXCOORD3; -#if USE_CUSTOM_VERTEX_INTERPOLATORS - float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; -#endif - float3 InstanceOrigin : TEXCOORD6; - float2 InstanceParams : TEXCOORD7; - float TessellationMultiplier : TESS; -}; - -// Interpolants passed from the domain shader and to the pixel shader -struct TessalationDSToPS -{ - float4 Position : SV_Position; - float3 WorldPosition : TEXCOORD0; - float2 TexCoord : TEXCOORD1; - float2 LightmapUV : TEXCOORD2; -#if USE_VERTEX_COLOR - half4 VertexColor : COLOR; -#endif - float3x3 TBN : TEXCOORD3; -#if USE_CUSTOM_VERTEX_INTERPOLATORS - float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; -#endif - float3 InstanceOrigin : TEXCOORD6; - float2 InstanceParams : TEXCOORD7; -}; - -MaterialInput GetMaterialInput(TessalationDSToPS input) -{ - MaterialInput result = (MaterialInput)0; - result.WorldPosition = input.WorldPosition; - result.TexCoord = input.TexCoord; -#if USE_LIGHTMAP - result.LightmapUV = input.LightmapUV; -#endif -#if USE_VERTEX_COLOR - result.VertexColor = input.VertexColor; -#endif - result.TBN = input.TBN; - result.TwoSidedSign = WorldDeterminantSign; - result.InstanceOrigin = input.InstanceOrigin; - result.InstanceParams = input.InstanceParams; - result.SvPosition = input.Position; -#if USE_CUSTOM_VERTEX_INTERPOLATORS - result.CustomVSToPS = input.CustomVSToPS; -#endif - return result; -} - -struct TessalationPatch -{ - float EdgeTessFactor[3] : SV_TessFactor; - float InsideTessFactor : SV_InsideTessFactor; -#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PN - float3 B210 : POSITION4; - float3 B120 : POSITION5; - float3 B021 : POSITION6; - float3 B012 : POSITION7; - float3 B102 : POSITION8; - float3 B201 : POSITION9; - float3 B111 : CENTER; -#endif -}; - -TessalationPatch HS_PatchConstant(InputPatch input) -{ - TessalationPatch output; - - // Average tess factors along edges, and pick an edge tess factor for the interior tessellation - float4 tessellationMultipliers; - tessellationMultipliers.x = 0.5f * (input[1].TessellationMultiplier + input[2].TessellationMultiplier); - tessellationMultipliers.y = 0.5f * (input[2].TessellationMultiplier + input[0].TessellationMultiplier); - tessellationMultipliers.z = 0.5f * (input[0].TessellationMultiplier + input[1].TessellationMultiplier); - tessellationMultipliers.w = 0.333f * (input[0].TessellationMultiplier + input[1].TessellationMultiplier + input[2].TessellationMultiplier); - tessellationMultipliers = clamp(tessellationMultipliers, 1, MAX_TESSELLATION_FACTOR); - - output.EdgeTessFactor[0] = tessellationMultipliers.x; // 1->2 edge - output.EdgeTessFactor[1] = tessellationMultipliers.y; // 2->0 edge - output.EdgeTessFactor[2] = tessellationMultipliers.z; // 0->1 edge - output.InsideTessFactor = tessellationMultipliers.w; - -#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PN - // Calculate PN-Triangle coefficients - // Refer to Vlachos 2001 for the original formula - float3 p1 = input[0].WorldPosition; - float3 p2 = input[1].WorldPosition; - float3 p3 = input[2].WorldPosition; - float3 n1 = input[0].TBN[2]; - float3 n2 = input[1].TBN[2]; - float3 n3 = input[2].TBN[2]; - - // Calculate control points - output.B210 = (2.0f * p1 + p2 - dot((p2 - p1), n1) * n1) / 3.0f; - output.B120 = (2.0f * p2 + p1 - dot((p1 - p2), n2) * n2) / 3.0f; - output.B021 = (2.0f * p2 + p3 - dot((p3 - p2), n2) * n2) / 3.0f; - output.B012 = (2.0f * p3 + p2 - dot((p2 - p3), n3) * n3) / 3.0f; - output.B102 = (2.0f * p3 + p1 - dot((p1 - p3), n3) * n3) / 3.0f; - output.B201 = (2.0f * p1 + p3 - dot((p3 - p1), n1) * n1) / 3.0f; - float3 e = (output.B210 + output.B120 + output.B021 + - output.B012 + output.B102 + output.B201) / 6.0f; - float3 v = (p1 + p2 + p3) / 3.0f; - output.B111 = e + ((e - v) / 2.0f); -#endif - - return output; -} - -META_HS(USE_TESSELLATION, FEATURE_LEVEL_SM5) -META_HS_PATCH(TESSELLATION_IN_CONTROL_POINTS) -[domain("tri")] -[partitioning("fractional_odd")] -[outputtopology("triangle_cw")] -[maxtessfactor(MAX_TESSELLATION_FACTOR)] -[outputcontrolpoints(3)] -[patchconstantfunc("HS_PatchConstant")] -TessalationHSToDS HS(InputPatch input, uint ControlPointID : SV_OutputControlPointID) -{ - TessalationHSToDS output; - - // Pass through shader -#define COPY(thing) output.thing = input[ControlPointID].thing; - COPY(Position); - COPY(WorldPosition); - COPY(TexCoord); - COPY(LightmapUV); -#if USE_VERTEX_COLOR - COPY(VertexColor); -#endif - COPY(TBN); - COPY(InstanceOrigin); - COPY(InstanceParams); - COPY(TessellationMultiplier); -#if USE_CUSTOM_VERTEX_INTERPOLATORS - COPY(CustomVSToPS); -#endif -#undef COPY - - return output; -} - -#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PHONG - -// Orthogonal projection on to plane -float3 ProjectOntoPlane(float3 planeNormal, float3 planePoint, float3 pointToProject) -{ - return pointToProject - dot(pointToProject-planePoint, planeNormal) * planeNormal; -} - -#endif - -META_DS(USE_TESSELLATION, FEATURE_LEVEL_SM5) -[domain("tri")] -TessalationDSToPS DS(TessalationPatch constantData, float3 barycentricCoords : SV_DomainLocation, const OutputPatch input) -{ - TessalationDSToPS output; - - // Get the barycentric coords - float U = barycentricCoords.x; - float V = barycentricCoords.y; - float W = barycentricCoords.z; - - // Interpolate patch attributes to generated vertices -#define INTERPOLATE(thing) output.thing = U * input[0].thing + V * input[1].thing + W * input[2].thing -#define COPY(thing) output.thing = input[0].thing - INTERPOLATE(Position); -#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PN - float UU = U * U; - float VV = V * V; - float WW = W * W; - float UU3 = UU * 3.0f; - float VV3 = VV * 3.0f; - float WW3 = WW * 3.0f; - - // Interpolate using barycentric coordinates and PN Triangle control points - output.WorldPosition = - input[0].WorldPosition * UU * U + - input[1].WorldPosition * VV * V + - input[2].WorldPosition * WW * W + - constantData.B210 * UU3 * V + - constantData.B120 * VV3 * U + - constantData.B021 * VV3 * W + - constantData.B012 * WW3 * V + - constantData.B102 * WW3 * U + - constantData.B201 * UU3 * W + - constantData.B111 * 6.0f * W * U * V; -#else - INTERPOLATE(WorldPosition); -#endif - INTERPOLATE(TexCoord); - INTERPOLATE(LightmapUV); -#if USE_VERTEX_COLOR - INTERPOLATE(VertexColor); -#endif - INTERPOLATE(TBN[0]); - INTERPOLATE(TBN[1]); - INTERPOLATE(TBN[2]); - COPY(InstanceOrigin); - COPY(InstanceParams); -#if USE_CUSTOM_VERTEX_INTERPOLATORS - UNROLL - for (int i = 0; i < CUSTOM_VERTEX_INTERPOLATORS_COUNT; i++) - { - INTERPOLATE(CustomVSToPS[i]); - } -#endif -#undef INTERPOLATE -#undef COPY - - // Interpolating normal can unnormalize it, so normalize it - output.TBN[0] = normalize(output.TBN[0]); - output.TBN[1] = normalize(output.TBN[1]); - output.TBN[2] = normalize(output.TBN[2]); - -#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PHONG - // Orthogonal projection in the tangent planes - float3 posProjectedU = ProjectOntoPlane(input[0].TBN[2], input[0].WorldPosition, output.WorldPosition); - float3 posProjectedV = ProjectOntoPlane(input[1].TBN[2], input[1].WorldPosition, output.WorldPosition); - float3 posProjectedW = ProjectOntoPlane(input[2].TBN[2], input[2].WorldPosition, output.WorldPosition); - - // Interpolate the projected points - output.WorldPosition = U * posProjectedU + V * posProjectedV + W * posProjectedW; -#endif - - // Perform displacement mapping -#if USE_DISPLACEMENT - MaterialInput materialInput = GetMaterialInput(output); - Material material = GetMaterialDS(materialInput); - output.WorldPosition += material.WorldDisplacement; -#endif - - // Recalculate the clip space position - output.Position = mul(float4(output.WorldPosition, 1), ViewProjectionMatrix); - - return output; -} - -#endif - -#if USE_DITHERED_LOD_TRANSITION - -void ClipLODTransition(PixelInput input) -{ - float ditherFactor = input.InstanceParams.y; - if (abs(ditherFactor) > 0.001) - { - float randGrid = cos(dot(floor(input.Position.xy), float2(347.83452793, 3343.28371863))); - float randGridFrac = frac(randGrid * 1000.0); - half mask = (ditherFactor < 0.0) ? (ditherFactor + 1.0 > randGridFrac) : (ditherFactor < randGridFrac); - clip(mask - 0.001); - } -} - -#else - -void ClipLODTransition(PixelInput input) -{ -} - -#endif - -// Pixel Shader function for Forward Pass -META_PS(USE_FORWARD, FEATURE_LEVEL_ES2) -float4 PS_Forward(PixelInput input) : SV_Target0 -{ - float4 output = 0; - - // LOD masking - ClipLODTransition(input); - // TODO: make model LOD transition smoother for transparent materials by using opacity to reduce aliasing - - // Get material parameters - MaterialInput materialInput = GetMaterialInput(input); - Material material = GetMaterialPS(materialInput); - - // Masking -#if MATERIAL_MASKED - clip(material.Mask - MATERIAL_MASK_THRESHOLD); -#endif - - // Add emissive light - output = float4(material.Emissive, material.Opacity); - -#if MATERIAL_SHADING_MODEL != SHADING_MODEL_UNLIT - - // Setup GBuffer data as proxy for lighting - GBufferSample gBuffer; - gBuffer.Normal = material.WorldNormal; - gBuffer.Roughness = material.Roughness; - gBuffer.Metalness = material.Metalness; - gBuffer.Color = material.Color; - gBuffer.Specular = material.Specular; - gBuffer.AO = material.AO; - gBuffer.ViewPos = mul(float4(materialInput.WorldPosition, 1), ViewMatrix).xyz; -#if MATERIAL_SHADING_MODEL == SHADING_MODEL_SUBSURFACE - gBuffer.CustomData = float4(material.SubsurfaceColor, material.Opacity); -#elif MATERIAL_SHADING_MODEL == SHADING_MODEL_FOLIAGE - gBuffer.CustomData = float4(material.SubsurfaceColor, material.Opacity); -#else - gBuffer.CustomData = float4(0, 0, 0, 0); -#endif - gBuffer.WorldPos = materialInput.WorldPosition; - gBuffer.ShadingModel = MATERIAL_SHADING_MODEL; - - // Calculate lighting from a single directional light - float4 shadowMask = 1.0f; - if (DirectionalLight.CastShadows > 0) - { - LightShadowData directionalLightShadowData = GetDirectionalLightShadowData(); - shadowMask.r = SampleShadow(DirectionalLight, directionalLightShadowData, DirectionalLightShadowMap, gBuffer, shadowMask.g); - } - float4 light = GetLighting(ViewPos, DirectionalLight, gBuffer, shadowMask, false, false); - - // Calculate lighting from sky light - light += GetSkyLightLighting(SkyLight, gBuffer, SkyLightTexture); - - // Calculate lighting from local lights - LOOP - for (uint localLightIndex = 0; localLightIndex < LocalLightsCount; localLightIndex++) - { - const LightData localLight = LocalLights[localLightIndex]; - bool isSpotLight = localLight.SpotAngles.x > -2.0f; - shadowMask = 1.0f; - light += GetLighting(ViewPos, localLight, gBuffer, shadowMask, true, isSpotLight); - } - -#if USE_REFLECTIONS - // Calculate reflections - light.rgb += GetEnvProbeLighting(ViewPos, EnvProbe, EnvironmentProbe, gBuffer) * light.a; -#endif - - // Add lighting (apply ambient occlusion) - output.rgb += light.rgb * gBuffer.AO; - -#if USE_FOG - // Calculate exponential height fog - float4 fog = GetExponentialHeightFog(ExponentialHeightFog, materialInput.WorldPosition, ViewPos, 0); - - // Apply fog to the output color -#if MATERIAL_BLEND == MATERIAL_BLEND_OPAQUE - output = float4(output.rgb * fog.a + fog.rgb, output.a); -#elif MATERIAL_BLEND == MATERIAL_BLEND_TRANSPARENT - output = float4(output.rgb * fog.a + fog.rgb, output.a); -#elif MATERIAL_BLEND == MATERIAL_BLEND_ADDITIVE - output = float4(output.rgb * fog.a + fog.rgb, output.a * fog.a); -#elif MATERIAL_BLEND == MATERIAL_BLEND_MULTIPLY - output = float4(lerp(float3(1, 1, 1), output.rgb, fog.aaa * fog.aaa), output.a); -#endif - -#endif - -#endif - - return output; -} - -#if USE_DISTORTION - -// Pixel Shader function for Distortion Pass -META_PS(USE_DISTORTION, FEATURE_LEVEL_ES2) -float4 PS_Distortion(PixelInput input) : SV_Target0 -{ - // LOD masking - ClipLODTransition(input); - - // Get material parameters - MaterialInput materialInput = GetMaterialInput(input); - Material material = GetMaterialPS(materialInput); - - // Masking -#if MATERIAL_MASKED - clip(material.Mask - MATERIAL_MASK_THRESHOLD); -#endif - - float3 viewNormal = normalize(TransformWorldVectorToView(materialInput, material.WorldNormal)); - float airIOR = 1.0f; -#if USE_PIXEL_NORMAL_OFFSET_REFRACTION - float3 viewVertexNormal = TransformWorldVectorToView(materialInput, TransformTangentVectorToWorld(materialInput, float3(0, 0, 1))); - float2 distortion = (viewVertexNormal.xy - viewNormal.xy) * (material.Refraction - airIOR); -#else - float2 distortion = viewNormal.xy * (material.Refraction - airIOR); -#endif - - // Clip if the distortion distance (squared) is too small to be noticed - clip(dot(distortion, distortion) - 0.00001); - - // Scale up for better precision in low/subtle refractions at the expense of artefacts at higher refraction - distortion *= 4.0f; - - // Use separate storage for positive and negative offsets - float2 addOffset = max(distortion, 0); - float2 subOffset = abs(min(distortion, 0)); - return float4(addOffset.x, addOffset.y, subOffset.x, subOffset.y); -} - -#endif - -// Pixel Shader function for Depth Pass -META_PS(true, FEATURE_LEVEL_ES2) -void PS_Depth(PixelInput input -#if GLSL - , out float4 OutColor : SV_Target0 -#endif - ) -{ - // LOD masking - ClipLODTransition(input); - - // Get material parameters - MaterialInput materialInput = GetMaterialInput(input); - Material material = GetMaterialPS(materialInput); - - // Perform per pixel clipping -#if MATERIAL_MASKED - clip(material.Mask - MATERIAL_MASK_THRESHOLD); -#endif - clip(material.Opacity - MATERIAL_OPACITY_THRESHOLD); - -#if GLSL - OutColor = 0; -#endif -} diff --git a/Content/Editor/MaterialTemplates/Terrain.shader b/Content/Editor/MaterialTemplates/Terrain.shader index 03a2081bf..b1c3a2910 100644 --- a/Content/Editor/MaterialTemplates/Terrain.shader +++ b/Content/Editor/MaterialTemplates/Terrain.shader @@ -3,7 +3,6 @@ #define MATERIAL 1 @3 - // Enables/disables smooth terrain chunks LOD transitions (with morphing higher LOD near edges to the lower LOD in the neighbour) #define USE_SMOOTH_LOD_TRANSITION 1 @@ -26,7 +25,6 @@ float3 ViewDir; float TimeParam; float4 ViewInfo; float4 ScreenSize; -float4 LightmapArea; float3 WorldInvScale; float WorldDeterminantSign; float PerInstanceRandom; @@ -39,35 +37,31 @@ float2 OffsetUV; float2 Dummy0; @1META_CB_END -#if CAN_USE_LIGHTMAP - -// Irradiance and directionality prebaked lightmaps -Texture2D Lightmap0 : register(t0); -Texture2D Lightmap1 : register(t1); -Texture2D Lightmap2 : register(t2); - -#endif - // Terrain data -Texture2D Heightmap : register(t3); -Texture2D Splatmap0 : register(t4); -Texture2D Splatmap1 : register(t5); +Texture2D Heightmap : register(t0); +Texture2D Splatmap0 : register(t1); +Texture2D Splatmap1 : register(t2); -// Material shader resources +// Shader resources @2 +// Geometry data passed though the graphics rendering stages up to the pixel shader +struct GeometryData +{ + float3 WorldPosition : TEXCOORD0; + float2 TexCoord : TEXCOORD1; + float2 LightmapUV : TEXCOORD2; + float3 WorldNormal : TEXCOORD3; + float HolesMask : TEXCOORD4; +#if USE_TERRAIN_LAYERS + float4 Layers[TERRAIN_LAYERS_DATA_SIZE] : TEXCOORD5; +#endif +}; // Interpolants passed from the vertex shader struct VertexOutput { - float4 Position : SV_Position; - float3 WorldPosition : TEXCOORD0; - float2 TexCoord : TEXCOORD1; - float2 LightmapUV : TEXCOORD2; - float3 WorldNormal : TEXCOORD3; - float HolesMask : TEXCOORD4; -#if USE_TERRAIN_LAYERS - float4 Layers[TERRAIN_LAYERS_DATA_SIZE] : TEXCOORD5; -#endif + float4 Position : SV_Position; + GeometryData Geometry; #if USE_CUSTOM_VERTEX_INTERPOLATORS float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; #endif @@ -79,19 +73,12 @@ struct VertexOutput // Interpolants passed to the pixel shader struct PixelInput { - float4 Position : SV_Position; - float3 WorldPosition : TEXCOORD0; - float2 TexCoord : TEXCOORD1; - float2 LightmapUV : TEXCOORD2; - float3 WorldNormal : TEXCOORD3; - float HolesMask : TEXCOORD4; -#if USE_TERRAIN_LAYERS - float4 Layers[TERRAIN_LAYERS_DATA_SIZE] : TEXCOORD5; -#endif + float4 Position : SV_Position; + GeometryData Geometry; #if USE_CUSTOM_VERTEX_INTERPOLATORS float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; #endif - bool IsFrontFace : SV_IsFrontFace; + bool IsFrontFace : SV_IsFrontFace; }; // Material properties generation input @@ -116,25 +103,66 @@ struct MaterialInput #endif }; +// Extracts geometry data to the material input +MaterialInput GetGeometryMaterialInput(GeometryData geometry) +{ + MaterialInput output = (MaterialInput)0; + output.WorldPosition = geometry.WorldPosition; + output.TexCoord = geometry.TexCoord; +#if USE_LIGHTMAP + output.LightmapUV = geometry.LightmapUV; +#endif + output.TBN = CalcTangentBasisFromWorldNormal(geometry.WorldNormal); + output.HolesMask = geometry.HolesMask; +#if USE_TERRAIN_LAYERS + output.Layers = geometry.Layers; +#endif + return output; +} + +#if USE_TESSELLATION + +// Interpolates the geometry positions data only (used by the tessallation when generating vertices) +#define InterpolateGeometryPositions(output, p0, w0, p1, w1, p2, w2, offset) output.WorldPosition = p0.WorldPosition * w0 + p1.WorldPosition * w1 + p2.WorldPosition * w2 + offset + +// Offsets the geometry positions data only (used by the tessallation when generating vertices) +#define OffsetGeometryPositions(geometry, offset) geometry.WorldPosition += offset + +// Applies the Phong tessallation to the geometry positions (used by the tessallation when doing Phong tess) +#define ApplyGeometryPositionsPhongTess(geometry, p0, p1, p2, U, V, W) \ + float3 posProjectedU = TessalationProjectOntoPlane(p0.WorldNormal, p0.WorldPosition, geometry.WorldPosition); \ + float3 posProjectedV = TessalationProjectOntoPlane(p1.WorldNormal, p1.WorldPosition, geometry.WorldPosition); \ + float3 posProjectedW = TessalationProjectOntoPlane(p2.WorldNormal, p2.WorldPosition, geometry.WorldPosition); \ + geometry.WorldPosition = U * posProjectedU + V * posProjectedV + W * posProjectedW + +// Interpolates the geometry data except positions (used by the tessallation when generating vertices) +GeometryData InterpolateGeometry(GeometryData p0, float w0, GeometryData p1, float w1, GeometryData p2, float w2) +{ + GeometryData output = (GeometryData)0; + output.TexCoord = p0.TexCoord * w0 + p1.TexCoord * w1 + p2.TexCoord * w2; + output.LightmapUV = p0.LightmapUV * w0 + p1.LightmapUV * w1 + p2.LightmapUV * w2; + output.WorldNormal = p0.WorldNormal * w0 + p1.WorldNormal * w1 + p2.WorldNormal * w2; + output.WorldNormal = normalize(output.WorldNormal); + output.HolesMask = p0.HolesMask * w0 + p1.HolesMask * w1 + p2.HolesMask * w2; +#if USE_TERRAIN_LAYERS + UNROLL + for (int i = 0; i < TERRAIN_LAYERS_DATA_SIZE; i++) + output.Layers[i] = p0.Layers[i] * w0 + p1.Layers[i] * w1 + p2.Layers[i] * w2; +#endif + return output; +} + +#endif + MaterialInput GetMaterialInput(PixelInput input) { - MaterialInput result = (MaterialInput)0; - result.WorldPosition = input.WorldPosition; - result.TexCoord = input.TexCoord; -#if USE_LIGHTMAP - result.LightmapUV = input.LightmapUV; -#endif - result.TBN = CalcTangentBasisFromWorldNormal(input.WorldNormal); - result.TwoSidedSign = WorldDeterminantSign * (input.IsFrontFace ? 1.0 : -1.0); - result.SvPosition = input.Position; - result.HolesMask = input.HolesMask; -#if USE_TERRAIN_LAYERS - result.Layers = input.Layers; -#endif + MaterialInput output = GetGeometryMaterialInput(input.Geometry); + output.TwoSidedSign = WorldDeterminantSign * (input.IsFrontFace ? 1.0 : -1.0); + output.SvPosition = input.Position; #if USE_CUSTOM_VERTEX_INTERPOLATORS - result.CustomVSToPS = input.CustomVSToPS; + output.CustomVSToPS = input.CustomVSToPS; #endif - return result; + return output; } // Removes the scale vector from the local to world transformation matrix @@ -216,6 +244,8 @@ float4 GetVertexColor(MaterialInput input) return 1; } +@8 + // Get material properties function (for vertex shader) Material GetMaterialVS(MaterialInput input) { @@ -234,9 +264,6 @@ Material GetMaterialPS(MaterialInput input) @4 } -// Fix line for errors/warnings for shader code from template -#line 1000 - // Calculates LOD value (with fractional part for blending) float CalcLOD(float2 xy, float4 morph) { @@ -285,7 +312,7 @@ float3x3 CalcTangentToWorld(float4x4 world, float3x3 tangentToLocal) struct TerrainVertexInput { float2 TexCoord : TEXCOORD0; - float4 Morph : TEXCOORD1; + float4 Morph : TEXCOORD1; }; // Vertex Shader function for terrain rendering @@ -336,7 +363,7 @@ VertexOutput VS(TerrainVertexInput input) float2 normalTemp = float2(heightmapValue.b, heightmapValue.a) * 2.0f - 1.0f; float3 normal = float3(normalTemp.x, sqrt(1.0 - saturate(dot(normalTemp, normalTemp))), normalTemp.y); normal = normalize(normal); - output.HolesMask = isHole ? 0 : 1; + output.Geometry.HolesMask = isHole ? 0 : 1; if (isHole) { normal = float3(0, 1, 0); @@ -353,10 +380,10 @@ VertexOutput VS(TerrainVertexInput input) float3 position = float3(positionXZ.x, height, positionXZ.y); // Compute world space vertex position - output.WorldPosition = mul(float4(position, 1), WorldMatrix).xyz; + output.Geometry.WorldPosition = mul(float4(position, 1), WorldMatrix).xyz; // Compute clip space position - output.Position = mul(float4(output.WorldPosition.xyz, 1), ViewProjectionMatrix); + output.Position = mul(float4(output.Geometry.WorldPosition, 1), ViewProjectionMatrix); // Pass vertex attributes #if USE_SMOOTH_LOD_TRANSITION @@ -364,46 +391,46 @@ VertexOutput VS(TerrainVertexInput input) #else float2 texCoord = input.TexCoord; #endif - output.TexCoord = positionXZ * (1.0f / TerrainChunkSizeLOD0) + OffsetUV; - output.LightmapUV = texCoord * LightmapArea.zw + LightmapArea.xy; + output.Geometry.TexCoord = positionXZ * (1.0f / TerrainChunkSizeLOD0) + OffsetUV; + output.Geometry.LightmapUV = texCoord * LightmapArea.zw + LightmapArea.xy; // Extract terrain layers weights from the splatmap #if USE_TERRAIN_LAYERS - output.Layers[0] = splatmap0Value; + output.Geometry.Layers[0] = splatmap0Value; #if TERRAIN_LAYERS_DATA_SIZE > 1 - output.Layers[1] = splatmap1Value; + output.Geometry.Layers[1] = splatmap1Value; #endif #endif // Compute world space normal vector float3x3 tangentToLocal = CalcTangentBasisFromWorldNormal(normal); float3x3 tangentToWorld = CalcTangentToWorld(WorldMatrix, tangentToLocal); - output.WorldNormal = tangentToWorld[2]; + output.Geometry.WorldNormal = tangentToWorld[2]; // Get material input params if need to evaluate any material property #if USE_POSITION_OFFSET || USE_TESSELLATION || USE_CUSTOM_VERTEX_INTERPOLATORS MaterialInput materialInput = (MaterialInput)0; - materialInput.WorldPosition = output.WorldPosition; - materialInput.TexCoord = output.TexCoord; + materialInput.WorldPosition = output.Geometry.WorldPosition; + materialInput.TexCoord = output.Geometry.TexCoord; #if USE_LIGHTMAP - materialInput.LightmapUV = output.LightmapUV; + materialInput.LightmapUV = output.Geometry.LightmapUV; #endif - materialInput.TBN = CalcTangentBasisFromWorldNormal(output.WorldNormal); + materialInput.TBN = CalcTangentBasisFromWorldNormal(output.Geometry.WorldNormal); materialInput.TwoSidedSign = WorldDeterminantSign; materialInput.SvPosition = output.Position; materialInput.PreSkinnedPosition = position; materialInput.PreSkinnedNormal = normal; - materialInput.HolesMask = output.HolesMask; + materialInput.HolesMask = output.Geometry.HolesMask; #if USE_TERRAIN_LAYERS - materialInput.Layers = output.Layers; + materialInput.Layers = output.Geometry.Layers; #endif Material material = GetMaterialVS(materialInput); #endif // Apply world position offset per-vertex #if USE_POSITION_OFFSET - output.WorldPosition += material.PositionOffset; - output.Position = mul(float4(output.WorldPosition.xyz, 1), ViewProjectionMatrix); + output.Geometry.WorldPosition += material.PositionOffset; + output.Position = mul(float4(output.Geometry.WorldPosition, 1), ViewProjectionMatrix); #endif // Get tessalation multiplier (per vertex) @@ -419,356 +446,9 @@ VertexOutput VS(TerrainVertexInput input) return output; } -#if USE_TESSELLATION - -// Interpolants passed from the hull shader to the domain shader -struct TessalationHSToDS -{ - float4 Position : SV_Position; - float3 WorldPosition : TEXCOORD0; - float2 TexCoord : TEXCOORD1; - float2 LightmapUV : TEXCOORD2; - float3 WorldNormal : TEXCOORD3; - float HolesMask : TEXCOORD4; -#if USE_TERRAIN_LAYERS - float4 Layers[TERRAIN_LAYERS_DATA_SIZE] : TEXCOORD5; -#endif -#if USE_CUSTOM_VERTEX_INTERPOLATORS - float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; -#endif - float TessellationMultiplier : TESS; -}; - -// Interpolants passed from the domain shader and to the pixel shader -struct TessalationDSToPS -{ - float4 Position : SV_Position; - float3 WorldPosition : TEXCOORD0; - float2 TexCoord : TEXCOORD1; - float2 LightmapUV : TEXCOORD2; - float3 WorldNormal : TEXCOORD3; - float HolesMask : TEXCOORD4; -#if USE_TERRAIN_LAYERS - float4 Layers[TERRAIN_LAYERS_DATA_SIZE] : TEXCOORD5; -#endif -#if USE_CUSTOM_VERTEX_INTERPOLATORS - float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; -#endif -}; - -MaterialInput GetMaterialInput(TessalationDSToPS input) -{ - MaterialInput result = (MaterialInput)0; - result.WorldPosition = input.WorldPosition; - result.TexCoord = input.TexCoord; -#if USE_LIGHTMAP - result.LightmapUV = input.LightmapUV; -#endif - result.TBN = CalcTangentBasisFromWorldNormal(input.WorldNormal); - result.TwoSidedSign = WorldDeterminantSign; - result.SvPosition = input.Position; - result.HolesMask = input.HolesMask; -#if USE_TERRAIN_LAYERS - result.Layers = input.Layers; -#endif -#if USE_CUSTOM_VERTEX_INTERPOLATORS - result.CustomVSToPS = input.CustomVSToPS; -#endif - return result; -} - -struct TessalationPatch -{ - float EdgeTessFactor[3] : SV_TessFactor; - float InsideTessFactor : SV_InsideTessFactor; -#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PN - float3 B210 : POSITION4; - float3 B120 : POSITION5; - float3 B021 : POSITION6; - float3 B012 : POSITION7; - float3 B102 : POSITION8; - float3 B201 : POSITION9; - float3 B111 : CENTER; -#endif -}; - -TessalationPatch HS_PatchConstant(InputPatch input) -{ - TessalationPatch output; - - // Average tess factors along edges, and pick an edge tess factor for the interior tessellation - float4 TessellationMultipliers; - TessellationMultipliers.x = 0.5f * (input[1].TessellationMultiplier + input[2].TessellationMultiplier); - TessellationMultipliers.y = 0.5f * (input[2].TessellationMultiplier + input[0].TessellationMultiplier); - TessellationMultipliers.z = 0.5f * (input[0].TessellationMultiplier + input[1].TessellationMultiplier); - TessellationMultipliers.w = 0.333f * (input[0].TessellationMultiplier + input[1].TessellationMultiplier + input[2].TessellationMultiplier); - - TessellationMultipliers = clamp(TessellationMultipliers, 1, MAX_TESSELLATION_FACTOR); - - output.EdgeTessFactor[0] = TessellationMultipliers.x; // 1->2 edge - output.EdgeTessFactor[1] = TessellationMultipliers.y; // 2->0 edge - output.EdgeTessFactor[2] = TessellationMultipliers.z; // 0->1 edge - output.InsideTessFactor = TessellationMultipliers.w; - -#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PN - // Calculate PN-Triangle coefficients - // Refer to Vlachos 2001 for the original formula - float3 p1 = input[0].WorldPosition; - float3 p2 = input[1].WorldPosition; - float3 p3 = input[2].WorldPosition; - float3 n1 = input[0].WorldNormal; - float3 n2 = input[1].WorldNormal; - float3 n3 = input[2].WorldNormal; - - // Calculate control points - output.B210 = (2.0f * p1 + p2 - dot((p2 - p1), n1) * n1) / 3.0f; - output.B120 = (2.0f * p2 + p1 - dot((p1 - p2), n2) * n2) / 3.0f; - output.B021 = (2.0f * p2 + p3 - dot((p3 - p2), n2) * n2) / 3.0f; - output.B012 = (2.0f * p3 + p2 - dot((p2 - p3), n3) * n3) / 3.0f; - output.B102 = (2.0f * p3 + p1 - dot((p1 - p3), n3) * n3) / 3.0f; - output.B201 = (2.0f * p1 + p3 - dot((p3 - p1), n1) * n1) / 3.0f; - float3 e = (output.B210 + output.B120 + output.B021 + - output.B012 + output.B102 + output.B201) / 6.0f; - float3 v = (p1 + p2 + p3) / 3.0f; - output.B111 = e + ((e - v) / 2.0f); -#endif - - return output; -} - -META_HS(USE_TESSELLATION, FEATURE_LEVEL_SM5) -META_HS_PATCH(TESSELLATION_IN_CONTROL_POINTS) -[domain("tri")] -[partitioning("fractional_odd")] -[outputtopology("triangle_cw")] -[maxtessfactor(MAX_TESSELLATION_FACTOR)] -[outputcontrolpoints(3)] -[patchconstantfunc("HS_PatchConstant")] -TessalationHSToDS HS(InputPatch input, uint ControlPointID : SV_OutputControlPointID) -{ - TessalationHSToDS output; - - // Pass through shader -#define COPY(thing) output.thing = input[ControlPointID].thing; - COPY(Position); - COPY(WorldPosition); - COPY(TexCoord); - COPY(LightmapUV); - COPY(WorldNormal); - COPY(HolesMask); - COPY(TessellationMultiplier); -#if USE_TERRAIN_LAYERS - COPY(Layers); -#endif -#if USE_CUSTOM_VERTEX_INTERPOLATORS - COPY(CustomVSToPS); -#endif -#undef COPY - - return output; -} - -#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PHONG - -// Orthogonal projection on to plane -float3 ProjectOntoPlane(float3 planeNormal, float3 planePoint, float3 pointToProject) -{ - return pointToProject - dot(pointToProject-planePoint, planeNormal) * planeNormal; -} - -#endif - -META_DS(USE_TESSELLATION, FEATURE_LEVEL_SM5) -[domain("tri")] -TessalationDSToPS DS(TessalationPatch constantData, float3 barycentricCoords : SV_DomainLocation, const OutputPatch input) -{ - TessalationDSToPS output; - - // Get the barycentric coords - float U = barycentricCoords.x; - float V = barycentricCoords.y; - float W = barycentricCoords.z; - - // Interpolate patch attributes to generated vertices -#define INTERPOLATE(thing) output.thing = U * input[0].thing + V * input[1].thing + W * input[2].thing -#define COPY(thing) output.thing = input[0].thing - INTERPOLATE(Position); -#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PN - float UU = U * U; - float VV = V * V; - float WW = W * W; - float UU3 = UU * 3.0f; - float VV3 = VV * 3.0f; - float WW3 = WW * 3.0f; - - // Interpolate using barycentric coordinates and PN Triangle control points - output.WorldPosition = - input[0].WorldPosition * UU * U + - input[1].WorldPosition * VV * V + - input[2].WorldPosition * WW * W + - constantData.B210 * UU3 * V + - constantData.B120 * VV3 * U + - constantData.B021 * VV3 * W + - constantData.B012 * WW3 * V + - constantData.B102 * WW3 * U + - constantData.B201 * UU3 * W + - constantData.B111 * 6.0f * W * U * V; -#else - INTERPOLATE(WorldPosition); -#endif - INTERPOLATE(TexCoord); - INTERPOLATE(LightmapUV); - INTERPOLATE(WorldNormal); - INTERPOLATE(HolesMask); -#if USE_TERRAIN_LAYERS - UNROLL - for (int i = 0; i < TERRAIN_LAYERS_DATA_SIZE; i++) - { - INTERPOLATE(Layers[i]); - } -#endif -#if USE_CUSTOM_VERTEX_INTERPOLATORS - UNROLL - for (int i = 0; i < CUSTOM_VERTEX_INTERPOLATORS_COUNT; i++) - { - INTERPOLATE(CustomVSToPS[i]); - } -#endif -#undef INTERPOLATE -#undef COPY - - // Interpolating normal can unnormalize it, so normalize it - output.WorldNormal = normalize(output.WorldNormal); - -#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PHONG - // Orthogonal projection in the tangent planes - float3 posProjectedU = ProjectOntoPlane(input[0].WorldNormal, input[0].WorldPosition, output.WorldPosition); - float3 posProjectedV = ProjectOntoPlane(input[1].WorldNormal, input[1].WorldPosition, output.WorldPosition); - float3 posProjectedW = ProjectOntoPlane(input[2].WorldNormal, input[2].WorldPosition, output.WorldPosition); - - // Interpolate the projected points - output.WorldPosition = U * posProjectedU + V * posProjectedV + W * posProjectedW; -#endif - - // Perform displacement mapping -#if USE_DISPLACEMENT - MaterialInput materialInput = GetMaterialInput(output); - Material material = GetMaterialDS(materialInput); - output.WorldPosition += material.WorldDisplacement; -#endif - - // Recalculate the clip space position - output.Position = mul(float4(output.WorldPosition, 1), ViewProjectionMatrix); - - return output; -} - -#endif - -#if USE_LIGHTMAP - -float3 SampleLightmap(Material material, MaterialInput materialInput) -{ - // Sample lightmaps - float4 lightmap0 = Lightmap0.Sample(SamplerLinearClamp, materialInput.LightmapUV); - float4 lightmap1 = Lightmap1.Sample(SamplerLinearClamp, materialInput.LightmapUV); - float4 lightmap2 = Lightmap2.Sample(SamplerLinearClamp, materialInput.LightmapUV); - - // Unpack H-basis - float3 h0 = float3(lightmap0.x, lightmap1.x, lightmap2.x); - float3 h1 = float3(lightmap0.y, lightmap1.y, lightmap2.y); - float3 h2 = float3(lightmap0.z, lightmap1.z, lightmap2.z); - float3 h3 = float3(lightmap0.w, lightmap1.w, lightmap2.w); - - // Sample baked diffuse irradiance from the H-basis coefficients - float3 normal = material.TangentNormal; -#if MATERIAL_SHADING_MODEL == SHADING_MODEL_FOLIAGE - normal *= material.TangentNormal; -#endif - return GetHBasisIrradiance(normal, h0, h1, h2, h3) / PI; -} - -#endif - -// Pixel Shader function for GBuffer Pass -META_PS(true, FEATURE_LEVEL_ES2) -META_PERMUTATION_1(USE_LIGHTMAP=0) -META_PERMUTATION_1(USE_LIGHTMAP=1) -void PS_GBuffer( - in PixelInput input - ,out float4 Light : SV_Target0 -#if MATERIAL_BLEND == MATERIAL_BLEND_OPAQUE - ,out float4 RT0 : SV_Target1 - ,out float4 RT1 : SV_Target2 - ,out float4 RT2 : SV_Target3 -#if USE_GBUFFER_CUSTOM_DATA - ,out float4 RT3 : SV_Target4 -#endif -#endif - ) -{ - Light = 0; - - // Get material parameters - MaterialInput materialInput = GetMaterialInput(input); - Material material = GetMaterialPS(materialInput); - - // Masking -#if MATERIAL_MASKED - clip(material.Mask - MATERIAL_MASK_THRESHOLD); -#endif - -#if USE_LIGHTMAP - - float3 diffuseColor = GetDiffuseColor(material.Color, material.Metalness); - float3 specularColor = GetSpecularColor(material.Color, material.Specular, material.Metalness); - - // Sample lightmap - float3 diffuseIndirectLighting = SampleLightmap(material, materialInput); - - // Apply static indirect light - Light.rgb = diffuseColor * diffuseIndirectLighting * AOMultiBounce(material.AO, diffuseColor); - -#endif - -#if MATERIAL_BLEND == MATERIAL_BLEND_OPAQUE - - // Pack material properties to GBuffer - RT0 = float4(material.Color, material.AO); - RT1 = float4(material.WorldNormal * 0.5 + 0.5, MATERIAL_SHADING_MODEL * (1.0 / 3.0)); - RT2 = float4(material.Roughness, material.Metalness, material.Specular, 0); - - // Custom data -#if USE_GBUFFER_CUSTOM_DATA -#if MATERIAL_SHADING_MODEL == SHADING_MODEL_SUBSURFACE - RT3 = float4(material.SubsurfaceColor, material.Opacity); -#elif MATERIAL_SHADING_MODEL == SHADING_MODEL_FOLIAGE - RT3 = float4(material.SubsurfaceColor, material.Opacity); -#else - RT3 = float4(0, 0, 0, 0); -#endif -#endif - - // Add light emission -#if USE_EMISSIVE - Light.rgb += material.Emissive; -#endif - -#else - - // Handle blending as faked forward pass (use Light buffer and skip GBuffer modification) - Light = float4(material.Emissive, material.Opacity); - -#endif -} - // Pixel Shader function for Depth Pass META_PS(true, FEATURE_LEVEL_ES2) -void PS_Depth(PixelInput input -#if GLSL - , out float4 OutColor : SV_Target0 -#endif - ) +void PS_Depth(PixelInput input) { #if MATERIAL_MASKED // Perform per pixel clipping if material requries it @@ -776,8 +456,6 @@ void PS_Depth(PixelInput input Material material = GetMaterialPS(materialInput); clip(material.Mask - MATERIAL_MASK_THRESHOLD); #endif - -#if GLSL - OutColor = 0; -#endif } + +@9 diff --git a/Content/Editor/Particles/Particle Material Color.flax b/Content/Editor/Particles/Particle Material Color.flax index 6c83bfc35..30b136e26 100644 --- a/Content/Editor/Particles/Particle Material Color.flax +++ b/Content/Editor/Particles/Particle Material Color.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0bf5ada0acae289fa7fbbbd6135779ef544d36999de9df0f29b2b9ce10ad52bb -size 30474 +oid sha256:de2cfa951e89d51dbe8453525c94a527cb9d1ad9fbd77166b56e6e4eb35f2806 +size 29232 diff --git a/Content/Editor/Particles/Smoke Material.flax b/Content/Editor/Particles/Smoke Material.flax index a3ae100ca..a0c1124e0 100644 --- a/Content/Editor/Particles/Smoke Material.flax +++ b/Content/Editor/Particles/Smoke Material.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4c8642a2a7024ae38ae371d95c3411d8e362f42ef98c54dc9da9c061d2a568c1 -size 36552 +oid sha256:d55de18eb0aba44d084546b4a5aabf3482219c9b2c14930e9446fe86b711e61c +size 35331 diff --git a/Content/Editor/Primitives/Plane.flax b/Content/Editor/Primitives/Plane.flax index bf2052f02..1e5eaf76b 100644 --- a/Content/Editor/Primitives/Plane.flax +++ b/Content/Editor/Primitives/Plane.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8ab6d87b9f36f4ad231f38f415223130eea7b411be7ff8fd174b6ff7790258fb -size 2232 +oid sha256:5683a761f198d01d7189490d526b55e393eb01fffbd6761fb969d11067b3db73 +size 2277 diff --git a/Content/Editor/Terrain/Circle Brush Material.flax b/Content/Editor/Terrain/Circle Brush Material.flax index dc30e39fe..1a9236d73 100644 --- a/Content/Editor/Terrain/Circle Brush Material.flax +++ b/Content/Editor/Terrain/Circle Brush Material.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bd0529d1088670eb9edd3533f9493d259bcc9797d0e01f12922f6f3b05daef4d -size 33136 +oid sha256:b6cd8240338f9b506ebb6ef42646958d72f6ed10ceae1ec1fa49291e2f4deeb4 +size 27548 diff --git a/Content/Editor/Terrain/Highlight Terrain Material.flax b/Content/Editor/Terrain/Highlight Terrain Material.flax index 49f76cb07..34de2594f 100644 --- a/Content/Editor/Terrain/Highlight Terrain Material.flax +++ b/Content/Editor/Terrain/Highlight Terrain Material.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:820f0265ca7249f84f0c91b402f817dc5ca12a0a5884756b6d0bf712c12a9b06 -size 26815 +oid sha256:ec51e766fb690f614663e39dc466c38d23b44ed08eec927c49f49cd46795e07e +size 21227 diff --git a/Content/Editor/TexturePreviewMaterial.flax b/Content/Editor/TexturePreviewMaterial.flax index 0a68763cd..7a6c1e659 100644 --- a/Content/Editor/TexturePreviewMaterial.flax +++ b/Content/Editor/TexturePreviewMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9029bd6c09c686f551356800b6f16c60f7392fb3a396d2fe583e6cb1f83c46f7 -size 10907 +oid sha256:d36c699f69d20d9d9aaf03011bdcaa2cbdfd42e6ceed7defe3dd70fc8b9e9a78 +size 10653 diff --git a/Content/Editor/Wires Debug Material.flax b/Content/Editor/Wires Debug Material.flax index 9dedbfea2..b1af939b3 100644 --- a/Content/Editor/Wires Debug Material.flax +++ b/Content/Editor/Wires Debug Material.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cb08e24a379cd67db22025bf5e3ae84979f0686179db871387d388f11aae20f0 -size 35356 +oid sha256:da9378517b42e1c7c4e9c7a79d1f86d41f979655d6f8d2900708ae668517e486 +size 28875 diff --git a/Content/Engine/DefaultDeformableMaterial.flax b/Content/Engine/DefaultDeformableMaterial.flax new file mode 100644 index 000000000..197d82b3f --- /dev/null +++ b/Content/Engine/DefaultDeformableMaterial.flax @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d9835db8c0994006b43042b26164ace74c8a2027a9586edc3ec80d16ca75f680 +size 18800 diff --git a/Content/Engine/DefaultMaterial.flax b/Content/Engine/DefaultMaterial.flax index cd7cd19e0..4212fadbd 100644 --- a/Content/Engine/DefaultMaterial.flax +++ b/Content/Engine/DefaultMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d195b3434472636eff8e34a2052f1ef5d02b1415802414bad6e15f21d0b16de8 -size 39864 +oid sha256:757da751c813fd60224d2000faa2de848f36b8555163791b9c852ac7e1c6b59a +size 31806 diff --git a/Content/Engine/DefaultTerrainMaterial.flax b/Content/Engine/DefaultTerrainMaterial.flax index be5145720..2a98d9c36 100644 --- a/Content/Engine/DefaultTerrainMaterial.flax +++ b/Content/Engine/DefaultTerrainMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:98a8347591e895fccbe327f393d6376ad1113321daa8a7ad4f47d0753c2e76a1 -size 28681 +oid sha256:c0a0cd43b9fb5a84e85c4376049db1ef3d2c64dd0cca338ff8f790e6aa1f86a9 +size 23307 diff --git a/Content/Engine/SingleColorMaterial.flax b/Content/Engine/SingleColorMaterial.flax index d11046007..a22fabbb8 100644 --- a/Content/Engine/SingleColorMaterial.flax +++ b/Content/Engine/SingleColorMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:502f451e0b232b4288a85a274d81d22a7053c84f174286a05043707dc75fe193 -size 38381 +oid sha256:889439f17a4523cf18a17c46f4348654cce5bc25c1ab1928e411405d8eacfd99 +size 30130 diff --git a/Content/Engine/SkyboxMaterial.flax b/Content/Engine/SkyboxMaterial.flax index 24d56521b..46e0c9304 100644 --- a/Content/Engine/SkyboxMaterial.flax +++ b/Content/Engine/SkyboxMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e4b60416f9e2deb1f717860ae9a4cc1a74be0517db921526d21a79434b7506e7 -size 39603 +oid sha256:1a5bfb7e11eeccf5c44ee2a284b3d0479a752cb8af470b5b93fd8fe8213947be +size 31328 diff --git a/Content/Shaders/BakeLightmap.flax b/Content/Shaders/BakeLightmap.flax index 6e813205f..fac992469 100644 --- a/Content/Shaders/BakeLightmap.flax +++ b/Content/Shaders/BakeLightmap.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:17295767d488dc2b64a1794ceda8b4d68f20dd2c5f1a8fdbe6f7940a070f9724 +oid sha256:cc4b141137661d995ff571191150c5997fa6f6576572b5c2281c395a12772d7c size 16095 diff --git a/Content/Shaders/BitonicSort.flax b/Content/Shaders/BitonicSort.flax index c8a9e901c..1d5b8a581 100644 --- a/Content/Shaders/BitonicSort.flax +++ b/Content/Shaders/BitonicSort.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c8371a1f4e0631e69f6dc6dbb8e11c8618351a7083293078270d60599f739e9f -size 6725 +oid sha256:f46a61cf8d5183230176e661a51208bfeece16cc7238655f406288ff448af64e +size 6721 diff --git a/Development/Scripts/Linux/CallBuildTool.sh b/Development/Scripts/Linux/CallBuildTool.sh index 3b4568918..1a8affe20 100755 --- a/Development/Scripts/Linux/CallBuildTool.sh +++ b/Development/Scripts/Linux/CallBuildTool.sh @@ -3,6 +3,12 @@ set -e +testfilesize=$(wc -c < 'Source/Logo.png') +if [ $testfilesize -le 1000 ]; then + echo "CallBuildTool ERROR: Repository was not cloned using Git LFS" 1>&2 + exit 1 +fi + # Compile the build tool. xbuild /nologo /verbosity:quiet "Source/Tools/Flax.Build/Flax.Build.csproj" /property:Configuration=Release /property:Platform=AnyCPU /target:Build diff --git a/Development/Scripts/Windows/CallBuildTool.bat b/Development/Scripts/Windows/CallBuildTool.bat index 4d9124776..c9729cf55 100644 --- a/Development/Scripts/Windows/CallBuildTool.bat +++ b/Development/Scripts/Windows/CallBuildTool.bat @@ -4,6 +4,10 @@ rem Copyright (c) 2012-2020 Wojciech Figat. All rights reserved. if not exist "Development\Scripts\Windows\GetMSBuildPath.bat" goto Error_InvalidLocation +for %%I in (Source\Logo.png) do if %%~zI LSS 2000 ( + goto Error_MissingLFS +) + call "Development\Scripts\Windows\GetMSBuildPath.bat" if errorlevel 1 goto Error_NoVisualStudioEnvironment @@ -33,6 +37,9 @@ Binaries\Tools\Flax.Build.exe %* if errorlevel 1 goto Error_FlaxBuildFailed exit /B 0 +:Error_MissingLFS +echo CallBuildTool ERROR: Repository was not cloned using Git LFS +goto Exit :Error_InvalidLocation echo CallBuildTool ERROR: The script is in invalid directory. goto Exit diff --git a/Flax.flaxproj b/Flax.flaxproj index 26fb340ba..0c2683afb 100644 --- a/Flax.flaxproj +++ b/Flax.flaxproj @@ -3,7 +3,7 @@ "Version": { "Major": 1, "Minor": 0, - "Build": 6215 + "Build": 6216 }, "Company": "Flax", "Copyright": "Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.", diff --git a/GenerateProjectFiles.bat b/GenerateProjectFiles.bat index 5db8aaf55..aeeac1c67 100644 --- a/GenerateProjectFiles.bat +++ b/GenerateProjectFiles.bat @@ -16,6 +16,7 @@ exit /B 0 :BuildToolFailed echo Flax.Build tool failed. +pause goto Exit :Exit diff --git a/README.md b/README.md index 51628caf0..7a664b7c4 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,10 @@ This repository contains full source code of the Flax (excluding NDA-protected p Follow the instructions below to compile and run the engine from source. +## Flax plugin for Visual Studio + +Flax Visual Studio extension provides better programming workflow, C# scripts debugging functionality and allows to attach to running engine instance to debug C# source. This extension is available to download [here](https://marketplace.visualstudio.com/items?itemName=Flax.FlaxVS). + ## Windows * Install Visual Studio 2015 or newer @@ -34,7 +38,8 @@ Follow the instructions below to compile and run the engine from source. * Clone repo (with LFS) * Run **GenerateProjectFiles.bat** * Open `Flax.sln` and set solution configuration to **Editor.Development** and solution platform to **Win64** -* Compile Flax project (hit F7 key) +* Set Flax or FlaxEngine as startup project +* Compile Flax project (hit F7 or CTRL+Shift+B) * Run Flax (hit F5 key) ## Linux @@ -49,10 +54,6 @@ Follow the instructions below to compile and run the engine from source. * Open workspace with Visual Code * Build and run -# Flax plugin for Visual Studio - -Flax Visual Studio extension provides better programming workflow, C# scripts debugging functionality and allows to attach to running engine instance to debug C# source. This extension is available to download [here](https://marketplace.visualstudio.com/items?itemName=Flax.FlaxVS). - ## Workspace directory - **Binaries/** - executable files diff --git a/Source/Editor/Content/Import/ModelImportEntry.cs b/Source/Editor/Content/Import/ModelImportEntry.cs index e7cbe96ea..37525e6d3 100644 --- a/Source/Editor/Content/Import/ModelImportEntry.cs +++ b/Source/Editor/Content/Import/ModelImportEntry.cs @@ -262,9 +262,9 @@ namespace FlaxEditor.Content.Import public int BaseLOD { get; set; } = 0; /// - /// The amount of LODs to include in the model (all reaming ones starting from Base LOD will be generated). + /// The amount of LODs to include in the model (all remaining ones starting from Base LOD will be generated). /// - [EditorOrder(1120), DefaultValue(4), Limit(1, Model.MaxLODs), EditorDisplay("Level Of Detail", "LOD Count"), Tooltip("The amount of LODs to include in the model (all reaming ones starting from Base LOD will be generated).")] + [EditorOrder(1120), DefaultValue(4), Limit(1, Model.MaxLODs), EditorDisplay("Level Of Detail", "LOD Count"), Tooltip("The amount of LODs to include in the model (all remaining ones starting from Base LOD will be generated).")] public int LODCount { get; set; } = 4; /// diff --git a/Source/Editor/Content/Items/VisualScriptItem.cs b/Source/Editor/Content/Items/VisualScriptItem.cs index 86be2e3de..fd23887ec 100644 --- a/Source/Editor/Content/Items/VisualScriptItem.cs +++ b/Source/Editor/Content/Items/VisualScriptItem.cs @@ -50,6 +50,9 @@ namespace FlaxEditor.Content /// public bool IsMethod => false; + /// + public bool IsEvent => false; + /// public bool HasGet => true; @@ -174,6 +177,9 @@ namespace FlaxEditor.Content /// public bool IsMethod => true; + /// + public bool IsEvent => false; + /// public bool HasGet => false; diff --git a/Source/Editor/Content/Proxy/PrefabProxy.cs b/Source/Editor/Content/Proxy/PrefabProxy.cs index 39d77f25c..276bc0d30 100644 --- a/Source/Editor/Content/Proxy/PrefabProxy.cs +++ b/Source/Editor/Content/Proxy/PrefabProxy.cs @@ -162,8 +162,7 @@ namespace FlaxEditor.Content // Auto fit actor to camera float targetSize = 30.0f; - BoundingBox bounds; - Editor.GetActorEditorBox(_preview.Instance, out bounds); + Editor.GetActorEditorBox(_preview.Instance, out var bounds); float maxSize = Mathf.Max(0.001f, bounds.Size.MaxValue); _preview.Instance.Scale = new Vector3(targetSize / maxSize); _preview.Instance.Position = Vector3.Zero; @@ -175,6 +174,7 @@ namespace FlaxEditor.Content /// public override void OnThumbnailDrawEnd(ThumbnailRequest request, ContainerControl guiRoot) { + _preview.RemoveChildren(); _preview.Prefab = null; _preview.Parent = null; } diff --git a/Source/Editor/Content/Settings/AndroidPlatformSettings.cs b/Source/Editor/Content/Settings/AndroidPlatformSettings.cs deleted file mode 100644 index 2c540109f..000000000 --- a/Source/Editor/Content/Settings/AndroidPlatformSettings.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -using FlaxEngine; - -namespace FlaxEditor.Content.Settings -{ - /// - /// The Android platform settings asset archetype. Allows to edit asset via editor. - /// - public class AndroidPlatformSettings : SettingsBase - { - /// - /// The application package name (eg. com.company.product). Custom tokens: ${PROJECT_NAME}, ${COMPANY_NAME}. - /// - [EditorOrder(0), EditorDisplay("General"), Tooltip("The application package name (eg. com.company.product). Custom tokens: ${PROJECT_NAME}, ${COMPANY_NAME}.")] - public string PackageName = "com.${COMPANY_NAME}.${PROJECT_NAME}"; - - /// - /// The application permissions list (eg. android.media.action.IMAGE_CAPTURE). Added to the generated manifest file. - /// - [EditorOrder(100), EditorDisplay("General"), Tooltip("The application permissions list (eg. android.media.action.IMAGE_CAPTURE). Added to the generated manifest file.")] - public string[] Permissions; - - /// - /// Custom icon texture to use for the application (overrides the default one). - /// - [EditorOrder(1030), EditorDisplay("Other"), Tooltip("Custom icon texture to use for the application (overrides the default one).")] - public Texture OverrideIcon; - } -} diff --git a/Source/Editor/Content/Settings/AudioSettings.cs b/Source/Editor/Content/Settings/AudioSettings.cs deleted file mode 100644 index f011cad46..000000000 --- a/Source/Editor/Content/Settings/AudioSettings.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -using System.ComponentModel; -using FlaxEngine; - -namespace FlaxEditor.Content.Settings -{ - /// - /// The audio payback engine settings container. Allows to edit asset via editor. - /// - public sealed class AudioSettings : SettingsBase - { - /// - /// If checked, audio playback will be disabled in build game. Can be used if game uses custom audio playback engine. - /// - [DefaultValue(false)] - [EditorOrder(0), EditorDisplay("General"), Tooltip("If checked, audio playback will be disabled in build game. Can be used if game uses custom audio playback engine.")] - public bool DisableAudio; - - /// - /// The doppler doppler effect factor. Scale for source and listener velocities. Default is 1. - /// - [DefaultValue(1.0f)] - [EditorOrder(100), EditorDisplay("General"), Limit(0, 10.0f, 0.01f), Tooltip("The doppler doppler effect factor. Scale for source and listener velocities. Default is 1.")] - public float DopplerFactor = 1.0f; - - /// - /// True if mute all audio playback when game has no use focus. - /// - [DefaultValue(true)] - [EditorOrder(200), EditorDisplay("General", "Mute On Focus Loss"), Tooltip("If checked, engine will mute all audio playback when game has no use focus.")] - public bool MuteOnFocusLoss = true; - } -} diff --git a/Source/Editor/Content/Settings/BuildSettings.cs b/Source/Editor/Content/Settings/BuildSettings.cs index e74a5fd41..5dc6310c3 100644 --- a/Source/Editor/Content/Settings/BuildSettings.cs +++ b/Source/Editor/Content/Settings/BuildSettings.cs @@ -1,77 +1,12 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; -using System.ComponentModel; using FlaxEngine; namespace FlaxEditor.Content.Settings { - /// - /// The game building settings container. Allows to edit asset via editor. - /// - public sealed class BuildSettings : SettingsBase + partial class BuildSettings { - /// - /// The maximum amount of assets to include into a single assets package. Assets will be split into several packages if need to. - /// - [DefaultValue(4096)] - [EditorOrder(10), Limit(32, short.MaxValue), EditorDisplay("General", "Max assets per package"), Tooltip("The maximum amount of assets to include into a single assets package. Assets will be split into several packages if need to.")] - public int MaxAssetsPerPackage = 4096; - - /// - /// The maximum size of the single assets package (in megabytes). Assets will be split into several packages if need to. - /// - [DefaultValue(1024)] - [EditorOrder(20), Limit(16, short.MaxValue), EditorDisplay("General", "Max package size (in MB)"), Tooltip("The maximum size of the single assets package (in megabytes). Assets will be split into several packages if need to.")] - public int MaxPackageSizeMB = 1024; - - /// - /// The game content cooking Keys. Use the same value for a game and DLC packages to support loading them by the build game. Use 0 to randomize it during building. - /// - [DefaultValue(0)] - [EditorOrder(30), EditorDisplay("General"), Tooltip("The game content cooking Keys. Use the same value for a game and DLC packages to support loading them by the build game. Use 0 to randomize it during building.")] - public int ContentKey = 0; - - /// - /// If checked, the builds produced by the Game Cooker will be treated as for final game distribution (eg. for game store upload). Builds done this way cannot be tested on console devkits (eg. Xbox One, Xbox Scarlett). - /// - [DefaultValue(false)] - [EditorOrder(40), EditorDisplay("General"), Tooltip("If checked, the builds produced by the Game Cooker will be treated as for final game distribution (eg. for game store upload). Builds done this way cannot be tested on console devkits (eg. Xbox One, Xbox Scarlett).")] - public bool ForDistribution; - - /// - /// If checked, the output build files won't be packaged for the destination platform. Useful when debugging build from local PC. - /// - [DefaultValue(false)] - [EditorOrder(50), EditorDisplay("General"), Tooltip("If checked, the output build files won't be packaged for the destination platform. Useful when debugging build from local PC.")] - public bool SkipPackaging; - - /// - /// The additional assets to include into build (into root assets set). - /// - [EditorOrder(1000), EditorDisplay("Additional Data"), Tooltip("The additional assets to include into build (into root assets set).")] - public Asset[] AdditionalAssets; - - /// - /// The additional folders with assets to include into build (into root assets set). List of paths relative to the project directory (or absolute). - /// - [EditorOrder(1010), EditorDisplay("Additional Data"), Tooltip("The additional folders with assets to include into build (to root assets set). List of paths relative to the project directory (or absolute).")] - public string[] AdditionalAssetFolders; - - /// - /// Disables shaders compiler optimizations in cooked game. Can be used to debug shaders on a target platform or to speed up the shaders compilation time. - /// - [DefaultValue(false)] - [EditorOrder(2000), EditorDisplay("Content", "Shaders No Optimize"), Tooltip("Disables shaders compiler optimizations in cooked game. Can be used to debug shaders on a target platform or to speed up the shaders compilation time.")] - public bool ShadersNoOptimize; - - /// - /// Enables shader debug data generation for shaders in cooked game (depends on the target platform rendering backend). - /// - [DefaultValue(false)] - [EditorOrder(2010), EditorDisplay("Content"), Tooltip("Enables shader debug data generation for shaders in cooked game (depends on the target platform rendering backend).")] - public bool ShadersGenerateDebugData; - /// /// The build presets. /// @@ -90,13 +25,6 @@ namespace FlaxEditor.Content.Settings Platform = BuildPlatform.Windows64, Mode = BuildConfiguration.Development, }, - new BuildTarget - { - Name = "Windows 32bit", - Output = "Output\\Win32", - Platform = BuildPlatform.Windows32, - Mode = BuildConfiguration.Development, - }, } }, new BuildPreset @@ -109,14 +37,7 @@ namespace FlaxEditor.Content.Settings Name = "Windows 64bit", Output = "Output\\Win64", Platform = BuildPlatform.Windows64, - Mode = BuildConfiguration.Development, - }, - new BuildTarget - { - Name = "Windows 32bit", - Output = "Output\\Win32", - Platform = BuildPlatform.Windows32, - Mode = BuildConfiguration.Development, + Mode = BuildConfiguration.Release, }, } }, diff --git a/Source/Editor/Content/Settings/BuildTarget.cs b/Source/Editor/Content/Settings/BuildTarget.cs index 7cdc500f6..97c0b8c3d 100644 --- a/Source/Editor/Content/Settings/BuildTarget.cs +++ b/Source/Editor/Content/Settings/BuildTarget.cs @@ -35,6 +35,12 @@ namespace FlaxEditor.Content.Settings [EditorOrder(30), Tooltip("Configuration build mode")] public BuildConfiguration Mode; + /// + /// The list of custom defines passed to the build tool when compiling project scripts. Can be used in build scripts for configuration (Configuration.CustomDefines). + /// + [EditorOrder(90), Tooltip("The list of custom defines passed to the build tool when compiling project scripts. Can be used in build scripts for configuration (Configuration.CustomDefines).")] + public string[] CustomDefines; + /// /// The pre-build action command line. /// @@ -46,11 +52,5 @@ namespace FlaxEditor.Content.Settings /// [EditorOrder(110)] public string PostBuildAction; - - /// - /// Gets the build options computed from the target configuration. - /// - [HideInEditor, NoSerialize] - public virtual BuildOptions Options => BuildOptions.None; } } diff --git a/Source/Editor/Content/Settings/GameSettings.cs b/Source/Editor/Content/Settings/GameSettings.cs index 28c13c905..eaca85c55 100644 --- a/Source/Editor/Content/Settings/GameSettings.cs +++ b/Source/Editor/Content/Settings/GameSettings.cs @@ -7,32 +7,11 @@ using FlaxEngine; namespace FlaxEditor.Content.Settings { - /// - /// The game settings asset archetype. Allows to edit asset via editor. - /// - public sealed class GameSettings : SettingsBase + partial class GameSettings { internal const string PS4PlatformSettingsTypename = "FlaxEditor.Content.Settings.PS4PlatformSettings"; internal const string XboxScarlettPlatformSettingsTypename = "FlaxEditor.Content.Settings.XboxScarlettPlatformSettings"; - /// - /// The product full name. - /// - [EditorOrder(0), EditorDisplay("General"), Tooltip("The name of your product.")] - public string ProductName; - - /// - /// The company full name. - /// - [EditorOrder(10), EditorDisplay("General"), Tooltip("The name of your company or organization.")] - public string CompanyName; - - /// - /// The copyright note used for content signing (eg. source code header). - /// - [EditorOrder(15), EditorDisplay("General"), Tooltip("The copyright note used for content signing (eg. source code header).")] - public string CopyrightNotice; - /// /// The default application icon. /// diff --git a/Source/Editor/Content/Settings/GraphicsSettings.cs b/Source/Editor/Content/Settings/GraphicsSettings.cs deleted file mode 100644 index 731bc750d..000000000 --- a/Source/Editor/Content/Settings/GraphicsSettings.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -using FlaxEngine; - -namespace FlaxEditor.Content.Settings -{ - /// - /// The graphics rendering settings container. Allows to edit asset via editor. To modify those settings at runtime use . - /// - /// - public sealed class GraphicsSettings : SettingsBase - { - /// - /// Enables rendering synchronization with the refresh rate of the display device to avoid "tearing" artifacts. - /// - [EditorOrder(20), EditorDisplay("General", "Use V-Sync"), Tooltip("Enables rendering synchronization with the refresh rate of the display device to avoid \"tearing\" artifacts.")] - public bool UseVSync = false; - - /// - /// Anti Aliasing quality setting. - /// - [EditorOrder(1000), EditorDisplay("Quality", "AA Quality"), Tooltip("Anti Aliasing quality.")] - public Quality AAQuality = Quality.Medium; - - /// - /// Screen Space Reflections quality. - /// - [EditorOrder(1100), EditorDisplay("Quality", "SSR Quality"), Tooltip("Screen Space Reflections quality.")] - public Quality SSRQuality = Quality.Medium; - - /// - /// Screen Space Ambient Occlusion quality setting. - /// - [EditorOrder(1200), EditorDisplay("Quality", "SSAO Quality"), Tooltip("Screen Space Ambient Occlusion quality setting.")] - public Quality SSAOQuality = Quality.Medium; - - /// - /// Volumetric Fog quality setting. - /// - [EditorOrder(1250), EditorDisplay("Quality", "Volumetric Fog Quality"), Tooltip("Volumetric Fog quality setting.")] - public Quality VolumetricFogQuality = Quality.High; - - /// - /// The shadows quality. - /// - [EditorOrder(1300), EditorDisplay("Quality", "Shadows Quality"), Tooltip("The shadows quality.")] - public Quality ShadowsQuality = Quality.Medium; - - /// - /// The shadow maps quality (textures resolution). - /// - [EditorOrder(1310), EditorDisplay("Quality", "Shadow Maps Quality"), Tooltip("The shadow maps quality (textures resolution).")] - public Quality ShadowMapsQuality = Quality.Medium; - - /// - /// Enables cascades splits blending for directional light shadows. - /// - [EditorOrder(1320), EditorDisplay("Quality", "Allow CSM Blending"), Tooltip("Enables cascades splits blending for directional light shadows.")] - public bool AllowCSMBlending = false; - } -} diff --git a/Source/Editor/Content/Settings/InputSettings.cs b/Source/Editor/Content/Settings/InputSettings.cs index 69a8dfba3..03ed4b7c7 100644 --- a/Source/Editor/Content/Settings/InputSettings.cs +++ b/Source/Editor/Content/Settings/InputSettings.cs @@ -4,10 +4,7 @@ using FlaxEngine; namespace FlaxEditor.Content.Settings { - /// - /// The input settings container. Allows to edit asset via editor. - /// - public sealed class InputSettings : SettingsBase + partial class InputSettings { /// /// Maps a discrete button or key press events to a "friendly name" that will later be bound to event-driven behavior. The end effect is that pressing (and/or releasing) a key, mouse button, or keypad button. diff --git a/Source/Editor/Content/Settings/LayersAndTagsSettings.cs b/Source/Editor/Content/Settings/LayersAndTagsSettings.cs index 5f3884ee1..31e8dfff8 100644 --- a/Source/Editor/Content/Settings/LayersAndTagsSettings.cs +++ b/Source/Editor/Content/Settings/LayersAndTagsSettings.cs @@ -6,10 +6,7 @@ using FlaxEngine; namespace FlaxEditor.Content.Settings { - /// - /// The layers and objects tags settings. Allows to edit asset via editor. - /// - public sealed class LayersAndTagsSettings : SettingsBase + partial class LayersAndTagsSettings { /// /// The tag names. diff --git a/Source/Editor/Content/Settings/LinuxPlatformSettings.cs b/Source/Editor/Content/Settings/LinuxPlatformSettings.cs deleted file mode 100644 index 8b26e77d4..000000000 --- a/Source/Editor/Content/Settings/LinuxPlatformSettings.cs +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -using FlaxEngine; - -namespace FlaxEditor.Content.Settings -{ - /// - /// The Linux platform settings asset archetype. Allows to edit asset via editor. - /// - public class LinuxPlatformSettings : SettingsBase - { - /// - /// The default game window mode. - /// - [EditorOrder(10), EditorDisplay("Window"), Tooltip("The default game window mode.")] - public GameWindowMode WindowMode = GameWindowMode.Windowed; - - /// - /// The default game window width (in pixels). - /// - [EditorOrder(20), EditorDisplay("Window"), Tooltip("The default game window width (in pixels).")] - public int ScreenWidth = 1280; - - /// - /// The default game window height (in pixels). - /// - [EditorOrder(30), EditorDisplay("Window"), Tooltip("The default game window height (in pixels).")] - public int ScreenHeight = 720; - - /// - /// Enables resizing the game window by the user. - /// - [EditorOrder(40), EditorDisplay("Window"), Tooltip("Enables resizing the game window by the user.")] - public bool ResizableWindow = false; - - /// - /// Enables game running when application window loses focus. - /// - [EditorOrder(1010), EditorDisplay("Other", "Run In Background"), Tooltip("Enables game running when application window loses focus.")] - public bool RunInBackground = false; - - /// - /// Limits maximum amount of concurrent game instances running to one, otherwise user may launch application more than once. - /// - [EditorOrder(1020), EditorDisplay("Other"), Tooltip("Limits maximum amount of concurrent game instances running to one, otherwise user may launch application more than once.")] - public bool ForceSingleInstance = false; - - /// - /// Custom icon texture to use for the application (overrides the default one). - /// - [EditorOrder(1030), EditorDisplay("Other"), Tooltip("Custom icon texture to use for the application (overrides the default one).")] - public Texture OverrideIcon; - - /// - /// Enables support for Vulkan. Disabling it reduces compiled shaders count. - /// - [EditorOrder(2020), EditorDisplay("Graphics", "Support Vulkan"), Tooltip("Enables support for Vulkan. Disabling it reduces compiled shaders count.")] - public bool SupportVulkan = true; - } -} diff --git a/Source/Editor/Content/Settings/NavigationSettings.cs b/Source/Editor/Content/Settings/NavigationSettings.cs deleted file mode 100644 index cba8f2688..000000000 --- a/Source/Editor/Content/Settings/NavigationSettings.cs +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -using System.ComponentModel; -using FlaxEngine; - -namespace FlaxEditor.Content.Settings -{ - /// - /// The navigation system settings container. - /// - public sealed class NavigationSettings : SettingsBase - { - /// - /// The height of a grid cell in the navigation mesh building steps using heightfields. - /// A lower number means higher precision on the vertical axis but longer build times. - /// - [DefaultValue(10.0f), Limit(1, 400)] - [EditorOrder(10), EditorDisplay("Nav Mesh Options"), Tooltip("The height of a grid cell in the navigation mesh building steps using heightfields. A lower number means higher precision on the vertical axis but longer build times.")] - public float CellHeight = 10.0f; - - /// - /// The width/height of a grid cell in the navigation mesh building steps using heightfields. - /// A lower number means higher precision on the horizontal axes but longer build times. - /// - [DefaultValue(30.0f), Limit(1, 400)] - [EditorOrder(20), EditorDisplay("Nav Mesh Options"), Tooltip("The width/height of a grid cell in the navigation mesh building steps using heightfields. A lower number means higher precision on the vertical axis but longer build times.")] - public float CellSize = 30.0f; - - /// - /// Tile size used for Navigation mesh tiles, the final size of a tile is CellSize*TileSize. - /// - [DefaultValue(64), Limit(8, 4096)] - [EditorOrder(30), EditorDisplay("Nav Mesh Options"), Tooltip("Tile size used for Navigation mesh tiles, the final size of a tile is CellSize*TileSize.")] - public int TileSize = 64; - - /// - /// The minimum number of cells allowed to form isolated island areas. - /// - [DefaultValue(0), Limit(0, 100)] - [EditorOrder(40), EditorDisplay("Nav Mesh Options"), Tooltip("The minimum number of cells allowed to form isolated island areas.")] - public int MinRegionArea = 0; - - /// - /// Any regions with a span count smaller than this value will, if possible, be merged with larger regions. - /// - [DefaultValue(20), Limit(0, 100)] - [EditorOrder(50), EditorDisplay("Nav Mesh Options"), Tooltip("Any regions with a span count smaller than this value will, if possible, be merged with larger regions.")] - public int MergeRegionArea = 20; - - /// - /// The maximum allowed length for contour edges along the border of the mesh. - /// - [DefaultValue(1200.0f), Limit(100)] - [EditorOrder(60), EditorDisplay("Nav Mesh Options", "Max Edge Length"), Tooltip("The maximum allowed length for contour edges along the border of the mesh.")] - public float MaxEdgeLen = 1200.0f; - - /// - /// The maximum distance a simplified contour's border edges should deviate from the original raw contour. - /// - [DefaultValue(1.3f), Limit(0.1f, 4)] - [EditorOrder(70), EditorDisplay("Nav Mesh Options"), Tooltip("The maximum distance a simplified contour's border edges should deviate from the original raw contour.")] - public float MaxEdgeError = 1.3f; - - /// - /// The sampling distance to use when generating the detail mesh. For height detail only. - /// - [DefaultValue(600.0f), Limit(1)] - [EditorOrder(80), EditorDisplay("Nav Mesh Options", "Detail Sampling Distance"), Tooltip("The sampling distance to use when generating the detail mesh.")] - public float DetailSamplingDist = 600.0f; - - /// - /// The maximum distance the detail mesh surface should deviate from heightfield data. For height detail only. - /// - [DefaultValue(1.0f), Limit(0, 3)] - [EditorOrder(90), EditorDisplay("Nav Mesh Options"), Tooltip("The maximum distance the detail mesh surface should deviate from heightfield data.")] - public float MaxDetailSamplingError = 1.0f; - - /// - /// The radius of the smallest objects to traverse this nav mesh. Objects can't pass through gaps of less than twice the radius. - /// - [DefaultValue(34.0f), Limit(0)] - [EditorOrder(1000), EditorDisplay("Agent Options"), Tooltip("The radius of the smallest objects to traverse this nav mesh. Objects can't pass through gaps of less than twice the radius.")] - public float WalkableRadius = 34.0f; - - /// - /// The height of the smallest objects to traverse this nav mesh. Objects can't enter areas with ceilings lower than this value. - /// - [DefaultValue(144.0f), Limit(0)] - [EditorOrder(1010), EditorDisplay("Agent Options"), Tooltip("The height of the smallest objects to traverse this nav mesh. Objects can't enter areas with ceilings lower than this value.")] - public float WalkableHeight = 144.0f; - - /// - /// The maximum ledge height that is considered to still be traversable. - /// - [DefaultValue(35.0f), Limit(0)] - [EditorOrder(1020), EditorDisplay("Agent Options"), Tooltip("The maximum ledge height that is considered to still be traversable.")] - public float WalkableMaxClimb = 35.0f; - - /// - /// The maximum slope that is considered walkable (in degrees). Objects can't go up or down slopes higher than this value. - /// - [DefaultValue(60.0f), Limit(0, 89.0f)] - [EditorOrder(1030), EditorDisplay("Agent Options"), Tooltip("The maximum slope that is considered walkable (in degrees). Objects can't go up or down slopes higher than this value.")] - public float WalkableMaxSlopeAngle = 60.0f; - } -} diff --git a/Source/Editor/Content/Settings/PhysicsSettings.cs b/Source/Editor/Content/Settings/PhysicsSettings.cs index 540b11772..d217512b3 100644 --- a/Source/Editor/Content/Settings/PhysicsSettings.cs +++ b/Source/Editor/Content/Settings/PhysicsSettings.cs @@ -1,112 +1,17 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. -using System.ComponentModel; using FlaxEngine; namespace FlaxEditor.Content.Settings { - /// - /// The physics simulation settings container. Allows to edit asset via editor. - /// - public sealed class PhysicsSettings : SettingsBase + partial class PhysicsSettings { - /// - /// The default gravity force value (in cm^2/s). - /// - [DefaultValue(typeof(Vector3), "0,-981.0,0")] - [EditorOrder(0), EditorDisplay("Simulation"), Tooltip("The default gravity force value (in cm^2/s).")] - public Vector3 DefaultGravity = new Vector3(0, -981.0f, 0); - - /// - /// If enabled, any Raycast or other scene query that intersects with a Collider marked as a Trigger will returns with a hit. Individual raycasts can override this behavior. - /// - [DefaultValue(true)] - [EditorOrder(10), EditorDisplay("Simulation"), Tooltip("If enabled, any Raycast or other scene query that intersects with a Collider marked as a Trigger will returns with a hit. Individual raycasts can override this behavior.")] - public bool QueriesHitTriggers = true; - - /// - /// Triangles from triangle meshes (CSG) with an area less than or equal to this value will be removed from physics collision data. Set to less than or equal 0 to disable. - /// - [DefaultValue(5.0f)] - [EditorOrder(20), EditorDisplay("Simulation"), Limit(-1, 10), Tooltip("Triangles from triangle meshes (CSG) with an area less than or equal to this value will be removed from physics collision data. Set to less than or equal 0 to disable.")] - public float TriangleMeshTriangleMinAreaThreshold = 5.0f; - - /// - /// Minimum relative velocity required for an object to bounce. A typical value for simulation stability is about 0.2 * gravity - /// - [DefaultValue(200.0f)] - [EditorOrder(30), EditorDisplay("Simulation"), Limit(0), Tooltip("Minimum relative velocity required for an object to bounce. A typical value for simulation stability is about 0.2 * gravity")] - public float BounceThresholdVelocity = 200.0f; - - /// - /// Default friction combine mode, controls how friction is computed for multiple materials. - /// - [DefaultValue(PhysicsCombineMode.Average)] - [EditorOrder(40), EditorDisplay("Simulation"), Tooltip("Default friction combine mode, controls how friction is computed for multiple materials.")] - public PhysicsCombineMode FrictionCombineMode = PhysicsCombineMode.Average; - - /// - /// Default restitution combine mode, controls how restitution is computed for multiple materials. - /// - [DefaultValue(PhysicsCombineMode.Average)] - [EditorOrder(50), EditorDisplay("Simulation"), Tooltip("Default restitution combine mode, controls how restitution is computed for multiple materials.")] - public PhysicsCombineMode RestitutionCombineMode = PhysicsCombineMode.Average; - - /// - /// If true CCD will be ignored. This is an optimization when CCD is never used which removes the need for PhysX to check it internally. - /// - [DefaultValue(false)] - [EditorOrder(70), EditorDisplay("Simulation", "Disable CCD"), Tooltip("If true CCD will be ignored. This is an optimization when CCD is never used which removes the need for PhysX to check it internally.")] - public bool DisableCCD; - - /// - /// Enables adaptive forces to accelerate convergence of the solver. Can improve physics simulation performance but lead to artifacts. - /// - [DefaultValue(false)] - [EditorOrder(80), EditorDisplay("Simulation"), Tooltip("Enables adaptive forces to accelerate convergence of the solver. Can improve physics simulation performance but lead to artifacts.")] - public bool EnableAdaptiveForce; - - /// - /// The maximum allowed delta time (in seconds) for the physics simulation step. - /// - [DefaultValue(1.0f / 10.0f)] - [EditorOrder(1000), EditorDisplay("Framerate"), Limit(0.0013f, 2.0f), Tooltip("The maximum allowed delta time (in seconds) for the physics simulation step.")] - public float MaxDeltaTime = 1.0f / 10.0f; - - /// - /// Whether to substep the physics simulation. - /// - [DefaultValue(false)] - [EditorOrder(1005), EditorDisplay("Framerate"), Tooltip("Whether to substep the physics simulation.")] - public bool EnableSubstepping; - - /// - /// Delta time (in seconds) for an individual simulation substep. - /// - [DefaultValue(1.0f / 120.0f)] - [EditorOrder(1010), EditorDisplay("Framerate"), Limit(0.0013f, 1.0f), Tooltip("Delta time (in seconds) for an individual simulation substep.")] - public float SubstepDeltaTime = 1.0f / 120.0f; - - /// - /// The maximum number of substeps for physics simulation. - /// - [DefaultValue(5)] - [EditorOrder(1020), EditorDisplay("Framerate"), Limit(1, 16), Tooltip("The maximum number of substeps for physics simulation.")] - public int MaxSubsteps = 5; - /// /// The collision layers masks. Used to define layer-based collision detection. /// [EditorOrder(1040), EditorDisplay("Layers Matrix"), CustomEditor(typeof(FlaxEditor.CustomEditors.Dedicated.LayersMatrixEditor))] public uint[] LayerMasks = new uint[32]; - /// - /// Enables support for cooking physical collision shapes geometry at runtime. Use it to enable generating runtime terrain collision or convex mesh colliders. - /// - [DefaultValue(false)] - [EditorOrder(1100), EditorDisplay("Other", "Support Cooking At Runtime"), Tooltip("Enables support for cooking physical collision shapes geometry at runtime. Use it to enable generating runtime terrain collision or convex mesh colliders.")] - public bool SupportCookingAtRuntime; - /// /// Initializes a new instance of the class. /// diff --git a/Source/Editor/Content/Settings/TimeSettings.cs b/Source/Editor/Content/Settings/TimeSettings.cs deleted file mode 100644 index af352f5e1..000000000 --- a/Source/Editor/Content/Settings/TimeSettings.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -using System.ComponentModel; -using FlaxEngine; - -namespace FlaxEditor.Content.Settings -{ - /// - /// The time settings asset archetype. Allows to edit asset via editor. - /// - public sealed class TimeSettings : SettingsBase - { - /// - /// The target amount of the game logic updates per second (script updates frequency). Use 0 for infinity. - /// - [DefaultValue(30.0f)] - [EditorOrder(1), Limit(0, 1000), EditorDisplay(null, "Update FPS"), Tooltip("Target amount of the game logic updates per second (script updates frequency). Use 0 for infinity.")] - public float UpdateFPS = 30.0f; - - /// - /// The target amount of the physics simulation updates per second (also fixed updates frequency). Use 0 for infinity. - /// - [DefaultValue(60.0f)] - [EditorOrder(2), Limit(0, 1000), EditorDisplay(null, "Physics FPS"), Tooltip("Target amount of the physics simulation updates per second (also fixed updates frequency). Use 0 for infinity.")] - public float PhysicsFPS = 60.0f; - - /// - /// The target amount of the frames rendered per second (actual game FPS). Use 0 for infinity. - /// - [DefaultValue(60.0f)] - [EditorOrder(3), Limit(0, 1000), EditorDisplay(null, "Draw FPS"), Tooltip("Target amount of the frames rendered per second (actual game FPS). Use 0 for infinity.")] - public float DrawFPS = 60.0f; - - /// - /// The game time scale factor. Default is 1. - /// - [DefaultValue(1.0f)] - [EditorOrder(10), Limit(0, 1000.0f, 0.1f), Tooltip("Game time scaling factor. Default is 1 for real-time simulation.")] - public float TimeScale = 1.0f; - - /// - /// The maximum allowed delta time (in seconds) for the game logic update step. - /// - [DefaultValue(1.0f / 10.0f)] - [EditorOrder(20), Limit(0.1f, 1000.0f, 0.01f), Tooltip("The maximum allowed delta time (in seconds) for the game logic update step.")] - public float MaxUpdateDeltaTime = 1.0f / 10.0f; - } -} diff --git a/Source/Editor/Content/Settings/UWPPlatformSettings.cs b/Source/Editor/Content/Settings/UWPPlatformSettings.cs deleted file mode 100644 index 5c5696936..000000000 --- a/Source/Editor/Content/Settings/UWPPlatformSettings.cs +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -using System; -using System.ComponentModel; -using FlaxEngine; - -namespace FlaxEditor.Content.Settings -{ - /// - /// The Universal Windows Platform (UWP) platform settings asset archetype. Allows to edit asset via editor. - /// - public class UWPPlatformSettings : SettingsBase - { - /// - /// The preferred launch windowing mode. - /// - public enum WindowMode - { - /// - /// The full screen mode - /// - FullScreen = 0, - - /// - /// The view size. - /// - ViewSize = 1, - } - - /// - /// The display orientation modes. Can be combined as flags. - /// - [Flags] - public enum DisplayOrientations - { - /// - /// The none. - /// - None = 0, - - /// - /// The landscape. - /// - Landscape = 1, - - /// - /// The landscape flipped. - /// - LandscapeFlipped = 2, - - /// - /// The portrait. - /// - Portrait = 4, - - /// - /// The portrait flipped. - /// - PortraitFlipped = 8, - } - - /// - /// The preferred launch windowing mode. Always fullscreen on Xbox. - /// - [DefaultValue(WindowMode.FullScreen)] - [EditorOrder(10), EditorDisplay("Window"), Tooltip("The preferred launch windowing mode. Always fullscreen on Xbox.")] - public WindowMode PreferredLaunchWindowingMode = WindowMode.FullScreen; - - /// - /// The display orientation modes. Can be combined as flags. - /// - [DefaultValue(DisplayOrientations.Landscape | DisplayOrientations.LandscapeFlipped | DisplayOrientations.Portrait | DisplayOrientations.PortraitFlipped)] - [EditorOrder(20), EditorDisplay("Window"), Tooltip("The display orientation modes. Can be combined as flags.")] - public DisplayOrientations AutoRotationPreferences = DisplayOrientations.Landscape | DisplayOrientations.LandscapeFlipped | DisplayOrientations.Portrait | DisplayOrientations.PortraitFlipped; - - /// - /// The location of the package certificate (relative to the project). - /// - [DefaultValue("")] - [EditorOrder(1010), EditorDisplay("Other"), Tooltip("The location of the package certificate (relative to the project).")] - public string CertificateLocation = string.Empty; - - /// - /// Enables support for DirectX 11. Disabling it reduces compiled shaders count. - /// - [DefaultValue(true)] - [EditorOrder(2000), EditorDisplay("Graphics", "Support DirectX 11"), Tooltip("Enables support for DirectX 11. Disabling it reduces compiled shaders count.")] - public bool SupportDX11 = true; - - /// - /// Enables support for DirectX 10 and DirectX 10.1. Disabling it reduces compiled shaders count. - /// - [DefaultValue(false)] - [EditorOrder(2010), EditorDisplay("Graphics", "Support DirectX 10"), Tooltip("Enables support for DirectX 10 and DirectX 10.1. Disabling it reduces compiled shaders count.")] - public bool SupportDX10 = false; - } -} diff --git a/Source/Editor/Content/Settings/WindowsPlatformSettings.cs b/Source/Editor/Content/Settings/WindowsPlatformSettings.cs deleted file mode 100644 index dc873e9f4..000000000 --- a/Source/Editor/Content/Settings/WindowsPlatformSettings.cs +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -using System.ComponentModel; -using FlaxEngine; - -namespace FlaxEditor.Content.Settings -{ - /// - /// The Windows platform settings asset archetype. Allows to edit asset via editor. - /// - public class WindowsPlatformSettings : SettingsBase - { - /// - /// The default game window mode. - /// - [DefaultValue(GameWindowMode.Windowed)] - [EditorOrder(10), EditorDisplay("Window"), Tooltip("The default game window mode.")] - public GameWindowMode WindowMode = GameWindowMode.Windowed; - - /// - /// The default game window width (in pixels). - /// - [DefaultValue(1280)] - [EditorOrder(20), EditorDisplay("Window"), Tooltip("The default game window width (in pixels).")] - public int ScreenWidth = 1280; - - /// - /// The default game window height (in pixels). - /// - [DefaultValue(720)] - [EditorOrder(30), EditorDisplay("Window"), Tooltip("The default game window height (in pixels).")] - public int ScreenHeight = 720; - - /// - /// Enables resizing the game window by the user. - /// - [DefaultValue(false)] - [EditorOrder(40), EditorDisplay("Window"), Tooltip("Enables resizing the game window by the user.")] - public bool ResizableWindow = false; - - /// - /// Enables game running when application window loses focus. - /// - [DefaultValue(false)] - [EditorOrder(1010), EditorDisplay("Other", "Run In Background"), Tooltip("Enables game running when application window loses focus.")] - public bool RunInBackground = false; - - /// - /// Limits maximum amount of concurrent game instances running to one, otherwise user may launch application more than once. - /// - [DefaultValue(false)] - [EditorOrder(1020), EditorDisplay("Other"), Tooltip("Limits maximum amount of concurrent game instances running to one, otherwise user may launch application more than once.")] - public bool ForceSingleInstance = false; - - /// - /// Custom icon texture to use for the application (overrides the default one). - /// - [DefaultValue(null)] - [EditorOrder(1030), EditorDisplay("Other"), Tooltip("Custom icon texture to use for the application (overrides the default one).")] - public Texture OverrideIcon; - - /// - /// Enables support for DirectX 12. Disabling it reduces compiled shaders count. - /// - [DefaultValue(false)] - [EditorOrder(2000), EditorDisplay("Graphics", "Support DirectX 12"), Tooltip("Enables support for DirectX 12. Disabling it reduces compiled shaders count.")] - public bool SupportDX12 = false; - - /// - /// Enables support for DirectX 11. Disabling it reduces compiled shaders count. - /// - [DefaultValue(true)] - [EditorOrder(2010), EditorDisplay("Graphics", "Support DirectX 11"), Tooltip("Enables support for DirectX 11. Disabling it reduces compiled shaders count.")] - public bool SupportDX11 = true; - - /// - /// Enables support for DirectX 10 and DirectX 10.1. Disabling it reduces compiled shaders count. - /// - [DefaultValue(false)] - [EditorOrder(2020), EditorDisplay("Graphics", "Support DirectX 10"), Tooltip("Enables support for DirectX 10 and DirectX 10.1. Disabling it reduces compiled shaders count.")] - public bool SupportDX10 = false; - - /// - /// Enables support for Vulkan. Disabling it reduces compiled shaders count. - /// - [DefaultValue(false)] - [EditorOrder(2030), EditorDisplay("Graphics", "Support Vulkan"), Tooltip("Enables support for Vulkan. Disabling it reduces compiled shaders count.")] - public bool SupportVulkan = false; - } -} diff --git a/Source/Editor/Content/Tree/ContentTreeNode.cs b/Source/Editor/Content/Tree/ContentTreeNode.cs index 7df407e08..4370bc8e7 100644 --- a/Source/Editor/Content/Tree/ContentTreeNode.cs +++ b/Source/Editor/Content/Tree/ContentTreeNode.cs @@ -295,7 +295,8 @@ namespace FlaxEditor.Content StartRenaming(); return true; case KeyboardKeys.Delete: - Editor.Instance.Windows.ContentWin.Delete(Folder); + if (Folder.Exists) + Editor.Instance.Windows.ContentWin.Delete(Folder); return true; } if (RootWindow.GetKey(KeyboardKeys.Control)) @@ -303,7 +304,8 @@ namespace FlaxEditor.Content switch (key) { case KeyboardKeys.D: - Editor.Instance.Windows.ContentWin.Duplicate(Folder); + if (Folder.Exists) + Editor.Instance.Windows.ContentWin.Duplicate(Folder); return true; } } diff --git a/Source/Editor/Cooker/CookingData.h b/Source/Editor/Cooker/CookingData.h index b46b8ff72..56b15af23 100644 --- a/Source/Editor/Cooker/CookingData.h +++ b/Source/Editor/Cooker/CookingData.h @@ -174,6 +174,11 @@ struct FLAXENGINE_API CookingData /// BuildOptions Options; + /// + /// The list of custom defines passed to the build tool when compiling project scripts. Can be used in build scripts for configuration (Configuration.CustomDefines). + /// + Array CustomDefines; + /// /// The original output path (actual OutputPath could be modified by the Platform Tools or a plugin for additional layout customizations or packaging). This path is preserved. /// diff --git a/Source/Editor/Cooker/GameCooker.cpp b/Source/Editor/Cooker/GameCooker.cpp index 1fdf0755f..efd6763d8 100644 --- a/Source/Editor/Cooker/GameCooker.cpp +++ b/Source/Editor/Cooker/GameCooker.cpp @@ -43,53 +43,11 @@ #endif #if PLATFORM_TOOLS_XBOX_SCARLETT #include "Platforms/XboxScarlett/Editor/PlatformTools/XboxScarlettPlatformTools.h" -#include "Platforms/XboxScarlett/Engine/Platform/XboxScarlettPlatformSettings.h" #endif #if PLATFORM_TOOLS_ANDROID #include "Platform/Android/AndroidPlatformTools.h" -#include "Engine/Platform/Android/AndroidPlatformSettings.h" #endif -void LoadPlatformSettingsEditor(ISerializable::DeserializeStream& data) -{ -#define LOAD_SETTINGS(nodeName, settingsType) \ - { \ - Guid id = JsonTools::GetGuid(data, nodeName); \ - if (id.IsValid()) \ - { \ - AssetReference subAsset = Content::LoadAsync(id); \ - if (subAsset) \ - { \ - if (!subAsset->WaitForLoaded()) \ - { \ - settingsType::Instance()->Deserialize(*subAsset->Data, nullptr); \ - settingsType::Instance()->Apply(); \ - } \ - } \ - else \ - { LOG(Warning, "Cannot load " nodeName " settings"); } \ - } \ - } -#if PLATFORM_TOOLS_WINDOWS - LOAD_SETTINGS("WindowsPlatform", WindowsPlatformSettings); -#endif -#if PLATFORM_TOOLS_UWP || PLATFORM_TOOLS_XBOX_ONE - LOAD_SETTINGS("UWPPlatform", UWPPlatformSettings); -#endif -#if PLATFORM_TOOLS_LINUX - LOAD_SETTINGS("LinuxPlatform", LinuxPlatformSettings); -#endif -#if PLATFORM_TOOLS_PS4 - LOAD_SETTINGS("PS4Platform", PS4PlatformSettings); -#endif -#if PLATFORM_TOOLS_XBOX_SCARLETT - LOAD_SETTINGS("XboxScarlettPlatform", XboxScarlettPlatformSettings); -#endif -#if PLATFORM_TOOLS_ANDROID - LOAD_SETTINGS("AndroidPlatform", AndroidPlatformSettings); -#endif -} - namespace GameCookerImpl { MMethod* Internal_OnEvent = nullptr; @@ -299,7 +257,7 @@ PlatformTools* GameCooker::GetTools(BuildPlatform platform) return result; } -void GameCooker::Build(BuildPlatform platform, BuildConfiguration configuration, const StringView& outputPath, BuildOptions options) +void GameCooker::Build(BuildPlatform platform, BuildConfiguration configuration, const StringView& outputPath, BuildOptions options, const Array& customDefines) { if (IsRunning()) { @@ -323,6 +281,7 @@ void GameCooker::Build(BuildPlatform platform, BuildConfiguration configuration, data.Platform = platform; data.Configuration = configuration; data.Options = options; + data.CustomDefines = customDefines; data.OutputPath = outputPath; FileSystem::NormalizePath(data.OutputPath); data.OutputPath = data.OriginalOutputPath = FileSystem::ConvertRelativePathToAbsolute(Globals::ProjectFolder, data.OutputPath); diff --git a/Source/Editor/Cooker/GameCooker.h b/Source/Editor/Cooker/GameCooker.h index 3defe51ff..f1bfc4ad3 100644 --- a/Source/Editor/Cooker/GameCooker.h +++ b/Source/Editor/Cooker/GameCooker.h @@ -84,7 +84,8 @@ public: /// The build configuration. /// The output path (output directory). /// The build options. - API_FUNCTION() static void Build(BuildPlatform platform, BuildConfiguration configuration, const StringView& outputPath, BuildOptions options); + /// The list of custom defines passed to the build tool when compiling project scripts. Can be used in build scripts for configuration (Configuration.CustomDefines). + API_FUNCTION() static void Build(BuildPlatform platform, BuildConfiguration configuration, const StringView& outputPath, BuildOptions options, const Array& customDefines); /// /// Sends a cancel event to the game building service. diff --git a/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.cpp b/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.cpp index bce6aa860..6a42450d5 100644 --- a/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.cpp +++ b/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.cpp @@ -12,6 +12,10 @@ #include "Engine/Core/Config/GameSettings.h" #include "Engine/Core/Config/BuildSettings.h" #include "Editor/Utilities/EditorUtilities.h" +#include "Engine/Content/Content.h" +#include "Engine/Content/JsonAsset.h" + +IMPLEMENT_SETTINGS_GETTER(AndroidPlatformSettings, AndroidPlatform); namespace { @@ -106,7 +110,8 @@ void AndroidPlatformTools::OnBuildStarted(CookingData& data) bool AndroidPlatformTools::OnPostProcess(CookingData& data) { - const auto platformSettings = AndroidPlatformSettings::Instance(); + const auto gameSettings = GameSettings::Get(); + const auto platformSettings = AndroidPlatformSettings::Get(); const auto platformDataPath = data.GetPlatformBinariesRoot(); const auto assetsPath = data.OutputPath; const auto jniLibsPath = data.OriginalOutputPath / TEXT("app/jniLibs"); @@ -125,11 +130,11 @@ bool AndroidPlatformTools::OnPostProcess(CookingData& data) // Setup package name (eg. com.company.project) String packageName = platformSettings->PackageName; { - String productName = GameSettings::ProductName; + String productName = gameSettings->ProductName; productName.Replace(TEXT(" "), TEXT("")); productName.Replace(TEXT("."), TEXT("")); productName.Replace(TEXT("-"), TEXT("")); - String companyName = GameSettings::CompanyName; + String companyName = gameSettings->CompanyName; companyName.Replace(TEXT(" "), TEXT("")); companyName.Replace(TEXT("."), TEXT("")); companyName.Replace(TEXT("-"), TEXT("")); @@ -141,7 +146,7 @@ bool AndroidPlatformTools::OnPostProcess(CookingData& data) const auto c = packageName[i]; if (c != '_' && c != '.' && !StringUtils::IsAlnum(c)) { - LOG(Error, "Android Package Name \'{0}\' contains invalid chaarcter. Only letters, numbers, dots and underscore characters are allowed.", packageName); + LOG(Error, "Android Package Name \'{0}\' contains invalid character. Only letters, numbers, dots and underscore characters are allowed.", packageName); return true; } } @@ -235,7 +240,7 @@ bool AndroidPlatformTools::OnPostProcess(CookingData& data) EditorUtilities::ReplaceInFile(manifestPath, TEXT("${AndroidPermissions}"), permissions); EditorUtilities::ReplaceInFile(manifestPath, TEXT("${AndroidAttributes}"), attributes); const String stringsPath = data.OriginalOutputPath / TEXT("app/src/main/res/values/strings.xml"); - EditorUtilities::ReplaceInFile(stringsPath, TEXT("${ProjectName}"), GameSettings::ProductName); + EditorUtilities::ReplaceInFile(stringsPath, TEXT("${ProjectName}"), gameSettings->ProductName); // Deploy native binaries to the output location (per-ABI) const String abiBinariesPath = jniLibsPath / abi; @@ -256,7 +261,8 @@ bool AndroidPlatformTools::OnPostProcess(CookingData& data) // TODO: expose event to inject custom gradle and manifest options or custom binaries into app - if (BuildSettings::Instance()->SkipPackaging) + const auto buildSettings = BuildSettings::Get(); + if (buildSettings->SkipPackaging) { return false; } @@ -286,7 +292,7 @@ bool AndroidPlatformTools::OnPostProcess(CookingData& data) #else const Char* gradlew = TEXT("gradlew"); #endif - const bool distributionPackage = BuildSettings::Instance()->ForDistribution; + const bool distributionPackage = buildSettings->ForDistribution; const String gradleCommand = String::Format(TEXT("\"{0}\" {1}"), data.OriginalOutputPath / gradlew, distributionPackage ? TEXT("assemble") : TEXT("assembleDebug")); const int32 result = Platform::RunProcess(gradleCommand, data.OriginalOutputPath, envVars, true); if (result != 0) @@ -297,7 +303,7 @@ bool AndroidPlatformTools::OnPostProcess(CookingData& data) // Copy result package const String apk = data.OriginalOutputPath / (distributionPackage ? TEXT("app/build/outputs/apk/release/app-release-unsigned.apk") : TEXT("app/build/outputs/apk/debug/app-debug.apk")); - const String outputApk = data.OriginalOutputPath / GameSettings::ProductName + TEXT(".apk"); + const String outputApk = data.OriginalOutputPath / gameSettings->ProductName + TEXT(".apk"); if (FileSystem::CopyFile(outputApk, apk)) { LOG(Error, "Failed to copy package from {0} to {1}", apk, outputApk); diff --git a/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp b/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp index eccc299bb..fa47c5ee5 100644 --- a/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp +++ b/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp @@ -9,6 +9,10 @@ #include "Editor/Utilities/EditorUtilities.h" #include "Engine/Tools/TextureTool/TextureTool.h" #include "Engine/Graphics/Textures/TextureData.h" +#include "Engine/Content/Content.h" +#include "Engine/Content/JsonAsset.h" + +IMPLEMENT_SETTINGS_GETTER(LinuxPlatformSettings, LinuxPlatform); const Char* LinuxPlatformTools::GetDisplayName() const { @@ -32,7 +36,8 @@ ArchitectureType LinuxPlatformTools::GetArchitecture() const bool LinuxPlatformTools::OnDeployBinaries(CookingData& data) { - const auto platformSettings = LinuxPlatformSettings::Instance(); + const auto gameSettings = GameSettings::Get(); + const auto platformSettings = LinuxPlatformSettings::Get(); const auto outputPath = data.OutputPath; // Copy binaries @@ -60,7 +65,7 @@ bool LinuxPlatformTools::OnDeployBinaries(CookingData& data) // Apply game executable file name #if !BUILD_DEBUG const String outputExePath = outputPath / TEXT("FlaxGame"); - const String gameExePath = outputPath / GameSettings::ProductName; + const String gameExePath = outputPath / gameSettings->ProductName; if (FileSystem::FileExists(outputExePath) && gameExePath.Compare(outputExePath, StringSearchCase::IgnoreCase) == 0) { if (FileSystem::MoveFile(gameExePath, outputExePath, true)) diff --git a/Source/Editor/Cooker/Platform/UWP/UWPPlatformTools.cpp b/Source/Editor/Cooker/Platform/UWP/UWPPlatformTools.cpp index f6c846fed..c8f9e45a7 100644 --- a/Source/Editor/Cooker/Platform/UWP/UWPPlatformTools.cpp +++ b/Source/Editor/Cooker/Platform/UWP/UWPPlatformTools.cpp @@ -11,6 +11,10 @@ #include "Engine/Serialization/FileWriteStream.h" #include "Editor/Utilities/EditorUtilities.h" #include "Engine/Engine/Globals.h" +#include "Engine/Content/Content.h" +#include "Engine/Content/JsonAsset.h" + +IMPLEMENT_SETTINGS_GETTER(UWPPlatformSettings, UWPPlatform); bool UWPPlatformTools::UseAOT() const { @@ -24,7 +28,7 @@ bool UWPPlatformTools::OnScriptsStepDone(CookingData& data) const String assembliesPath = data.OutputPath; if (FileSystem::CopyFile(assembliesPath / TEXT("Newtonsoft.Json.dll"), customBinPath)) { - data.Error(TEXT("Failed to copy deloy custom assembly.")); + data.Error(TEXT("Failed to copy deploy custom assembly.")); return true; } FileSystem::DeleteFile(assembliesPath / TEXT("Newtonsoft.Json.pdb")); @@ -37,7 +41,8 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data) bool isXboxOne = data.Platform == BuildPlatform::XboxOne; const auto platformDataPath = Globals::StartupFolder / TEXT("Source/Platforms"); const auto uwpDataPath = platformDataPath / (isXboxOne ? TEXT("XboxOne") : TEXT("UWP")) / TEXT("Binaries"); - const auto platformSettings = UWPPlatformSettings::Instance(); + const auto gameSettings = GameSettings::Get(); + const auto platformSettings = UWPPlatformSettings::Get(); Array fileTemplate; // Copy binaries @@ -66,7 +71,7 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data) } } - const auto projectName = GameSettings::ProductName; + const auto projectName = gameSettings->ProductName; auto defaultNamespace = projectName; ScriptsBuilder::FilterNamespaceText(defaultNamespace); const StringAnsi projectGuid = "{3A9A2246-71DD-4567-9ABF-3E040310E30E}"; @@ -102,7 +107,7 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data) // Generate new temp cert if missing if (!FileSystem::FileExists(dstCertificatePath)) { - if (EditorUtilities::GenerateCertificate(GameSettings::CompanyName, dstCertificatePath)) + if (EditorUtilities::GenerateCertificate(gameSettings->CompanyName, dstCertificatePath)) { LOG(Warning, "Failed to create certificate."); } @@ -159,8 +164,8 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data) auto now = DateTime::Now(); file->WriteTextFormatted( (char*)fileTemplate.Get() - , GameSettings::ProductName.ToStringAnsi() - , GameSettings::CompanyName.ToStringAnsi() + , gameSettings->ProductName.ToStringAnsi() + , gameSettings->CompanyName.ToStringAnsi() , now.GetYear() ); hasError = file->HasError(); @@ -382,7 +387,7 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data) file->WriteTextFormatted( (char*)fileTemplate.Get() , projectName.ToStringAnsi() // {0} Display Name - , GameSettings::CompanyName.ToStringAnsi() // {1} Company Name + , gameSettings->CompanyName.ToStringAnsi() // {1} Company Name , productId.ToStringAnsi() // {2} Product ID , defaultNamespace.ToStringAnsi() // {3} Default Namespace ); diff --git a/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp b/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp index 35ed27639..348930dd5 100644 --- a/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp +++ b/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp @@ -8,6 +8,10 @@ #include "Engine/Core/Config/GameSettings.h" #include "Editor/Utilities/EditorUtilities.h" #include "Engine/Graphics/Textures/TextureData.h" +#include "Engine/Content/Content.h" +#include "Engine/Content/JsonAsset.h" + +IMPLEMENT_SETTINGS_GETTER(WindowsPlatformSettings, WindowsPlatform); const Char* WindowsPlatformTools::GetDisplayName() const { @@ -31,7 +35,7 @@ ArchitectureType WindowsPlatformTools::GetArchitecture() const bool WindowsPlatformTools::OnDeployBinaries(CookingData& data) { - const auto platformSettings = WindowsPlatformSettings::Instance(); + const auto platformSettings = WindowsPlatformSettings::Get(); const auto& outputPath = data.OutputPath; // Apply executable icon diff --git a/Source/Editor/Cooker/PlatformTools.h b/Source/Editor/Cooker/PlatformTools.h index 28f6346ca..c98c008bc 100644 --- a/Source/Editor/Cooker/PlatformTools.h +++ b/Source/Editor/Cooker/PlatformTools.h @@ -48,7 +48,7 @@ public: /// /// Gets the value indicating whenever platform requires AOT. /// - /// True if platform uses AOT and needs C# assemblies to be be precompiled, otherwise false. + /// True if platform uses AOT and needs C# assemblies to be precompiled, otherwise false. virtual bool UseAOT() const { return false; diff --git a/Source/Editor/Cooker/Steps/CompileScriptsStep.cpp b/Source/Editor/Cooker/Steps/CompileScriptsStep.cpp index 3bb2e1eb6..dd7c77a94 100644 --- a/Source/Editor/Cooker/Steps/CompileScriptsStep.cpp +++ b/Source/Editor/Cooker/Steps/CompileScriptsStep.cpp @@ -217,6 +217,11 @@ bool CompileScriptsStep::Perform(CookingData& data) args += TEXT(" -SkipTargets=FlaxGame"); } #endif + for (auto& define : data.CustomDefines) + { + args += TEXT(" -D"); + args += define; + } if (ScriptsBuilder::RunBuildTool(args)) { data.Error(TEXT("Failed to compile game scripts.")); diff --git a/Source/Editor/Cooker/Steps/CookAssetsStep.cpp b/Source/Editor/Cooker/Steps/CookAssetsStep.cpp index 396626fc1..7f2824c42 100644 --- a/Source/Editor/Cooker/Steps/CookAssetsStep.cpp +++ b/Source/Editor/Cooker/Steps/CookAssetsStep.cpp @@ -160,8 +160,9 @@ void CookAssetsStep::CacheData::Load(CookingData& data) // Invalidate shaders and assets with shaders if need to rebuild them bool invalidateShaders = false; - const bool shadersNoOptimize = BuildSettings::Instance()->ShadersNoOptimize; - const bool shadersGenerateDebugData = BuildSettings::Instance()->ShadersGenerateDebugData; + const auto buildSettings = BuildSettings::Get(); + const bool shadersNoOptimize = buildSettings->ShadersNoOptimize; + const bool shadersGenerateDebugData = buildSettings->ShadersGenerateDebugData; if (shadersNoOptimize != Settings.Global.ShadersNoOptimize) { LOG(Info, "ShadersNoOptimize option has been modified."); @@ -175,7 +176,7 @@ void CookAssetsStep::CacheData::Load(CookingData& data) #if PLATFORM_TOOLS_WINDOWS if (data.Platform == BuildPlatform::Windows32 || data.Platform == BuildPlatform::Windows64) { - const auto settings = WindowsPlatformSettings::Instance(); + const auto settings = WindowsPlatformSettings::Get(); const bool modified = Settings.Windows.SupportDX11 != settings->SupportDX11 || Settings.Windows.SupportDX10 != settings->SupportDX10 || @@ -190,7 +191,7 @@ void CookAssetsStep::CacheData::Load(CookingData& data) #if PLATFORM_TOOLS_UWP if (data.Platform == BuildPlatform::UWPx86 || data.Platform == BuildPlatform::UWPx64) { - const auto settings = UWPPlatformSettings::Instance(); + const auto settings = UWPPlatformSettings::Get(); const bool modified = Settings.UWP.SupportDX11 != settings->SupportDX11 || Settings.UWP.SupportDX10 != settings->SupportDX10; @@ -204,7 +205,7 @@ void CookAssetsStep::CacheData::Load(CookingData& data) #if PLATFORM_TOOLS_LINUX if (data.Platform == BuildPlatform::LinuxX64) { - const auto settings = LinuxPlatformSettings::Instance(); + const auto settings = LinuxPlatformSettings::Get(); const bool modified = Settings.Linux.SupportVulkan != settings->SupportVulkan; if (modified) @@ -322,6 +323,8 @@ bool ProcessShaderBase(CookAssetsStep::AssetCookData& data, ShaderAssetBase* ass auto sourceLength = sourceChunk->Size(); Encryption::DecryptBytes((byte*)source, sourceLength); source[sourceLength - 1] = 0; + while (sourceLength > 2 && source[sourceLength - 1] == 0) + sourceLength--; // Init shader cache output stream // TODO: reuse MemoryWriteStream per cooking process to reduce dynamic memory allocations @@ -369,7 +372,7 @@ bool ProcessShaderBase(CookAssetsStep::AssetCookData& data, ShaderAssetBase* ass case BuildPlatform::Windows64: { const char* platformDefineName = "PLATFORM_WINDOWS"; - const auto settings = WindowsPlatformSettings::Instance(); + const auto settings = WindowsPlatformSettings::Get(); if (settings->SupportDX12) { COMPILE_PROFILE(DirectX_SM6, SHADER_FILE_CHUNK_INTERNAL_D3D_SM6_CACHE); @@ -393,7 +396,7 @@ bool ProcessShaderBase(CookAssetsStep::AssetCookData& data, ShaderAssetBase* ass case BuildPlatform::UWPx64: { const char* platformDefineName = "PLATFORM_UWP"; - const auto settings = UWPPlatformSettings::Instance(); + const auto settings = UWPPlatformSettings::Get(); if (settings->SupportDX11) { COMPILE_PROFILE(DirectX_SM5, SHADER_FILE_CHUNK_INTERNAL_D3D_SM5_CACHE); @@ -415,7 +418,7 @@ bool ProcessShaderBase(CookAssetsStep::AssetCookData& data, ShaderAssetBase* ass case BuildPlatform::LinuxX64: { const char* platformDefineName = "PLATFORM_LINUX"; - const auto settings = LinuxPlatformSettings::Instance(); + const auto settings = LinuxPlatformSettings::Get(); if (settings->SupportVulkan) { COMPILE_PROFILE(Vulkan_SM5, SHADER_FILE_CHUNK_INTERNAL_VULKAN_SM5_CACHE); @@ -881,7 +884,8 @@ bool CookAssetsStep::Perform(CookingData& data) data.StepProgress(TEXT("Loading build cache"), 0); // Prepare - const auto buildSettings = BuildSettings::Instance(); + const auto gameSettings = GameSettings::Get(); + const auto buildSettings = BuildSettings::Get(); const int32 contentKey = buildSettings->ContentKey == 0 ? rand() : buildSettings->ContentKey; AssetsRegistry.Clear(); AssetPathsMapping.Clear(); @@ -892,22 +896,21 @@ bool CookAssetsStep::Perform(CookingData& data) // Update build settings { - const auto settings = WindowsPlatformSettings::Instance(); + const auto settings = WindowsPlatformSettings::Get(); cache.Settings.Windows.SupportDX11 = settings->SupportDX11; cache.Settings.Windows.SupportDX10 = settings->SupportDX10; cache.Settings.Windows.SupportVulkan = settings->SupportVulkan; } { - const auto settings = UWPPlatformSettings::Instance(); + const auto settings = UWPPlatformSettings::Get(); cache.Settings.UWP.SupportDX11 = settings->SupportDX11; cache.Settings.UWP.SupportDX10 = settings->SupportDX10; } { - const auto settings = LinuxPlatformSettings::Instance(); + const auto settings = LinuxPlatformSettings::Get(); cache.Settings.Linux.SupportVulkan = settings->SupportVulkan; } { - const auto buildSettings = BuildSettings::Instance(); cache.Settings.Global.ShadersNoOptimize = buildSettings->ShadersNoOptimize; cache.Settings.Global.ShadersGenerateDebugData = buildSettings->ShadersGenerateDebugData; } @@ -1004,7 +1007,7 @@ bool CookAssetsStep::Perform(CookingData& data) // Create build game header { GameHeaderFlags gameFlags = GameHeaderFlags::None; - if (!GameSettings::NoSplashScreen) + if (!gameSettings->NoSplashScreen) gameFlags |= GameHeaderFlags::ShowSplashScreen; // Open file @@ -1022,17 +1025,17 @@ bool CookAssetsStep::Perform(CookingData& data) Array bytes; bytes.Resize(808 + sizeof(Guid)); Platform::MemoryClear(bytes.Get(), bytes.Count()); - int32 length = sizeof(Char) * GameSettings::ProductName.Length(); - Platform::MemoryCopy(bytes.Get() + 0, GameSettings::ProductName.Get(), length); + int32 length = sizeof(Char) * gameSettings->ProductName.Length(); + Platform::MemoryCopy(bytes.Get() + 0, gameSettings->ProductName.Get(), length); bytes[length] = 0; bytes[length + 1] = 0; - length = sizeof(Char) * GameSettings::CompanyName.Length(); - Platform::MemoryCopy(bytes.Get() + 400, GameSettings::CompanyName.Get(), length); + length = sizeof(Char) * gameSettings->CompanyName.Length(); + Platform::MemoryCopy(bytes.Get() + 400, gameSettings->CompanyName.Get(), length); bytes[length + 400] = 0; bytes[length + 401] = 0; *(int32*)(bytes.Get() + 800) = (int32)gameFlags; *(int32*)(bytes.Get() + 804) = contentKey; - *(Guid*)(bytes.Get() + 808) = GameSettings::SplashScreen; + *(Guid*)(bytes.Get() + 808) = gameSettings->SplashScreen; Encryption::EncryptBytes(bytes.Get(), bytes.Count()); stream->WriteArray(bytes); diff --git a/Source/Editor/Cooker/Steps/DeployDataStep.cpp b/Source/Editor/Cooker/Steps/DeployDataStep.cpp index b5ea2d268..96f8bfad2 100644 --- a/Source/Editor/Cooker/Steps/DeployDataStep.cpp +++ b/Source/Editor/Cooker/Steps/DeployDataStep.cpp @@ -12,6 +12,7 @@ bool DeployDataStep::Perform(CookingData& data) { data.StepProgress(TEXT("Deploying engine data"), 0); const String depsRoot = data.GetPlatformBinariesRoot(); + const auto gameSettings = GameSettings::Get(); // Setup output folders and copy required data const auto contentDir = data.OutputPath / TEXT("Content"); @@ -73,8 +74,9 @@ bool DeployDataStep::Perform(CookingData& data) data.AddRootEngineAsset(TEXT("Shaders/SSR")); data.AddRootEngineAsset(TEXT("Shaders/VolumetricFog")); data.AddRootEngineAsset(TEXT("Engine/DefaultMaterial")); + data.AddRootEngineAsset(TEXT("Engine/DefaultDeformableMaterial")); data.AddRootEngineAsset(TEXT("Engine/DefaultTerrainMaterial")); - if (!GameSettings::NoSplashScreen && !GameSettings::SplashScreen.IsValid()) + if (!gameSettings->NoSplashScreen && !gameSettings->SplashScreen.IsValid()) data.AddRootEngineAsset(TEXT("Engine/Textures/Logo")); data.AddRootEngineAsset(TEXT("Engine/Textures/NormalTexture")); data.AddRootEngineAsset(TEXT("Engine/Textures/BlackTexture")); @@ -98,7 +100,7 @@ bool DeployDataStep::Perform(CookingData& data) // Register game assets data.StepProgress(TEXT("Deploying game data"), 50); - auto& buildSettings = *BuildSettings::Instance(); + auto& buildSettings = *BuildSettings::Get(); for (auto& e : buildSettings.AdditionalAssets) data.AddRootAsset(e.GetID()); Array files; diff --git a/Source/Editor/Cooker/Steps/ValidateStep.cpp b/Source/Editor/Cooker/Steps/ValidateStep.cpp index 219301544..915f7839d 100644 --- a/Source/Editor/Cooker/Steps/ValidateStep.cpp +++ b/Source/Editor/Cooker/Steps/ValidateStep.cpp @@ -38,18 +38,23 @@ bool ValidateStep::Perform(CookingData& data) #endif // Load game settings (may be modified via editor) - GameSettings::Load(); + if (GameSettings::Load()) + { + data.Error(TEXT("Failed to load game settings.")); + return true; + } data.AddRootAsset(Globals::ProjectContentFolder / TEXT("GameSettings.json")); // Validate game settings + auto gameSettings = GameSettings::Get(); { - if (GameSettings::ProductName.IsEmpty()) + if (gameSettings->ProductName.IsEmpty()) { data.Error(TEXT("Missing product name.")); return true; } - if (GameSettings::CompanyName.IsEmpty()) + if (gameSettings->CompanyName.IsEmpty()) { data.Error(TEXT("Missing company name.")); return true; @@ -58,9 +63,9 @@ bool ValidateStep::Perform(CookingData& data) // TODO: validate version AssetInfo info; - if (!Content::GetAssetInfo(GameSettings::FirstScene, info)) + if (!Content::GetAssetInfo(gameSettings->FirstScene, info)) { - data.Error(TEXT("Missing first scene.")); + data.Error(TEXT("Missing first scene. Set it in the game settings.")); return true; } } diff --git a/Source/Editor/CustomEditors/CustomEditor.cs b/Source/Editor/CustomEditors/CustomEditor.cs index 5f992729f..347eb608f 100644 --- a/Source/Editor/CustomEditors/CustomEditor.cs +++ b/Source/Editor/CustomEditors/CustomEditor.cs @@ -624,7 +624,7 @@ namespace FlaxEditor.CustomEditors return FindPrefabRoot(actor.Parent); } - private ISceneObject FindObjectWithPrefabObjectId(Actor actor, ref Guid prefabObjectId) + private SceneObject FindObjectWithPrefabObjectId(Actor actor, ref Guid prefabObjectId) { if (actor.PrefabObjectID == prefabObjectId) return actor; @@ -667,7 +667,7 @@ namespace FlaxEditor.CustomEditors { // Special case for object references // If prefab object has reference to other object in prefab needs to revert to matching prefab instance object not the reference prefab object value - if (Values.ReferenceValue is ISceneObject referenceSceneObject && referenceSceneObject.HasPrefabLink) + if (Values.ReferenceValue is SceneObject referenceSceneObject && referenceSceneObject.HasPrefabLink) { if (Values.Count > 1) { diff --git a/Source/Editor/CustomEditors/CustomEditorsUtil.cs b/Source/Editor/CustomEditors/CustomEditorsUtil.cs index 2f97da333..9b83dd820 100644 --- a/Source/Editor/CustomEditors/CustomEditorsUtil.cs +++ b/Source/Editor/CustomEditors/CustomEditorsUtil.cs @@ -71,7 +71,7 @@ namespace FlaxEditor.CustomEditors // Space before word starting with uppercase letter if (char.IsUpper(c) && i > 0) { - if (i + 2 < length && !char.IsUpper(name[i + 1]) && !char.IsUpper(name[i + 2])) + if (i + 1 < length && !char.IsUpper(name[i + 1])) sb.Append(' '); } // Space instead of underscore diff --git a/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs b/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs index ad974c55a..b8491abfa 100644 --- a/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs @@ -24,43 +24,6 @@ namespace FlaxEditor.CustomEditors.Dedicated { private Guid _linkedPrefabId; - /// - protected override void SpawnProperty(LayoutElementsContainer itemLayout, ValueContainer itemValues, ItemInfo item) - { - // Note: we cannot specify actor properties editor types directly because we want to keep editor classes in FlaxEditor assembly - int order = item.Order?.Order ?? int.MinValue; - switch (order) - { - // Override static flags editor - case -80: - item.CustomEditor = new CustomEditorAttribute(typeof(ActorStaticFlagsEditor)); - break; - - // Override layer editor - case -69: - item.CustomEditor = new CustomEditorAttribute(typeof(ActorLayerEditor)); - break; - - // Override tag editor - case -68: - item.CustomEditor = new CustomEditorAttribute(typeof(ActorTagEditor)); - break; - - // Override position/scale editor - case -30: - case -10: - item.CustomEditor = new CustomEditorAttribute(typeof(ActorTransformEditor.PositionScaleEditor)); - break; - - // Override orientation editor - case -20: - item.CustomEditor = new CustomEditorAttribute(typeof(ActorTransformEditor.OrientationEditor)); - break; - } - - base.SpawnProperty(itemLayout, itemValues, item); - } - /// protected override List GetItemsForType(ScriptType type) { @@ -243,7 +206,7 @@ namespace FlaxEditor.CustomEditors.Dedicated node.Text = CustomEditorsUtil.GetPropertyNameUI(removed.PrefabObject.GetType().Name); } // Actor or Script - else if (editor.Values[0] is ISceneObject sceneObject) + else if (editor.Values[0] is SceneObject sceneObject) { node.TextColor = sceneObject.HasPrefabLink ? FlaxEngine.GUI.Style.Current.ProgressNormal : FlaxEngine.GUI.Style.Current.BackgroundSelected; node.Text = CustomEditorsUtil.GetPropertyNameUI(sceneObject.GetType().Name); diff --git a/Source/Editor/CustomEditors/Dedicated/LayersMatrixEditor.cs b/Source/Editor/CustomEditors/Dedicated/LayersMatrixEditor.cs index 44a273ee4..6c825f46d 100644 --- a/Source/Editor/CustomEditors/Dedicated/LayersMatrixEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/LayersMatrixEditor.cs @@ -76,7 +76,7 @@ namespace FlaxEditor.CustomEditors.Dedicated upperRightCell.AddChild(new Label { Height = labelsHeight, - Text = layerNames[layerIndex], + Text = layerNames[layerNames.Length - layerIndex - 1], HorizontalAlignment = TextAlignment.Near, }); bottomLeftCell.AddChild(new Label diff --git a/Source/Editor/CustomEditors/Dedicated/NavAgentMaskEditor.cs b/Source/Editor/CustomEditors/Dedicated/NavAgentMaskEditor.cs new file mode 100644 index 000000000..ab0151bb9 --- /dev/null +++ b/Source/Editor/CustomEditors/Dedicated/NavAgentMaskEditor.cs @@ -0,0 +1,92 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System.Linq; +using FlaxEditor.Content.Settings; +using FlaxEngine; +using FlaxEngine.GUI; + +namespace FlaxEditor.CustomEditors.Dedicated +{ + /// + /// Custom editor for . + /// + /// + [CustomEditor(typeof(NavAgentMask)), DefaultEditor] + internal class NavAgentMaskEditor : CustomEditor + { + private CheckBox[] _checkBoxes; + + /// + public override void Initialize(LayoutElementsContainer layout) + { + var settings = GameSettings.Load(); + if (settings.NavMeshes == null || settings.NavMeshes.Length == 0) + { + layout.Label("Missing navmesh settings"); + return; + } + + _checkBoxes = new CheckBox[settings.NavMeshes.Length]; + for (int i = 0; i < settings.NavMeshes.Length; i++) + { + ref var navmesh = ref settings.NavMeshes[i]; + var property = layout.AddPropertyItem(navmesh.Name, navmesh.Agent.ToString()); + property.Labels.Last().TextColorHighlighted = navmesh.Color; + var checkbox = property.Checkbox().CheckBox; + UpdateCheckbox(checkbox, i); + checkbox.Tag = i; + checkbox.StateChanged += OnCheckboxStateChanged; + _checkBoxes[i] = checkbox; + } + } + + /// + protected override void Deinitialize() + { + _checkBoxes = null; + + base.Deinitialize(); + } + + /// + public override void Refresh() + { + if (_checkBoxes != null) + { + for (int i = 0; i < _checkBoxes.Length; i++) + { + UpdateCheckbox(_checkBoxes[i], i); + } + } + + base.Refresh(); + } + + private void OnCheckboxStateChanged(CheckBox checkBox) + { + var i = (int)checkBox.Tag; + var value = (NavAgentMask)Values[0]; + var mask = 1u << i; + value.Mask &= ~mask; + value.Mask |= checkBox.Checked ? mask : 0; + SetValue(value); + } + + private void UpdateCheckbox(CheckBox checkbox, int i) + { + for (var j = 0; j < Values.Count; j++) + { + var value = (((NavAgentMask)Values[j]).Mask & (1 << i)) != 0; + if (j == 0) + { + checkbox.Checked = value; + } + else if (checkbox.State != CheckBoxState.Intermediate) + { + if (checkbox.Checked != value) + checkbox.State = CheckBoxState.Intermediate; + } + } + } + } +} diff --git a/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs b/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs new file mode 100644 index 000000000..51d137ea9 --- /dev/null +++ b/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs @@ -0,0 +1,67 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using FlaxEditor.Actions; +using FlaxEditor.SceneGraph.Actors; +using FlaxEngine; +using FlaxEngine.GUI; + +namespace FlaxEditor.CustomEditors.Dedicated +{ + /// + /// Custom editor for . + /// + /// + [CustomEditor(typeof(Spline)), DefaultEditor] + public class SplineEditor : ActorEditor + { + /// + public override void Initialize(LayoutElementsContainer layout) + { + base.Initialize(layout); + + if (Values.HasDifferentTypes == false) + { + layout.Space(10); + var grid = layout.CustomContainer(); + grid.CustomControl.SlotsHorizontally = 2; + grid.CustomControl.SlotsVertically = 1; + grid.Button("Set Linear Tangents").Button.Clicked += OnSetTangentsLinear; + grid.Button("Set Smooth Tangents").Button.Clicked += OnSetTangentsSmooth; + } + } + + private void OnSetTangentsLinear() + { + var enableUndo = Presenter.Undo != null && Presenter.Undo.Enabled; + for (int i = 0; i < Values.Count; i++) + { + if (Values[i] is Spline spline) + { + var before = enableUndo ? (BezierCurve.Keyframe[])spline.SplineKeyframes.Clone() : null; + spline.SetTangentsLinear(); + if (enableUndo) + Presenter.Undo.AddAction(new EditSplineAction(spline, before)); + SplineNode.OnSplineEdited(spline); + Editor.Instance.Scene.MarkSceneEdited(spline.Scene); + } + } + } + + private void OnSetTangentsSmooth() + { + var enableUndo = Presenter.Undo != null && Presenter.Undo.Enabled; + for (int i = 0; i < Values.Count; i++) + { + if (Values[i] is Spline spline) + { + var before = enableUndo ? (BezierCurve.Keyframe[])spline.SplineKeyframes.Clone() : null; + spline.SetTangentsSmooth(); + if (enableUndo) + Presenter.Undo.AddAction(new EditSplineAction(spline, before)); + SplineNode.OnSplineEdited(spline); + Editor.Instance.Scene.MarkSceneEdited(spline.Scene); + } + } + } + } +} diff --git a/Source/Editor/CustomEditors/Editors/ActorStaticFlagsEditor.cs b/Source/Editor/CustomEditors/Editors/ActorStaticFlagsEditor.cs index 86dd45812..cf649552a 100644 --- a/Source/Editor/CustomEditors/Editors/ActorStaticFlagsEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ActorStaticFlagsEditor.cs @@ -23,7 +23,7 @@ namespace FlaxEditor.CustomEditors.Editors /// protected override void OnValueChanged() { - var value = (StaticFlags)element.EnumComboBox.EnumTypeValue; + var value = (StaticFlags)element.ComboBox.EnumTypeValue; // If selected is single actor that has children, ask if apply flags to the sub objects as well if (Values.IsSingleObject && (StaticFlags)Values[0] != value && ParentEditor.Values[0] is Actor actor && actor.HasChildren) diff --git a/Source/Editor/CustomEditors/Editors/ArrayEditor.cs b/Source/Editor/CustomEditors/Editors/ArrayEditor.cs index f12dadde1..335fd64a7 100644 --- a/Source/Editor/CustomEditors/Editors/ArrayEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ArrayEditor.cs @@ -2,6 +2,7 @@ using System; using System.Collections; +using FlaxEditor.Scripting; using FlaxEngine; namespace FlaxEditor.CustomEditors.Editors @@ -48,6 +49,15 @@ namespace FlaxEditor.CustomEditors.Editors Array.Copy(array, oldSize - 1, newValues, i, 1); } } + else if (newSize > 0) + { + // Initialize new entries with default values + var defaultValue = TypeUtils.GetDefaultValue(new ScriptType(elementType)); + for (int i = 0; i < newSize; i++) + { + newValues.SetValue(defaultValue, i); + } + } SetValue(newValues); } diff --git a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs index ecbbef0f9..2e5aeade7 100644 --- a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs +++ b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs @@ -116,7 +116,7 @@ namespace FlaxEditor.CustomEditors.Editors // Try get CollectionAttribute for collection editor meta var attributes = Values.GetAttributes(); Type overrideEditorType = null; - float spacing = 0.0f; + float spacing = 10.0f; var collection = (CollectionAttribute)attributes?.FirstOrDefault(x => x is CollectionAttribute); if (collection != null) { diff --git a/Source/Editor/CustomEditors/Editors/EnumEditor.cs b/Source/Editor/CustomEditors/Editors/EnumEditor.cs index e277324e5..7954e18bf 100644 --- a/Source/Editor/CustomEditors/Editors/EnumEditor.cs +++ b/Source/Editor/CustomEditors/Editors/EnumEditor.cs @@ -40,7 +40,7 @@ namespace FlaxEditor.CustomEditors.Editors { var enumType = Values.Type.Type != typeof(object) || Values[0] == null ? TypeUtils.GetType(Values.Type) : Values[0].GetType(); element = layout.Enum(enumType, null, mode); - element.EnumComboBox.ValueChanged += OnValueChanged; + element.ComboBox.ValueChanged += OnValueChanged; } } @@ -49,7 +49,7 @@ namespace FlaxEditor.CustomEditors.Editors /// protected virtual void OnValueChanged() { - SetValue(element.EnumComboBox.EnumTypeValue); + SetValue(element.ComboBox.EnumTypeValue); } /// @@ -63,7 +63,7 @@ namespace FlaxEditor.CustomEditors.Editors } else { - element.EnumComboBox.EnumTypeValue = Values[0]; + element.ComboBox.EnumTypeValue = Values[0]; } } } diff --git a/Source/Editor/CustomEditors/Editors/GenericEditor.cs b/Source/Editor/CustomEditors/Editors/GenericEditor.cs index 485a9b735..9e7295e7e 100644 --- a/Source/Editor/CustomEditors/Editors/GenericEditor.cs +++ b/Source/Editor/CustomEditors/Editors/GenericEditor.cs @@ -517,23 +517,22 @@ namespace FlaxEditor.CustomEditors.Editors if (item.Header != null) itemLayout.Header(item.Header.Text); - // Peek values - ValueContainer itemValues; try { - itemValues = item.GetValues(Values); + // Peek values + ValueContainer itemValues = item.GetValues(Values); + + // Spawn property editor + SpawnProperty(itemLayout, itemValues, item); } catch (Exception ex) { - Editor.LogWarning("Failed to get object values for item " + item); + Editor.LogWarning("Failed to setup values and UI for item " + item); Editor.LogWarning(ex.Message); Editor.LogWarning(ex.StackTrace); return; } - // Spawn property editor - SpawnProperty(itemLayout, itemValues, item); - // Expand all parent groups if need to if (item.ExpandGroups) { diff --git a/Source/Editor/CustomEditors/Editors/ListEditor.cs b/Source/Editor/CustomEditors/Editors/ListEditor.cs index 305d2e2c9..19ed9675b 100644 --- a/Source/Editor/CustomEditors/Editors/ListEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ListEditor.cs @@ -58,7 +58,7 @@ namespace FlaxEditor.CustomEditors.Editors } else if (newSize > 0) { - // Fill new entries + // Fill new entries with default value var defaultValue = Scripting.TypeUtils.GetDefaultValue(ElementType); for (int i = oldSize; i < newSize; i++) { diff --git a/Source/Editor/CustomEditors/Elements/EnumElement.cs b/Source/Editor/CustomEditors/Elements/EnumElement.cs index 27b4dfdb9..be4085f40 100644 --- a/Source/Editor/CustomEditors/Elements/EnumElement.cs +++ b/Source/Editor/CustomEditors/Elements/EnumElement.cs @@ -16,7 +16,7 @@ namespace FlaxEditor.CustomEditors.Elements /// /// The combo box used to show enum values. /// - public EnumComboBox EnumComboBox; + public EnumComboBox ComboBox; /// /// Initializes a new instance of the class. @@ -26,10 +26,10 @@ namespace FlaxEditor.CustomEditors.Elements /// The formatting mode. public EnumElement(Type type, EnumComboBox.BuildEntriesDelegate customBuildEntriesDelegate = null, EnumDisplayAttribute.FormatMode formatMode = EnumDisplayAttribute.FormatMode.Default) { - EnumComboBox = new EnumComboBox(type, customBuildEntriesDelegate, formatMode); + ComboBox = new EnumComboBox(type, customBuildEntriesDelegate, formatMode); } /// - public override Control Control => EnumComboBox; + public override Control Control => ComboBox; } } diff --git a/Source/Editor/CustomEditors/LayoutElementsContainer.cs b/Source/Editor/CustomEditors/LayoutElementsContainer.cs index 9df2de8be..59058056d 100644 --- a/Source/Editor/CustomEditors/LayoutElementsContainer.cs +++ b/Source/Editor/CustomEditors/LayoutElementsContainer.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using FlaxEditor.CustomEditors.Elements; using FlaxEditor.CustomEditors.GUI; using FlaxEditor.GUI; +using FlaxEditor.GUI.ContextMenu; using FlaxEngine; using FlaxEngine.Assertions; using FlaxEngine.GUI; @@ -553,6 +554,8 @@ namespace FlaxEditor.CustomEditors var group = Group(name, true); group.Panel.Close(false); group.Panel.TooltipText = tooltip; + group.Panel.Tag = editor; + group.Panel.MouseButtonRightClicked += OnGroupPanelMouseButtonRightClicked; return group.Object(values, editor); } @@ -560,6 +563,23 @@ namespace FlaxEditor.CustomEditors return property.Object(values, editor); } + private void OnGroupPanelMouseButtonRightClicked(DropPanel groupPanel, Vector2 location) + { + var linkedEditor = (CustomEditor)groupPanel.Tag; + var menu = new ContextMenu(); + + var revertToPrefab = menu.AddButton("Revert to Prefab", linkedEditor.RevertToReferenceValue); + revertToPrefab.Enabled = linkedEditor.CanRevertReferenceValue; + var resetToDefault = menu.AddButton("Reset to default", linkedEditor.RevertToDefaultValue); + resetToDefault.Enabled = linkedEditor.CanRevertDefaultValue; + menu.AddSeparator(); + menu.AddButton("Copy", linkedEditor.Copy); + var paste = menu.AddButton("Paste", linkedEditor.Paste); + paste.Enabled = linkedEditor.CanPaste; + + menu.Show(groupPanel, location); + } + /// /// Adds object property editor. Selects proper based on overrides. /// diff --git a/Source/Editor/CustomEditors/Values/ValueContainer.cs b/Source/Editor/CustomEditors/Values/ValueContainer.cs index b2f26bb7b..f8cecb801 100644 --- a/Source/Editor/CustomEditors/Values/ValueContainer.cs +++ b/Source/Editor/CustomEditors/Values/ValueContainer.cs @@ -167,14 +167,14 @@ namespace FlaxEditor.CustomEditors { if (_hasReferenceValue) { - if (_referenceValue is ISceneObject referenceSceneObject && referenceSceneObject.HasPrefabLink) + if (_referenceValue is SceneObject referenceSceneObject && referenceSceneObject.HasPrefabLink) { for (int i = 0; i < Count; i++) { if (this[i] == referenceSceneObject) continue; - if (this[i] == null || (this[i] is ISceneObject valueSceneObject && valueSceneObject.PrefabObjectID != referenceSceneObject.PrefabObjectID)) + if (this[i] == null || (this[i] is SceneObject valueSceneObject && valueSceneObject.PrefabObjectID != referenceSceneObject.PrefabObjectID)) return true; } } diff --git a/Source/Editor/Editor.Build.cs b/Source/Editor/Editor.Build.cs index a767ecac9..4d7d33d65 100644 --- a/Source/Editor/Editor.Build.cs +++ b/Source/Editor/Editor.Build.cs @@ -26,7 +26,7 @@ public class Editor : EditorModule // Platform Tools inside external platform implementation location options.PrivateDefinitions.Add(macro); options.SourcePaths.Add(externalPath); - options.SourceFiles.Add(Path.Combine(Globals.EngineRoot, "Source", "Platforms", platform, "Engine", "Platform", platform + "PlatformSettings.cs")); + AddSourceFileIfExists(options, Path.Combine(Globals.EngineRoot, "Source", "Platforms", platform, "Engine", "Platform", platform + "PlatformSettings.cs")); } } } diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs index 59810043d..3aa8346c8 100644 --- a/Source/Editor/Editor.cs +++ b/Source/Editor/Editor.cs @@ -242,8 +242,6 @@ namespace FlaxEditor StateMachine = new EditorStateMachine(this); Undo = new EditorUndo(this); - ScriptsBuilder.ScriptsReloadBegin += ScriptsBuilder_ScriptsReloadBegin; - ScriptsBuilder.ScriptsReloadEnd += ScriptsBuilder_ScriptsReloadEnd; UIControl.FallbackParentGetDelegate += OnUIControlFallbackParentGet; } @@ -254,24 +252,12 @@ namespace FlaxEditor if (loadingPreview != null) { // Link it to the prefab preview to see it in the editor - loadingPreview.customControlLinked = control.Control; + loadingPreview.customControlLinked = control; return loadingPreview; } return null; } - private void ScriptsBuilder_ScriptsReloadBegin() - { - EnsureState(); - StateMachine.GoToState(); - } - - private void ScriptsBuilder_ScriptsReloadEnd() - { - EnsureState(); - StateMachine.GoToState(); - } - internal void RegisterModule(EditorModule module) { Log("Register Editor module " + module); @@ -497,9 +483,6 @@ namespace FlaxEditor Surface.VisualScriptSurface.NodesCache.Clear(); Instance = null; - ScriptsBuilder.ScriptsReloadBegin -= ScriptsBuilder_ScriptsReloadBegin; - ScriptsBuilder.ScriptsReloadEnd -= ScriptsBuilder_ScriptsReloadEnd; - // Invoke new instance if need to open a project if (!string.IsNullOrEmpty(_projectToOpen)) { diff --git a/Source/Editor/Editor.h b/Source/Editor/Editor.h index 761e4b059..8c56043d9 100644 --- a/Source/Editor/Editor.h +++ b/Source/Editor/Editor.h @@ -43,7 +43,7 @@ public: public: /// - /// The flag used to determine if a project was used with the older engine version last time it was opened. Some cached data should be regenerated to prevent version difference issues. The version number comparision is based on major and minor part of the version. Build number is ignored. + /// The flag used to determine if a project was used with the older engine version last time it was opened. Some cached data should be regenerated to prevent version difference issues. The version number comparison is based on major and minor part of the version. Build number is ignored. /// static bool IsOldProjectOpened; diff --git a/Source/Editor/GUI/AssetPicker.cs b/Source/Editor/GUI/AssetPicker.cs index 63c1276ab..db012ab1e 100644 --- a/Source/Editor/GUI/AssetPicker.cs +++ b/Source/Editor/GUI/AssetPicker.cs @@ -355,12 +355,10 @@ namespace FlaxEditor.GUI _mousePos = location; // Check if start drag drop - if (_isMouseDown && Vector2.Distance(location, _mouseDownPos) > 10.0f) + if (_isMouseDown && Vector2.Distance(location, _mouseDownPos) > 10.0f && IconRect.Contains(_mouseDownPos)) { - // Clear flag - _isMouseDown = false; - // Do the drag + _isMouseDown = false; DoDrag(); } @@ -370,35 +368,35 @@ namespace FlaxEditor.GUI /// public override bool OnMouseUp(Vector2 location, MouseButton button) { - if (button == MouseButton.Left) + if (button == MouseButton.Left && _isMouseDown) { _isMouseDown = false; - } - // Buttons logic - if (Button1Rect.Contains(location)) - { - // Show asset picker popup - Focus(); - AssetSearchPopup.Show(this, Button1Rect.BottomLeft, IsValid, assetItem => + // Buttons logic + if (Button1Rect.Contains(location)) { - SelectedItem = assetItem; - RootWindow.Focus(); + // Show asset picker popup Focus(); - }); - } - else if (_selected != null || _selectedItem != null) - { - if (Button2Rect.Contains(location) && _selectedItem != null) - { - // Select asset - Editor.Instance.Windows.ContentWin.Select(_selectedItem); + AssetSearchPopup.Show(this, Button1Rect.BottomLeft, IsValid, assetItem => + { + SelectedItem = assetItem; + RootWindow.Focus(); + Focus(); + }); } - else if (Button3Rect.Contains(location)) + else if (_selected != null || _selectedItem != null) { - // Deselect asset - Focus(); - SelectedItem = null; + if (Button2Rect.Contains(location) && _selectedItem != null) + { + // Select asset + Editor.Instance.Windows.ContentWin.Select(_selectedItem); + } + else if (Button3Rect.Contains(location)) + { + // Deselect asset + Focus(); + SelectedItem = null; + } } } @@ -409,8 +407,7 @@ namespace FlaxEditor.GUI /// public override bool OnMouseDown(Vector2 location, MouseButton button) { - // Set flag for dragging asset - if (button == MouseButton.Left && IconRect.Contains(location)) + if (button == MouseButton.Left) { _isMouseDown = true; _mouseDownPos = location; diff --git a/Source/Editor/GUI/CurveEditor.cs b/Source/Editor/GUI/CurveEditor.cs index 8840a7f06..3819b5758 100644 --- a/Source/Editor/GUI/CurveEditor.cs +++ b/Source/Editor/GUI/CurveEditor.cs @@ -16,6 +16,7 @@ namespace FlaxEditor.GUI /// /// The base class for editors. Allows to use generic curve editor without type information at compile-time. /// + [HideInEditor] public abstract class CurveEditorBase : ContainerControl { /// @@ -221,7 +222,7 @@ namespace FlaxEditor.GUI } /// - /// Filters teh given value using the the . + /// Filters the given value using the . /// /// The mode. /// The value to process. diff --git a/Source/Editor/GUI/Dialogs/Dialog.cs b/Source/Editor/GUI/Dialogs/Dialog.cs index b8897b2e1..173dd9128 100644 --- a/Source/Editor/GUI/Dialogs/Dialog.cs +++ b/Source/Editor/GUI/Dialogs/Dialog.cs @@ -186,7 +186,7 @@ namespace FlaxEditor.GUI.Dialogs // Clean up _window = null; - // Check if any thead is blocked during ShowDialog, then wait for it + // Check if any thread is blocked during ShowDialog, then wait for it bool wait = true; while (wait) { diff --git a/Source/Editor/GUI/Docking/DockWindow.cs b/Source/Editor/GUI/Docking/DockWindow.cs index aa6d94ca8..56c78efed 100644 --- a/Source/Editor/GUI/Docking/DockWindow.cs +++ b/Source/Editor/GUI/Docking/DockWindow.cs @@ -307,6 +307,14 @@ namespace FlaxEditor.GUI.Docking _dockedTo?.SelectTab(this, autoFocus); } + /// + /// Brings the window to the front of the Z order. + /// + public void BringToFront() + { + _dockedTo?.RootWindow?.BringToFront(); + } + internal void OnUnlinkInternal() { OnUnlink(); @@ -412,6 +420,7 @@ namespace FlaxEditor.GUI.Docking base.Focus(); SelectTab(); + BringToFront(); } /// diff --git a/Source/Editor/GUI/Docking/MasterDockPanel.cs b/Source/Editor/GUI/Docking/MasterDockPanel.cs index f7acdbed4..a560de195 100644 --- a/Source/Editor/GUI/Docking/MasterDockPanel.cs +++ b/Source/Editor/GUI/Docking/MasterDockPanel.cs @@ -64,7 +64,7 @@ namespace FlaxEditor.GUI.Docking for (int i = 0; i < childPanels.Length; i++) childPanels[i].Dispose(); - // Delete reaming controls (except tabs proxy) + // Delete remaining controls (except tabs proxy) if (TabsProxy != null) TabsProxy.Parent = null; DisposeChildren(); diff --git a/Source/Editor/GUI/Input/SliderControl.cs b/Source/Editor/GUI/Input/SliderControl.cs index 90288cfd0..77e8a5da8 100644 --- a/Source/Editor/GUI/Input/SliderControl.cs +++ b/Source/Editor/GUI/Input/SliderControl.cs @@ -168,6 +168,7 @@ namespace FlaxEditor.GUI.Input { if (button == MouseButton.Left) { + Focus(); float mousePosition = location.X; if (_thumbRect.Contains(ref location)) @@ -208,7 +209,6 @@ namespace FlaxEditor.GUI.Input { if (button == MouseButton.Left && _isSliding) { - // End sliding EndSliding(); return true; } diff --git a/Source/Editor/GUI/Input/UIntValueBox.cs b/Source/Editor/GUI/Input/UIntValueBox.cs index 9f1b2c1f4..d691be116 100644 --- a/Source/Editor/GUI/Input/UIntValueBox.cs +++ b/Source/Editor/GUI/Input/UIntValueBox.cs @@ -143,6 +143,8 @@ namespace FlaxEditor.GUI.Input try { var value = ShuntingYard.Parse(Text); + if (value < 0) + value = 0; Value = (uint)value; } catch (Exception ex) diff --git a/Source/Editor/GUI/Input/ValueBox.cs b/Source/Editor/GUI/Input/ValueBox.cs index 8ee1c3887..d34acd17b 100644 --- a/Source/Editor/GUI/Input/ValueBox.cs +++ b/Source/Editor/GUI/Input/ValueBox.cs @@ -49,6 +49,11 @@ namespace FlaxEditor.GUI.Input /// protected T _startSlideValue; + /// + /// The text cached on editing start. Used to compare with the end result to detect changes. + /// + protected string _startEditText; + private Vector2 _startSlideLocation; /// @@ -257,11 +262,23 @@ namespace FlaxEditor.GUI.Input return base.OnMouseUp(location, button); } + /// + protected override void OnEditBegin() + { + base.OnEditBegin(); + + _startEditText = _text; + } + /// protected override void OnEditEnd() { - // Update value - TryGetValue(); + if (_startEditText != _text) + { + // Update value + TryGetValue(); + } + _startEditText = null; base.OnEditEnd(); } diff --git a/Source/Editor/GUI/Table.cs b/Source/Editor/GUI/Table.cs index e070cba17..cca26cadc 100644 --- a/Source/Editor/GUI/Table.cs +++ b/Source/Editor/GUI/Table.cs @@ -120,7 +120,7 @@ namespace FlaxEditor.GUI /// /// Draws the column. /// - /// The the header area rectangle. + /// The header area rectangle. /// The zero-based index of the column. protected virtual void DrawColumn(ref Rectangle rect, int columnIndex) { diff --git a/Source/Editor/GUI/Tabs/Tabs.cs b/Source/Editor/GUI/Tabs/Tabs.cs index 377c6827d..5b1317929 100644 --- a/Source/Editor/GUI/Tabs/Tabs.cs +++ b/Source/Editor/GUI/Tabs/Tabs.cs @@ -11,6 +11,7 @@ namespace FlaxEditor.GUI.Tabs /// Represents control which contains collection of . /// /// + [HideInEditor] public class Tabs : ContainerControl { /// diff --git a/Source/Editor/GUI/Timeline/GUI/KeyframesEditor.cs b/Source/Editor/GUI/Timeline/GUI/KeyframesEditor.cs index abd3e78a8..25f6ffb0a 100644 --- a/Source/Editor/GUI/Timeline/GUI/KeyframesEditor.cs +++ b/Source/Editor/GUI/Timeline/GUI/KeyframesEditor.cs @@ -14,6 +14,7 @@ namespace FlaxEditor.GUI /// The generic keyframes animation editor control. /// /// + [HideInEditor] public class KeyframesEditor : ContainerControl { /// @@ -401,7 +402,7 @@ namespace FlaxEditor.GUI } /// - /// Converts the input point from editor editor contents control space into the keyframes time/value coordinates. + /// Converts the input point from editor contents control space into the keyframes time/value coordinates. /// /// The point. /// The keyframes contents area bounds. diff --git a/Source/Editor/GUI/Timeline/Timeline.cs b/Source/Editor/GUI/Timeline/Timeline.cs index 149f4a3c8..e0e4083c1 100644 --- a/Source/Editor/GUI/Timeline/Timeline.cs +++ b/Source/Editor/GUI/Timeline/Timeline.cs @@ -20,6 +20,7 @@ namespace FlaxEditor.GUI.Timeline /// The timeline control that contains tracks section and headers. Can be used to create time-based media interface for camera tracks editing, audio mixing and events tracking. /// /// + [HideInEditor] public class Timeline : ContainerControl { private static readonly KeyValuePair[] FPSValues = diff --git a/Source/Editor/GUI/Timeline/Track.cs b/Source/Editor/GUI/Timeline/Track.cs index 4f5278251..6ba8191d6 100644 --- a/Source/Editor/GUI/Timeline/Track.cs +++ b/Source/Editor/GUI/Timeline/Track.cs @@ -16,6 +16,7 @@ namespace FlaxEditor.GUI.Timeline /// The Timeline track that contains a header and custom timeline events/media. /// /// + [HideInEditor] public class Track : ContainerControl { /// diff --git a/Source/Editor/GUI/Timeline/Tracks/MemberTrack.cs b/Source/Editor/GUI/Timeline/Tracks/MemberTrack.cs index 57de46ee4..250fb73c3 100644 --- a/Source/Editor/GUI/Timeline/Tracks/MemberTrack.cs +++ b/Source/Editor/GUI/Timeline/Tracks/MemberTrack.cs @@ -179,7 +179,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks } /// - /// Evaluates the member value value at the specified time. + /// Evaluates the member value at the specified time. /// /// The time to evaluate the member at. /// The member value at provided time. diff --git a/Source/Editor/GUI/Tree/TreeNode.cs b/Source/Editor/GUI/Tree/TreeNode.cs index 727da3edd..f7e5f115a 100644 --- a/Source/Editor/GUI/Tree/TreeNode.cs +++ b/Source/Editor/GUI/Tree/TreeNode.cs @@ -21,7 +21,7 @@ namespace FlaxEditor.GUI.Tree /// /// The default node offset on Y axis. /// - public const float DefaultNodeOffsetY = 1; + public const float DefaultNodeOffsetY = 0; private Tree _tree; @@ -539,7 +539,7 @@ namespace FlaxEditor.GUI.Tree { if (new Rectangle(_headerRect.X, _headerRect.Y - DefaultDragInsertPositionMargin - DefaultNodeOffsetY, _headerRect.Width, DefaultDragInsertPositionMargin * 2.0f).Contains(location)) _dragOverMode = DragItemPositioning.Above; - else if (IsCollapsed && new Rectangle(_headerRect.X, _headerRect.Bottom - DefaultDragInsertPositionMargin, _headerRect.Width, DefaultDragInsertPositionMargin * 2.0f).Contains(location)) + else if ((IsCollapsed || !HasAnyVisibleChild) && new Rectangle(_headerRect.X, _headerRect.Bottom - DefaultDragInsertPositionMargin, _headerRect.Width, DefaultDragInsertPositionMargin * 2.0f).Contains(location)) _dragOverMode = DragItemPositioning.Below; else _dragOverMode = DragItemPositioning.At; diff --git a/Source/Editor/Gizmo/GizmosCollection.cs b/Source/Editor/Gizmo/GizmosCollection.cs index a246aeeb0..5530a41a3 100644 --- a/Source/Editor/Gizmo/GizmosCollection.cs +++ b/Source/Editor/Gizmo/GizmosCollection.cs @@ -7,7 +7,7 @@ using FlaxEngine; namespace FlaxEditor.Gizmo { /// - /// Represents collection of Gizmo tools where one is active and in use. + /// Represents collection of gizmo tools where one is active and in use. /// /// [HideInEditor] diff --git a/Source/Editor/Gizmo/TransformGizmo.cs b/Source/Editor/Gizmo/TransformGizmo.cs index eca5475ed..f7aed5597 100644 --- a/Source/Editor/Gizmo/TransformGizmo.cs +++ b/Source/Editor/Gizmo/TransformGizmo.cs @@ -71,7 +71,7 @@ namespace FlaxEditor.Gizmo if (hit != null) { // For child actor nodes (mesh, link or sth) we need to select it's owning actor node first or any other child node (but not a child actor) - if (hit is ActorChildNode actorChildNode) + if (hit is ActorChildNode actorChildNode && !actorChildNode.CanBeSelectedDirectly) { var parentNode = actorChildNode.ParentNode; bool canChildBeSelected = sceneEditing.Selection.Contains(parentNode); diff --git a/Source/Editor/Gizmo/TransformGizmoBase.Draw.cs b/Source/Editor/Gizmo/TransformGizmoBase.Draw.cs index 8f871c635..0a11ecd77 100644 --- a/Source/Editor/Gizmo/TransformGizmoBase.Draw.cs +++ b/Source/Editor/Gizmo/TransformGizmoBase.Draw.cs @@ -45,7 +45,7 @@ namespace FlaxEditor.Gizmo ) { // Error - Platform.Fatal("Failed to load Transform Gizmo resources."); + Platform.Fatal("Failed to load transform gizmo resources."); } } diff --git a/Source/Editor/Gizmo/TransformGizmoBase.Settings.cs b/Source/Editor/Gizmo/TransformGizmoBase.Settings.cs index 786e001e6..2955fe6d7 100644 --- a/Source/Editor/Gizmo/TransformGizmoBase.Settings.cs +++ b/Source/Editor/Gizmo/TransformGizmoBase.Settings.cs @@ -104,7 +104,7 @@ namespace FlaxEditor.Gizmo public Axis ActiveAxis => _activeAxis; /// - /// Gets or sts the current gizmo mode. + /// Gets or sets the current gizmo mode. /// public Mode ActiveMode { diff --git a/Source/Editor/Gizmo/TransformGizmoBase.cs b/Source/Editor/Gizmo/TransformGizmoBase.cs index 87dcb6fa8..014143685 100644 --- a/Source/Editor/Gizmo/TransformGizmoBase.cs +++ b/Source/Editor/Gizmo/TransformGizmoBase.cs @@ -151,7 +151,7 @@ namespace FlaxEditor.Gizmo // Set positions of the gizmo UpdateGizmoPosition(); - // Scale Gizmo to fit on-screen + // Scale gizmo to fit on-screen Vector3 vLength = Owner.ViewPosition - Position; float gizmoSize = Editor.Instance.Options.Options.Visual.GizmoSize; _screenScale = vLength.Length / GizmoScaleFactor * gizmoSize; @@ -196,12 +196,10 @@ namespace FlaxEditor.Gizmo private void UpdateTranslateScale() { bool isScaling = _activeMode == Mode.Scale; - Vector3 delta = Vector3.Zero; Ray ray = Owner.MouseRay; - Matrix invRotationMatrix; - Matrix.Invert(ref _rotationMatrix, out invRotationMatrix); + Matrix.Invert(ref _rotationMatrix, out var invRotationMatrix); ray.Position = Vector3.Transform(ray.Position, invRotationMatrix); Vector3.TransformNormal(ref ray.Direction, ref invRotationMatrix, out ray.Direction); @@ -211,9 +209,7 @@ namespace FlaxEditor.Gizmo case Axis.X: { var plane = new Plane(Vector3.Backward, Vector3.Transform(Position, invRotationMatrix).Z); - - float intersection; - if (ray.Intersects(ref plane, out intersection)) + if (ray.Intersects(ref plane, out float intersection)) { _intersectPosition = ray.Position + ray.Direction * intersection; if (_lastIntersectionPosition != Vector3.Zero) @@ -222,18 +218,14 @@ namespace FlaxEditor.Gizmo ? new Vector3(_tDelta.X, 0, 0) : new Vector3(_tDelta.X, _tDelta.Y, 0); } - break; } - case Axis.Z: case Axis.YZ: case Axis.Y: { var plane = new Plane(Vector3.Left, Vector3.Transform(Position, invRotationMatrix).X); - - float intersection; - if (ray.Intersects(ref plane, out intersection)) + if (ray.Intersects(ref plane, out float intersection)) { _intersectPosition = ray.Position + ray.Direction * intersection; if (_lastIntersectionPosition != Vector3.Zero) @@ -251,41 +243,31 @@ namespace FlaxEditor.Gizmo break; } } - break; } - case Axis.ZX: { var plane = new Plane(Vector3.Down, Vector3.Transform(Position, invRotationMatrix).Y); - - float intersection; - if (ray.Intersects(ref plane, out intersection)) + if (ray.Intersects(ref plane, out float intersection)) { _intersectPosition = ray.Position + ray.Direction * intersection; if (_lastIntersectionPosition != Vector3.Zero) _tDelta = _intersectPosition - _lastIntersectionPosition; delta = new Vector3(_tDelta.X, 0, _tDelta.Z); } - break; } - case Axis.Center: { - Vector3 gizmoToView = Position - Owner.ViewPosition; + var gizmoToView = Position - Owner.ViewPosition; var plane = new Plane(-Vector3.Normalize(gizmoToView), gizmoToView.Length); - - float intersection; - if (ray.Intersects(ref plane, out intersection)) + if (ray.Intersects(ref plane, out float intersection)) { _intersectPosition = ray.Position + ray.Direction * intersection; if (_lastIntersectionPosition != Vector3.Zero) _tDelta = _intersectPosition - _lastIntersectionPosition; } - delta = _tDelta; - break; } } @@ -299,14 +281,11 @@ namespace FlaxEditor.Gizmo if ((isScaling ? ScaleSnapEnabled : TranslationSnapEnable) || Owner.UseSnapping) { float snapValue = isScaling ? ScaleSnapValue : TranslationSnapValue; - _translationScaleSnapDelta += delta; - delta = new Vector3( (int)(_translationScaleSnapDelta.X / snapValue) * snapValue, (int)(_translationScaleSnapDelta.Y / snapValue) * snapValue, (int)(_translationScaleSnapDelta.Z / snapValue) * snapValue); - _translationScaleSnapDelta -= delta; } @@ -318,7 +297,30 @@ namespace FlaxEditor.Gizmo } else if (_activeMode == Mode.Scale) { - // Apply Scale + // Scale + if (_activeTransformSpace == TransformSpace.World && _activeAxis != Axis.Center) + { + var deltaLocal = delta; + Quaternion orientation = GetSelectedObject(0).Orientation; + delta = Vector3.Transform(delta, orientation); + + // Fix axis sign of delta movement for rotated object in some cases (eg. rotated object by 90 deg on Y axis and scale in world space with Red/X axis) + switch (_activeAxis) + { + case Axis.X: + if (deltaLocal.X < 0) + delta *= -1; + break; + case Axis.Y: + if (deltaLocal.Y < 0) + delta *= -1; + break; + case Axis.Z: + if (deltaLocal.Z < 0) + delta *= -1; + break; + } + } _scaleDelta = delta; } } @@ -382,7 +384,7 @@ namespace FlaxEditor.Gizmo // Snap to ground if (_activeAxis == Axis.None && SelectionCount != 0 && Owner.SnapToGround) { - if (Physics.RayCast(Position, Vector3.Down, out var hit, float.MaxValue, int.MaxValue, false)) + if (Physics.RayCast(Position, Vector3.Down, out var hit, float.MaxValue, uint.MaxValue, false)) { StartTransforming(); var translationDelta = hit.Point - Position; @@ -408,7 +410,6 @@ namespace FlaxEditor.Gizmo case Mode.Translate: UpdateTranslateScale(); break; - case Mode.Rotate: UpdateRotate(dt); break; @@ -437,7 +438,7 @@ namespace FlaxEditor.Gizmo translationDelta = _translationDelta; _translationDelta = Vector3.Zero; - // Prevent from moving objects too far away, like to different galaxy or sth + // Prevent from moving objects too far away, like to a different galaxy or sth Vector3 prevMoveDelta = _accMoveDelta; _accMoveDelta += _translationDelta; if (_accMoveDelta.Length > Owner.ViewFarPlane * 0.7f) diff --git a/Source/Editor/History/HistoryStack.cs b/Source/Editor/History/HistoryStack.cs index c7a63efc4..e2d828ce9 100644 --- a/Source/Editor/History/HistoryStack.cs +++ b/Source/Editor/History/HistoryStack.cs @@ -74,7 +74,7 @@ namespace FlaxEditor.History _reverseActions.PushBack(reverse[i]); } - // Cleanup reaming actions + // Cleanup remaining actions for (int i = _historyActionsLimit; i < history.Length; i++) { history[i].Dispose(); diff --git a/Source/Editor/Managed/ManagedEditor.Internal.cpp b/Source/Editor/Managed/ManagedEditor.Internal.cpp index 4084c7f01..28d4df32a 100644 --- a/Source/Editor/Managed/ManagedEditor.Internal.cpp +++ b/Source/Editor/Managed/ManagedEditor.Internal.cpp @@ -24,7 +24,7 @@ #include "Engine/ContentImporters/ImportAudio.h" #include "Engine/ContentImporters/CreateCollisionData.h" #include "Engine/ContentImporters/CreateJson.h" -#include "Engine/Core/Config/LayersTagsSettings.h" +#include "Engine/Level/Level.h" #include "Engine/Core/Config/GameSettings.h" #include "Engine/Core/Cache.h" #include "Engine/CSG/CSGBuilder.h" @@ -302,22 +302,20 @@ namespace CustomEditorsUtilInternal } } -namespace LayersAndTagsSettingsInternal +namespace LayersAndTagsSettingsInternal1 { MonoArray* GetCurrentTags() { - auto settings = LayersAndTagsSettings::Instance(); - return MUtils::ToArray(settings->Tags); + return MUtils::ToArray(Level::Tags); } MonoArray* GetCurrentLayers() { - const auto settings = LayersAndTagsSettings::Instance(); - return MUtils::ToArray(Span(settings->Layers, Math::Max(1, settings->GetNonEmptyLayerNamesCount()))); + return MUtils::ToArray(Span(Level::Layers, Math::Max(1, Level::GetNonEmptyLayerNamesCount()))); } } -namespace GameSettingsInternal +namespace GameSettingsInternal1 { void Apply() { @@ -1054,9 +1052,9 @@ public: ADD_INTERNAL_CALL("FlaxEditor.Content.Import.TextureImportEntry::Internal_GetTextureImportOptions", &GetTextureImportOptions); ADD_INTERNAL_CALL("FlaxEditor.Content.Import.ModelImportEntry::Internal_GetModelImportOptions", &GetModelImportOptions); ADD_INTERNAL_CALL("FlaxEditor.Content.Import.AudioImportEntry::Internal_GetAudioImportOptions", &GetAudioImportOptions); - ADD_INTERNAL_CALL("FlaxEditor.Content.Settings.LayersAndTagsSettings::GetCurrentTags", &LayersAndTagsSettingsInternal::GetCurrentTags); - ADD_INTERNAL_CALL("FlaxEditor.Content.Settings.LayersAndTagsSettings::GetCurrentLayers", &LayersAndTagsSettingsInternal::GetCurrentLayers); - ADD_INTERNAL_CALL("FlaxEditor.Content.Settings.GameSettings::Apply", &GameSettingsInternal::Apply); + ADD_INTERNAL_CALL("FlaxEditor.Content.Settings.LayersAndTagsSettings::GetCurrentTags", &LayersAndTagsSettingsInternal1::GetCurrentTags); + ADD_INTERNAL_CALL("FlaxEditor.Content.Settings.LayersAndTagsSettings::GetCurrentLayers", &LayersAndTagsSettingsInternal1::GetCurrentLayers); + ADD_INTERNAL_CALL("FlaxEditor.Content.Settings.GameSettings::Apply", &GameSettingsInternal1::Apply); ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_CloseSplashScreen", &CloseSplashScreen); ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_CreateAsset", &CreateAsset); ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_CreateVisualScript", &CreateVisualScript); diff --git a/Source/Editor/Modules/ContentFindingModule.cs b/Source/Editor/Modules/ContentFindingModule.cs index 88b5832af..9be15e0d1 100644 --- a/Source/Editor/Modules/ContentFindingModule.cs +++ b/Source/Editor/Modules/ContentFindingModule.cs @@ -108,7 +108,7 @@ namespace FlaxEditor.Modules /// /// Removes a quick action by name. /// - /// Thr action's name. + /// The action's name. /// True when it succeed, false if there is no Quick Action with this name. public bool RemoveQuickAction(string name) { diff --git a/Source/Editor/Modules/ContentImportingModule.cs b/Source/Editor/Modules/ContentImportingModule.cs index 03f434b9c..0c934f130 100644 --- a/Source/Editor/Modules/ContentImportingModule.cs +++ b/Source/Editor/Modules/ContentImportingModule.cs @@ -410,7 +410,7 @@ namespace FlaxEditor.Modules { if (request.Settings != null && entry.TryOverrideSettings(request.Settings)) { - // Use overriden settings + // Use overridden settings } else if (!request.SkipSettingsDialog) { diff --git a/Source/Editor/Modules/SceneEditingModule.cs b/Source/Editor/Modules/SceneEditingModule.cs index 78ffd26b0..8c0914d58 100644 --- a/Source/Editor/Modules/SceneEditingModule.cs +++ b/Source/Editor/Modules/SceneEditingModule.cs @@ -200,7 +200,8 @@ namespace FlaxEditor.Modules /// /// The actor. /// The parent actor. Set null as default. - public void Spawn(Actor actor, Actor parent = null) + /// True if automatically select the spawned actor, otherwise false. + public void Spawn(Actor actor, Actor parent = null, bool autoSelect = true) { bool isPlayMode = Editor.StateMachine.IsPlayMode; @@ -225,7 +226,15 @@ namespace FlaxEditor.Modules actorNode.PostSpawn(); // Create undo action - var action = new DeleteActorsAction(new List(1) { actorNode }, true); + IUndoAction action = new DeleteActorsAction(new List(1) { actorNode }, true); + if (autoSelect) + { + var before = Selection.ToArray(); + Selection.Clear(); + Selection.Add(actorNode); + OnSelectionChanged(); + action = new MultiUndoAction(action, new SelectionChangeAction(before, Selection.ToArray(), OnSelectionUndo)); + } Undo.AddAction(action); // Mark scene as dirty @@ -358,7 +367,15 @@ namespace FlaxEditor.Modules var pasteAction = PasteActorsAction.Paste(data, pasteTargetActor?.ID ?? Guid.Empty); if (pasteAction != null) { - OnPasteAction(pasteAction); + pasteAction.Do(out _, out var nodeParents); + + // Select spawned objects (parents only) + var selectAction = new SelectionChangeAction(Selection.ToArray(), nodeParents.Cast().ToArray(), OnSelectionUndo); + selectAction.Do(); + + // Build single compound undo action that pastes the actors and selects the created objects (parents only) + Undo.AddAction(new MultiUndoAction(pasteAction, selectAction)); + OnSelectionChanged(); } } @@ -377,12 +394,57 @@ namespace FlaxEditor.Modules public void Duplicate() { // Peek things that can be copied (copy all actors) - var objects = Selection.Where(x => x.CanCopyPaste).ToList().BuildAllNodes().Where(x => x.CanCopyPaste && x is ActorNode).ToList(); - if (objects.Count == 0) + var nodes = Selection.Where(x => x.CanDuplicate).ToList().BuildAllNodes(); + if (nodes.Count == 0) return; + var actors = new List(); + var newSelection = new List(); + List customUndoActions = null; + foreach (var node in nodes) + { + if (node.CanDuplicate) + { + if (node is ActorNode actorNode) + { + actors.Add(actorNode.Actor); + } + else + { + var customDuplicatedObject = node.Duplicate(out var customUndoAction); + if (customDuplicatedObject != null) + newSelection.Add(customDuplicatedObject); + if (customUndoAction != null) + { + if (customUndoActions == null) + customUndoActions = new List(); + customUndoActions.Add(customUndoAction); + } + } + } + } + if (actors.Count == 0) + { + // Duplicate custom scene graph nodes only without actors + if (newSelection.Count != 0) + { + // Select spawned objects (parents only) + var selectAction = new SelectionChangeAction(Selection.ToArray(), newSelection.ToArray(), OnSelectionUndo); + selectAction.Do(); + + // Build a single compound undo action that pastes the actors, pastes custom stuff (scene graph extension) and selects the created objects (parents only) + var customUndoActionsCount = customUndoActions?.Count ?? 0; + var undoActions = new IUndoAction[1 + customUndoActionsCount]; + for (int i = 0; i < customUndoActionsCount; i++) + undoActions[i] = customUndoActions[i]; + undoActions[undoActions.Length - 1] = selectAction; + + Undo.AddAction(new MultiUndoAction(undoActions)); + OnSelectionChanged(); + } + return; + } // Serialize actors - var actors = objects.ConvertAll(x => ((ActorNode)x).Actor); var data = Actor.ToBytes(actors.ToArray()); if (data == null) { @@ -394,22 +456,26 @@ namespace FlaxEditor.Modules var pasteAction = PasteActorsAction.Duplicate(data, Guid.Empty); if (pasteAction != null) { - OnPasteAction(pasteAction); + pasteAction.Do(out _, out var nodeParents); + + // Select spawned objects (parents only) + newSelection.AddRange(nodeParents); + var selectAction = new SelectionChangeAction(Selection.ToArray(), newSelection.ToArray(), OnSelectionUndo); + selectAction.Do(); + + // Build a single compound undo action that pastes the actors, pastes custom stuff (scene graph extension) and selects the created objects (parents only) + var customUndoActionsCount = customUndoActions?.Count ?? 0; + var undoActions = new IUndoAction[2 + customUndoActionsCount]; + undoActions[0] = pasteAction; + for (int i = 0; i < customUndoActionsCount; i++) + undoActions[i + 1] = customUndoActions[i]; + undoActions[undoActions.Length - 1] = selectAction; + + Undo.AddAction(new MultiUndoAction(undoActions)); + OnSelectionChanged(); } } - private void OnPasteAction(PasteActorsAction pasteAction) - { - pasteAction.Do(out _, out var nodeParents); - - // Select spawned objects - var selectAction = new SelectionChangeAction(Selection.ToArray(), nodeParents.Cast().ToArray(), OnSelectionUndo); - selectAction.Do(); - - Undo.AddAction(new MultiUndoAction(pasteAction, selectAction)); - OnSelectionChanged(); - } - /// /// Called when selection gets changed. Invokes the other events and updates editor. Call it when you manually modify selected objects collection. /// diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs index 0ed5e379c..e875b1fd3 100644 --- a/Source/Editor/Modules/UIModule.cs +++ b/Source/Editor/Modules/UIModule.cs @@ -509,7 +509,7 @@ namespace FlaxEditor.Modules _toolStripRotate = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Rotate32, () => Editor.MainTransformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate).LinkTooltip("Change Gizmo tool mode to Rotate (2)"); _toolStripScale = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Scale32, () => Editor.MainTransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale).LinkTooltip("Change Gizmo tool mode to Scale (3)"); ToolStrip.AddSeparator(); - _toolStripBuildScenes = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Build32, Editor.BuildScenesOrCancel).LinkTooltip("Build scenes data - CSG, navmesh, static lighting, env probes (Ctrl+F10)"); + _toolStripBuildScenes = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Build32, Editor.BuildScenesOrCancel).LinkTooltip("Build scenes data - CSG, navmesh, static lighting, env probes - configurable via Build Actions in editor options (Ctrl+F10)"); ToolStrip.AddSeparator(); _toolStripPlay = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Play32, Editor.Simulation.RequestPlayOrStopPlay).LinkTooltip("Start/Stop game (F5)"); _toolStripPause = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Pause32, Editor.Simulation.RequestResumeOrPause).LinkTooltip("Pause/Resume game(F6)"); diff --git a/Source/Editor/Modules/WindowsModule.cs b/Source/Editor/Modules/WindowsModule.cs index 0f12c442f..4cb3d242b 100644 --- a/Source/Editor/Modules/WindowsModule.cs +++ b/Source/Editor/Modules/WindowsModule.cs @@ -276,8 +276,8 @@ namespace FlaxEditor.Modules // Get metadata int version = int.Parse(root.Attributes["Version"].Value, CultureInfo.InvariantCulture); var virtualDesktopBounds = Platform.VirtualDesktopBounds; - var virtualDesktopSafeLeftCorner = virtualDesktopBounds.Location + new Vector2(0, 23); // 23 is a window strip size - var virtualDesktopSafeRightCorner = virtualDesktopBounds.BottomRight - new Vector2(50, 50); // apply some safe area + var virtualDesktopSafeLeftCorner = virtualDesktopBounds.Location; + var virtualDesktopSafeRightCorner = virtualDesktopBounds.BottomRight; switch (version) { @@ -976,7 +976,7 @@ namespace FlaxEditor.Modules } #region Window Events - + private void OnEditorStateChanged() { for (int i = 0; i < Windows.Count; i++) diff --git a/Source/Editor/Options/GeneralOptions.cs b/Source/Editor/Options/GeneralOptions.cs index 9b5450f45..85174030f 100644 --- a/Source/Editor/Options/GeneralOptions.cs +++ b/Source/Editor/Options/GeneralOptions.cs @@ -32,6 +32,42 @@ namespace FlaxEditor.Options LastOpened, } + /// + /// The build actions. + /// + public enum BuildAction + { + /// + /// Builds Constructive Solid Geometry brushes into meshes. + /// + [Tooltip("Builds Constructive Solid Geometry brushes into meshes.")] + CSG, + + /// + /// Builds Env Probes and Sky Lights to prerendered cube textures. + /// + [Tooltip("Builds Env Probes and Sky Lights to prerendered cube textures.")] + EnvProbes, + + /// + /// Builds static lighting into lightmaps. + /// + [Tooltip("Builds static lighting into lightmaps.")] + StaticLighting, + + /// + /// Builds navigation meshes. + /// + [Tooltip("Builds navigation meshes.")] + NavMesh, + + /// + /// Compiles the scripts. + /// + [Tooltip("Compiles the scripts.")] + CompileScripts, + } + /// /// Gets or sets the scene to load on editor startup. /// @@ -53,6 +89,19 @@ namespace FlaxEditor.Options [EditorDisplay("General", "Editor FPS"), EditorOrder(110), Tooltip("Limit for the editor draw/update frames per second rate (FPS). Use higher values if you need more responsive interface or lower values to use less device power. Value 0 disables any limits.")] public float EditorFPS { get; set; } = 60.0f; + /// + /// Gets or sets the sequence of actions to perform when using Build Scenes button. Can be used to configure this as button (eg. compile code or just update navmesh). + /// + [EditorDisplay("General"), EditorOrder(200), Tooltip("The sequence of actions to perform when using Build Scenes button. Can be used to configure this as button (eg. compile code or just update navmesh).")] + public BuildAction[] BuildActions { get; set; } = + { + BuildAction.CSG, + BuildAction.EnvProbes, + BuildAction.StaticLighting, + BuildAction.EnvProbes, + BuildAction.NavMesh, + }; + /// /// Gets or sets a value indicating whether perform automatic scripts reload on main window focus. /// diff --git a/Source/Editor/Options/InputBinding.cs b/Source/Editor/Options/InputBinding.cs index e28e8fa0c..eddd76882 100644 --- a/Source/Editor/Options/InputBinding.cs +++ b/Source/Editor/Options/InputBinding.cs @@ -143,7 +143,31 @@ namespace FlaxEditor.Options { var root = control.Root; - if (root.GetKeyDown(Key)) + if (root.GetKey(Key)) + { + if (Modifier1 == KeyboardKeys.None || root.GetKey(Modifier1)) + { + if (Modifier2 == KeyboardKeys.None || root.GetKey(Modifier2)) + { + return true; + } + } + } + + return false; + } + + /// + /// Processes this input binding to check if state matches. + /// + /// The input providing control. + /// The input key. + /// True if input has been processed, otherwise false. + public bool Process(Control control, KeyboardKeys key) + { + var root = control.Root; + + if (key == Key) { if (Modifier1 == KeyboardKeys.None || root.GetKey(Modifier1)) { @@ -435,16 +459,10 @@ namespace FlaxEditor.Options for (int i = 0; i < _bindings.Count; i++) { var binding = _bindings[i].Binder(options); - if (binding.Key == key) + if (binding.Process(control, key)) { - if (binding.Modifier1 == KeyboardKeys.None || root.GetKey(binding.Modifier1)) - { - if (binding.Modifier2 == KeyboardKeys.None || root.GetKey(binding.Modifier2)) - { - _bindings[i].Callback(); - return true; - } - } + _bindings[i].Callback(); + return true; } } diff --git a/Source/Editor/Options/OptionsModule.cs b/Source/Editor/Options/OptionsModule.cs index 7c0cd11da..3c501de85 100644 --- a/Source/Editor/Options/OptionsModule.cs +++ b/Source/Editor/Options/OptionsModule.cs @@ -41,7 +41,7 @@ namespace FlaxEditor.Options private readonly Dictionary _customSettings = new Dictionary(); /// - /// Gets the custom settings factories. Each entry defines the custom settings type identified by teh given key name. The value si a factory function that returns the default options fpr a given type. + /// Gets the custom settings factories. Each entry defines the custom settings type identified by the given key name. The value is a factory function that returns the default options for a given type. /// public IReadOnlyDictionary CustomSettings => _customSettings; diff --git a/Source/Editor/Options/ViewportOptions.cs b/Source/Editor/Options/ViewportOptions.cs index 6e1f8a9d6..5558ac89d 100644 --- a/Source/Editor/Options/ViewportOptions.cs +++ b/Source/Editor/Options/ViewportOptions.cs @@ -45,5 +45,12 @@ namespace FlaxEditor.Options [DefaultValue(60.0f), Limit(35.0f, 160.0f, 0.1f)] [EditorDisplay("Defaults", "Default Field Of View"), EditorOrder(140), Tooltip("The default field of view angle (in degrees) for the viewport camera.")] public float DefaultFieldOfView { get; set; } = 60.0f; + + /// + /// Gets or sets if the panning direction is inverted for the viewport camera. + /// + [DefaultValue(false)] + [EditorDisplay("Defaults"), EditorOrder(150), Tooltip( "Invert the panning direction for the viewport camera." )] + public bool DefaultInvertPanning { get; set; } = false; } } diff --git a/Source/Editor/Progress/ProgressHandler.cs b/Source/Editor/Progress/ProgressHandler.cs index da2782d16..c83258091 100644 --- a/Source/Editor/Progress/ProgressHandler.cs +++ b/Source/Editor/Progress/ProgressHandler.cs @@ -8,6 +8,7 @@ namespace FlaxEditor.Progress /// /// Base class for all editor handlers used to report actions progress to the user. /// + [HideInEditor] public abstract class ProgressHandler { /// @@ -79,7 +80,7 @@ namespace FlaxEditor.Progress } /// - /// Called when progress action gets updated (changed nfo text or progress value). + /// Called when progress action gets updated (changed info text or progress value). /// /// The progress (normalized to range [0;1]). /// The information text. diff --git a/Source/Editor/ProjectInfo.cpp b/Source/Editor/ProjectInfo.cpp index 0d325a823..0396217c7 100644 --- a/Source/Editor/ProjectInfo.cpp +++ b/Source/Editor/ProjectInfo.cpp @@ -204,7 +204,7 @@ bool ProjectInfo::LoadProject(const String& projectPath) reference.Project = Load(referencePath); if (reference.Project == nullptr) { - LOG(Error, "Faield to load referenced project ({0}, from {1})", reference.Name, referencePath); + LOG(Error, "Failed to load referenced project ({0}, from {1})", reference.Name, referencePath); return true; } } @@ -277,7 +277,7 @@ bool ProjectInfo::LoadOldProject(const String& projectPath) flaxReference.Project = Load(Globals::StartupFolder / TEXT("Flax.flaxproj")); if (!flaxReference.Project) { - ShowProjectLoadError(TEXT("Failed to load Flax Engien project."), projectPath); + ShowProjectLoadError(TEXT("Failed to load Flax Engine project."), projectPath); return true; } diff --git a/Source/Editor/SceneGraph/ActorChildNode.cs b/Source/Editor/SceneGraph/ActorChildNode.cs index 0711aa20a..d514534ca 100644 --- a/Source/Editor/SceneGraph/ActorChildNode.cs +++ b/Source/Editor/SceneGraph/ActorChildNode.cs @@ -19,6 +19,11 @@ namespace FlaxEditor.SceneGraph /// public readonly int Index; + /// + /// Gets a value indicating whether this node can be selected directly without selecting parent actor node first. + /// + public virtual bool CanBeSelectedDirectly => false; + /// /// Initializes a new instance of the class. /// @@ -58,6 +63,9 @@ namespace FlaxEditor.SceneGraph /// public override bool CanCopyPaste => false; + /// + public override bool CanDuplicate => false; + /// public override bool CanDrag => false; @@ -66,6 +74,18 @@ namespace FlaxEditor.SceneGraph /// public override object UndoRecordObject => ParentNode.UndoRecordObject; + + /// + public override void Dispose() + { + // Unlink from the parent + if (parentNode is ActorNode parentActorNode && parentActorNode.ActorChildNodes != null) + { + parentActorNode.ActorChildNodes.Remove(this); + } + + base.Dispose(); + } } /// @@ -77,20 +97,28 @@ namespace FlaxEditor.SceneGraph public abstract class ActorChildNode : ActorChildNode where T : ActorNode { /// - /// The actor. + /// The actor node. /// - protected readonly T _actor; + protected T _node; /// /// Initializes a new instance of the class. /// - /// The parent actor. + /// The parent actor node. /// The child id. /// The child index. - protected ActorChildNode(T actor, Guid id, int index) + protected ActorChildNode(T node, Guid id, int index) : base(id, index) { - _actor = actor; + _node = node; + } + + /// + public override void OnDispose() + { + _node = null; + + base.OnDispose(); } } } diff --git a/Source/Editor/SceneGraph/ActorNode.cs b/Source/Editor/SceneGraph/ActorNode.cs index 753440378..07ff6a3b5 100644 --- a/Source/Editor/SceneGraph/ActorNode.cs +++ b/Source/Editor/SceneGraph/ActorNode.cs @@ -41,7 +41,7 @@ namespace FlaxEditor.SceneGraph /// /// The actor child nodes used to represent special parts of the actor (meshes, links, surfaces). /// - public readonly List ActorChildNodes = new List(); + public List ActorChildNodes; /// /// Initializes a new instance of the class. @@ -108,6 +108,8 @@ namespace FlaxEditor.SceneGraph /// The node public ActorChildNode AddChildNode(ActorChildNode node) { + if (ActorChildNodes == null) + ActorChildNodes = new List(); ActorChildNodes.Add(node); node.ParentNode = this; return node; @@ -125,9 +127,12 @@ namespace FlaxEditor.SceneGraph root.OnActorChildNodesDispose(this); } - for (int i = 0; i < ActorChildNodes.Count; i++) - ActorChildNodes[i].Dispose(); - ActorChildNodes.Clear(); + if (ActorChildNodes != null) + { + for (int i = 0; i < ActorChildNodes.Count; i++) + ActorChildNodes[i].Dispose(); + ActorChildNodes.Clear(); + } } /// @@ -177,6 +182,9 @@ namespace FlaxEditor.SceneGraph /// public override bool CanCopyPaste => (_actor.HideFlags & HideFlags.HideInHierarchy) == 0; + /// + public override bool CanDuplicate => (_actor.HideFlags & HideFlags.HideInHierarchy) == 0; + /// public override bool IsActive => _actor.IsActive; @@ -275,8 +283,12 @@ namespace FlaxEditor.SceneGraph /// public override void Dispose() { - // Cleanup UI _treeNode.Dispose(); + if (ActorChildNodes != null) + { + ActorChildNodes.Clear(); + ActorChildNodes = null; + } base.Dispose(); } diff --git a/Source/Editor/SceneGraph/Actors/BoxVolumeNode.cs b/Source/Editor/SceneGraph/Actors/BoxVolumeNode.cs index 92b252138..ec62ebd47 100644 --- a/Source/Editor/SceneGraph/Actors/BoxVolumeNode.cs +++ b/Source/Editor/SceneGraph/Actors/BoxVolumeNode.cs @@ -78,14 +78,14 @@ namespace FlaxEditor.SceneGraph.Actors { get { - var actor = (BoxVolume)((BoxVolumeNode)ParentNode).Actor; + var actor = (BoxVolume)_node.Actor; var localOffset = _offset * actor.Size; Transform localTrans = new Transform(localOffset); return actor.Transform.LocalToWorld(localTrans); } set { - var actor = (BoxVolume)((BoxVolumeNode)ParentNode).Actor; + var actor = (BoxVolume)_node.Actor; Transform localTrans = actor.Transform.WorldToLocal(value); var prevLocalOffset = _offset * actor.Size; var localOffset = Vector3.Abs(_offset) * 2.0f * localTrans.Translation; diff --git a/Source/Editor/SceneGraph/Actors/NavLinkNode.cs b/Source/Editor/SceneGraph/Actors/NavLinkNode.cs index 8eb5fbc8b..62bc0a2e7 100644 --- a/Source/Editor/SceneGraph/Actors/NavLinkNode.cs +++ b/Source/Editor/SceneGraph/Actors/NavLinkNode.cs @@ -37,13 +37,13 @@ namespace FlaxEditor.SceneGraph.Actors { get { - var actor = (NavLink)((NavLinkNode)ParentNode).Actor; + var actor = (NavLink)_node.Actor; Transform localTrans = new Transform(_isStart ? actor.Start : actor.End); return actor.Transform.LocalToWorld(localTrans); } set { - var actor = (NavLink)((NavLinkNode)ParentNode).Actor; + var actor = (NavLink)_node.Actor; Transform localTrans = actor.Transform.WorldToLocal(value); if (_isStart) actor.Start = localTrans.Translation; diff --git a/Source/Editor/SceneGraph/Actors/NavModifierVolumeNode.cs b/Source/Editor/SceneGraph/Actors/NavModifierVolumeNode.cs new file mode 100644 index 000000000..38faba8ef --- /dev/null +++ b/Source/Editor/SceneGraph/Actors/NavModifierVolumeNode.cs @@ -0,0 +1,20 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using FlaxEngine; + +namespace FlaxEditor.SceneGraph.Actors +{ + /// + /// Actor node for . + /// + /// + [HideInEditor] + public sealed class NavModifierVolumeNode : BoxVolumeNode + { + /// + public NavModifierVolumeNode(Actor actor) + : base(actor) + { + } + } +} diff --git a/Source/Editor/SceneGraph/Actors/SceneNode.cs b/Source/Editor/SceneGraph/Actors/SceneNode.cs index 359d3d207..35c71bf02 100644 --- a/Source/Editor/SceneGraph/Actors/SceneNode.cs +++ b/Source/Editor/SceneGraph/Actors/SceneNode.cs @@ -57,6 +57,9 @@ namespace FlaxEditor.SceneGraph.Actors /// public override bool CanCopyPaste => false; + /// + public override bool CanDuplicate => false; + /// public override bool CanDelete => false; diff --git a/Source/Editor/SceneGraph/Actors/SplineNode.cs b/Source/Editor/SceneGraph/Actors/SplineNode.cs new file mode 100644 index 000000000..3d41d0904 --- /dev/null +++ b/Source/Editor/SceneGraph/Actors/SplineNode.cs @@ -0,0 +1,372 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System; +using FlaxEditor.GUI.ContextMenu; +using FlaxEditor.Modules; +using FlaxEngine; +using FlaxEngine.Json; +using Object = FlaxEngine.Object; + +namespace FlaxEditor.SceneGraph.Actors +{ + /// + /// Scene tree node for actor type. + /// + [HideInEditor] + public sealed class SplineNode : ActorNode + { + private sealed class SplinePointNode : ActorChildNode + { + public unsafe SplinePointNode(SplineNode node, Guid id, int index) + : base(node, id, index) + { + var g = (JsonSerializer.GuidInterop*)&id; + g->D++; + AddChild(new SplinePointTangentNode(node, id, index, true)); + g->D++; + AddChild(new SplinePointTangentNode(node, id, index, false)); + } + + public override bool CanBeSelectedDirectly => true; + + public override bool CanDuplicate => true; + + public override bool CanDelete => true; + + public override bool CanUseState => true; + + public override Transform Transform + { + get + { + var actor = (Spline)_node.Actor; + return actor.GetSplineTransform(Index); + } + set + { + var actor = (Spline)_node.Actor; + actor.SetSplineTransform(Index, value); + OnSplineEdited(actor); + } + } + + struct Data + { + public Guid Spline; + public int Index; + public BezierCurve.Keyframe Keyframe; + } + + public override StateData State + { + get + { + var actor = (Spline)_node.Actor; + return new StateData + { + TypeName = typeof(SplinePointNode).FullName, + CreateMethodName = nameof(Create), + State = JsonSerializer.Serialize(new Data + { + Spline = actor.ID, + Index = Index, + Keyframe = actor.GetSplineKeyframe(Index), + }), + }; + } + set => throw new NotImplementedException(); + } + + public override void Delete() + { + var actor = (Spline)_node.Actor; + actor.RemoveSplinePoint(Index); + } + + class DuplicateUndoAction : IUndoAction, ISceneEditAction + { + public Guid SplineId; + public int Index; + public float Time; + public Transform Value; + + public string ActionString => "Duplicate spline point"; + + public void Dispose() + { + } + + public void Do() + { + var spline = Object.Find(ref SplineId); + spline.InsertSplineLocalPoint(Index, Time, Value); + } + + public void Undo() + { + var spline = Object.Find(ref SplineId); + spline.RemoveSplinePoint(Index); + } + + public void MarkSceneEdited(SceneModule sceneModule) + { + var spline = Object.Find(ref SplineId); + sceneModule.MarkSceneEdited(spline.Scene); + OnSplineEdited(spline); + } + } + + public override SceneGraphNode Duplicate(out IUndoAction undoAction) + { + var actor = (Spline)_node.Actor; + int newIndex; + float newTime; + if (Index == actor.SplinePointsCount - 1) + { + // Append to the end + newIndex = actor.SplinePointsCount; + newTime = actor.GetSplineTime(newIndex - 1) + 1.0f; + } + else + { + // Insert between this and next point + newIndex = Index + 1; + newTime = (actor.GetSplineTime(Index) + actor.GetSplineTime(Index + 1)) * 0.5f; + } + var action = new DuplicateUndoAction + { + SplineId = actor.ID, + Index = newIndex, + Time = newTime, + Value = actor.GetSplineLocalTransform(Index), + }; + actor.InsertSplineLocalPoint(newIndex, newTime, action.Value); + undoAction = action; + var splineNode = (SplineNode)SceneGraphFactory.FindNode(action.SplineId); + splineNode.OnUpdate(); + OnSplineEdited(actor); + return splineNode.ActorChildNodes[newIndex]; + } + + public override bool RayCastSelf(ref RayCastData ray, out float distance, out Vector3 normal) + { + var actor = (Spline)_node.Actor; + var pos = actor.GetSplinePoint(Index); + normal = -ray.Ray.Direction; + return new BoundingSphere(pos, 7.0f).Intersects(ref ray.Ray, out distance); + } + + public override void OnDebugDraw(ViewportDebugDrawData data) + { + var actor = (Spline)_node.Actor; + var pos = actor.GetSplinePoint(Index); + var tangentIn = actor.GetSplineTangent(Index, true).Translation; + var tangentOut = actor.GetSplineTangent(Index, false).Translation; + + // Draw spline path + ParentNode.OnDebugDraw(data); + + // Draw selected point highlight + DebugDraw.DrawSphere(new BoundingSphere(pos, 5.0f), Color.Yellow, 0, false); + + // Draw tangent points + if (tangentIn != pos) + { + DebugDraw.DrawLine(pos, tangentIn, Color.White.AlphaMultiplied(0.6f), 0, false); + DebugDraw.DrawWireSphere(new BoundingSphere(tangentIn, 4.0f), Color.White, 0, false); + } + if (tangentIn != pos) + { + DebugDraw.DrawLine(pos, tangentOut, Color.White.AlphaMultiplied(0.6f), 0, false); + DebugDraw.DrawWireSphere(new BoundingSphere(tangentOut, 4.0f), Color.White, 0, false); + } + } + + public override void OnContextMenu(ContextMenu contextMenu) + { + ParentNode.OnContextMenu(contextMenu); + } + + public static SceneGraphNode Create(StateData state) + { + var data = JsonSerializer.Deserialize(state.State); + var spline = Object.Find(ref data.Spline); + spline.InsertSplineLocalPoint(data.Index, data.Keyframe.Time, data.Keyframe.Value, false); + spline.SetSplineKeyframe(data.Index, data.Keyframe); + var splineNode = (SplineNode)SceneGraphFactory.FindNode(data.Spline); + if (splineNode == null) + return null; + splineNode.OnUpdate(); + OnSplineEdited(spline); + return splineNode.ActorChildNodes[data.Index]; + } + } + + private sealed class SplinePointTangentNode : ActorChildNode + { + private SplineNode _node; + private int _index; + private bool _isIn; + + public SplinePointTangentNode(SplineNode node, Guid id, int index, bool isIn) + : base(id, isIn ? 0 : 1) + { + _node = node; + _index = index; + _isIn = isIn; + } + + public override Transform Transform + { + get + { + var actor = (Spline)_node.Actor; + return actor.GetSplineTangent(_index, _isIn); + } + set + { + var actor = (Spline)_node.Actor; + actor.SetSplineTangent(_index, value, _isIn); + } + } + + public override bool RayCastSelf(ref RayCastData ray, out float distance, out Vector3 normal) + { + var actor = (Spline)_node.Actor; + var pos = actor.GetSplineTangent(_index, _isIn).Translation; + normal = -ray.Ray.Direction; + return new BoundingSphere(pos, 7.0f).Intersects(ref ray.Ray, out distance); + } + + public override void OnDebugDraw(ViewportDebugDrawData data) + { + // Draw spline and spline point + ParentNode.OnDebugDraw(data); + + // Draw selected tangent highlight + var actor = (Spline)_node.Actor; + var pos = actor.GetSplineTangent(_index, _isIn).Translation; + DebugDraw.DrawSphere(new BoundingSphere(pos, 5.0f), Color.YellowGreen, 0, false); + } + + public override void OnContextMenu(ContextMenu contextMenu) + { + ParentNode.OnContextMenu(contextMenu); + } + + public override void OnDispose() + { + _node = null; + + base.OnDispose(); + } + } + + /// + public SplineNode(Actor actor) + : base(actor) + { + OnUpdate(); + FlaxEngine.Scripting.Update += OnUpdate; + } + + private unsafe void OnUpdate() + { + // Sync spline points with gizmo handles + var actor = (Spline)Actor; + var dstCount = actor.SplinePointsCount; + if (dstCount > 1 && actor.IsLoop) + dstCount--; // The last point is the same as the first one for loop mode + var srcCount = ActorChildNodes?.Count ?? 0; + if (dstCount != srcCount) + { + // Remove unused points + while (srcCount > dstCount) + ActorChildNodes[srcCount-- - 1].Dispose(); + + // Add new points + var id = ID; + var g = (JsonSerializer.GuidInterop*)&id; + g->D += (uint)srcCount * 3; + while (srcCount < dstCount) + { + g->D += 3; + AddChildNode(new SplinePointNode(this, id, srcCount++)); + } + } + } + + /// + public override void PostSpawn() + { + base.PostSpawn(); + + // Setup for an initial spline + var spline = (Spline)Actor; + spline.AddSplineLocalPoint(Vector3.Zero, false); + spline.AddSplineLocalPoint(new Vector3(0, 0, 100.0f)); + } + + /// + public override void OnContextMenu(ContextMenu contextMenu) + { + base.OnContextMenu(contextMenu); + + contextMenu.AddButton("Add spline model", OnAddSplineModel); + contextMenu.AddButton("Add spline collider", OnAddSplineCollider); + contextMenu.AddButton("Add spline rope body", OnAddSplineRopeBody); + } + + private void OnAddSplineModel() + { + var actor = new SplineModel + { + StaticFlags = Actor.StaticFlags, + Transform = Actor.Transform, + }; + Editor.Instance.SceneEditing.Spawn(actor, Actor); + } + + private void OnAddSplineCollider() + { + var actor = new SplineCollider + { + StaticFlags = Actor.StaticFlags, + Transform = Actor.Transform, + }; + // TODO: auto pick the collision data if already using spline model + Editor.Instance.SceneEditing.Spawn(actor, Actor); + } + + private void OnAddSplineRopeBody() + { + var actor = new SplineRopeBody + { + StaticFlags = StaticFlags.None, + Transform = Actor.Transform, + }; + Editor.Instance.SceneEditing.Spawn(actor, Actor); + } + + internal static void OnSplineEdited(Spline spline) + { + var collider = spline.GetChild(); + if (collider && collider.Scene && collider.IsActiveInHierarchy && collider.HasStaticFlag(StaticFlags.Navigation) && !Editor.IsPlayMode) + { + var options = Editor.Instance.Options.Options.General; + if (options.AutoRebuildNavMesh) + { + Navigation.BuildNavMesh(collider.Scene, collider.Box, options.AutoRebuildNavMeshTimeoutMs); + } + } + } + + /// + public override void OnDispose() + { + FlaxEngine.Scripting.Update -= OnUpdate; + + base.OnDispose(); + } + } +} diff --git a/Source/Editor/SceneGraph/RootNode.cs b/Source/Editor/SceneGraph/RootNode.cs index fec8f68ad..10af88489 100644 --- a/Source/Editor/SceneGraph/RootNode.cs +++ b/Source/Editor/SceneGraph/RootNode.cs @@ -56,6 +56,9 @@ namespace FlaxEditor.SceneGraph /// public override bool CanCopyPaste => false; + /// + public override bool CanDuplicate => false; + /// public override bool CanDelete => false; diff --git a/Source/Editor/SceneGraph/SceneGraphFactory.cs b/Source/Editor/SceneGraph/SceneGraphFactory.cs index 68cb627f0..8103f5f05 100644 --- a/Source/Editor/SceneGraph/SceneGraphFactory.cs +++ b/Source/Editor/SceneGraph/SceneGraphFactory.cs @@ -62,8 +62,14 @@ namespace FlaxEditor.SceneGraph CustomNodesTypes.Add(typeof(NavMeshBoundsVolume), typeof(NavMeshBoundsVolumeNode)); CustomNodesTypes.Add(typeof(BoxVolume), typeof(BoxVolumeNode)); CustomNodesTypes.Add(typeof(NavLink), typeof(NavLinkNode)); + CustomNodesTypes.Add(typeof(NavModifierVolume), typeof(NavModifierVolumeNode)); CustomNodesTypes.Add(typeof(ParticleEffect), typeof(ParticleEffectNode)); CustomNodesTypes.Add(typeof(SceneAnimationPlayer), typeof(SceneAnimationPlayerNode)); + CustomNodesTypes.Add(typeof(Spline), typeof(SplineNode)); + CustomNodesTypes.Add(typeof(SplineModel), typeof(ActorNode)); + CustomNodesTypes.Add(typeof(SplineCollider), typeof(ColliderNode)); + CustomNodesTypes.Add(typeof(SplineRopeBody), typeof(ActorNode)); + CustomNodesTypes.Add(typeof(NavMesh), typeof(ActorNode)); } /// diff --git a/Source/Editor/SceneGraph/SceneGraphNode.cs b/Source/Editor/SceneGraph/SceneGraphNode.cs index 755aea139..835295147 100644 --- a/Source/Editor/SceneGraph/SceneGraphNode.cs +++ b/Source/Editor/SceneGraph/SceneGraphNode.cs @@ -15,7 +15,7 @@ namespace FlaxEditor.SceneGraph /// A class is responsible for Scene Graph management. /// [HideInEditor] - public abstract class SceneGraphNode : ITransformable + public abstract class SceneGraphNode { /// /// The parent node. @@ -65,6 +65,11 @@ namespace FlaxEditor.SceneGraph /// public virtual bool CanCopyPaste => true; + /// + /// Gets a value indicating whether this instance can be duplicated by the user. + /// + public virtual bool CanDuplicate => true; + /// /// Gets a value indicating whether this node can be deleted by the user. /// @@ -312,6 +317,53 @@ namespace FlaxEditor.SceneGraph { } + /// + /// Called when scene tree window wants to show the context menu. Allows to add custom options. + /// + public virtual void OnContextMenu(FlaxEditor.GUI.ContextMenu.ContextMenu contextMenu) + { + } + + /// + /// The scene graph node state container. Used for Editor undo actions (eg. restoring deleted node). + /// + public struct StateData + { + /// + /// The name of the scene graph node type (full). + /// + public string TypeName; + + /// + /// The name of the method (in ) that takes this state as a parameter and returns the created scene graph node. Used by the undo actions to restore deleted objects. + /// + public string CreateMethodName; + + /// + /// The custom state data (as string). + /// + public string State; + + /// + /// The custom state data (as raw bytes). + /// + public byte[] StateRaw; + } + + /// + /// Gets a value indicating whether this node can use property for editor undo operations. + /// + public virtual bool CanUseState => false; + + /// + /// Gets or sets the node state. + /// + public virtual StateData State + { + get => throw new NotImplementedException(); + set => throw new NotImplementedException(); + } + /// /// Deletes object represented by this node eg. actor. /// @@ -319,6 +371,17 @@ namespace FlaxEditor.SceneGraph { } + /// + /// Duplicates this object. Valid only if returns true. + /// + /// The undo action that duplicated the object (already performed), null if skip it. + /// The duplicated object node. + public virtual SceneGraphNode Duplicate(out IUndoAction undoAction) + { + undoAction = null; + return null; + } + /// /// Releases the node and the child tree. Disposed all GUI parts and used resources. /// diff --git a/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioConnection.cpp b/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioConnection.cpp index d73c5279a..54ba119f2 100644 --- a/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioConnection.cpp +++ b/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioConnection.cpp @@ -49,7 +49,7 @@ public: { if (dwRejectType == SERVERCALL_RETRYLATER) { - // Retry immediatey + // Retry immediately return 99; } diff --git a/Source/Editor/Scripting/ScriptType.cs b/Source/Editor/Scripting/ScriptType.cs index 038c1b745..53c84a1b5 100644 --- a/Source/Editor/Scripting/ScriptType.cs +++ b/Source/Editor/Scripting/ScriptType.cs @@ -53,6 +53,8 @@ namespace FlaxEditor.Scripting return fieldInfo.IsPublic; if (_managed is PropertyInfo propertyInfo) return (propertyInfo.GetMethod == null || propertyInfo.GetMethod.IsPublic) && (propertyInfo.SetMethod == null || propertyInfo.SetMethod.IsPublic); + if (_managed is EventInfo eventInfo) + return eventInfo.GetAddMethod().IsPublic; if (_custom != null) return _custom.IsPublic; return false; @@ -72,6 +74,8 @@ namespace FlaxEditor.Scripting return fieldInfo.IsStatic; if (_managed is PropertyInfo propertyInfo) return (propertyInfo.GetMethod == null || propertyInfo.GetMethod.IsStatic) && (propertyInfo.SetMethod == null || propertyInfo.SetMethod.IsStatic); + if (_managed is EventInfo eventInfo) + return eventInfo.GetAddMethod().IsStatic; if (_custom != null) return _custom.IsStatic; return false; @@ -138,6 +142,11 @@ namespace FlaxEditor.Scripting /// public bool IsMethod => _managed is MethodInfo || (_custom?.IsMethod ?? false); + /// + /// Gets a value indicating whether this member is an event. + /// + public bool IsEvent => _managed is EventInfo || (_custom?.IsEvent ?? false); + /// /// Gets a value indicating whether this member value can be gathered (via getter method or directly from the field). /// @@ -173,7 +182,7 @@ namespace FlaxEditor.Scripting } /// - /// Gets the method parameters count (valid for methods only). + /// Gets the method parameters count (valid for methods and events only). /// public int ParametersCount { @@ -181,6 +190,8 @@ namespace FlaxEditor.Scripting { if (_managed is MethodInfo methodInfo) return methodInfo.GetParameters().Length; + if (_managed is EventInfo eventInfo) + return eventInfo.EventHandlerType.GetMethod("Invoke").GetParameters().Length; if (_custom != null) return _custom.ParametersCount; return 0; @@ -383,7 +394,7 @@ namespace FlaxEditor.Scripting } /// - /// Gets the method parameters metadata. + /// Gets the method parameters metadata (or event delegate signature parameters). /// public Parameter[] GetParameters() { @@ -416,6 +427,11 @@ namespace FlaxEditor.Scripting } return result; } + if (_managed is EventInfo eventInfo) + { + var invokeMethod = eventInfo.EventHandlerType.GetMethod("Invoke"); + return new ScriptMemberInfo(invokeMethod).GetParameters(); + } return _custom.GetParameters(); } @@ -634,6 +650,11 @@ namespace FlaxEditor.Scripting /// public static readonly ScriptType Null; + /// + /// A that is System.Void. + /// + public static readonly ScriptType Void = new ScriptType(typeof(void)); + /// /// Gets the type of the script as . /// @@ -1463,6 +1484,11 @@ namespace FlaxEditor.Scripting /// bool IsMethod { get; } + /// + /// Gets a value indicating whether this member is an event. + /// + bool IsEvent { get; } + /// /// Gets a value indicating whether this member value can be gathered (via getter method or directly from the field). /// @@ -1504,7 +1530,7 @@ namespace FlaxEditor.Scripting object[] GetAttributes(bool inherit); /// - /// Gets the method parameters metadata. + /// Gets the method parameters metadata (or event delegate signature parameters). /// ScriptMemberInfo.Parameter[] GetParameters(); diff --git a/Source/Editor/Scripting/ScriptsBuilder.cpp b/Source/Editor/Scripting/ScriptsBuilder.cpp index bafab0519..8c4ea4b58 100644 --- a/Source/Editor/Scripting/ScriptsBuilder.cpp +++ b/Source/Editor/Scripting/ScriptsBuilder.cpp @@ -565,7 +565,7 @@ bool ScriptsBuilderService::Init() LOG(Warning, "Missing EditorTarget property in opened project, using deducted target name {0}", Editor::Project->EditorTarget); } - // Remove any reaming files from previous Editor run hot-reloads + // Remove any remaining files from previous Editor run hot-reloads const Char *target, *platform, *architecture, *configuration; ScriptsBuilder::GetBinariesConfiguration(target, platform, architecture, configuration); if (target) diff --git a/Source/Editor/Scripting/TypeUtils.cs b/Source/Editor/Scripting/TypeUtils.cs index 70108f06f..60f64ab8e 100644 --- a/Source/Editor/Scripting/TypeUtils.cs +++ b/Source/Editor/Scripting/TypeUtils.cs @@ -59,11 +59,19 @@ namespace FlaxEditor.Scripting if (type.Type == typeof(MaterialSceneTextures)) return MaterialSceneTextures.BaseColor; if (type.IsValueType) - return type.CreateInstance(); + { + var value = type.CreateInstance(); + Utilities.Utils.InitDefaultValues(value); + return value; + } if (new ScriptType(typeof(object)).IsAssignableFrom(type)) return null; if (type.CanCreateInstance) - return type.CreateInstance(); + { + var value = type.CreateInstance(); + Utilities.Utils.InitDefaultValues(value); + return value; + } throw new NotSupportedException("Cannot create default value for type " + type); } diff --git a/Source/Editor/States/BuildingScenesState.cs b/Source/Editor/States/BuildingScenesState.cs index 998292626..40ccff829 100644 --- a/Source/Editor/States/BuildingScenesState.cs +++ b/Source/Editor/States/BuildingScenesState.cs @@ -1,6 +1,9 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; +using System.Collections.Generic; +using System.Linq; +using FlaxEditor.Options; using FlaxEditor.SceneGraph.Actors; using FlaxEngine; using FlaxEngine.Utilities; @@ -16,6 +19,9 @@ namespace FlaxEditor.States { private sealed class SubStateMachine : StateMachine { + public int ActionIndex = -1; + public readonly List Actions = new List(); + protected override void SwitchState(State nextState) { if (CurrentState != null && nextState != null) @@ -27,10 +33,40 @@ namespace FlaxEditor.States private abstract class SubState : State { + public virtual bool DirtyScenes => true; + + public virtual bool CanReloadScripts => false; + + public virtual void Before() + { + } + public virtual void Update() { } + public virtual void Done() + { + var stateMachine = (SubStateMachine)StateMachine; + stateMachine.ActionIndex++; + if (stateMachine.ActionIndex < stateMachine.Actions.Count) + { + var action = stateMachine.Actions[stateMachine.ActionIndex]; + var state = stateMachine.States.FirstOrDefault(x => x is ActionState a && a.Action == action); + if (state != null) + { + StateMachine.GoToState(state); + } + else + { + Editor.LogError($"Missing or invalid build scene action {action}."); + } + return; + } + + StateMachine.GoToState(); + } + public virtual void Cancel() { StateMachine.GoToState(); @@ -45,18 +81,31 @@ namespace FlaxEditor.States { public override void OnEnter() { - var editor = Editor.Instance; - foreach (var scene in Level.Scenes) + var stateMachine = (SubStateMachine)StateMachine; + var scenesDirty = false; + foreach (var state in stateMachine.States) { - scene.ClearLightmaps(); - editor.Scene.MarkSceneEdited(scene); + ((SubState)state).Before(); + scenesDirty |= ((SubState)state).DirtyScenes; } - StateMachine.GoToState(); + if (scenesDirty) + { + foreach (var scene in Level.Scenes) + Editor.Instance.Scene.MarkSceneEdited(scene); + } + Done(); } } - private sealed class CSGState : SubState + private abstract class ActionState : SubState { + public abstract GeneralOptions.BuildAction Action { get; } + } + + private sealed class CSGState : ActionState + { + public override GeneralOptions.BuildAction Action => GeneralOptions.BuildAction.CSG; + public override void OnEnter() { foreach (var scene in Level.Scenes) @@ -68,13 +117,14 @@ namespace FlaxEditor.States public override void Update() { if (!Editor.Internal_GetIsCSGActive()) - StateMachine.GoToState(); + Done(); } } - - private class EnvProbesNoGIState : SubState + private class EnvProbesState : ActionState { + public override GeneralOptions.BuildAction Action => GeneralOptions.BuildAction.EnvProbes; + public override void OnEnter() { Editor.Instance.Scene.ExecuteOnGraph(node => @@ -94,12 +144,20 @@ namespace FlaxEditor.States public override void Update() { if (!Editor.Instance.ProgressReporting.BakeEnvProbes.IsActive) - StateMachine.GoToState(); + Done(); } } - private sealed class StaticLightingState : SubState + private sealed class StaticLightingState : ActionState { + public override GeneralOptions.BuildAction Action => GeneralOptions.BuildAction.StaticLighting; + + public override void Before() + { + foreach (var scene in Level.Scenes) + scene.ClearLightmaps(); + } + public override void OnEnter() { Editor.LightmapsBakeEnd += OnLightmapsBakeEnd; @@ -110,7 +168,6 @@ namespace FlaxEditor.States OnLightmapsBakeEnd(false); } - /// public override void Cancel() { Editor.Internal_BakeLightmaps(true); @@ -125,21 +182,14 @@ namespace FlaxEditor.States private void OnLightmapsBakeEnd(bool failed) { - StateMachine.GoToState(); + Done(); } } - private sealed class EnvProbesWithGIState : EnvProbesNoGIState + private sealed class NavMeshState : ActionState { - public override void Update() - { - if (!Editor.Instance.ProgressReporting.BakeEnvProbes.IsActive) - StateMachine.GoToState(); - } - } + public override GeneralOptions.BuildAction Action => GeneralOptions.BuildAction.NavMesh; - private sealed class NavMeshState : SubState - { public override void OnEnter() { foreach (var scene in Level.Scenes) @@ -151,7 +201,58 @@ namespace FlaxEditor.States public override void Update() { if (!Navigation.IsBuildingNavMesh) - StateMachine.GoToState(); + Done(); + } + } + + private sealed class CompileScriptsState : ActionState + { + private bool _compiled, _reloaded; + + public override GeneralOptions.BuildAction Action => GeneralOptions.BuildAction.CompileScripts; + + public override bool DirtyScenes => false; + + public override bool CanReloadScripts => true; + + public override void OnEnter() + { + _compiled = _reloaded = false; + ScriptsBuilder.Compile(); + + ScriptsBuilder.CompilationSuccess += OnCompilationSuccess; + ScriptsBuilder.CompilationFailed += OnCompilationFailed; + ScriptsBuilder.ScriptsReloadEnd += OnScriptsReloadEnd; + } + + public override void OnExit(State nextState) + { + ScriptsBuilder.CompilationSuccess -= OnCompilationSuccess; + ScriptsBuilder.CompilationFailed -= OnCompilationFailed; + ScriptsBuilder.ScriptsReloadEnd -= OnScriptsReloadEnd; + + base.OnExit(nextState); + } + + private void OnCompilationSuccess() + { + _compiled = true; + } + + private void OnCompilationFailed() + { + Cancel(); + } + + private void OnScriptsReloadEnd() + { + _reloaded = true; + } + + public override void Update() + { + if (_compiled && _reloaded) + Done(); } } @@ -173,10 +274,10 @@ namespace FlaxEditor.States _stateMachine.AddState(new BeginState()); _stateMachine.AddState(new SetupState()); _stateMachine.AddState(new CSGState()); - _stateMachine.AddState(new EnvProbesNoGIState()); + _stateMachine.AddState(new EnvProbesState()); _stateMachine.AddState(new StaticLightingState()); - _stateMachine.AddState(new EnvProbesWithGIState()); _stateMachine.AddState(new NavMeshState()); + _stateMachine.AddState(new CompileScriptsState()); _stateMachine.AddState(new EndState()); _stateMachine.GoToState(); } @@ -192,6 +293,9 @@ namespace FlaxEditor.States /// public override bool CanEditContent => false; + /// + public override bool CanReloadScripts => ((SubState)_stateMachine.CurrentState).CanReloadScripts; + /// public override bool IsPerformanceHeavy => true; @@ -215,6 +319,11 @@ namespace FlaxEditor.States { Editor.Log("Starting scenes build..."); _startTime = DateTime.Now; + _stateMachine.ActionIndex = -1; + _stateMachine.Actions.Clear(); + var actions = (GeneralOptions.BuildAction[])Editor.Options.Options.General.BuildActions?.Clone(); + if (actions != null) + _stateMachine.Actions.AddRange(actions); _stateMachine.GoToState(); } diff --git a/Source/Editor/States/EditingSceneState.cs b/Source/Editor/States/EditingSceneState.cs index 7aa9ce13d..176039948 100644 --- a/Source/Editor/States/EditingSceneState.cs +++ b/Source/Editor/States/EditingSceneState.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using FlaxEngine; +using FlaxEngine.Utilities; namespace FlaxEditor.States { @@ -39,5 +40,26 @@ namespace FlaxEditor.States { UpdateFPS(); } + + /// + public override void OnEnter() + { + base.OnEnter(); + + ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin; + } + + /// + public override void OnExit(State nextState) + { + ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin; + + base.OnExit(nextState); + } + + private void OnScriptsReloadBegin() + { + StateMachine.GoToState(); + } } } diff --git a/Source/Editor/States/ReloadingScriptsState.cs b/Source/Editor/States/ReloadingScriptsState.cs index 17f1abdab..1fb34317d 100644 --- a/Source/Editor/States/ReloadingScriptsState.cs +++ b/Source/Editor/States/ReloadingScriptsState.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using FlaxEngine; +using FlaxEngine.Utilities; namespace FlaxEditor.States { @@ -18,5 +19,26 @@ namespace FlaxEditor.States : base(editor) { } + + /// + public override void OnEnter() + { + base.OnEnter(); + + ScriptsBuilder.ScriptsReloadEnd += OnScriptsReloadEnd; + } + + /// + public override void OnExit(State nextState) + { + ScriptsBuilder.ScriptsReloadEnd -= OnScriptsReloadEnd; + + base.OnExit(nextState); + } + + private void OnScriptsReloadEnd() + { + StateMachine.GoToState(); + } } } diff --git a/Source/Editor/Surface/AnimGraphSurface.cs b/Source/Editor/Surface/AnimGraphSurface.cs index 8621ed257..abffc4432 100644 --- a/Source/Editor/Surface/AnimGraphSurface.cs +++ b/Source/Editor/Surface/AnimGraphSurface.cs @@ -65,8 +65,8 @@ namespace FlaxEditor.Surface NodeElementArchetype.Factory.Output(0, "Length", typeof(float), 0), NodeElementArchetype.Factory.Output(1, "Time", typeof(float), 1), NodeElementArchetype.Factory.Output(2, "Normalized Time", typeof(float), 2), - NodeElementArchetype.Factory.Output(3, "Reaming Time", typeof(float), 3), - NodeElementArchetype.Factory.Output(4, "Reaming Normalized Time", typeof(float), 4), + NodeElementArchetype.Factory.Output(3, "Remaining Time", typeof(float), 3), + NodeElementArchetype.Factory.Output(4, "Remaining Normalized Time", typeof(float), 4), } }, } diff --git a/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs b/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs index 9f9e21f4f..2faa9feef 100644 --- a/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs +++ b/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs @@ -1046,7 +1046,7 @@ namespace FlaxEditor.Surface.Archetypes startPos += nrm; endPos += nrm; - // Swap fo the other arrow + // Swap to the other arrow if (!diff) { var tmp = startPos; diff --git a/Source/Editor/Surface/Archetypes/Animation.cs b/Source/Editor/Surface/Archetypes/Animation.cs index b27fb9bd6..15096e696 100644 --- a/Source/Editor/Surface/Archetypes/Animation.cs +++ b/Source/Editor/Surface/Archetypes/Animation.cs @@ -764,8 +764,8 @@ namespace FlaxEditor.Surface.Archetypes NodeElementArchetype.Factory.Output(0, "Length", typeof(float), 0), NodeElementArchetype.Factory.Output(1, "Time", typeof(float), 1), NodeElementArchetype.Factory.Output(2, "Normalized Time", typeof(float), 2), - NodeElementArchetype.Factory.Output(3, "Reaming Time", typeof(float), 3), - NodeElementArchetype.Factory.Output(4, "Reaming Normalized Time", typeof(float), 4), + NodeElementArchetype.Factory.Output(3, "Remaining Time", typeof(float), 3), + NodeElementArchetype.Factory.Output(4, "Remaining Normalized Time", typeof(float), 4), } }, new NodeArchetype @@ -791,7 +791,7 @@ namespace FlaxEditor.Surface.Archetypes Title = "Transform Node (local space)", Description = "Transforms the skeleton node", Flags = NodeFlags.AnimGraph, - Size = new Vector2(270, 130), + Size = new Vector2(280, 130), DefaultValues = new object[] { string.Empty, @@ -816,7 +816,7 @@ namespace FlaxEditor.Surface.Archetypes Title = "Transform Node (model space)", Description = "Transforms the skeleton node", Flags = NodeFlags.AnimGraph, - Size = new Vector2(270, 130), + Size = new Vector2(280, 130), DefaultValues = new object[] { string.Empty, @@ -872,7 +872,7 @@ namespace FlaxEditor.Surface.Archetypes Title = "Get Node Transform (model space)", Description = "Samples the skeleton node transformation (in model space)", Flags = NodeFlags.AnimGraph, - Size = new Vector2(250, 40), + Size = new Vector2(324, 40), DefaultValues = new object[] { string.Empty, @@ -880,7 +880,7 @@ namespace FlaxEditor.Surface.Archetypes Elements = new[] { NodeElementArchetype.Factory.Input(0, string.Empty, true, typeof(void), 0), - NodeElementArchetype.Factory.SkeletonNodeNameSelect(40, Surface.Constants.LayoutOffsetY * 1, 120, 0), + NodeElementArchetype.Factory.SkeletonNodeNameSelect(40, Surface.Constants.LayoutOffsetY * 1, 160, 0), NodeElementArchetype.Factory.Text(0, Surface.Constants.LayoutOffsetY * 1, "Node:"), NodeElementArchetype.Factory.Output(0, "Transform", typeof(Transform), 1), } @@ -903,7 +903,7 @@ namespace FlaxEditor.Surface.Archetypes NodeElementArchetype.Factory.Input(0, string.Empty, true, typeof(void), 1), NodeElementArchetype.Factory.Input(1, "Target", true, typeof(Vector3), 2), NodeElementArchetype.Factory.Input(2, "Weight", true, typeof(float), 3, 1), - NodeElementArchetype.Factory.SkeletonNodeNameSelect(40, Surface.Constants.LayoutOffsetY * 3, 120, 0), + NodeElementArchetype.Factory.SkeletonNodeNameSelect(40, Surface.Constants.LayoutOffsetY * 3, 160, 0), NodeElementArchetype.Factory.Text(0, Surface.Constants.LayoutOffsetY * 3, "Node:"), } }, @@ -913,7 +913,7 @@ namespace FlaxEditor.Surface.Archetypes Title = "Get Node Transform (local space)", Description = "Samples the skeleton node transformation (in local space)", Flags = NodeFlags.AnimGraph, - Size = new Vector2(250, 40), + Size = new Vector2(316, 40), DefaultValues = new object[] { string.Empty, diff --git a/Source/Editor/Surface/Archetypes/Constants.cs b/Source/Editor/Surface/Archetypes/Constants.cs index 63cec366b..3ae9d6a8a 100644 --- a/Source/Editor/Surface/Archetypes/Constants.cs +++ b/Source/Editor/Surface/Archetypes/Constants.cs @@ -379,7 +379,7 @@ namespace FlaxEditor.Surface.Archetypes Elements = new[] { NodeElementArchetype.Factory.Output(0, "Value", typeof(uint), 0), - NodeElementArchetype.Factory.Integer(0, 0, 0) + NodeElementArchetype.Factory.UnsignedInteger(0, 0, 0, -1, 0, int.MaxValue) } }, }; diff --git a/Source/Editor/Surface/Archetypes/Function.cs b/Source/Editor/Surface/Archetypes/Function.cs index 7c4963072..c5707d1dd 100644 --- a/Source/Editor/Surface/Archetypes/Function.cs +++ b/Source/Editor/Surface/Archetypes/Function.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; @@ -867,8 +868,15 @@ namespace FlaxEditor.Surface.Archetypes for (int i = 0; i < signature.Params.Length; i++) { ref var param = ref signature.Params[i]; - if (param.Type != memberParameters[i].Type || param.IsOut != memberParameters[i].IsOut) + ref var paramMember = ref memberParameters[i]; + if (param.Type != paramMember.Type || param.IsOut != paramMember.IsOut) { + // Special case: param.Type is serialized as just a type while paramMember.Type might be a reference for output parameters (eg. `out Int32` vs `out Int32&`) + var paramMemberTypeName = paramMember.Type.TypeName; + if (param.IsOut && param.IsOut == paramMember.IsOut && paramMember.Type.IsReference && !param.Type.IsReference && + paramMemberTypeName.Substring(0, paramMemberTypeName.Length - 1) == param.Type.TypeName) + continue; + isInvalid = true; break; } @@ -1176,10 +1184,16 @@ namespace FlaxEditor.Surface.Archetypes [EditorOrder(0), Tooltip("The name of the parameter."), ExpandGroups] public string Name; - [EditorOrder(1), Tooltip("The type fo the parameter value.")] + [EditorOrder(1), Tooltip("The type for the parameter value.")] [TypeReference(typeof(object), nameof(IsTypeValid))] public ScriptType Type; + public Parameter(ref ScriptMemberInfo.Parameter param) + { + Name = param.Name; + Type = param.Type; + } + private static bool IsTypeValid(ScriptType type) { return SurfaceUtils.IsValidVisualScriptFunctionType(type) && !type.IsVoid; @@ -1387,6 +1401,9 @@ namespace FlaxEditor.Surface.Archetypes } } + /// + /// The cached signature. This might not be loaded if called from other not initialization (eg. node initialized before this function node). Then use GetSignature method. + /// internal Signature _signature; /// @@ -1395,6 +1412,13 @@ namespace FlaxEditor.Surface.Archetypes { } + public void GetSignature(out Signature signature) + { + if (_signature.Node == null) + LoadSignature(); + signature = _signature; + } + private void SaveSignature() { using (var stream = new MemoryStream()) @@ -1547,7 +1571,7 @@ namespace FlaxEditor.Surface.Archetypes // Check if return type has been changed if (_signature.ReturnType != prevReturnType) { - // Update all return nodes used by this function to match teh new type + // Update all return nodes used by this function to match the new type var usedNodes = DepthFirstTraversal(false); var hasAnyReturnNode = false; foreach (var node in usedNodes) @@ -1568,6 +1592,13 @@ namespace FlaxEditor.Surface.Archetypes // Update node interface UpdateUI(); + + // Send event + for (int i = 0; i < Surface.Nodes.Count; i++) + { + if (Surface.Nodes[i] is IFunctionsDependantNode node) + node.OnFunctionEdited(this); + } }; editor.Show(this, Vector2.Zero); } @@ -1622,6 +1653,13 @@ namespace FlaxEditor.Surface.Archetypes base.OnLoaded(); LoadSignature(); + + // Send event + for (int i = 0; i < Surface.Nodes.Count; i++) + { + if (Surface.Nodes[i] is IFunctionsDependantNode node) + node.OnFunctionCreated(this); + } } /// @@ -1630,6 +1668,7 @@ namespace FlaxEditor.Surface.Archetypes base.OnSpawned(); // Setup initial signature + var defaultSignature = _signature.Node == null; CheckFunctionName(ref _signature.Name); if (_signature.ReturnType == ScriptType.Null) _signature.ReturnType = new ScriptType(typeof(void)); @@ -1637,8 +1676,31 @@ namespace FlaxEditor.Surface.Archetypes SaveSignature(); UpdateUI(); - // Start editing - OnEditSignature(); + if (defaultSignature) + { + // Start editing + OnEditSignature(); + } + + // Send event + for (int i = 0; i < Surface.Nodes.Count; i++) + { + if (Surface.Nodes[i] is IFunctionsDependantNode node) + node.OnFunctionCreated(this); + } + } + + /// + public override void OnDeleted() + { + // Send event + for (int i = 0; i < Surface.Nodes.Count; i++) + { + if (Surface.Nodes[i] is IFunctionsDependantNode node) + node.OnFunctionDeleted(this); + } + + base.OnDeleted(); } /// @@ -1647,6 +1709,13 @@ namespace FlaxEditor.Surface.Archetypes base.OnValuesChanged(); LoadSignature(); + + // Send event + for (int i = 0; i < Surface.Nodes.Count; i++) + { + if (Surface.Nodes[i] is IFunctionsDependantNode node) + node.OnFunctionEdited(this); + } } /// @@ -1752,7 +1821,6 @@ namespace FlaxEditor.Surface.Archetypes { private bool _isTypesChangedEventRegistered; - /// public SetFieldNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch) : base(id, context, nodeArch, groupArch) { @@ -1840,6 +1908,254 @@ namespace FlaxEditor.Surface.Archetypes } } + private abstract class EventBaseNode : SurfaceNode, IFunctionsDependantNode + { + private ComboBoxElement _combobox; + private Image _helperButton; + private bool _isBind; + private bool _isUpdateLocked = true; + private List _tooltips = new List(); + private List _functionNodesIds = new List(); + private ScriptMemberInfo.Parameter[] _signature; + + protected EventBaseNode(bool isBind, uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch) + : base(id, context, nodeArch, groupArch) + { + _isBind = isBind; + } + + private bool IsValidFunctionSignature(ref VisualScriptFunctionNode.Signature sig) + { + if (!sig.ReturnType.IsVoid || sig.Parameters == null || sig.Parameters.Length != _signature.Length) + return false; + for (int i = 0; i < _signature.Length; i++) + { + if (_signature[i].Type != sig.Parameters[i].Type) + return false; + } + return true; + } + + private void UpdateUI() + { + if (_isUpdateLocked) + return; + _isUpdateLocked = true; + if (_combobox == null) + { + _combobox = (ComboBoxElement)_children[4]; + _combobox.TooltipText = _isBind ? "Select the function to call when the event occurs" : "Select the function to unbind from the event"; + _combobox.SelectedIndexChanged += OnSelectedChanged; + _helperButton = new Image + { + Location = _combobox.UpperRight + new Vector2(4, 3), + Size = new Vector2(12.0f), + Parent = this, + }; + _helperButton.Clicked += OnHelperButtonClicked; + } + int toSelect = -1; + var handlerFunctionNodeId = Convert.ToUInt32(Values[2]); + _combobox.ClearItems(); + _tooltips.Clear(); + _functionNodesIds.Clear(); + var nodes = Surface.Nodes; + var count = _signature != null ? nodes.Count : 0; + for (int i = 0; i < count; i++) + { + if (nodes[i] is VisualScriptFunctionNode functionNode) + { + // Get if function signature matches the event signature + functionNode.GetSignature(out var functionSig); + if (IsValidFunctionSignature(ref functionSig)) + { + if (functionNode.ID == handlerFunctionNodeId) + toSelect = _functionNodesIds.Count; + _functionNodesIds.Add(functionNode.ID); + _tooltips.Add(functionNode.TooltipText); + _combobox.AddItem(functionSig.ToString()); + } + } + } + _combobox.Tooltips = _tooltips.Count != 0 ? _tooltips.ToArray() : null; + _combobox.Enabled = _tooltips.Count != 0; + _combobox.SelectedIndex = toSelect; + if (toSelect != -1) + { + _helperButton.Brush = new SpriteBrush(Editor.Instance.Icons.Search12); + _helperButton.Color = Color.White; + _helperButton.TooltipText = "Navigate to the handler function"; + } + else if (_isBind) + { + _helperButton.Brush = new SpriteBrush(Editor.Instance.Icons.Add48); + _helperButton.Color = Color.Red; + _helperButton.TooltipText = "Add new handler function and bind it to this event"; + _helperButton.Enabled = _signature != null; + } + else + { + _helperButton.Enabled = false; + } + ResizeAuto(); + _isUpdateLocked = false; + } + + private void OnHelperButtonClicked(Image img, MouseButton mouseButton) + { + if (mouseButton != MouseButton.Left) + return; + if (_combobox.SelectedIndex != -1) + { + // Focus selected function + var handlerFunctionNodeId = Convert.ToUInt32(Values[2]); + var handlerFunctionNode = Surface.FindNode(handlerFunctionNodeId); + Surface.FocusNode(handlerFunctionNode); + } + else if (_isBind) + { + // Create new function that matches the event signature + var surfaceBounds = Surface.AllNodesBounds; + Surface.ShowArea(new Rectangle(surfaceBounds.BottomLeft, new Vector2(200, 150)).MakeExpanded(400.0f)); + var node = Surface.Context.SpawnNode(16, 6, surfaceBounds.BottomLeft + new Vector2(0, 50), null, OnBeforeSpawnedNewHandler); + Surface.Select(node); + + // Bind this function + SetValue(2, node.ID); + } + } + + private void OnBeforeSpawnedNewHandler(SurfaceNode node) + { + // Initialize signature to match the event + var functionNode = (VisualScriptFunctionNode)node; + functionNode._signature = new VisualScriptFunctionNode.Signature + { + Name = "On" + (string)Values[1], + IsStatic = false, + IsVirtual = false, + Node = functionNode, + ReturnType = ScriptType.Void, + Parameters = new VisualScriptFunctionNode.Parameter[_signature.Length], + }; + for (int i = 0; i < _signature.Length; i++) + functionNode._signature.Parameters[i] = new VisualScriptFunctionNode.Parameter(ref _signature[i]); + } + + private void OnSelectedChanged(ComboBox cb) + { + if (_isUpdateLocked) + return; + var handlerFunctionNodeId = Convert.ToUInt32(Values[2]); + var selectedID = cb.SelectedIndex != -1 ? _functionNodesIds[cb.SelectedIndex] : 0u; + if (selectedID != handlerFunctionNodeId) + { + SetValue(2, selectedID); + UpdateUI(); + } + } + + public void OnFunctionCreated(SurfaceNode node) + { + UpdateUI(); + } + + public void OnFunctionEdited(SurfaceNode node) + { + UpdateUI(); + } + + public void OnFunctionDeleted(SurfaceNode node) + { + // Deselect if that function was selected + var handlerFunctionNodeId = Convert.ToUInt32(Values[2]); + if (node.ID == handlerFunctionNodeId) + _combobox.SelectedIndex = -1; + + UpdateUI(); + } + + public override void OnSurfaceLoaded() + { + base.OnSurfaceLoaded(); + + // Find reflection information about event + _signature = null; + var isStatic = false; + var eventName = (string)Values[1]; + var eventType = TypeUtils.GetType((string)Values[0]); + var member = eventType.GetMember(eventName, MemberTypes.Event, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance); + if (member && SurfaceUtils.IsValidVisualScriptEvent(member)) + { + isStatic = member.IsStatic; + _signature = member.GetParameters(); + TooltipText = SurfaceUtils.GetVisualScriptMemberInfoDescription(member); + } + + // Setup instance box (static events don't need it) + var instanceBox = GetBox(1); + instanceBox.Visible = !isStatic; + if (isStatic) + instanceBox.RemoveConnections(); + else + instanceBox.CurrentType = eventType; + + _isUpdateLocked = false; + UpdateUI(); + } + + public override void OnValuesChanged() + { + base.OnValuesChanged(); + + UpdateUI(); + } + + /// + public override void OnDestroy() + { + _combobox = null; + _helperButton = null; + _tooltips.Clear(); + _tooltips = null; + _functionNodesIds.Clear(); + _functionNodesIds = null; + _signature = null; + + base.OnDestroy(); + } + } + + private sealed class BindEventNode : EventBaseNode + { + public BindEventNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch) + : base(true, id, context, nodeArch, groupArch) + { + } + + public override void OnSurfaceLoaded() + { + Title = "Bind " + (string)Values[1]; + + base.OnSurfaceLoaded(); + } + } + + private sealed class UnbindEventNode : EventBaseNode + { + public UnbindEventNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch) + : base(false, id, context, nodeArch, groupArch) + { + } + + public override void OnSurfaceLoaded() + { + Title = "Unbind " + (string)Values[1]; + + base.OnSurfaceLoaded(); + } + } + /// /// The nodes for that group. /// @@ -1981,6 +2297,50 @@ namespace FlaxEditor.Surface.Archetypes null, // Default value }, }, + new NodeArchetype + { + TypeID = 9, + Create = (id, context, arch, groupArch) => new BindEventNode(id, context, arch, groupArch), + Title = string.Empty, + Flags = NodeFlags.VisualScriptGraph | NodeFlags.NoSpawnViaGUI, + Size = new Vector2(260, 60), + DefaultValues = new object[] + { + string.Empty, // Event type + string.Empty, // Event name + (uint)0, // Handler function nodeId + }, + Elements = new[] + { + NodeElementArchetype.Factory.Input(0, string.Empty, true, typeof(void), 0), + NodeElementArchetype.Factory.Input(2, "Instance", true, typeof(object), 1), + NodeElementArchetype.Factory.Output(0, string.Empty, typeof(void), 2, true), + NodeElementArchetype.Factory.Text(2, 20, "Handler function:"), + NodeElementArchetype.Factory.ComboBox(100, 20, 140), + } + }, + new NodeArchetype + { + TypeID = 10, + Create = (id, context, arch, groupArch) => new UnbindEventNode(id, context, arch, groupArch), + Title = string.Empty, + Flags = NodeFlags.VisualScriptGraph | NodeFlags.NoSpawnViaGUI, + Size = new Vector2(260, 60), + DefaultValues = new object[] + { + string.Empty, // Event type + string.Empty, // Event name + (uint)0, // Handler function nodeId + }, + Elements = new[] + { + NodeElementArchetype.Factory.Input(0, string.Empty, true, typeof(void), 0), + NodeElementArchetype.Factory.Input(2, "Instance", true, typeof(object), 1), + NodeElementArchetype.Factory.Output(0, string.Empty, typeof(void), 2, true), + NodeElementArchetype.Factory.Text(2, 20, "Handler function:"), + NodeElementArchetype.Factory.ComboBox(100, 20, 140), + } + }, }; } } diff --git a/Source/Editor/Surface/Archetypes/Material.cs b/Source/Editor/Surface/Archetypes/Material.cs index a007db7d7..4d65c760c 100644 --- a/Source/Editor/Surface/Archetypes/Material.cs +++ b/Source/Editor/Surface/Archetypes/Material.cs @@ -156,6 +156,7 @@ namespace FlaxEditor.Surface.Archetypes case MaterialDomain.Surface: case MaterialDomain.Terrain: case MaterialDomain.Particle: + case MaterialDomain.Deformable: { bool isNotUnlit = info.ShadingModel != MaterialShadingModel.Unlit; bool isTransparent = info.BlendMode == MaterialBlendMode.Transparent; @@ -642,6 +643,179 @@ namespace FlaxEditor.Surface.Archetypes NodeElementArchetype.Factory.Output(0, "XYZ", typeof(Vector3), 0), } }, + new NodeArchetype + { + TypeID = 26, + Title = "Blend Normals", + Description = "Blend two normal maps to create a single normal map", + Flags = NodeFlags.MaterialGraph, + Size = new Vector2(170, 40), + ConnectionsHints = ConnectionsHint.Vector, + Elements = new[] + { + NodeElementArchetype.Factory.Input(0, "Base Normal", true, typeof(Vector3), 0), + NodeElementArchetype.Factory.Input(1, "Additional Normal", true, typeof(Vector3), 1), + NodeElementArchetype.Factory.Output(0, "Result", typeof(Vector3), 2) + } + }, + new NodeArchetype + { + TypeID = 27, + Title = "Rotator", + Description = "Rotates UV coordinates according to a scalar angle (0-1)", + Flags = NodeFlags.MaterialGraph, + Size = new Vector2(150, 55), + Elements = new[] + { + NodeElementArchetype.Factory.Input(0, "UV", true, typeof(Vector2), 0), + NodeElementArchetype.Factory.Input(1, "Center", true, typeof(Vector2), 1), + NodeElementArchetype.Factory.Input(2, "Rotation Angle", true, typeof(float), 2), + NodeElementArchetype.Factory.Output(0, string.Empty, typeof(Vector2), 3), + } + }, + new NodeArchetype + { + TypeID = 28, + Title = "Sphere Mask", + Description = "Creates a sphere mask", + Flags = NodeFlags.MaterialGraph, + Size = new Vector2(150, 100), + ConnectionsHints = ConnectionsHint.Vector, + IndependentBoxes = new[] + { + 0, + 1 + }, + DefaultValues = new object[] + { + 0.3f, + 0.5f, + false + }, + Elements = new[] + { + NodeElementArchetype.Factory.Input(0, "A", true, null, 0), + NodeElementArchetype.Factory.Input(1, "B", true, null, 1), + NodeElementArchetype.Factory.Input(2, "Radius", true, typeof(float), 2, 0), + NodeElementArchetype.Factory.Input(3, "Hardness", true, typeof(float), 3, 1), + NodeElementArchetype.Factory.Input(4, "Invert", true, typeof(bool), 4, 2), + NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 5), + } + }, + new NodeArchetype + { + TypeID = 29, + Title = "UV Tiling & Offset", + Description = "Takes UVs and applies tiling and offset", + Flags = NodeFlags.MaterialGraph, + Size = new Vector2(175, 60), + DefaultValues = new object[] + { + Vector2.One, + Vector2.Zero + }, + Elements = new[] + { + NodeElementArchetype.Factory.Input(0, "UV", true, typeof(Vector2), 0), + NodeElementArchetype.Factory.Input(1, "Tiling", true, typeof(Vector2), 1, 0), + NodeElementArchetype.Factory.Input(2, "Offset", true, typeof(Vector2), 2, 1), + NodeElementArchetype.Factory.Output(0, string.Empty, typeof(Vector2), 3), + } + }, + new NodeArchetype + { + TypeID = 30, + Title = "DDX", + Description = "Returns the partial derivative of the specified value with respect to the screen-space x-coordinate", + Flags = NodeFlags.MaterialGraph, + Size = new Vector2(90, 25), + ConnectionsHints = ConnectionsHint.Numeric, + IndependentBoxes = new[] { 0 }, + DependentBoxes = new[] { 1 }, + Elements = new[] + { + NodeElementArchetype.Factory.Input(0, "Value", true, null, 0), + NodeElementArchetype.Factory.Output(0, string.Empty, null, 1), + } + }, + new NodeArchetype + { + TypeID = 31, + Title = "DDY", + Description = "Returns the partial derivative of the specified value with respect to the screen-space y-coordinate", + Flags = NodeFlags.MaterialGraph, + Size = new Vector2(90, 25), + ConnectionsHints = ConnectionsHint.Numeric, + IndependentBoxes = new[] { 0 }, + DependentBoxes = new[] { 1 }, + Elements = new[] + { + NodeElementArchetype.Factory.Input(0, "Value", true, null, 0), + NodeElementArchetype.Factory.Output(0, string.Empty, null, 1), + } + }, + new NodeArchetype + { + TypeID = 32, + Title = "Sign", + Description = "Returns -1 if value is less than zero; 0 if value equals zero; and 1 if value is greater than zero", + Flags = NodeFlags.MaterialGraph, + Size = new Vector2(90, 25), + ConnectionsHints = ConnectionsHint.Numeric, + IndependentBoxes = new[] { 0 }, + Elements = new[] + { + NodeElementArchetype.Factory.Input(0, "Value", true, null, 0), + NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 1), + } + }, + new NodeArchetype + { + TypeID = 33, + Title = "Any", + Description = "True if any components of value are non-zero; otherwise, false", + Flags = NodeFlags.MaterialGraph, + Size = new Vector2(90, 25), + ConnectionsHints = ConnectionsHint.Numeric, + IndependentBoxes = new[] { 0 }, + Elements = new[] + { + NodeElementArchetype.Factory.Input(0, "Value", true, null, 0), + NodeElementArchetype.Factory.Output(0, string.Empty, typeof(bool), 1), + } + }, + new NodeArchetype + { + TypeID = 34, + Title = "All", + Description = "Determines if all components of the specified value are non-zero", + Flags = NodeFlags.MaterialGraph, + Size = new Vector2(90, 25), + ConnectionsHints = ConnectionsHint.Numeric, + IndependentBoxes = new[] { 0 }, + Elements = new[] + { + NodeElementArchetype.Factory.Input(0, "Value", true, null, 0), + NodeElementArchetype.Factory.Output(0, string.Empty, typeof(bool), 1), + } + }, + new NodeArchetype + { + TypeID = 35, + Title = "Black Body", + Description = "Simulates black body radiation via a given temperature in kelvin", + Flags = NodeFlags.MaterialGraph, + Size = new Vector2(120, 25), + DefaultValues = new object[] + { + 0.0f, + }, + Elements = new[] + { + NodeElementArchetype.Factory.Input(0, "Temp", true, typeof(float), 0, 0), + NodeElementArchetype.Factory.Output(0, string.Empty, typeof(Vector3), 1), + } + }, }; } } diff --git a/Source/Editor/Surface/Archetypes/Math.cs b/Source/Editor/Surface/Archetypes/Math.cs index 92ae69a12..7416ea498 100644 --- a/Source/Editor/Surface/Archetypes/Math.cs +++ b/Source/Editor/Surface/Archetypes/Math.cs @@ -404,6 +404,29 @@ namespace FlaxEditor.Surface.Archetypes NodeElementArchetype.Factory.Output(0, "A | B", null, 2), } }, + new NodeArchetype + { + TypeID = 48, + Title = "Remap", + Description = "Remaps a value from one range to another, so for example having 25 in a range of 0 to 100 being remapped to 0 to 1 would return 0.25", + Flags = NodeFlags.AllGraphs, + Size = new Vector2(175, 75), + DefaultValues = new object[] + { + 25.0f, + new Vector2(0.0f, 100.0f), + new Vector2(0.0f, 1.0f), + false + }, + Elements = new[] + { + NodeElementArchetype.Factory.Input(0, "Value", true, typeof(float), 0, 0), + NodeElementArchetype.Factory.Input(1, "In Range", true, typeof(Vector2), 1, 1), + NodeElementArchetype.Factory.Input(2, "Out Range", true, typeof(Vector2), 2, 2), + NodeElementArchetype.Factory.Input(3, "Clamp", true, typeof(bool), 3, 3), + NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 4), + } + }, }; } } diff --git a/Source/Editor/Surface/Archetypes/Packing.cs b/Source/Editor/Surface/Archetypes/Packing.cs index 04f3819ea..f5b466ab3 100644 --- a/Source/Editor/Surface/Archetypes/Packing.cs +++ b/Source/Editor/Surface/Archetypes/Packing.cs @@ -486,7 +486,7 @@ namespace FlaxEditor.Surface.Archetypes Description = "Unpack X component from Vector", Flags = NodeFlags.AllGraphs, ConnectionsHints = ConnectionsHint.Vector, - Size = new Vector2(160, 30), + Size = new Vector2(110, 30), Elements = new[] { NodeElementArchetype.Factory.Input(0, "Value", true, null, 0), @@ -500,7 +500,7 @@ namespace FlaxEditor.Surface.Archetypes Description = "Unpack Y component from Vector", Flags = NodeFlags.AllGraphs, ConnectionsHints = ConnectionsHint.Vector, - Size = new Vector2(160, 30), + Size = new Vector2(110, 30), Elements = new[] { NodeElementArchetype.Factory.Input(0, "Value", true, null, 0), @@ -514,7 +514,7 @@ namespace FlaxEditor.Surface.Archetypes Description = "Unpack Z component from Vector", Flags = NodeFlags.AllGraphs, ConnectionsHints = ConnectionsHint.Vector, - Size = new Vector2(160, 30), + Size = new Vector2(110, 30), Elements = new[] { NodeElementArchetype.Factory.Input(0, "Value", true, null, 0), @@ -528,7 +528,7 @@ namespace FlaxEditor.Surface.Archetypes Description = "Unpack W component from Vector", Flags = NodeFlags.AllGraphs, ConnectionsHints = ConnectionsHint.Vector, - Size = new Vector2(160, 30), + Size = new Vector2(110, 30), Elements = new[] { NodeElementArchetype.Factory.Input(0, "Value", true, null, 0), @@ -544,7 +544,7 @@ namespace FlaxEditor.Surface.Archetypes Description = "Unpack XY components from Vector", Flags = NodeFlags.AllGraphs, ConnectionsHints = ConnectionsHint.Vector, - Size = new Vector2(160, 30), + Size = new Vector2(110, 30), Elements = new[] { NodeElementArchetype.Factory.Input(0, "Value", true, null, 0), @@ -558,7 +558,7 @@ namespace FlaxEditor.Surface.Archetypes Description = "Unpack XZ components from Vector", Flags = NodeFlags.AllGraphs, ConnectionsHints = ConnectionsHint.Vector, - Size = new Vector2(160, 30), + Size = new Vector2(110, 30), Elements = new[] { NodeElementArchetype.Factory.Input(0, "Value", true, null, 0), @@ -572,7 +572,7 @@ namespace FlaxEditor.Surface.Archetypes Description = "Unpack YZ components from Vector", Flags = NodeFlags.AllGraphs, ConnectionsHints = ConnectionsHint.Vector, - Size = new Vector2(160, 30), + Size = new Vector2(110, 30), Elements = new[] { NodeElementArchetype.Factory.Input(0, "Value", true, null, 0), @@ -588,7 +588,7 @@ namespace FlaxEditor.Surface.Archetypes Description = "Unpack XYZ components from Vector", Flags = NodeFlags.AllGraphs, ConnectionsHints = ConnectionsHint.Vector, - Size = new Vector2(160, 30), + Size = new Vector2(110, 30), Elements = new[] { NodeElementArchetype.Factory.Input(0, "Value", true, null, 0), diff --git a/Source/Editor/Surface/Archetypes/Parameters.cs b/Source/Editor/Surface/Archetypes/Parameters.cs index 43048d19d..5332b3475 100644 --- a/Source/Editor/Surface/Archetypes/Parameters.cs +++ b/Source/Editor/Surface/Archetypes/Parameters.cs @@ -32,7 +32,7 @@ namespace FlaxEditor.Surface.Archetypes public Dictionary Prototypes = DefaultPrototypes; /// - /// The default prototypes for thr node elements to use for the given parameter type. + /// The default prototypes for the node elements to use for the given parameter type. /// public static readonly Dictionary DefaultPrototypes = new Dictionary { diff --git a/Source/Editor/Surface/Archetypes/Particles.cs b/Source/Editor/Surface/Archetypes/Particles.cs index 4d72bfa7b..1f3ee8ce6 100644 --- a/Source/Editor/Surface/Archetypes/Particles.cs +++ b/Source/Editor/Surface/Archetypes/Particles.cs @@ -338,7 +338,7 @@ namespace FlaxEditor.Surface.Archetypes Size = new Vector2(300, 600), DefaultValues = new object[] { - 500, // Capacity + 1000, // Capacity (int)ParticlesSimulationMode.Default, // Simulation Mode (int)ParticlesSimulationSpace.Local, // Simulation Space true, // Enable Pooling diff --git a/Source/Editor/Surface/Elements/Box.cs b/Source/Editor/Surface/Elements/Box.cs index 886ab1e37..b8d88741f 100644 --- a/Source/Editor/Surface/Elements/Box.cs +++ b/Source/Editor/Surface/Elements/Box.cs @@ -97,7 +97,7 @@ namespace FlaxEditor.Surface.Elements var connections = Connections.ToArray(); for (int i = 0; i < connections.Length; i++) { - var targetBox = Connections[i]; + var targetBox = connections[i]; // Break connection Connections.Remove(targetBox); @@ -565,7 +565,28 @@ namespace FlaxEditor.Surface.Elements { _isMouseDown = false; if (Surface.CanEdit) - Surface.ConnectingStart(this); + { + if (!IsOutput && HasSingleConnection) + { + var connectedBox = Connections[0]; + if (Surface.Undo != null) + { + var action = new ConnectBoxesAction((InputBox)this, (OutputBox)connectedBox, false); + BreakConnection(connectedBox); + action.End(); + Surface.Undo.AddAction(action); + } + else + { + BreakConnection(connectedBox); + } + Surface.ConnectingStart(connectedBox); + } + else + { + Surface.ConnectingStart(this); + } + } } base.OnMouseLeave(); } diff --git a/Source/Editor/Surface/Elements/SkeletonNodeNameSelectElement.cs b/Source/Editor/Surface/Elements/SkeletonNodeNameSelectElement.cs index 0d8d676c7..4321eabd0 100644 --- a/Source/Editor/Surface/Elements/SkeletonNodeNameSelectElement.cs +++ b/Source/Editor/Surface/Elements/SkeletonNodeNameSelectElement.cs @@ -32,8 +32,8 @@ namespace FlaxEditor.Surface.Elements } set { - if (!string.IsNullOrEmpty(value)) - SelectedIndex = _nodeNameToIndex[value]; + if (!string.IsNullOrEmpty(value) && _nodeNameToIndex.TryGetValue(value, out var index)) + SelectedIndex = index; else SelectedIndex = -1; } @@ -60,8 +60,8 @@ namespace FlaxEditor.Surface.Elements { _selectedIndices.Clear(); var selectedNode = (string)ParentNode.Values[Archetype.ValueIndex]; - if (!string.IsNullOrEmpty(selectedNode)) - _selectedIndices.Add(_nodeNameToIndex[selectedNode]); + if (!string.IsNullOrEmpty(selectedNode) && _nodeNameToIndex.TryGetValue(selectedNode, out var index)) + _selectedIndices.Add(index); OnSelectedIndexChanged(); } @@ -92,7 +92,7 @@ namespace FlaxEditor.Surface.Elements { sb.Clear(); var node = nodes[nodeIndex]; - _nodeNameToIndex.Add(node.Name, nodeIndex); + _nodeNameToIndex[node.Name] = nodeIndex; int parent = node.ParentIndex; while (parent != -1) { diff --git a/Source/Editor/Surface/Elements/UnsignedIntegerValue.cs b/Source/Editor/Surface/Elements/UnsignedIntegerValue.cs index 9668105f1..a1822a268 100644 --- a/Source/Editor/Surface/Elements/UnsignedIntegerValue.cs +++ b/Source/Editor/Surface/Elements/UnsignedIntegerValue.cs @@ -85,7 +85,7 @@ namespace FlaxEditor.Surface.Elements else if (value is Vector4 valueVec4) result = (uint)(arch.BoxID == 0 ? valueVec4.X : arch.BoxID == 1 ? valueVec4.Y : arch.BoxID == 2 ? valueVec4.Z : valueVec4.W); else - result = 0; + result = 0u; return result; } diff --git a/Source/Editor/Surface/IFunctionDependantNode.cs b/Source/Editor/Surface/IFunctionDependantNode.cs new file mode 100644 index 000000000..e66c10e92 --- /dev/null +++ b/Source/Editor/Surface/IFunctionDependantNode.cs @@ -0,0 +1,31 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using FlaxEngine; + +namespace FlaxEditor.Surface +{ + /// + /// Interface for surface nodes that depend on surface function nodes collection. + /// + [HideInEditor] + public interface IFunctionsDependantNode + { + /// + /// On function created. + /// + /// The function node. + void OnFunctionCreated(SurfaceNode node); + + /// + /// On function signature changed (new name or parameters change). + /// + /// The function node. + void OnFunctionEdited(SurfaceNode node); + + /// + /// On function removed. + /// + /// The function node. + void OnFunctionDeleted(SurfaceNode node); + } +} diff --git a/Source/Editor/Surface/NodeElementArchetype.cs b/Source/Editor/Surface/NodeElementArchetype.cs index 07ea35875..cc2f41106 100644 --- a/Source/Editor/Surface/NodeElementArchetype.cs +++ b/Source/Editor/Surface/NodeElementArchetype.cs @@ -239,6 +239,32 @@ namespace FlaxEditor.Surface }; } + /// + /// Creates new Unsigned Integer value element description. + /// + /// The x location (in node area space). + /// The y location (in node area space). + /// The index of the node variable linked as the input. Useful to make a physical connection between input box and default value for it. + /// The index of the component to edit. For vectors this can be set to modify only single component of it. Eg. for vec2 value component set to 1 will edit only Y component. Default value -1 will be used to edit whole value. + /// The minimum value range. + /// The maximum value range. + /// The archetype. + public static NodeElementArchetype UnsignedInteger(float x, float y, int valueIndex = -1, int component = -1, uint valueMin = 0, uint valueMax = 1000000) + { + return new NodeElementArchetype + { + Type = NodeElementType.UnsignedIntegerValue, + Position = new Vector2(Constants.NodeMarginX + x, Constants.NodeMarginY + Constants.NodeHeaderSize + y), + Text = null, + Single = false, + ValueIndex = valueIndex, + ValueMin = valueMin, + ValueMax = valueMax, + BoxID = -1, + ConnectionsType = ScriptType.Null + }; + } + /// /// Creates new Float value element description. /// diff --git a/Source/Editor/Surface/NodeElementType.cs b/Source/Editor/Surface/NodeElementType.cs index 48b2356dd..f104b234c 100644 --- a/Source/Editor/Surface/NodeElementType.cs +++ b/Source/Editor/Surface/NodeElementType.cs @@ -94,5 +94,10 @@ namespace FlaxEditor.Surface /// The actor picker. /// Actor = 19, + + /// + /// The unsigned integer value. + /// + UnsignedIntegerValue = 20, } } diff --git a/Source/Editor/Surface/SurfaceNode.cs b/Source/Editor/Surface/SurfaceNode.cs index cf39384ce..bdce94513 100644 --- a/Source/Editor/Surface/SurfaceNode.cs +++ b/Source/Editor/Surface/SurfaceNode.cs @@ -185,9 +185,12 @@ namespace FlaxEditor.Surface var rightWidth = 40.0f; var boxLabelFont = Style.Current.FontSmall; var titleLabelFont = Style.Current.FontLarge; - for (int i = 0; i < Elements.Count; i++) + for (int i = 0; i < Children.Count; i++) { - if (Elements[i] is InputBox inputBox) + var child = Children[i]; + if (!child.Visible) + continue; + if (child is InputBox inputBox) { var boxWidth = boxLabelFont.MeasureText(inputBox.Text).X + 20; if (inputBox.DefaultValueEditor != null) @@ -195,15 +198,23 @@ namespace FlaxEditor.Surface leftWidth = Mathf.Max(leftWidth, boxWidth); leftHeight = Mathf.Max(leftHeight, inputBox.Archetype.Position.Y - Constants.NodeMarginY - Constants.NodeHeaderSize + 20.0f); } - else if (Elements[i] is OutputBox outputBox) + else if (child is OutputBox outputBox) { rightWidth = Mathf.Max(rightWidth, boxLabelFont.MeasureText(outputBox.Text).X + 20); rightHeight = Mathf.Max(rightHeight, outputBox.Archetype.Position.Y - Constants.NodeMarginY - Constants.NodeHeaderSize + 20.0f); } - else if (Elements[i] is Control control) + else if (child is Control control) { - width = Mathf.Max(width, control.Width + 10); - height = Mathf.Max(height, control.Height + 10); + if (control.AnchorPreset == AnchorPresets.TopLeft) + { + width = Mathf.Max(width, control.Right + 4 - Constants.NodeMarginX); + height = Mathf.Max(height, control.Bottom + 4 - Constants.NodeMarginY - Constants.NodeHeaderSize); + } + else + { + width = Mathf.Max(width, control.Width + 4); + height = Mathf.Max(height, control.Height + 4); + } } } width = Mathf.Max(width, leftWidth + rightWidth + 10); @@ -267,6 +278,9 @@ namespace FlaxEditor.Surface case NodeElementType.Actor: element = new ActorSelect(this, arch); break; + case NodeElementType.UnsignedIntegerValue: + element = new UnsignedIntegerValue(this, arch); + break; //default: throw new NotImplementedException("Unknown node element type: " + arch.Type); } if (element != null) @@ -303,12 +317,16 @@ namespace FlaxEditor.Surface { if (type == ScriptType.Null) type = new ScriptType(typeof(object)); + + // Try to reuse box var box = GetBox(id); if ((isOut && box is InputBox) || (!isOut && box is OutputBox)) { box.Dispose(); box = null; } + + // Create new if missing if (box == null) { if (isOut) @@ -319,10 +337,15 @@ namespace FlaxEditor.Surface } else { + // Sync properties for exiting box box.Text = text; box.CurrentType = type; box.Y = Constants.NodeMarginY + Constants.NodeHeaderSize + yLevel * Constants.LayoutOffsetY; } + + // Update box + box.OnConnectionsChanged(); + return box; } @@ -984,7 +1007,7 @@ namespace FlaxEditor.Surface } // Secondary Context Menu - if (button == MouseButton.Right) + if (button == MouseButton.Right && false) { if (!IsSelected) Surface.Select(this); diff --git a/Source/Editor/Surface/SurfaceUtils.cs b/Source/Editor/Surface/SurfaceUtils.cs index 18f2e9693..74f1d6130 100644 --- a/Source/Editor/Surface/SurfaceUtils.cs +++ b/Source/Editor/Surface/SurfaceUtils.cs @@ -406,6 +406,11 @@ namespace FlaxEditor.Surface return member.IsField && IsValidVisualScriptType(member.ValueType); } + internal static bool IsValidVisualScriptEvent(ScriptMemberInfo member) + { + return member.IsEvent && member.HasAttribute(typeof(UnmanagedAttribute)); + } + internal static bool IsValidVisualScriptType(ScriptType scriptType) { if (scriptType.IsGenericType || !scriptType.IsPublic || scriptType.HasAttribute(typeof(HideInEditorAttribute), true)) diff --git a/Source/Editor/Surface/VisjectSurface.ContextMenu.cs b/Source/Editor/Surface/VisjectSurface.ContextMenu.cs index 6b38ffdfc..c2b0d1887 100644 --- a/Source/Editor/Surface/VisjectSurface.ContextMenu.cs +++ b/Source/Editor/Surface/VisjectSurface.ContextMenu.cs @@ -15,6 +15,7 @@ namespace FlaxEditor.Surface { private ContextMenuButton _cmCopyButton; private ContextMenuButton _cmDuplicateButton; + private ContextMenuButton _cmFormatNodesConnectionButton; private ContextMenuButton _cmRemoveNodeConnectionsButton; private ContextMenuButton _cmRemoveBoxConnectionsButton; private readonly Vector2 ContextMenuOffset = new Vector2(5); @@ -216,6 +217,13 @@ namespace FlaxEditor.Surface } }).Enabled = Nodes.Any(x => x.Breakpoint.Set && x.Breakpoint.Enabled); } + menu.AddSeparator(); + + _cmFormatNodesConnectionButton = menu.AddButton("Format node(s)", () => + { + FormatGraph(SelectedNodes); + }); + _cmFormatNodesConnectionButton.Enabled = HasNodesSelection; menu.AddSeparator(); _cmRemoveNodeConnectionsButton = menu.AddButton("Remove all connections to that node(s)", () => diff --git a/Source/Editor/Surface/VisjectSurface.DragDrop.cs b/Source/Editor/Surface/VisjectSurface.DragDrop.cs index 323ef8510..422198f5b 100644 --- a/Source/Editor/Surface/VisjectSurface.DragDrop.cs +++ b/Source/Editor/Surface/VisjectSurface.DragDrop.cs @@ -107,7 +107,7 @@ namespace FlaxEditor.Surface /// Validates the parameter drag operation. /// /// Name of the parameter. - /// Tre if can drag that parameter, otherwise false. + /// True if can drag that parameter, otherwise false. protected virtual bool ValidateDragParameter(string parameterName) { return GetParameter(parameterName) != null; diff --git a/Source/Editor/Surface/VisjectSurface.Formatting.cs b/Source/Editor/Surface/VisjectSurface.Formatting.cs new file mode 100644 index 000000000..848f79157 --- /dev/null +++ b/Source/Editor/Surface/VisjectSurface.Formatting.cs @@ -0,0 +1,287 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using FlaxEngine; +using FlaxEditor.Surface.Elements; +using FlaxEditor.Surface.Undo; + +namespace FlaxEditor.Surface +{ + public partial class VisjectSurface + { + // Reference https://github.com/stefnotch/xnode-graph-formatter/blob/812e08e71c7b9b7eb0810dbdfb0a9a1034da6941/Assets/Examples/MathGraph/Editor/MathGraphEditor.cs + + private class NodeFormattingData + { + /// + /// Starting from 0 at the main nodes + /// + public int Layer; + + /// + /// Position in the layer + /// + public int Offset; + + /// + /// How far the subtree needs to be moved additionally + /// + public int SubtreeOffset; + } + + /// + /// Formats a graph where the nodes can be disjointed. + /// Uses the Sugiyama method + /// + /// List of nodes + protected void FormatGraph(List nodes) + { + if (nodes.Count <= 1 || !CanEdit) return; + + var nodesToVisit = new HashSet(nodes); + + // While we haven't formatted every node + while (nodesToVisit.Count > 0) + { + // Run a search in both directions + var connectedNodes = new List(); + var queue = new Queue(); + + var startNode = nodesToVisit.First(); + nodesToVisit.Remove(startNode); + queue.Enqueue(startNode); + + while (queue.Count > 0) + { + var node = queue.Dequeue(); + connectedNodes.Add(node); + + for (int i = 0; i < node.Elements.Count; i++) + { + if (node.Elements[i] is Box box) + { + for (int j = 0; j < box.Connections.Count; j++) + { + if (nodesToVisit.Contains(box.Connections[j].ParentNode)) + { + nodesToVisit.Remove(box.Connections[j].ParentNode); + queue.Enqueue(box.Connections[j].ParentNode); + } + } + + } + } + } + + FormatConnectedGraph(connectedNodes); + } + } + + /// + /// Formats a graph where all nodes are connected + /// + /// List of connected nodes + protected void FormatConnectedGraph(List nodes) + { + if (nodes.Count <= 1 || !CanEdit) return; + + var boundingBox = GetNodesBounds(nodes); + + var nodeData = nodes.ToDictionary(n => n, n => new NodeFormattingData { }); + + // Rightmost nodes with none of our nodes to the right of them + var endNodes = nodes + .Where(n => !n.GetBoxes().Any(b => b.IsOutput && b.Connections.Any(c => nodeData.ContainsKey(c.ParentNode)))) + .OrderBy(n => n.Top) // Keep their relative order + .ToList(); + + // Longest path layering + int maxLayer = SetLayers(nodeData, endNodes); + + // Set the vertical offsets + int maxOffset = SetOffsets(nodeData, endNodes, maxLayer); + + // Layout the nodes + + // Get the largest nodes in the Y and X direction + float[] widths = new float[maxLayer + 1]; + float[] heights = new float[maxOffset + 1]; + for (int i = 0; i < nodes.Count; i++) + { + if (nodeData.TryGetValue(nodes[i], out var data)) + { + if (nodes[i].Width > widths[data.Layer]) + { + widths[data.Layer] = nodes[i].Width; + } + if (nodes[i].Height > heights[data.Offset]) + { + heights[data.Offset] = nodes[i].Height; + } + } + } + + Vector2 minDistanceBetweenNodes = new Vector2(30, 30); + + // Figure out the node positions (aligned to a grid) + float[] nodeXPositions = new float[widths.Length]; + for (int i = 1; i < widths.Length; i++) + { + // Go from right to left (backwards) through the nodes + nodeXPositions[i] = nodeXPositions[i - 1] + minDistanceBetweenNodes.X + widths[i]; + } + + float[] nodeYPositions = new float[heights.Length]; + for (int i = 1; i < heights.Length; i++) + { + // Go from top to bottom through the nodes + nodeYPositions[i] = nodeYPositions[i - 1] + heights[i - 1] + minDistanceBetweenNodes.Y; + } + + // Set the node positions + var undoActions = new List(); + var topRightPosition = endNodes[0].Location; + for (int i = 0; i < nodes.Count; i++) + { + if (nodeData.TryGetValue(nodes[i], out var data)) + { + Vector2 newLocation = new Vector2(-nodeXPositions[data.Layer], nodeYPositions[data.Offset]) + topRightPosition; + Vector2 locationDelta = newLocation - nodes[i].Location; + nodes[i].Location = newLocation; + + if (Undo != null) + undoActions.Add(new MoveNodesAction(Context, new[] { nodes[i].ID }, locationDelta)); + } + } + + MarkAsEdited(false); + Undo?.AddAction(new MultiUndoAction(undoActions, "Format nodes")); + } + + /// + /// Assigns a layer to every node + /// + /// The exta node data + /// The end nodes + /// The number of the maximum layer + private int SetLayers(Dictionary nodeData, List endNodes) + { + // Longest path layering + int maxLayer = 0; + var stack = new Stack(endNodes); + + while (stack.Count > 0) + { + var node = stack.Pop(); + int layer = nodeData[node].Layer; + + for (int i = 0; i < node.Elements.Count; i++) + { + if (node.Elements[i] is InputBox box && box.HasAnyConnection) + { + var childNode = box.Connections[0].ParentNode; + + if (nodeData.TryGetValue(childNode, out var data)) + { + int nodeLayer = Math.Max(data.Layer, layer + 1); + data.Layer = nodeLayer; + if (nodeLayer > maxLayer) + { + maxLayer = nodeLayer; + } + + stack.Push(childNode); + } + } + } + } + return maxLayer; + } + + + /// + /// Sets the node offsets + /// + /// The exta node data + /// The end nodes + /// The number of the maximum layer + /// The number of the maximum offset + private int SetOffsets(Dictionary nodeData, List endNodes, int maxLayer) + { + int maxOffset = 0; + + // Keeps track of the largest offset (Y axis) for every layer + int[] offsets = new int[maxLayer + 1]; + + var visitedNodes = new HashSet(); + + void SetOffsets(SurfaceNode node, NodeFormattingData straightParentData) + { + if (!nodeData.TryGetValue(node, out var data)) return; + + // If we realize that the current node would collide with an already existing node in this layer + if (data.Layer >= 0 && offsets[data.Layer] > data.Offset) + { + // Move the entire sub-tree down + straightParentData.SubtreeOffset = Math.Max(straightParentData.SubtreeOffset, offsets[data.Layer] - data.Offset); + } + + // Keeps track of the offset of the last direct child we visited + int childOffset = data.Offset; + bool straightChild = true; + + // Run the algorithm for every child + for (int i = 0; i < node.Elements.Count; i++) + { + if (node.Elements[i] is InputBox box && box.HasAnyConnection) + { + var childNode = box.Connections[0].ParentNode; + if (!visitedNodes.Contains(childNode) && nodeData.TryGetValue(childNode, out var childData)) + { + visitedNodes.Add(childNode); + childData.Offset = childOffset; + SetOffsets(childNode, straightChild ? straightParentData : childData); + childOffset = childData.Offset + 1; + straightChild = false; + } + } + } + + if (data.Layer >= 0) + { + // When coming out of the recursion, apply the extra subtree offsets + data.Offset += straightParentData.SubtreeOffset; + if (data.Offset > maxOffset) + { + maxOffset = data.Offset; + } + offsets[data.Layer] = data.Offset + 1; + } + } + + { + // An imaginary final node + var endNodeData = new NodeFormattingData { Layer = -1 }; + int childOffset = 0; + bool straightChild = true; + + for (int i = 0; i < endNodes.Count; i++) + { + if (nodeData.TryGetValue(endNodes[i], out var childData)) + { + visitedNodes.Add(endNodes[i]); + childData.Offset = childOffset; + SetOffsets(endNodes[i], straightChild ? endNodeData : childData); + childOffset = childData.Offset + 1; + straightChild = false; + } + } + } + + return maxOffset; + } + + } +} diff --git a/Source/Editor/Surface/VisjectSurface.Input.cs b/Source/Editor/Surface/VisjectSurface.Input.cs index 68966370c..dec89e143 100644 --- a/Source/Editor/Surface/VisjectSurface.Input.cs +++ b/Source/Editor/Surface/VisjectSurface.Input.cs @@ -24,7 +24,9 @@ namespace FlaxEditor.Surface private class InputBracket { + private readonly float DefaultWidth = 120f; private readonly Margin _padding = new Margin(10f); + public Box Box { get; } public Vector2 EndBracketPosition { get; } public List Nodes { get; } = new List(); @@ -33,7 +35,7 @@ namespace FlaxEditor.Surface public InputBracket(Box box, Vector2 nodePosition) { Box = box; - EndBracketPosition = nodePosition; + EndBracketPosition = nodePosition + new Vector2(DefaultWidth, 0); Update(); } @@ -47,11 +49,10 @@ namespace FlaxEditor.Surface } else { - area = new Rectangle(EndBracketPosition, new Vector2(120f, 80f)); + area = new Rectangle(EndBracketPosition, new Vector2(DefaultWidth, 80f)); } _padding.ExpandRectangle(ref area); - Vector2 endPoint = area.Location + new Vector2(area.Width, area.Height / 2f); - Vector2 offset = EndBracketPosition - endPoint; + Vector2 offset = EndBracketPosition - area.UpperRight; area.Location += offset; Area = area; if (!offset.IsZero) @@ -99,18 +100,6 @@ namespace FlaxEditor.Surface /// public event Window.MouseWheelDelegate CustomMouseWheel; - /// - /// Gets the node under the mouse location. - /// - /// The node or null if no intersection. - public SurfaceNode GetNodeUnderMouse() - { - var pos = _rootControl.PointFromParent(ref _mousePos); - if (_rootControl.GetChildAt(pos) is SurfaceNode node) - return node; - return null; - } - /// /// Gets the control under the mouse location. /// @@ -118,9 +107,7 @@ namespace FlaxEditor.Surface public SurfaceControl GetControlUnderMouse() { var pos = _rootControl.PointFromParent(ref _mousePos); - if (_rootControl.GetChildAtRecursive(pos) is SurfaceControl control) - return control; - return null; + return _rootControl.GetChildAt(pos) as SurfaceControl; } private void UpdateSelectionRectangle() @@ -464,15 +451,7 @@ namespace FlaxEditor.Surface { // Check if any control is under the mouse _cmStartPos = location; - if (controlUnderMouse != null) - { - if (!HasNodesSelection) - Select(controlUnderMouse); - - // Show secondary context menu - ShowSecondaryCM(_cmStartPos, controlUnderMouse); - } - else + if (controlUnderMouse == null) { // Show primary context menu ShowPrimaryMenu(_cmStartPos); @@ -708,31 +687,38 @@ namespace FlaxEditor.Surface private Vector2 FindEmptySpace(Box box) { - int boxIndex = 0; + Vector2 distanceBetweenNodes = new Vector2(30, 30); var node = box.ParentNode; + + // Same height as node + float yLocation = node.Top; + for (int i = 0; i < node.Elements.Count; i++) { - // Box on the same side above the current box if (node.Elements[i] is Box nodeBox && nodeBox.IsOutput == box.IsOutput && nodeBox.Y < box.Y) { - boxIndex++; + // Below connected node + yLocation = Mathf.Max(yLocation, nodeBox.ParentNode.Bottom + distanceBetweenNodes.Y); } } + // TODO: Dodge the other nodes - Vector2 distanceBetweenNodes = new Vector2(40, 20); - const float NodeHeight = 120; + float xLocation = node.Location.X; + if (box.IsOutput) + { + xLocation += node.Width + distanceBetweenNodes.X; + } + else + { + xLocation += -120 - distanceBetweenNodes.X; + } - float direction = box.IsOutput ? 1 : -1; - - Vector2 newNodeLocation = node.Location + - new Vector2( - (node.Width + distanceBetweenNodes.X) * direction, - boxIndex * (NodeHeight + distanceBetweenNodes.Y) - ); - - return newNodeLocation; + return new Vector2( + xLocation, + yLocation + ); } } } diff --git a/Source/Editor/Surface/VisjectSurfaceContext.cs b/Source/Editor/Surface/VisjectSurfaceContext.cs index d436cd91e..81b72bf4e 100644 --- a/Source/Editor/Surface/VisjectSurfaceContext.cs +++ b/Source/Editor/Surface/VisjectSurfaceContext.cs @@ -331,12 +331,13 @@ namespace FlaxEditor.Surface /// The node archetype ID. /// The location. /// The custom values array. Must match node archetype size. Pass null to use default values. + /// The custom callback action to call after node creation but just before invoking spawn event. Can be used to initialize custom node data. /// Created node. - public SurfaceNode SpawnNode(ushort groupID, ushort typeID, Vector2 location, object[] customValues = null) + public SurfaceNode SpawnNode(ushort groupID, ushort typeID, Vector2 location, object[] customValues = null, Action beforeSpawned = null) { if (NodeFactory.GetArchetype(_surface.NodeArchetypes, groupID, typeID, out var groupArchetype, out var nodeArchetype)) { - return SpawnNode(groupArchetype, nodeArchetype, location, customValues); + return SpawnNode(groupArchetype, nodeArchetype, location, customValues, beforeSpawned); } return null; } @@ -348,8 +349,9 @@ namespace FlaxEditor.Surface /// The node archetype. /// The location. /// The custom values array. Must match node archetype size. Pass null to use default values. + /// The custom callback action to call after node creation but just before invoking spawn event. Can be used to initialize custom node data. /// Created node. - public SurfaceNode SpawnNode(GroupArchetype groupArchetype, NodeArchetype nodeArchetype, Vector2 location, object[] customValues = null) + public SurfaceNode SpawnNode(GroupArchetype groupArchetype, NodeArchetype nodeArchetype, Vector2 location, object[] customValues = null, Action beforeSpawned = null) { if (groupArchetype == null || nodeArchetype == null) throw new ArgumentNullException(); @@ -387,6 +389,7 @@ namespace FlaxEditor.Surface } node.Location = location; OnControlLoaded(node); + beforeSpawned?.Invoke(node); node.OnSurfaceLoaded(); OnControlSpawned(node); diff --git a/Source/Editor/Surface/VisualScriptSurface.cs b/Source/Editor/Surface/VisualScriptSurface.cs index ccabc8f2f..b919c7c37 100644 --- a/Source/Editor/Surface/VisualScriptSurface.cs +++ b/Source/Editor/Surface/VisualScriptSurface.cs @@ -2,6 +2,11 @@ //#define DEBUG_INVOKE_METHODS_SEARCHING //#define DEBUG_FIELDS_SEARCHING +//#define DEBUG_EVENTS_SEARCHING + +#if DEBUG_INVOKE_METHODS_SEARCHING || DEBUG_FIELDS_SEARCHING || DEBUG_EVENTS_SEARCHING +#define DEBUG_SEARCH_TIME +#endif using System; using System.Collections.Generic; @@ -71,7 +76,7 @@ namespace FlaxEditor.Surface // Check if has cached groups if (_cache.Count != 0) { - // Check if context menu doesn;t have the recent cached groups + // Check if context menu doesn't have the recent cached groups if (!contextMenu.Groups.Any(g => g.Archetype.Tag is int asInt && asInt == _version)) { var groups = contextMenu.Groups.Where(g => g.Archetype.Tag is int).ToArray(); @@ -103,7 +108,7 @@ namespace FlaxEditor.Surface private static void OnActiveContextMenuShowAsync() { Profiler.BeginEvent("Setup Visual Script Context Menu (async)"); -#if DEBUG_INVOKE_METHODS_SEARCHING || DEBUG_FIELDS_SEARCHING +#if DEBUG_SEARCH_TIME var searchStartTime = DateTime.Now; var searchHitsCount = 0; #endif @@ -338,13 +343,65 @@ namespace FlaxEditor.Surface } } } + else if (member.IsEvent) + { + var name = member.Name; + + // Skip if searching by name doesn't return a match + var members = scriptType.GetMembers(name, MemberTypes.Event, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly); + if (!members.Contains(member)) + continue; + + // Check if field is valid for Visual Script usage + if (SurfaceUtils.IsValidVisualScriptEvent(member)) + { + var groupKey = new KeyValuePair(scriptTypeName, 16); + if (!_cache.TryGetValue(groupKey, out var group)) + { + group = new GroupArchetype + { + GroupID = groupKey.Value, + Name = groupKey.Key, + Color = new Color(109, 160, 24), + Tag = _version, + Archetypes = new List(), + }; + _cache.Add(groupKey, group); + } + + // Add Bind event node + var bindNode = (NodeArchetype)Archetypes.Function.Nodes[8].Clone(); + bindNode.DefaultValues[0] = scriptTypeTypeName; + bindNode.DefaultValues[1] = name; + bindNode.Flags &= ~NodeFlags.NoSpawnViaGUI; + bindNode.Title = "Bind " + name; + bindNode.Description = SurfaceUtils.GetVisualScriptMemberInfoDescription(member); + bindNode.SubTitle = string.Format(" (in {0})", scriptTypeName); + ((IList)group.Archetypes).Add(bindNode); + + // Add Unbind event node + var unbindNode = (NodeArchetype)Archetypes.Function.Nodes[9].Clone(); + unbindNode.DefaultValues[0] = scriptTypeTypeName; + unbindNode.DefaultValues[1] = name; + unbindNode.Flags &= ~NodeFlags.NoSpawnViaGUI; + unbindNode.Title = "Unbind " + name; + unbindNode.Description = bindNode.Description; + unbindNode.SubTitle = bindNode.SubTitle; + ((IList)group.Archetypes).Add(unbindNode); + +#if DEBUG_EVENTS_SEARCHING + Editor.LogWarning(scriptTypeTypeName + " -> " + member.GetSignature()); + searchHitsCount++; +#endif + } + } } } // Add group to context menu (on a main thread) FlaxEngine.Scripting.InvokeOnUpdate(() => { -#if DEBUG_INVOKE_METHODS_SEARCHING || DEBUG_FIELDS_SEARCHING +#if DEBUG_SEARCH_TIME var addStartTime = DateTime.Now; #endif lock (_locker) @@ -352,12 +409,12 @@ namespace FlaxEditor.Surface _taskContextMenu.AddGroups(_cache.Values); _taskContextMenu = null; } -#if DEBUG_INVOKE_METHODS_SEARCHING || DEBUG_FIELDS_SEARCHING +#if DEBUG_SEARCH_TIME Editor.LogError($"Added items to VisjectCM in: {(DateTime.Now - addStartTime).TotalMilliseconds} ms"); #endif }); -#if DEBUG_INVOKE_METHODS_SEARCHING || DEBUG_FIELDS_SEARCHING +#if DEBUG_SEARCH_TIME Editor.LogError($"Collected {searchHitsCount} items in: {(DateTime.Now - searchStartTime).TotalMilliseconds} ms"); #endif Profiler.EndEvent(); diff --git a/Source/Editor/Tools/Foliage/FoliageTab.cs b/Source/Editor/Tools/Foliage/FoliageTab.cs index 9dbb17e88..faf71bf1f 100644 --- a/Source/Editor/Tools/Foliage/FoliageTab.cs +++ b/Source/Editor/Tools/Foliage/FoliageTab.cs @@ -21,6 +21,7 @@ namespace FlaxEditor.Tools.Foliage private readonly Tabs _modes; private readonly ContainerControl _noFoliagePanel; private int _selectedFoliageTypeIndex = -1; + private Button _createNewFoliage; /// /// The editor instance. @@ -99,6 +100,7 @@ namespace FlaxEditor.Tools.Foliage public FoliageTab(SpriteHandle icon, Editor editor) : base(string.Empty, icon) { + Level.SceneLoaded += OnSceneLoaded; Editor = editor; Editor.SceneEditing.SelectionChanged += OnSelectionChanged; @@ -135,14 +137,31 @@ namespace FlaxEditor.Tools.Foliage Offsets = Margin.Zero, Parent = _noFoliagePanel }; - var noFoliageButton = new Button + _createNewFoliage = new Button { Text = "Create new foliage", AnchorPreset = AnchorPresets.MiddleCenter, Offsets = new Margin(-60, 120, -12, 24), Parent = _noFoliagePanel, + Enabled = false }; - noFoliageButton.Clicked += OnCreateNewFoliageClicked; + _createNewFoliage.Clicked += OnCreateNewFoliageClicked; + } + + private void OnSceneLoaded(Scene arg1, Guid arg2) + { + _createNewFoliage.Enabled = true; + + Level.SceneUnloaded += OnSceneUnloaded; + Level.SceneLoaded -= OnSceneLoaded; + } + + private void OnSceneUnloaded(Scene arg1, Guid arg2) + { + _createNewFoliage.Enabled = false; + + Level.SceneLoaded += OnSceneLoaded; + Level.SceneUnloaded -= OnSceneUnloaded; } private void OnSelected(Tab tab) @@ -164,11 +183,8 @@ namespace FlaxEditor.Tools.Foliage actor.StaticFlags = StaticFlags.FullyStatic; actor.Name = "Foliage"; - // Spawn + // Spawn and select Editor.SceneEditing.Spawn(actor); - - // Select - Editor.SceneEditing.Select(actor); } private void OnSelectionChanged() @@ -248,5 +264,16 @@ namespace FlaxEditor.Tools.Foliage { SelectedFoliageTypesChanged?.Invoke(); } + + /// + public override void OnDestroy() + { + if (_createNewFoliage.Enabled) + Level.SceneUnloaded -= OnSceneUnloaded; + else + Level.SceneLoaded -= OnSceneLoaded; + + base.OnDestroy(); + } } } diff --git a/Source/Editor/Tools/Foliage/FoliageTools.cpp b/Source/Editor/Tools/Foliage/FoliageTools.cpp index a0cbf8e14..3354be0b3 100644 --- a/Source/Editor/Tools/Foliage/FoliageTools.cpp +++ b/Source/Editor/Tools/Foliage/FoliageTools.cpp @@ -67,7 +67,7 @@ struct GeometryLookup static bool Search(Actor* actor, GeometryLookup& lookup) { // Early out if object is not intersecting with the foliage brush bounds - if (!actor->GetBox().Intersects(lookup.Brush)) + if (!actor->GetIsActive() || !actor->GetBox().Intersects(lookup.Brush)) return true; const auto brush = lookup.Brush; diff --git a/Source/Editor/Tools/Terrain/CarveTab.cs b/Source/Editor/Tools/Terrain/CarveTab.cs index a3a65c52a..e7b880414 100644 --- a/Source/Editor/Tools/Terrain/CarveTab.cs +++ b/Source/Editor/Tools/Terrain/CarveTab.cs @@ -18,6 +18,7 @@ namespace FlaxEditor.Tools.Terrain { private readonly Tabs _modes; private readonly ContainerControl _noTerrainPanel; + private readonly Button _createTerrainButton; /// /// The editor instance. @@ -57,6 +58,7 @@ namespace FlaxEditor.Tools.Terrain public CarveTab(SpriteHandle icon, Editor editor) : base(string.Empty, icon) { + Level.SceneLoaded += OnSceneLoaded; Editor = editor; Editor.SceneEditing.SelectionChanged += OnSelectionChanged; @@ -93,14 +95,31 @@ namespace FlaxEditor.Tools.Terrain Offsets = Margin.Zero, Parent = _noTerrainPanel }; - var noTerrainButton = new Button + _createTerrainButton = new Button { Text = "Create new terrain", AnchorPreset = AnchorPresets.MiddleCenter, Offsets = new Margin(-60, 120, -12, 24), - Parent = _noTerrainPanel + Parent = _noTerrainPanel, + Enabled = false }; - noTerrainButton.Clicked += OnCreateNewTerrainClicked; + _createTerrainButton.Clicked += OnCreateNewTerrainClicked; + } + + private void OnSceneLoaded(Scene arg1, Guid arg2) + { + _createTerrainButton.Enabled = true; + + Level.SceneUnloaded += OnSceneUnloaded; + Level.SceneLoaded -= OnSceneLoaded; + } + + private void OnSceneUnloaded(Scene arg1, Guid arg2) + { + _createTerrainButton.Enabled = false; + + Level.SceneLoaded += OnSceneLoaded; + Level.SceneUnloaded -= OnSceneUnloaded; } private void OnSelected(Tab tab) @@ -183,5 +202,16 @@ namespace FlaxEditor.Tools.Terrain default: throw new IndexOutOfRangeException("Invalid carve tab mode."); } } + + /// + public override void OnDestroy() + { + if (_createTerrainButton.Enabled) + Level.SceneUnloaded -= OnSceneUnloaded; + else + Level.SceneLoaded -= OnSceneLoaded; + + base.OnDestroy(); + } } } diff --git a/Source/Editor/Tools/Terrain/Paint/Mode.cs b/Source/Editor/Tools/Terrain/Paint/Mode.cs index 1a1373d05..e53071aa3 100644 --- a/Source/Editor/Tools/Terrain/Paint/Mode.cs +++ b/Source/Editor/Tools/Terrain/Paint/Mode.cs @@ -33,7 +33,7 @@ namespace FlaxEditor.Tools.Terrain.Paint } /// - /// The tool strength (normalized to range 0-1). Defines the intensity of the paint operation to make it stronger or mre subtle. + /// The tool strength (normalized to range 0-1). Defines the intensity of the paint operation to make it stronger or more subtle. /// [EditorOrder(0), Limit(0, 10, 0.01f), Tooltip("The tool strength (normalized to range 0-1). Defines the intensity of the paint operation to make it stronger or more subtle.")] public float Strength = 1.0f; diff --git a/Source/Editor/Tools/Terrain/Sculpt/Mode.cs b/Source/Editor/Tools/Terrain/Sculpt/Mode.cs index 14fbe0175..eb32b3934 100644 --- a/Source/Editor/Tools/Terrain/Sculpt/Mode.cs +++ b/Source/Editor/Tools/Terrain/Sculpt/Mode.cs @@ -33,7 +33,7 @@ namespace FlaxEditor.Tools.Terrain.Sculpt } /// - /// The tool strength (normalized to range 0-1). Defines the intensity of the sculpt operation to make it stronger or mre subtle. + /// The tool strength (normalized to range 0-1). Defines the intensity of the sculpt operation to make it stronger or more subtle. /// [EditorOrder(0), Limit(0, 6, 0.01f), Tooltip("The tool strength (normalized to range 0-1). Defines the intensity of the sculpt operation to make it stronger or more subtle.")] public float Strength = 1.2f; diff --git a/Source/Editor/Tools/Terrain/TerrainTools.cpp b/Source/Editor/Tools/Terrain/TerrainTools.cpp index 6bf4f8fd4..e89b5e3e9 100644 --- a/Source/Editor/Tools/Terrain/TerrainTools.cpp +++ b/Source/Editor/Tools/Terrain/TerrainTools.cpp @@ -2,7 +2,7 @@ #include "TerrainTools.h" #include "Engine/Core/Cache.h" -#include "Engine/Core/Math/VectorInt.h" +#include "Engine/Core/Math/Int2.h" #include "Engine/Core/Math/Color32.h" #include "Engine/Core/Collections/CollectionPoolCache.h" #include "Engine/Terrain/TerrainPatch.h" diff --git a/Source/Editor/Tools/VertexPainting.cs b/Source/Editor/Tools/VertexPainting.cs index c130370e7..29d9daedc 100644 --- a/Source/Editor/Tools/VertexPainting.cs +++ b/Source/Editor/Tools/VertexPainting.cs @@ -49,7 +49,7 @@ namespace FlaxEditor.Tools set => Tab._gizmoMode.BrushStrength = value; } - [EditorOrder(20), EditorDisplay("Brush"), Limit(0.0f, 1.0f, 0.01f), Tooltip("The falloff parameter fo the brush. Adjusts the paint strength for the vertices that are far from the brush center. Use lower values to make painting smoother and softer.")] + [EditorOrder(20), EditorDisplay("Brush"), Limit(0.0f, 1.0f, 0.01f), Tooltip("The falloff parameter for the brush. Adjusts the paint strength for the vertices that are far from the brush center. Use lower values to make painting smoother and softer.")] public float BrushFalloff { get => Tab._gizmoMode.BrushFalloff; diff --git a/Source/Editor/Undo/Actions/DeleteActorsAction.cs b/Source/Editor/Undo/Actions/DeleteActorsAction.cs index fa319e6f3..41dac3ccc 100644 --- a/Source/Editor/Undo/Actions/DeleteActorsAction.cs +++ b/Source/Editor/Undo/Actions/DeleteActorsAction.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using FlaxEditor.SceneGraph; +using FlaxEditor.Scripting; using FlaxEngine; namespace FlaxEditor.Actions @@ -15,7 +16,13 @@ namespace FlaxEditor.Actions class DeleteActorsAction : IUndoAction { [Serialize] - private byte[] _data; + private byte[] _actorsData; + + [Serialize] + private List _nodesData; + + [Serialize] + private Guid[] _nodeParentsIDs; [Serialize] private Guid[] _prefabIds; @@ -30,31 +37,51 @@ namespace FlaxEditor.Actions /// The node parents. /// [Serialize] - protected List _nodeParents; + protected List _nodeParents; /// /// Initializes a new instance of the class. /// - /// The objects. + /// The objects. /// If set to true action will be inverted - instead of delete it will be create actors. - internal DeleteActorsAction(List objects, bool isInverted = false) + internal DeleteActorsAction(List nodes, bool isInverted = false) { _isInverted = isInverted; - _nodeParents = new List(objects.Count); - var actorNodes = new List(objects.Count); - var actors = new List(objects.Count); - for (int i = 0; i < objects.Count; i++) + + // Collect nodes to delete + var deleteNodes = new List(nodes.Count); + var actors = new List(nodes.Count); + for (int i = 0; i < nodes.Count; i++) { - if (objects[i] is ActorNode node) + var node = nodes[i]; + if (node is ActorNode actorNode) { - actorNodes.Add(node); - actors.Add(node.Actor); + deleteNodes.Add(actorNode); + actors.Add(actorNode.Actor); + } + else + { + deleteNodes.Add(node); + if (node.CanUseState) + { + if (_nodesData == null) + _nodesData = new List(); + _nodesData.Add(node.State); + } } } - actorNodes.BuildNodesParents(_nodeParents); - _data = Actor.ToBytes(actors.ToArray()); + // Collect parent nodes to delete + _nodeParents = new List(nodes.Count); + deleteNodes.BuildNodesParents(_nodeParents); + _nodeParentsIDs = new Guid[_nodeParents.Count]; + for (int i = 0; i < _nodeParentsIDs.Length; i++) + _nodeParentsIDs[i] = _nodeParents[i].ID; + // Serialize actors + _actorsData = Actor.ToBytes(actors.ToArray()); + + // Cache actors linkage to prefab objects _prefabIds = new Guid[actors.Count]; _prefabObjectIds = new Guid[actors.Count]; for (int i = 0; i < actors.Count; i++) @@ -88,9 +115,11 @@ namespace FlaxEditor.Actions /// public void Dispose() { - _data = null; + _actorsData = null; + _nodeParentsIDs = null; _prefabIds = null; _prefabObjectIds = null; + _nodeParents.Clear(); } /// @@ -123,28 +152,64 @@ namespace FlaxEditor.Actions /// protected virtual void Create() { - // Restore objects - var actors = Actor.FromBytes(_data); - if (actors == null) - return; - for (int i = 0; i < actors.Length; i++) + var nodes = new List(); + + // Restore actors + var actors = Actor.FromBytes(_actorsData); + if (actors != null) { - Guid prefabId = _prefabIds[i]; - if (prefabId != Guid.Empty) + nodes.Capacity = Math.Max(nodes.Capacity, actors.Length); + + // Preserve prefab objects linkage + for (int i = 0; i < actors.Length; i++) { - Actor.Internal_LinkPrefab(FlaxEngine.Object.GetUnmanagedPtr(actors[i]), ref prefabId, ref _prefabObjectIds[i]); + Guid prefabId = _prefabIds[i]; + if (prefabId != Guid.Empty) + { + Actor.Internal_LinkPrefab(FlaxEngine.Object.GetUnmanagedPtr(actors[i]), ref prefabId, ref _prefabObjectIds[i]); + } } } - var actorNodes = new List(actors.Length); - for (int i = 0; i < actors.Length; i++) + + // Restore nodes state + if (_nodesData != null) { - var foundNode = GetNode(actors[i].ID); + for (int i = 0; i < _nodesData.Count; i++) + { + var state = _nodesData[i]; + var type = TypeUtils.GetManagedType(state.TypeName); + if (type == null) + { + Editor.LogError($"Missing type {state.TypeName} for scene graph node undo state restore."); + continue; + } + var method = type.GetMethod(state.CreateMethodName); + if (method == null) + { + Editor.LogError($"Missing method {state.CreateMethodName} from type {state.TypeName} for scene graph node undo state restore."); + continue; + } + var node = method.Invoke(null, new object[] { state }); + if (node == null) + { + Editor.LogError($"Failed to restore scene graph node state via method {state.CreateMethodName} from type {state.TypeName}."); + continue; + } + } + } + + // Cache parent nodes ids + for (int i = 0; i < _nodeParentsIDs.Length; i++) + { + var foundNode = GetNode(_nodeParentsIDs[i]); if (foundNode is ActorNode node) { - actorNodes.Add(node); + nodes.Add(node); } } - actorNodes.BuildNodesParents(_nodeParents); + nodes.BuildNodesParents(_nodeParents); + + // Mark scenes as modified for (int i = 0; i < _nodeParents.Count; i++) { Editor.Instance.Scene.MarkSceneEdited(_nodeParents[i].ParentScene); diff --git a/Source/Editor/Undo/Actions/EditSplineAction.cs b/Source/Editor/Undo/Actions/EditSplineAction.cs new file mode 100644 index 000000000..7fb836459 --- /dev/null +++ b/Source/Editor/Undo/Actions/EditSplineAction.cs @@ -0,0 +1,76 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System; +using FlaxEditor.Modules; +using FlaxEditor.SceneGraph.Actors; +using FlaxEngine; + +namespace FlaxEditor.Actions +{ + /// + /// Change keyframes undo action. + /// + /// + /// + [Serializable] + public class EditSplineAction : IUndoAction, ISceneEditAction + { + [Serialize] + private Guid _splineId; + + [Serialize] + private BezierCurve.Keyframe[] _before; + + [Serialize] + private BezierCurve.Keyframe[] _after; + + /// + /// Initializes a new instance of the class. + /// + /// The spline. + /// The spline keyframes state before editing it. + public EditSplineAction(Spline spline, BezierCurve.Keyframe[] before) + { + _splineId = spline.ID; + _before = before; + _after = (BezierCurve.Keyframe[])spline.SplineKeyframes.Clone(); + } + + /// + public string ActionString => "Edit spline keyframes"; + + /// + public void Do() + { + var spline = FlaxEngine.Object.Find(ref _splineId); + if (spline == null) + return; + spline.SplineKeyframes = _after; + SplineNode.OnSplineEdited(spline); + } + + /// + public void Undo() + { + var spline = FlaxEngine.Object.Find(ref _splineId); + if (spline == null) + return; + spline.SplineKeyframes = _before; + SplineNode.OnSplineEdited(spline); + } + + /// + public void Dispose() + { + _before = _after = null; + } + + /// + public void MarkSceneEdited(SceneModule sceneModule) + { + var spline = FlaxEngine.Object.Find(ref _splineId); + if (spline != null) + sceneModule.MarkSceneEdited(spline.Scene); + } + } +} diff --git a/Source/Editor/Utilities/EditorUtilities.cpp b/Source/Editor/Utilities/EditorUtilities.cpp index d0ad412d9..aa26388c1 100644 --- a/Source/Editor/Utilities/EditorUtilities.cpp +++ b/Source/Editor/Utilities/EditorUtilities.cpp @@ -6,10 +6,8 @@ #include "Engine/Core/Log.h" #include "Engine/Graphics/Textures/TextureData.h" #include "Engine/Graphics/PixelFormatExtensions.h" -#include "Engine/Serialization/FileReadStream.h" #include "Engine/Tools/TextureTool/TextureTool.h" #include "Engine/Core/Math/Color32.h" -#include "Engine/Core/Math/VectorInt.h" #include "Engine/Core/Config/GameSettings.h" #include "Engine/Content/Content.h" #include "Engine/Content/AssetReference.h" @@ -248,7 +246,7 @@ void UpdateIconData(uint8* iconData, const TextureData* icon) iconTexSize = Math::RoundUpToPowerOf2(width); } - // Try to pick a proper mip (requrie the same size) + // Try to pick a proper mip (require the same size) const TextureMipData* srcPixels = nullptr; int32 mipLevels = icon->GetMipLevels(); for (int32 mipIndex = 0; mipIndex < mipLevels; mipIndex++) @@ -509,7 +507,11 @@ bool EditorUtilities::GetApplicationImage(const Guid& imageId, TextureData& imag AssetReference icon = Content::LoadAsync(imageId); if (icon == nullptr) { - icon = Content::LoadAsync(GameSettings::Icon); + const auto gameSettings = GameSettings::Get(); + if (gameSettings) + { + icon = Content::LoadAsync(gameSettings->Icon); + } } if (icon == nullptr) { @@ -745,6 +747,59 @@ bool EditorUtilities::GenerateCertificate(const String& name, const String& outp return false; } +bool EditorUtilities::IsInvalidPathChar(Char c) +{ + char illegalChars[] = + { + '?', + '\\', + '/', + '\"', + '<', + '>', + '|', + ':', + '*', + '\u0001', + '\u0002', + '\u0003', + '\u0004', + '\u0005', + '\u0006', + '\a', + '\b', + '\t', + '\n', + '\v', + '\f', + '\r', + '\u000E', + '\u000F', + '\u0010', + '\u0011', + '\u0012', + '\u0013', + '\u0014', + '\u0015', + '\u0016', + '\u0017', + '\u0018', + '\u0019', + '\u001A', + '\u001B', + '\u001C', + '\u001D', + '\u001E', + '\u001F' + }; + for (auto i : illegalChars) + { + if (c == i) + return true; + } + return false; +} + bool EditorUtilities::ReplaceInFiles(const String& folderPath, const Char* searchPattern, DirectorySearchOption searchOption, const String& findWhat, const String& replaceWith) { Array files; diff --git a/Source/Editor/Utilities/EditorUtilities.h b/Source/Editor/Utilities/EditorUtilities.h index 236d10e6f..a5f129c1b 100644 --- a/Source/Editor/Utilities/EditorUtilities.h +++ b/Source/Editor/Utilities/EditorUtilities.h @@ -42,6 +42,13 @@ public: public: + /// + /// Determines whether the specified path character is invalid. + /// + /// The path character. + /// true if the given character cannot be used as a path because it is illegal character; otherwise, false. + static bool IsInvalidPathChar(Char c); + /// /// Replaces the given text with other one in the files. /// diff --git a/Source/Editor/Utilities/ObjectSnapshot.cs b/Source/Editor/Utilities/ObjectSnapshot.cs index a64ddf306..8f29d1145 100644 --- a/Source/Editor/Utilities/ObjectSnapshot.cs +++ b/Source/Editor/Utilities/ObjectSnapshot.cs @@ -251,7 +251,7 @@ namespace FlaxEditor.Utilities var list = new List(); #if DEBUG_OBJECT_SNAPSHOT_COMPARISION - Debug.Logger.LogHandler.LogWrite(LogType.Warning, "-------------- Comparision --------------"); + Debug.Logger.LogHandler.LogWrite(LogType.Warning, "-------------- Comparison --------------"); #endif for (int i = _members.Count - 1; i >= 0; i--) { diff --git a/Source/Editor/Utilities/ShuntingYardParser.cs b/Source/Editor/Utilities/ShuntingYardParser.cs index dfd83b1f8..a07026d1c 100644 --- a/Source/Editor/Utilities/ShuntingYardParser.cs +++ b/Source/Editor/Utilities/ShuntingYardParser.cs @@ -126,7 +126,7 @@ namespace FlaxEditor.Utilities /// /// The first operator. /// The second operator. - /// The comparision result. + /// The comparison result. private static bool CompareOperators(string oper1, string oper2) { var op1 = Operators[oper1]; @@ -193,6 +193,7 @@ namespace FlaxEditor.Utilities { case 'x': case 'X': + { // Hexadecimal value i++; token.Clear(); @@ -200,22 +201,35 @@ namespace FlaxEditor.Utilities throw new ParsingException("invalid hexadecimal number"); while (i + 1 < text.Length && StringUtils.IsHexDigit(text[i + 1])) { - i++; - token.Append(text[i]); + token.Append(text[++i]); } var value = ulong.Parse(token.ToString(), NumberStyles.HexNumber); token.Clear(); token.Append(value.ToString()); break; + } default: + { // Decimal value while (i + 1 < text.Length && DetermineType(text[i + 1]) == TokenType.Number) { - i++; - token.Append(text[i]); + token.Append(text[++i]); + } + + // Exponential notation + if (i + 2 < text.Length && (text[i + 1] == 'e' || text[i + 1] == 'E')) + { + token.Append(text[++i]); + if (text[i + 1] == '-' || text[i + 1] == '+') + token.Append(text[++i]); + while (i + 1 < text.Length && DetermineType(text[i + 1]) == TokenType.Number) + { + token.Append(text[++i]); + } } break; } + } } // Discard solo '-' diff --git a/Source/Editor/Utilities/Utils.cs b/Source/Editor/Utilities/Utils.cs index 5f1636239..95ad9ade5 100644 --- a/Source/Editor/Utilities/Utils.cs +++ b/Source/Editor/Utilities/Utils.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; +using System.Reflection; using System.Runtime.InteropServices; using FlaxEditor.SceneGraph; using FlaxEditor.Scripting; @@ -532,6 +533,8 @@ namespace FlaxEditor.Utilities break; case VariantType.Enum: case VariantType.Structure: + case VariantType.ManagedObject: + case VariantType.Typename: stream.Write(int.MaxValue); stream.WriteStrAnsi(type.FullName, 77); break; @@ -742,6 +745,7 @@ namespace FlaxEditor.Utilities case VariantType.Array: return new ScriptType(typeof(object[])); case VariantType.Dictionary: return new ScriptType(typeof(Dictionary)); case VariantType.ManagedObject: return new ScriptType(typeof(object)); + case VariantType.Blob: return new ScriptType(typeof(byte[])); default: throw new ArgumentOutOfRangeException($"Unknown Variant Type {variantType} without typename."); } } @@ -760,7 +764,7 @@ namespace FlaxEditor.Utilities data[i] = (byte)(c ^ 77); } var typeName = System.Text.Encoding.ASCII.GetString(data); - return TypeUtils.GetType(typeName).Type; + return TypeUtils.GetManagedType(typeName); } if (typeNameLength > 0) { @@ -772,7 +776,7 @@ namespace FlaxEditor.Utilities data[i] = (char)(c ^ 77); } var typeName = new string(data); - return TypeUtils.GetType(typeName).Type; + return TypeUtils.GetManagedType(typeName); } switch (variantType) { @@ -805,6 +809,7 @@ namespace FlaxEditor.Utilities case VariantType.Array: return typeof(object[]); case VariantType.Dictionary: return typeof(Dictionary); case VariantType.ManagedObject: return typeof(object); + case VariantType.Blob: return typeof(byte[]); default: throw new ArgumentOutOfRangeException($"Unknown Variant Type {variantType} without typename."); } } @@ -824,7 +829,7 @@ namespace FlaxEditor.Utilities data[i] = (byte)(c ^ 77); } var typeName = System.Text.Encoding.ASCII.GetString(data); - type = TypeUtils.GetType(typeName).Type; + type = TypeUtils.GetManagedType(typeName); } else if (typeNameLength > 0) { @@ -836,7 +841,7 @@ namespace FlaxEditor.Utilities data[i] = (char)(c ^ 77); } var typeName = new string(data); - type = TypeUtils.GetType(typeName).Type; + type = TypeUtils.GetManagedType(typeName); } switch (variantType) { @@ -1641,7 +1646,8 @@ namespace FlaxEditor.Utilities /// /// The source code. /// The window title. - public static void ShowSourceCodeWindow(string source, string title) + /// The context control used to show source code window popup in a proper location. + public static void ShowSourceCodeWindow(string source, string title, Control control = null) { if (string.IsNullOrEmpty(source)) { @@ -1654,8 +1660,9 @@ namespace FlaxEditor.Utilities settings.AllowMaximize = false; settings.AllowMinimize = false; settings.HasSizingFrame = false; - settings.StartPosition = WindowStartPosition.CenterScreen; + settings.StartPosition = WindowStartPosition.CenterParent; settings.Size = new Vector2(500, 600) * Platform.DpiScale; + settings.Parent = control?.RootWindow?.Window ?? Editor.Instance.Windows.MainWindow; settings.Title = title; var dialog = Platform.CreateWindow(ref settings); @@ -1732,5 +1739,44 @@ namespace FlaxEditor.Utilities distance = 0; return false; } + + /// + /// Initializes the object fields and properties with their default values based on . + /// + /// The object. + public static void InitDefaultValues(object obj) + { + var scriptType = TypeUtils.GetObjectType(obj); + if (!scriptType) + return; + var isStructure = scriptType.IsStructure; + + var fields = scriptType.GetFields(BindingFlags.Default | BindingFlags.Instance | BindingFlags.Public); + for (var i = 0; i < fields.Length; i++) + { + var field = fields[i]; + var attr = field.GetAttribute(); + if (attr != null) + { + field.SetValue(obj, attr.Value); + } + else if (isStructure) + { + // C# doesn't support default values for structure members so initialize them + field.SetValue(obj, TypeUtils.GetDefaultValue(field.ValueType)); + } + } + + var properties = scriptType.GetProperties(BindingFlags.Default | BindingFlags.Instance | BindingFlags.Public); + for (var i = 0; i < properties.Length; i++) + { + var property = properties[i]; + var attr = property.GetAttribute(); + if (attr != null) + { + property.SetValue(obj, attr.Value); + } + } + } } } diff --git a/Source/Editor/Utilities/ViewportIconsRenderer.cpp b/Source/Editor/Utilities/ViewportIconsRenderer.cpp index 631e8b56f..889d94052 100644 --- a/Source/Editor/Utilities/ViewportIconsRenderer.cpp +++ b/Source/Editor/Utilities/ViewportIconsRenderer.cpp @@ -138,7 +138,7 @@ bool ViewportIconsRendererService::Init() void ViewportIconsRendererService::Dispose() { - QuadModel.Unlink(); + QuadModel = nullptr; for (int32 i = 0; i < ARRAY_COUNT(InstanceBuffers); i++) InstanceBuffers[i].Release(); ActorTypeToIconType.Clear(); diff --git a/Source/Editor/Viewport/Cameras/FPSCamera.cs b/Source/Editor/Viewport/Cameras/FPSCamera.cs index c9e65d6e4..b5098696d 100644 --- a/Source/Editor/Viewport/Cameras/FPSCamera.cs +++ b/Source/Editor/Viewport/Cameras/FPSCamera.cs @@ -24,7 +24,7 @@ namespace FlaxEditor.Viewport.Cameras public bool IsAnimatingMove => _moveStartTime > Mathf.Epsilon; /// - /// The target point location. It's used to orbit around it whe user clicks Alt+LMB. + /// The target point location. It's used to orbit around it when user clicks Alt+LMB. /// public Vector3 TargetPoint = new Vector3(-200); @@ -188,8 +188,16 @@ namespace FlaxEditor.Viewport.Cameras if (input.IsPanning) { var panningSpeed = 0.8f; - position -= right * (mouseDelta.X * panningSpeed); - position -= up * (mouseDelta.Y * panningSpeed); + if (Viewport.InvertPanning) + { + position += up * (mouseDelta.Y * panningSpeed); + position += right * (mouseDelta.X * panningSpeed); + } + else + { + position -= right * (mouseDelta.X * panningSpeed); + position -= up * (mouseDelta.Y * panningSpeed); + } } // Move diff --git a/Source/Editor/Viewport/Cameras/ViewportCamera.cs b/Source/Editor/Viewport/Cameras/ViewportCamera.cs index a01000035..85b7a917c 100644 --- a/Source/Editor/Viewport/Cameras/ViewportCamera.cs +++ b/Source/Editor/Viewport/Cameras/ViewportCamera.cs @@ -32,9 +32,9 @@ namespace FlaxEditor.Viewport.Cameras /// /// The target object bounds. /// The margin distance scale of the orbit radius. - public void SerArcBallView(BoundingBox objectBounds, float marginDistanceScale = 2.0f) + public void SetArcBallView(BoundingBox objectBounds, float marginDistanceScale = 2.0f) { - SerArcBallView(BoundingSphere.FromBox(objectBounds), marginDistanceScale); + SetArcBallView(BoundingSphere.FromBox(objectBounds), marginDistanceScale); } /// @@ -42,18 +42,18 @@ namespace FlaxEditor.Viewport.Cameras /// /// The target object bounds. /// The margin distance scale of the orbit radius. - public void SerArcBallView(BoundingSphere objectBounds, float marginDistanceScale = 2.0f) + public void SetArcBallView(BoundingSphere objectBounds, float marginDistanceScale = 2.0f) { - SerArcBallView(new Quaternion(-0.08f, -0.92f, 0.31f, -0.23f), objectBounds.Center, objectBounds.Radius * marginDistanceScale); + SetArcBallView(new Quaternion(-0.08f, -0.92f, 0.31f, -0.23f), objectBounds.Center, objectBounds.Radius * marginDistanceScale); } /// /// Sets view orientation and position to match the arc ball camera style view for the given orbit radius. /// /// The orbit radius. - public void SerArcBallView(float orbitRadius) + public void SetArcBallView(float orbitRadius) { - SerArcBallView(new Quaternion(-0.08f, -0.92f, 0.31f, -0.23f), Vector3.Zero, orbitRadius); + SetArcBallView(new Quaternion(-0.08f, -0.92f, 0.31f, -0.23f), Vector3.Zero, orbitRadius); } /// @@ -62,7 +62,7 @@ namespace FlaxEditor.Viewport.Cameras /// The view rotation. /// The orbit center location. /// The orbit radius. - public void SerArcBallView(Quaternion orientation, Vector3 orbitCenter, float orbitRadius) + public void SetArcBallView(Quaternion orientation, Vector3 orbitCenter, float orbitRadius) { // Rotate Viewport.ViewOrientation = orientation; diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index 02696f743..a19589c05 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -138,9 +138,7 @@ namespace FlaxEditor.Viewport private bool _isControllingMouse; private int _deltaFilteringStep; - private Vector2 _startPosMiddle; - private Vector2 _startPosRight; - private Vector2 _startPosLeft; + private Vector2 _startPos; private Vector2 _mouseDeltaRightLast; private Vector2[] _deltaFilteringBuffer = new Vector2[FpsCameraFilteringFrames]; @@ -180,6 +178,7 @@ namespace FlaxEditor.Viewport private float _orthoSize = 1.0f; private bool _isOrtho = false; private float _wheelMovementChangeDeltaSum = 0; + private bool _invertPanning; /// /// Speed of the mouse. @@ -403,6 +402,15 @@ namespace FlaxEditor.Viewport set => _isOrtho = value; } + /// + /// Gets or sets if the panning direction is inverted. + /// + public bool InvertPanning + { + get => _invertPanning; + set => _invertPanning = value; + } + /// /// The input actions collection to processed during user input. /// @@ -434,6 +442,7 @@ namespace FlaxEditor.Viewport _nearPlane = options.Viewport.DefaultNearPlane; _farPlane = options.Viewport.DefaultFarPlane; _fieldOfView = options.Viewport.DefaultFieldOfView; + _invertPanning = options.Viewport.DefaultInvertPanning; Editor.Instance.Options.OptionsChanged += OnEditorOptionsChanged; OnEditorOptionsChanged(options); @@ -454,7 +463,7 @@ namespace FlaxEditor.Viewport var button = camSpeedCM.AddButton(v.ToString()); button.Tag = v; } - camSpeedCM.ButtonClicked += (button) => MovementSpeed = (float)button.Tag; + camSpeedCM.ButtonClicked += button => MovementSpeed = (float)button.Tag; camSpeedCM.VisibleChanged += WidgetCamSpeedShowHide; camSpeedButton.Parent = camSpeed; camSpeed.Parent = this; @@ -515,9 +524,9 @@ namespace FlaxEditor.Viewport // Orthographic { var ortho = ViewWidgetButtonMenu.AddButton("Orthographic"); - var orthoValue = new CheckBox(75, 2, _isOrtho); + var orthoValue = new CheckBox(90, 2, _isOrtho); orthoValue.Parent = ortho; - orthoValue.StateChanged += (checkBox) => + orthoValue.StateChanged += checkBox => { if (checkBox.Checked != _isOrtho) { @@ -528,13 +537,25 @@ namespace FlaxEditor.Viewport ViewWidgetButtonMenu.VisibleChanged += control => orthoValue.Checked = _isOrtho; } + // Cara Orientation + { + var cameraView = ViewWidgetButtonMenu.AddChildMenu("Orientation").ContextMenu; + for (int i = 0; i < EditorViewportCameraOrientationValues.Length; i++) + { + var co = EditorViewportCameraOrientationValues[i]; + var button = cameraView.AddButton(co.Name); + button.Tag = co.Orientation; + } + cameraView.ButtonClicked += button => ViewOrientation = Quaternion.Euler((Vector3)button.Tag); + } + // Field of View { var fov = ViewWidgetButtonMenu.AddButton("Field Of View"); - var fovValue = new FloatValueBox(1, 75, 2, 50.0f, 35.0f, 160.0f, 0.1f); + var fovValue = new FloatValueBox(1, 90, 2, 70.0f, 35.0f, 160.0f, 0.1f); fovValue.Parent = fov; fovValue.ValueChanged += () => _fieldOfView = fovValue.Value; - ViewWidgetButtonMenu.VisibleChanged += (control) => + ViewWidgetButtonMenu.VisibleChanged += control => { fov.Visible = !_isOrtho; fovValue.Value = _fieldOfView; @@ -544,10 +565,10 @@ namespace FlaxEditor.Viewport // Ortho Scale { var orthoSize = ViewWidgetButtonMenu.AddButton("Ortho Scale"); - var orthoSizeValue = new FloatValueBox(_orthoSize, 75, 2, 50.0f, 0.001f, 100000.0f, 0.01f); + var orthoSizeValue = new FloatValueBox(_orthoSize, 90, 2, 70.0f, 0.001f, 100000.0f, 0.01f); orthoSizeValue.Parent = orthoSize; orthoSizeValue.ValueChanged += () => _orthoSize = orthoSizeValue.Value; - ViewWidgetButtonMenu.VisibleChanged += (control) => + ViewWidgetButtonMenu.VisibleChanged += control => { orthoSize.Visible = _isOrtho; orthoSizeValue.Value = _orthoSize; @@ -557,7 +578,7 @@ namespace FlaxEditor.Viewport // Near Plane { var nearPlane = ViewWidgetButtonMenu.AddButton("Near Plane"); - var nearPlaneValue = new FloatValueBox(2.0f, 75, 2, 50.0f, 0.001f, 1000.0f); + var nearPlaneValue = new FloatValueBox(2.0f, 90, 2, 70.0f, 0.001f, 1000.0f); nearPlaneValue.Parent = nearPlane; nearPlaneValue.ValueChanged += () => _nearPlane = nearPlaneValue.Value; ViewWidgetButtonMenu.VisibleChanged += control => nearPlaneValue.Value = _nearPlane; @@ -566,7 +587,7 @@ namespace FlaxEditor.Viewport // Far Plane { var farPlane = ViewWidgetButtonMenu.AddButton("Far Plane"); - var farPlaneValue = new FloatValueBox(1000, 75, 2, 50.0f, 10.0f); + var farPlaneValue = new FloatValueBox(1000, 90, 2, 70.0f, 10.0f); farPlaneValue.Parent = farPlane; farPlaneValue.ValueChanged += () => _farPlane = farPlaneValue.Value; ViewWidgetButtonMenu.VisibleChanged += control => farPlaneValue.Value = _farPlane; @@ -575,7 +596,7 @@ namespace FlaxEditor.Viewport // Brightness { var brightness = ViewWidgetButtonMenu.AddButton("Brightness"); - var brightnessValue = new FloatValueBox(1.0f, 75, 2, 50.0f, 0.001f, 10.0f, 0.001f); + var brightnessValue = new FloatValueBox(1.0f, 90, 2, 70.0f, 0.001f, 10.0f, 0.001f); brightnessValue.Parent = brightness; brightnessValue.ValueChanged += () => Brightness = brightnessValue.Value; ViewWidgetButtonMenu.VisibleChanged += control => brightnessValue.Value = Brightness; @@ -584,11 +605,26 @@ namespace FlaxEditor.Viewport // Resolution { var resolution = ViewWidgetButtonMenu.AddButton("Resolution"); - var resolutionValue = new FloatValueBox(1.0f, 75, 2, 50.0f, 0.1f, 4.0f, 0.001f); + var resolutionValue = new FloatValueBox(1.0f, 90, 2, 70.0f, 0.1f, 4.0f, 0.001f); resolutionValue.Parent = resolution; resolutionValue.ValueChanged += () => ResolutionScale = resolutionValue.Value; ViewWidgetButtonMenu.VisibleChanged += control => resolutionValue.Value = ResolutionScale; } + + // Invert Panning + { + var invert = ViewWidgetButtonMenu.AddButton("Invert Panning"); + var invertValue = new CheckBox(90, 2, _invertPanning); + invertValue.Parent = invert; + invertValue.StateChanged += checkBox => + { + if (checkBox.Checked != _invertPanning) + { + _invertPanning = checkBox.Checked; + } + }; + ViewWidgetButtonMenu.VisibleChanged += control => invertValue.Checked = _invertPanning; + } } // Link for task event @@ -812,7 +848,7 @@ namespace FlaxEditor.Viewport /// protected virtual void OnLeftMouseButtonDown() { - _startPosLeft = _viewMousePos; + _startPos = _viewMousePos; } /// @@ -827,7 +863,7 @@ namespace FlaxEditor.Viewport /// protected virtual void OnRightMouseButtonDown() { - _startPosRight = _viewMousePos; + _startPos = _viewMousePos; } /// @@ -842,7 +878,7 @@ namespace FlaxEditor.Viewport /// protected virtual void OnMiddleMouseButtonDown() { - _startPosMiddle = _viewMousePos; + _startPos = _viewMousePos; } /// @@ -1013,7 +1049,13 @@ namespace FlaxEditor.Viewport moveDelta *= 0.3f; // Calculate smooth mouse delta not dependant on viewport size - Vector2 offset = _viewMousePos - (_input.IsMouseMiddleDown ? _startPosMiddle : _startPosRight); + + Vector2 offset = _viewMousePos - _startPos; + if (_input.IsZooming && !_input.IsMouseRightDown && !_input.IsMouseLeftDown && !_input.IsMouseMiddleDown) + { + offset = Vector2.Zero; + } + offset.X = offset.X > 0 ? Mathf.Floor(offset.X) : Mathf.Ceil(offset.X); offset.Y = offset.Y > 0 ? Mathf.Floor(offset.Y) : Mathf.Ceil(offset.Y); _mouseDeltaRight = offset / size; @@ -1055,7 +1097,7 @@ namespace FlaxEditor.Viewport // Move mouse back to the root position if (centerMouse && (_input.IsMouseRightDown || _input.IsMouseLeftDown || _input.IsMouseMiddleDown)) { - Vector2 center = PointToWindow(_input.IsMouseMiddleDown ? _startPosMiddle : _startPosRight); + Vector2 center = PointToWindow(_startPos); win.MousePosition = center; } } @@ -1101,11 +1143,11 @@ namespace FlaxEditor.Viewport if (_input.IsMouseLeftDown) { // Calculate smooth mouse delta not dependant on viewport size - Vector2 offset = _viewMousePos - _startPosLeft; + Vector2 offset = _viewMousePos - _startPos; offset.X = offset.X > 0 ? Mathf.Floor(offset.X) : Mathf.Ceil(offset.X); offset.Y = offset.Y > 0 ? Mathf.Floor(offset.Y) : Mathf.Ceil(offset.Y); _mouseDeltaLeft = offset / size; - _startPosLeft = _viewMousePos; + _startPos = _viewMousePos; } else { @@ -1193,6 +1235,28 @@ namespace FlaxEditor.Viewport base.OnDestroy(); } + private struct CameraOrientation + { + public readonly string Name; + public readonly Vector3 Orientation; + + public CameraOrientation(string name, Vector3 orientation) + { + Name = name; + Orientation = orientation; + } + } + + private readonly CameraOrientation[] EditorViewportCameraOrientationValues = + { + new CameraOrientation("Front", new Vector3(0, 0, 0)), + new CameraOrientation("Back", new Vector3(0, 180, 0)), + new CameraOrientation("Left", new Vector3(0, 90, 0)), + new CameraOrientation("Right", new Vector3(0, -90, 0)), + new CameraOrientation("Top", new Vector3(-90, 0, 0)), + new CameraOrientation("Bottom", new Vector3(90, 0, 0)) + }; + private readonly float[] EditorViewportCameraSpeedValues = { 0.1f, diff --git a/Source/Editor/Viewport/PrefabWindowViewport.cs b/Source/Editor/Viewport/PrefabWindowViewport.cs index 94b994ac8..1276fc56d 100644 --- a/Source/Editor/Viewport/PrefabWindowViewport.cs +++ b/Source/Editor/Viewport/PrefabWindowViewport.cs @@ -485,6 +485,23 @@ namespace FlaxEditor.Viewport } } + /// + public override void Draw() + { + base.Draw(); + + // Selected UI controls outline + for (var i = 0; i < _window.Selection.Count; i++) + { + if (_window.Selection[i].EditableObject is UIControl controlActor && controlActor.Control != null) + { + var control = controlActor.Control; + var bounds = Rectangle.FromPoints(control.PointToParent(this, Vector2.Zero), control.PointToParent(this, control.Size)); + Render2D.DrawRectangle(bounds, Editor.Instance.Options.Options.Visual.SelectionOutlineColor0, Editor.Instance.Options.Options.Visual.UISelectionOutlineSize); + } + } + } + /// protected override void OnLeftMouseButtonUp() { @@ -513,7 +530,7 @@ namespace FlaxEditor.Viewport if (hit != null) { // For child actor nodes (mesh, link or sth) we need to select it's owning actor node first or any other child node (but not a child actor) - if (hit is ActorChildNode actorChildNode) + if (hit is ActorChildNode actorChildNode && !actorChildNode.CanBeSelectedDirectly) { var parentNode = actorChildNode.ParentNode; bool canChildBeSelected = _window.Selection.Contains(parentNode); diff --git a/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs b/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs index 24ae53261..b85895528 100644 --- a/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs +++ b/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs @@ -84,7 +84,7 @@ namespace FlaxEditor.Viewport.Previews // Preview LOD { var previewLOD = ViewWidgetButtonMenu.AddButton("Preview LOD"); - var previewLODValue = new IntValueBox(-1, 75, 2, 50.0f, -1, 10, 0.02f); + var previewLODValue = new IntValueBox(-1, 90, 2, 70.0f, -1, 10, 0.02f); previewLODValue.Parent = previewLOD; previewLODValue.ValueChanged += () => _previewModel.ForcedLOD = previewLODValue.Value; ViewWidgetButtonMenu.VisibleChanged += control => previewLODValue.Value = _previewModel.ForcedLOD; diff --git a/Source/Editor/Viewport/Previews/AssetPreview.cs b/Source/Editor/Viewport/Previews/AssetPreview.cs index 012837f87..9bf3573a1 100644 --- a/Source/Editor/Viewport/Previews/AssetPreview.cs +++ b/Source/Editor/Viewport/Previews/AssetPreview.cs @@ -85,7 +85,7 @@ namespace FlaxEditor.Viewport.Previews var orbitRadius = 200.0f; if (camera is ArcBallCamera arcBallCamera) orbitRadius = arcBallCamera.OrbitRadius; - camera.SerArcBallView(new Quaternion(-0.08f, -0.92f, 0.31f, -0.23f), Vector3.Zero, orbitRadius); + camera.SetArcBallView(new Quaternion(-0.08f, -0.92f, 0.31f, -0.23f), Vector3.Zero, orbitRadius); if (useWidgets) { diff --git a/Source/Editor/Viewport/Previews/MaterialPreview.cs b/Source/Editor/Viewport/Previews/MaterialPreview.cs index a03598193..4f7d71b18 100644 --- a/Source/Editor/Viewport/Previews/MaterialPreview.cs +++ b/Source/Editor/Viewport/Previews/MaterialPreview.cs @@ -23,9 +23,20 @@ namespace FlaxEditor.Viewport.Previews "Cone" }; + private static readonly Transform[] Transforms = + { + new Transform(Vector3.Zero, Quaternion.RotationY(Mathf.Pi), new Vector3(0.45f)), + new Transform(Vector3.Zero, Quaternion.RotationY(Mathf.Pi), new Vector3(0.45f)), + new Transform(Vector3.Zero, Quaternion.Identity, new Vector3(0.45f)), + new Transform(Vector3.Zero, Quaternion.RotationY(Mathf.Pi), new Vector3(0.45f)), + new Transform(Vector3.Zero, Quaternion.RotationY(Mathf.Pi), new Vector3(0.45f)), + }; + private StaticModel _previewModel; private Decal _decal; private Terrain _terrain; + private Spline _spline; + private SplineModel _splineModel; private ParticleEffect _particleEffect; private MaterialBase _particleEffectMaterial; private ParticleEmitter _particleEffectEmitter; @@ -65,6 +76,7 @@ namespace FlaxEditor.Viewport.Previews _selectedModelIndex = value; _previewModel.Model = FlaxEngine.Content.LoadAsyncInternal("Editor/Primitives/" + Models[value]); + _previewModel.Transform = Transforms[value]; } } @@ -77,7 +89,6 @@ namespace FlaxEditor.Viewport.Previews { // Setup preview scene _previewModel = new StaticModel(); - _previewModel.Transform = new Transform(Vector3.Zero, Quaternion.RotationY(Mathf.Pi), new Vector3(0.45f)); SelectedModelIndex = 0; // Link actors for rendering @@ -131,6 +142,7 @@ namespace FlaxEditor.Viewport.Previews MaterialBase guiMaterial = null; MaterialBase terrainMaterial = null; MaterialBase particleMaterial = null; + MaterialBase deformableMaterial = null; bool usePreviewActor = true; if (_material != null) { @@ -163,6 +175,10 @@ namespace FlaxEditor.Viewport.Previews usePreviewActor = false; particleMaterial = _material; break; + case MaterialDomain.Deformable: + usePreviewActor = false; + deformableMaterial = _material; + break; default: throw new ArgumentOutOfRangeException(); } } @@ -270,6 +286,25 @@ namespace FlaxEditor.Viewport.Previews } } } + + // Deformable + if (deformableMaterial && _spline == null) + { + _spline = new Spline(); + _spline.AddSplineLocalPoint(new Vector3(0, 0, -50.0f), false); + _spline.AddSplineLocalPoint(new Vector3(0, 0, 50.0f)); + _splineModel = new SplineModel + { + Scale = new Vector3(0.45f), + Parent = _spline, + }; + Task.AddCustomActor(_spline); + } + if (_splineModel != null) + { + _splineModel.Model = _previewModel.Model; + _splineModel.SetMaterial(0, deformableMaterial); + } } /// @@ -286,6 +321,8 @@ namespace FlaxEditor.Viewport.Previews Object.Destroy(ref _previewModel); Object.Destroy(ref _decal); Object.Destroy(ref _terrain); + Object.Destroy(ref _spline); + Object.Destroy(ref _splineModel); Object.Destroy(ref _particleEffect); Object.Destroy(ref _particleEffectEmitter); Object.Destroy(ref _particleEffectSystem); diff --git a/Source/Editor/Viewport/Previews/ModelPreview.cs b/Source/Editor/Viewport/Previews/ModelPreview.cs index e4789bdb8..aa03d0ee2 100644 --- a/Source/Editor/Viewport/Previews/ModelPreview.cs +++ b/Source/Editor/Viewport/Previews/ModelPreview.cs @@ -53,7 +53,7 @@ namespace FlaxEditor.Viewport.Previews // Preview LOD { var previewLOD = ViewWidgetButtonMenu.AddButton("Preview LOD"); - var previewLODValue = new IntValueBox(-1, 75, 2, 50.0f, -1, 10, 0.02f); + var previewLODValue = new IntValueBox(-1, 90, 2, 70.0f, -1, 10, 0.02f); previewLODValue.Parent = previewLOD; previewLODValue.ValueChanged += () => _previewModel.ForcedLOD = previewLODValue.Value; ViewWidgetButtonMenu.VisibleChanged += control => previewLODValue.Value = _previewModel.ForcedLOD; diff --git a/Source/Editor/Viewport/Previews/ParticleEmitterPreview.cs b/Source/Editor/Viewport/Previews/ParticleEmitterPreview.cs index e7021050b..638c8324c 100644 --- a/Source/Editor/Viewport/Previews/ParticleEmitterPreview.cs +++ b/Source/Editor/Viewport/Previews/ParticleEmitterPreview.cs @@ -67,7 +67,7 @@ namespace FlaxEditor.Viewport.Previews if (useWidgets) { var playbackDuration = ViewWidgetButtonMenu.AddButton("Duration"); - var playbackDurationValue = new FloatValueBox(_playbackDuration, 75, 2, 50.0f, 0.1f, 1000000.0f, 0.1f); + var playbackDurationValue = new FloatValueBox(_playbackDuration, 90, 2, 70.0f, 0.1f, 1000000.0f, 0.1f); playbackDurationValue.Parent = playbackDuration; playbackDurationValue.ValueChanged += () => PlaybackDuration = playbackDurationValue.Value; ViewWidgetButtonMenu.VisibleChanged += control => playbackDurationValue.Value = PlaybackDuration; diff --git a/Source/Editor/Viewport/Previews/PrefabPreview.cs b/Source/Editor/Viewport/Previews/PrefabPreview.cs index d0ed8a0c2..066e98456 100644 --- a/Source/Editor/Viewport/Previews/PrefabPreview.cs +++ b/Source/Editor/Viewport/Previews/PrefabPreview.cs @@ -1,7 +1,6 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using FlaxEngine; -using FlaxEngine.GUI; using Object = FlaxEngine.Object; namespace FlaxEditor.Viewport.Previews @@ -19,7 +18,7 @@ namespace FlaxEditor.Viewport.Previews private Prefab _prefab; private Actor _instance; - internal Control customControlLinked; + internal UIControl customControlLinked; /// /// Gets or sets the prefab asset to preview. @@ -29,39 +28,37 @@ namespace FlaxEditor.Viewport.Previews get => _prefab; set { - if (_prefab != value) + if (_prefab == value) + return; + + // Unset and cleanup spawned instance + if (_instance) { - if (_instance) + var instance = _instance; + Instance = null; + Object.Destroy(instance); + } + + _prefab = value; + + if (_prefab) + { + // Load prefab + _prefab.WaitForLoaded(); + + // Spawn prefab + var prevPreview = LoadingPreview; + LoadingPreview = this; + var instance = PrefabManager.SpawnPrefab(_prefab, null); + LoadingPreview = prevPreview; + if (instance == null) { - if (customControlLinked != null) - { - customControlLinked.Parent = null; - customControlLinked = null; - } - Task.RemoveCustomActor(_instance); - Object.Destroy(_instance); + _prefab = null; + throw new FlaxException("Failed to spawn a prefab for the preview."); } - _prefab = value; - - if (_prefab) - { - _prefab.WaitForLoaded(); // TODO: use lazy prefab spawning to reduce stalls - - var prevPreview = LoadingPreview; - LoadingPreview = this; - - _instance = PrefabManager.SpawnPrefab(_prefab, null); - - LoadingPreview = prevPreview; - - if (_instance == null) - { - _prefab = null; - throw new FlaxException("Failed to spawn a prefab for the preview."); - } - Task.AddCustomActor(_instance); - } + // Set instance + Instance = instance; } } } @@ -72,7 +69,47 @@ namespace FlaxEditor.Viewport.Previews public Actor Instance { get => _instance; - internal set => _instance = value; + internal set + { + if (_instance == value) + return; + + if (_instance) + { + // Unlink UI control + if (customControlLinked) + { + if (customControlLinked.Control?.Parent == this) + customControlLinked.Control.Parent = null; + customControlLinked = null; + } + + // Remove for the preview + Task.RemoveCustomActor(_instance); + } + + _instance = value; + + if (_instance) + { + // Add to the preview + Task.AddCustomActor(_instance); + + // Link UI canvases to the preview + LinkCanvas(_instance); + } + } + } + + private void LinkCanvas(Actor actor) + { + if (actor is UICanvas uiCanvas) + uiCanvas.EditorOverride(Task, this); + var children = actor.ChildrenCount; + for (int i = 0; i < children; i++) + { + LinkCanvas(actor.GetChild(i)); + } } /// @@ -87,7 +124,6 @@ namespace FlaxEditor.Viewport.Previews /// public override void OnDestroy() { - // Cleanup Prefab = null; base.OnDestroy(); diff --git a/Source/Editor/Viewport/Previews/TexturePreview.cs b/Source/Editor/Viewport/Previews/TexturePreview.cs index d1904027a..5e1024609 100644 --- a/Source/Editor/Viewport/Previews/TexturePreview.cs +++ b/Source/Editor/Viewport/Previews/TexturePreview.cs @@ -159,13 +159,14 @@ namespace FlaxEditor.Viewport.Previews float prevScale = _viewScale; _viewScale = Mathf.Clamp(_viewScale + delta * 0.24f, 0.001f, 20.0f); - // Move view to make use of the control much more soother - //float coeff = (prevScale + (_viewScale - prevScale)) / prevScale; - //_viewPos += (location * coeff - location) / _viewScale; - //_viewPos += location / _viewScale; - Vector2 sizeDelta = (_viewScale - prevScale) * _textureRect.Size; + // Compensate for the Rectangle.MakeScaled + Vector2 sizeDelta = (_viewScale - prevScale) * _textureRect.Size * 0.5f; _viewPos += sizeDelta * 0.5f; + // Move to zoom position + Vector2 locationOnTexture = (location - _textureRect.Location) / _textureRect.Size; + _viewPos -= sizeDelta * locationOnTexture; + return true; } diff --git a/Source/Editor/Windows/AboutDialog.cs b/Source/Editor/Windows/AboutDialog.cs index 03e60f43c..bb320fb87 100644 --- a/Source/Editor/Windows/AboutDialog.cs +++ b/Source/Editor/Windows/AboutDialog.cs @@ -115,7 +115,7 @@ namespace FlaxEditor.Windows { var thirdPartyPanel = new Panel(ScrollBars.Vertical) { - Bounds = new Rectangle(0, topParentControl.Bottom + 4, Width, Height - topParentControl.Bottom - 24), + Bounds = new Rectangle(4, topParentControl.Bottom + 4, Width - 8, Height - topParentControl.Bottom - 24), Parent = this }; var thirdPartyEntries = new[] diff --git a/Source/Editor/Windows/Assets/AssetEditorWindow.cs b/Source/Editor/Windows/Assets/AssetEditorWindow.cs index 756adbe05..8ad655d01 100644 --- a/Source/Editor/Windows/Assets/AssetEditorWindow.cs +++ b/Source/Editor/Windows/Assets/AssetEditorWindow.cs @@ -288,7 +288,7 @@ namespace FlaxEditor.Windows.Assets public abstract class AssetEditorWindowBase : AssetEditorWindow where T : Asset { /// - /// Flag set to true if window is is waiting for asset to be loaded (to send or events). + /// Flag set to true if window is waiting for asset to be loaded (to send or events). /// protected bool _isWaitingForLoaded; @@ -430,7 +430,7 @@ namespace FlaxEditor.Windows.Assets /// /// Gets the original asset. Note: is the cloned asset for local editing. Use to apply changes to the original asset. /// - public T OriginalAsset => (T)FlaxEngine.Content.GetAsset(_item.ID); + public T OriginalAsset => (T)FlaxEngine.Content.Load(_item.ID); /// protected ClonedAssetEditorWindowBase(Editor editor, AssetItem item) diff --git a/Source/Editor/Windows/Assets/JsonAssetWindow.cs b/Source/Editor/Windows/Assets/JsonAssetWindow.cs index 670414241..da463c501 100644 --- a/Source/Editor/Windows/Assets/JsonAssetWindow.cs +++ b/Source/Editor/Windows/Assets/JsonAssetWindow.cs @@ -40,6 +40,12 @@ namespace FlaxEditor.Windows.Assets _presenter.Modified += MarkAsEdited; } + private void OnScriptsReloadBegin() + { + Close(); + ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin; + } + /// public override void Save() { @@ -76,6 +82,10 @@ namespace FlaxEditor.Windows.Assets _presenter.Select(_object); ClearEditedFlag(); + // Auto-close on scripting reload if json asset is from game scripts (it might be reloaded) + if (_object != null && FlaxEngine.Scripting.IsTypeFromGameScripts(_object.GetType())) + ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin; + base.OnAssetLoaded(); } diff --git a/Source/Editor/Windows/Assets/MaterialWindow.cs b/Source/Editor/Windows/Assets/MaterialWindow.cs index f49e6a79c..53d85f493 100644 --- a/Source/Editor/Windows/Assets/MaterialWindow.cs +++ b/Source/Editor/Windows/Assets/MaterialWindow.cs @@ -233,18 +233,14 @@ namespace FlaxEditor.Windows.Assets // Toolstrip _toolstrip.AddSeparator(); - _toolstrip.AddButton(editor.Icons.BracketsSlash32, () => ShowSourceCode(_asset)).LinkTooltip("Show generated shader source code"); + _toolstrip.AddButton(editor.Icons.BracketsSlash32, ShowSourceCode).LinkTooltip("Show generated shader source code"); _toolstrip.AddButton(editor.Icons.Docs32, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/graphics/materials/index.html")).LinkTooltip("See documentation to learn more"); } - /// - /// Shows the material source code window. - /// - /// The material asset. - public static void ShowSourceCode(Material material) + private void ShowSourceCode() { - var source = Editor.GetShaderSourceCode(material); - Utilities.Utils.ShowSourceCodeWindow(source, "Material Source"); + var source = Editor.GetShaderSourceCode(_asset); + Utilities.Utils.ShowSourceCodeWindow(source, "Material Source", this); } /// diff --git a/Source/Editor/Windows/Assets/ModelWindow.cs b/Source/Editor/Windows/Assets/ModelWindow.cs index 9910c6e7a..533c9694c 100644 --- a/Source/Editor/Windows/Assets/ModelWindow.cs +++ b/Source/Editor/Windows/Assets/ModelWindow.cs @@ -1004,7 +1004,7 @@ namespace FlaxEditor.Windows.Assets protected override void OnAssetLoaded() { _refreshOnLODsLoaded = true; - _preview.ViewportCamera.SerArcBallView(Asset.GetBox()); + _preview.ViewportCamera.SetArcBallView(Asset.GetBox()); UpdateEffectsOnAsset(); // TODO: disable streaming for this model diff --git a/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs b/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs index 17b5a928d..ab61a9d68 100644 --- a/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs +++ b/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs @@ -139,18 +139,14 @@ namespace FlaxEditor.Windows.Assets // Toolstrip _toolstrip.AddSeparator(); - _toolstrip.AddButton(editor.Icons.BracketsSlash32, () => ShowSourceCode(_asset)).LinkTooltip("Show generated shader source code"); + _toolstrip.AddButton(editor.Icons.BracketsSlash32, ShowSourceCode).LinkTooltip("Show generated shader source code"); _toolstrip.AddButton(editor.Icons.Docs32, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/particles/index.html")).LinkTooltip("See documentation to learn more"); } - /// - /// Shows the ParticleEmitter source code window. - /// - /// The ParticleEmitter asset. - public static void ShowSourceCode(ParticleEmitter particleEmitter) + private void ShowSourceCode() { - var source = Editor.GetShaderSourceCode(particleEmitter); - Utilities.Utils.ShowSourceCodeWindow(source, "Particle Emitter GPU Simulation Source"); + var source = Editor.GetShaderSourceCode(_asset); + Utilities.Utils.ShowSourceCodeWindow(source, "Particle Emitter GPU Simulation Source", this); } /// diff --git a/Source/Editor/Windows/Assets/PrefabWindow.Actions.cs b/Source/Editor/Windows/Assets/PrefabWindow.Actions.cs index 00a96910f..af568cd3f 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.Actions.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.Actions.cs @@ -159,7 +159,7 @@ namespace FlaxEditor.Windows.Assets public void Duplicate() { // Peek things that can be copied (copy all actors) - var objects = Selection.Where(x => x.CanCopyPaste && x != Graph.Main).ToList().BuildAllNodes().Where(x => x.CanCopyPaste && x is ActorNode).ToList(); + var objects = Selection.Where(x => x.CanDuplicate && x != Graph.Main).ToList().BuildAllNodes().Where(x => x.CanDuplicate && x is ActorNode).ToList(); if (objects.Count == 0) return; @@ -194,8 +194,8 @@ namespace FlaxEditor.Windows.Assets private class CustomDeleteActorsAction : DeleteActorsAction { - public CustomDeleteActorsAction(List objects, bool isInverted = false) - : base(objects, isInverted) + public CustomDeleteActorsAction(List nodes, bool isInverted = false) + : base(nodes, isInverted) { } @@ -207,7 +207,8 @@ namespace FlaxEditor.Windows.Assets // Unlink nodes from parents (actors spawned for prefab editing are not in a gameplay and may not send some important events) for (int i = 0; i < nodes.Length; i++) { - nodes[i].Actor.Parent = null; + if (nodes[i] is ActorNode actorNode) + actorNode.Actor.Parent = null; } base.Delete(); diff --git a/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs b/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs index 08621f2ec..bded7e147 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs @@ -69,6 +69,7 @@ namespace FlaxEditor.Windows.Assets bool hasSthSelected = Selection.Count > 0; bool isSingleActorSelected = Selection.Count == 1 && Selection[0] is ActorNode; bool isRootSelected = isSingleActorSelected && Selection[0] == Graph.Main; + bool hasPrefabLink = isSingleActorSelected && (Selection[0] as ActorNode).HasPrefabLink; // Create popup @@ -97,7 +98,7 @@ namespace FlaxEditor.Windows.Assets b.Enabled = hasSthSelected && !isRootSelected; b = contextMenu.AddButton("Set Root", SetRoot); - b.Enabled = isSingleActorSelected && !isRootSelected; + b.Enabled = isSingleActorSelected && !isRootSelected && hasPrefabLink; // Prefab options @@ -108,8 +109,6 @@ namespace FlaxEditor.Windows.Assets (Selection[0] as ActorNode).CanCreatePrefab && Editor.Windows.ContentWin.CurrentViewFolder.CanHaveAssets; - bool hasPrefabLink = isSingleActorSelected && (Selection[0] as ActorNode).HasPrefabLink; - b = contextMenu.AddButton("Select Prefab", Editor.Prefabs.SelectPrefab); b.Enabled = hasPrefabLink; diff --git a/Source/Editor/Windows/Assets/PrefabWindow.cs b/Source/Editor/Windows/Assets/PrefabWindow.cs index a8b3e24a4..4cc7898ee 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.cs @@ -284,6 +284,9 @@ namespace FlaxEditor.Windows.Assets { // Simply update changes Editor.Prefabs.ApplyAll(_viewport.Instance); + + // Refresh properties panel to sync new prefab default values + _propertiesEditor.BuildLayout(); } catch (Exception) { diff --git a/Source/Editor/Windows/Assets/SceneAnimationWindow.cs b/Source/Editor/Windows/Assets/SceneAnimationWindow.cs index cbed724bc..c18edc716 100644 --- a/Source/Editor/Windows/Assets/SceneAnimationWindow.cs +++ b/Source/Editor/Windows/Assets/SceneAnimationWindow.cs @@ -277,6 +277,7 @@ namespace FlaxEditor.Windows.Assets private readonly StagingTexture[] _stagingTextures = new StagingTexture[FrameLatency + 1]; private RenderProgress _progress; private RenderEditorState _editorState; + private GameWindow _gameWindow; public RenderOptions Options => _options; @@ -373,6 +374,9 @@ namespace FlaxEditor.Windows.Assets gameWin.Viewport.Task.PostRender += OnPostRender; if (!gameWin.Visible) gameWin.Show(); + else if (!gameWin.IsFocused) + gameWin.Focus(); + _gameWindow = gameWin; _warmUpTimeLeft = _options.WarmUpTime; _animationFrame = 0; var stagingTextureDesc = GPUTextureDescription.New2D(resolution.X, resolution.Y, gameWin.Viewport.Task.Output.Format, GPUTextureFlags.None); @@ -385,6 +389,12 @@ namespace FlaxEditor.Windows.Assets _stagingTextures[i].TaskFrame = -1; } _player.Play(); + if (!_player.IsPlaying) + { + Editor.LogError("Scene Animation Player failed to start playing."); + CancelRendering(); + return; + } if (_warmUpTimeLeft > 0.0f) { // Start warmup time @@ -438,8 +448,11 @@ namespace FlaxEditor.Windows.Assets _stagingTextures[i].Texture.ReleaseGPU(); Object.Destroy(ref _stagingTextures[i].Texture); } - _progress.End(); - editor.ProgressReporting.UnregisterHandler(_progress); + if (_progress != null) + { + _progress.End(); + editor.ProgressReporting.UnregisterHandler(_progress); + } if (_editorState != null) { editor.StateMachine.GoToState(editor.StateMachine.EditingSceneState); @@ -452,6 +465,7 @@ namespace FlaxEditor.Windows.Assets gameWin.Viewport.BackgroundColor = Color.Transparent; gameWin.Viewport.KeepAspectRatio = false; gameWin.Viewport.Task.PostRender -= OnPostRender; + _gameWindow = null; _isRendering = false; _presenter.Panel.Enabled = true; _presenter.BuildLayoutOnUpdate(); @@ -467,6 +481,11 @@ namespace FlaxEditor.Windows.Assets { // Render first frame _player.Play(); + if (!_player.IsPlaying) + { + Editor.LogError("Scene Animation Player failed to start playing."); + CancelRendering(); + } _warmUpTimeLeft = -1; _state = States.Render; } @@ -490,7 +509,7 @@ namespace FlaxEditor.Windows.Assets } if (_player.IsStopped || _player.Time >= _options.EndTime) { - // End rendering but perform reaming copies of the staging textures so all data is captured (from GPU to CPU) + // End rendering but perform remaining copies of the staging textures so all data is captured (from GPU to CPU) _state = States.Staging; break; } @@ -503,8 +522,7 @@ namespace FlaxEditor.Windows.Assets private void OnPostRender(GPUContext context, RenderContext renderContext) { - var gameWin = Editor.Instance.Windows.GameWin; - var task = gameWin.Viewport.Task; + var task = _gameWindow.Viewport.Task; var taskFrame = task.FrameCount; // Check all staging textures for finished GPU to CPU transfers @@ -540,7 +558,7 @@ namespace FlaxEditor.Windows.Assets ref var stagingTexture = ref _stagingTextures[textureIdx]; stagingTexture.AnimationFrame = _animationFrame; stagingTexture.TaskFrame = taskFrame; - _options.VideoOutputFormat.RenderFrame(context, ref stagingTexture, _options, gameWin.Viewport.Task.Output); + _options.VideoOutputFormat.RenderFrame(context, ref stagingTexture, _options, _gameWindow.Viewport.Task.Output); // Now wait for the next animation frame to be updated _state = States.Update; diff --git a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs index 2f6c952ba..c599e7e2a 100644 --- a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs +++ b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs @@ -1097,7 +1097,7 @@ namespace FlaxEditor.Windows.Assets protected override void OnAssetLoaded() { _refreshOnLODsLoaded = true; - _preview.ViewportCamera.SerArcBallView(Asset.GetBox()); + _preview.ViewportCamera.SetArcBallView(Asset.GetBox()); UpdateEffectsOnAsset(); // TODO: disable streaming for this model diff --git a/Source/Editor/Windows/ContentWindow.ContextMenu.cs b/Source/Editor/Windows/ContentWindow.ContextMenu.cs index 1b4d2be31..f81220ac4 100644 --- a/Source/Editor/Windows/ContentWindow.ContextMenu.cs +++ b/Source/Editor/Windows/ContentWindow.ContextMenu.cs @@ -224,7 +224,7 @@ namespace FlaxEditor.Windows /// private void ExportSelection() { - if(FileSystem.ShowBrowseFolderDialog(Editor.Windows.MainWindow, null, "Select the output folder", out var outputFolder)) + if (FileSystem.ShowBrowseFolderDialog(Editor.Windows.MainWindow, null, "Select the output folder", out var outputFolder)) return; var selection = _view.Selection; diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs index 66ed50daf..145c0d972 100644 --- a/Source/Editor/Windows/ContentWindow.cs +++ b/Source/Editor/Windows/ContentWindow.cs @@ -396,7 +396,7 @@ namespace FlaxEditor.Windows /// The item to delete. public void Delete(ContentItem item) { - Delete(new List { item }); + Delete(new List(1) { item }); } /// @@ -405,36 +405,18 @@ namespace FlaxEditor.Windows /// The items to delete. public void Delete(List items) { + if (items.Count == 0) return; + // TODO: remove items that depend on different items in the list: use wants to remove `folderA` and `folderA/asset.x`, we should just remove `folderA` var toDelete = new List(items); + string msg = toDelete.Count == 1 ? + string.Format("Are you sure to delete \'{0}\'?\nThis action cannot be undone. Files will be deleted permanently.", items[0].Path) + : string.Format("Are you sure to delete {0} selected items?\nThis action cannot be undone. Files will be deleted permanently.", items.Count); + // Ask user - if (toDelete.Count == 1) - { - // Single item - if (MessageBox.Show(string.Format("Are you sure to delete \'{0}\'?\nThis action cannot be undone. Files will be deleted permanently.", items[0].Path), - "Delete asset(s)", - MessageBoxButtons.OKCancel, - MessageBoxIcon.Question) - != DialogResult.OK) - { - // Break - return; - } - } - else - { - // Many items - if (MessageBox.Show(string.Format("Are you sure to delete {0} selected items?\nThis action cannot be undone. Files will be deleted permanently.", items.Count), - "Delete asset(s)", - MessageBoxButtons.OKCancel, - MessageBoxIcon.Question) - != DialogResult.OK) - { - // Break - return; - } - } + if (MessageBox.Show(msg, "Delete asset(s)", MessageBoxButtons.OKCancel, MessageBoxIcon.Question) != DialogResult.OK) + return; // Clear navigation // TODO: just remove invalid locations from the history (those are removed) diff --git a/Source/Editor/Windows/GameCookerWindow.cs b/Source/Editor/Windows/GameCookerWindow.cs index 442cc4c34..278ce69d3 100644 --- a/Source/Editor/Windows/GameCookerWindow.cs +++ b/Source/Editor/Windows/GameCookerWindow.cs @@ -73,6 +73,9 @@ namespace FlaxEditor.Windows [EditorOrder(20), Tooltip("Configuration build mode")] public BuildConfiguration ConfigurationMode = BuildConfiguration.Development; + [EditorOrder(90), Tooltip("The list of custom defines passed to the build tool when compiling project scripts. Can be used in build scripts for configuration (Configuration.CustomDefines).")] + public string[] CustomDefines; + protected abstract BuildPlatform BuildPlatform { get; } protected virtual BuildOptions Options @@ -126,7 +129,7 @@ namespace FlaxEditor.Windows public virtual void Build() { var output = StringUtils.ConvertRelativePathToAbsolute(Globals.ProjectFolder, StringUtils.NormalizePath(Output)); - GameCooker.Build(BuildPlatform, ConfigurationMode, output, Options); + GameCooker.Build(BuildPlatform, ConfigurationMode, output, Options, CustomDefines); } } @@ -793,7 +796,7 @@ namespace FlaxEditor.Windows _preBuildAction = target.PreBuildAction; _postBuildAction = target.PostBuildAction; - GameCooker.Build(target.Platform, target.Mode, target.Output, target.Options); + GameCooker.Build(target.Platform, target.Mode, target.Output, BuildOptions.None, target.CustomDefines); } else if (_exitOnBuildEnd) { diff --git a/Source/Editor/Windows/GameWindow.cs b/Source/Editor/Windows/GameWindow.cs index a29869825..858f0e0ba 100644 --- a/Source/Editor/Windows/GameWindow.cs +++ b/Source/Editor/Windows/GameWindow.cs @@ -222,7 +222,7 @@ namespace FlaxEditor.Windows private void PlayingStateOnSceneDuplicating() { - // Remove reaming GUI controls so loaded scene can add own GUI + // Remove remaining GUI controls so loaded scene can add own GUI //_guiRoot.DisposeChildren(); // Show GUI @@ -231,7 +231,7 @@ namespace FlaxEditor.Windows private void PlayingStateOnSceneRestored() { - // Remove reaming GUI controls so loaded scene can add own GUI + // Remove remaining GUI controls so loaded scene can add own GUI //_guiRoot.DisposeChildren(); // Hide GUI @@ -294,7 +294,9 @@ namespace FlaxEditor.Windows { if (Editor.Instance.SceneEditing.Selection[i].EditableObject is UIControl controlActor && controlActor.Control != null) { - Render2D.DrawRectangle(controlActor.Control.Bounds, Editor.Instance.Options.Options.Visual.SelectionOutlineColor0, Editor.Instance.Options.Options.Visual.UISelectionOutlineSize); + var control = controlActor.Control; + var bounds = Rectangle.FromPoints(control.PointToParent(_viewport, Vector2.Zero), control.PointToParent(_viewport, control.Size)); + Render2D.DrawRectangle(bounds, Editor.Instance.Options.Options.Visual.SelectionOutlineColor0, Editor.Instance.Options.Options.Visual.UISelectionOutlineSize); } } @@ -387,6 +389,12 @@ namespace FlaxEditor.Windows } } + // Prevent closing the game window tab during a play session + if (Editor.StateMachine.IsPlayMode && Editor.Options.Options.Input.CloseTab.Process(this, key)) + { + return true; + } + return base.OnKeyDown(key); } diff --git a/Source/Editor/Windows/SceneTreeWindow.Actors.cs b/Source/Editor/Windows/SceneTreeWindow.Actors.cs index d3fff78d8..8970b6a48 100644 --- a/Source/Editor/Windows/SceneTreeWindow.Actors.cs +++ b/Source/Editor/Windows/SceneTreeWindow.Actors.cs @@ -97,7 +97,9 @@ namespace FlaxEditor.Windows new KeyValuePair("Audio Listener", typeof(AudioListener)), new KeyValuePair("Scene Animation", typeof(SceneAnimationPlayer)), new KeyValuePair("Nav Mesh Bounds Volume", typeof(NavMeshBoundsVolume)), - new KeyValuePair("Nav Mesh Link", typeof(NavLink)), + new KeyValuePair("Nav Link", typeof(NavLink)), + new KeyValuePair("Nav Modifier Volume", typeof(NavModifierVolume)), + new KeyValuePair("Spline", typeof(Spline)), } }, new ActorsGroup diff --git a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs index b6756b438..f407017df 100644 --- a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs +++ b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; +using System.Collections.Generic; using FlaxEditor.GUI.ContextMenu; using FlaxEditor.SceneGraph; using FlaxEngine; @@ -113,6 +114,24 @@ namespace FlaxEditor.Windows // Custom options + bool showCustomNodeOptions = Editor.SceneEditing.Selection.Count == 1; + if (!showCustomNodeOptions && Editor.SceneEditing.Selection.Count != 0) + { + showCustomNodeOptions = true; + for (int i = 1; i < Editor.SceneEditing.Selection.Count; i++) + { + if (Editor.SceneEditing.Selection[0].GetType() != Editor.SceneEditing.Selection[i].GetType()) + { + showCustomNodeOptions = false; + break; + } + } + } + if (showCustomNodeOptions) + { + Editor.SceneEditing.Selection[0].OnContextMenu(contextMenu); + } + ContextMenuShow?.Invoke(contextMenu); return contextMenu; diff --git a/Source/Editor/Windows/SplashScreen.cpp b/Source/Editor/Windows/SplashScreen.cpp index 98578950f..bc92a8d2a 100644 --- a/Source/Editor/Windows/SplashScreen.cpp +++ b/Source/Editor/Windows/SplashScreen.cpp @@ -110,6 +110,12 @@ const Char* SplashScreenQuotes[] = TEXT("You have my bow.\nAnd my axe!"), TEXT("To the bridge of Khazad-dum."), TEXT("One ring to rule them all.\nOne ring to find them."), + TEXT("Ladies and gentelman, we got him"), + TEXT("Cyberpunk of game engines"), + TEXT("That's what she said"), + TEXT("Compiling Shaders (93,788)"), + TEXT("Hi There"), + TEXT("BAGUETTE"), }; SplashScreen::~SplashScreen() diff --git a/Source/Editor/Windows/ToolboxWindow.cs b/Source/Editor/Windows/ToolboxWindow.cs index d10a111b6..63e1f1f05 100644 --- a/Source/Editor/Windows/ToolboxWindow.cs +++ b/Source/Editor/Windows/ToolboxWindow.cs @@ -165,7 +165,9 @@ namespace FlaxEditor.Windows groupOther.AddChild(CreateActorItem("Empty Actor", typeof(EmptyActor))); groupOther.AddChild(CreateActorItem("Scene Animation", typeof(SceneAnimationPlayer))); groupOther.AddChild(CreateActorItem("Nav Mesh Bounds Volume", typeof(NavMeshBoundsVolume))); - groupOther.AddChild(CreateActorItem("Nav Mesh Link", typeof(NavLink))); + groupOther.AddChild(CreateActorItem("Nav Link", typeof(NavLink))); + groupOther.AddChild(CreateActorItem("Nav Modifier Volume", typeof(NavModifierVolume))); + groupOther.AddChild(CreateActorItem("Spline", typeof(Spline))); var groupGui = CreateGroupWithList(actorGroups, "GUI"); groupGui.AddChild(CreateActorItem("UI Control", typeof(UIControl))); diff --git a/Source/Engine/Animations/AnimationUtils.h b/Source/Engine/Animations/AnimationUtils.h index 248efea0c..5df8cc2c0 100644 --- a/Source/Engine/Animations/AnimationUtils.h +++ b/Source/Engine/Animations/AnimationUtils.h @@ -7,6 +7,7 @@ #include "Engine/Core/Math/Vector2.h" #include "Engine/Core/Math/Vector3.h" #include "Engine/Core/Math/Quaternion.h" +#include "Engine/Core/Math/Transform.h" namespace AnimationUtils { @@ -40,6 +41,12 @@ namespace AnimationUtils return Quaternion::Identity; } + template<> + FORCE_INLINE Transform GetZero() + { + return Transform::Identity; + } + template<> FORCE_INLINE Color GetZero() { @@ -63,25 +70,43 @@ namespace AnimationUtils FORCE_INLINE void GetTangent(const Quaternion& a, const Quaternion& b, float length, Quaternion& result) { const float oneThird = 1.0f / 3.0f; - Quaternion::Slerp(a, b, oneThird, result); + Quaternion::Slerp(a, b, length * oneThird, result); + } + + template<> + FORCE_INLINE void GetTangent(const Transform& a, const Transform& b, float length, Transform& result) + { + const float oneThird = 1.0f / 3.0f; + const float oneThirdLength = length * oneThird; + result.Translation = a.Translation + b.Translation * oneThirdLength; + Quaternion::Slerp(a.Orientation, b.Orientation, oneThirdLength, result.Orientation); + result.Scale = a.Scale + (b.Scale - a.Scale) * oneThirdLength; } template - FORCE_INLINE static void Interpolate(const T& a, const T& b, float alpha, T& result) + FORCE_INLINE static void Interpolate(const T& a, const T& b, float t, T& result) { - result = (T)(a + alpha * (b - a)); + result = (T)(a + t * (b - a)); } template<> - FORCE_INLINE void Interpolate(const Vector3& a, const Vector3& b, float alpha, Vector3& result) + FORCE_INLINE void Interpolate(const Vector3& a, const Vector3& b, float t, Vector3& result) { - Vector3::Lerp(a, b, alpha, result); + Vector3::Lerp(a, b, t, result); } template<> - FORCE_INLINE void Interpolate(const Quaternion& a, const Quaternion& b, float alpha, Quaternion& result) + FORCE_INLINE void Interpolate(const Quaternion& a, const Quaternion& b, float t, Quaternion& result) { - Quaternion::Slerp(a, b, alpha, result); + Quaternion::Slerp(a, b, t, result); + } + + template<> + FORCE_INLINE void Interpolate(const Transform& a, const Transform& b, float t, Transform& result) + { + Vector3::Lerp(a.Translation, b.Translation, t, result.Translation); + Quaternion::Slerp(a.Orientation, b.Orientation, t, result.Orientation); + Vector3::Lerp(a.Scale, b.Scale, t, result.Scale); } static void WrapTime(float& time, float start, float end, bool loop) @@ -117,90 +142,111 @@ namespace AnimationUtils /// Evaluates a cubic Hermite curve at a specific point. /// /// The time parameter that at which to evaluate the curve, in range [0, 1]. - /// The starting point (at t=0). - /// The ending point (at t=1). - /// The starting tangent (at t=0). - /// The ending tangent (at t = 1). + /// The starting point (at t=0). + /// The ending point (at t=1). + /// The starting tangent (at t=0). + /// The ending tangent (at t = 1). /// The evaluated value. template - static void CubicHermite(const float t, const T& pointA, const T& pointB, const T& tangentA, const T& tangentB, T* result) + static void CubicHermite(const T& p0, const T& p1, const T& t0, const T& t1, float t, T& result) { - const float t2 = t * t; - const float t3 = t2 * t; - - float a = 2 * t3 - 3 * t2 + 1; - float b = t3 - 2 * t2 + t; - float c = -2 * t3 + 3 * t2; - float d = t3 - t2; - - *result = a * pointA + b * tangentA + c * pointB + d * tangentB; + const float tt = t * t; + const float ttt = tt * t; + result = (2 * ttt - 3 * tt + 1) * p0 + (ttt - 2 * tt + t) * t0 + (-2 * ttt + 3 * tt) * p1 + (ttt - tt) * t1; } /// /// Evaluates the first derivative of a cubic Hermite curve at a specific point. /// /// The time parameter that at which to evaluate the curve, in range [0, 1]. - /// The starting point (at t=0). - /// The ending point (at t=1). - /// The starting tangent (at t=0). - /// The ending tangent (at t = 1). + /// The starting point (at t=0). + /// The ending point (at t=1). + /// The starting tangent (at t=0). + /// The ending tangent (at t=1). /// The evaluated value. template - static void CubicHermiteD1(const float t, const T& pointA, const T& pointB, const T& tangentA, const T& tangentB, T* result) + static void CubicHermiteFirstDerivative(const T& p0, const T& p1, const T& t0, const T& t1, float t, T& result) { - const float t2 = t * t; - - float a = 6 * t2 - 6 * t; - float b = 3 * t2 - 4 * t + 1; - float c = -6 * t2 + 6 * t; - float d = 3 * t2 - 2 * t; - - *result = a * pointA + b * tangentA + c * pointB + d * tangentB; + const float tt = t * t; + result = (6 * tt - 6 * t) * p0 + (3 * tt - 4 * t + 1) * t0 + (-6 * tt + 6 * t) * p1 + (3 * tt - 2 * t) * t1; } template - static void Bezier(const T& p0, const T& p1, const T& p2, const T& p3, float alpha, T& result) + static void Bezier(const T& p0, const T& p1, const T& p2, const T& p3, float t, T& result) { T p01, p12, p23, p012, p123; - Interpolate(p0, p1, alpha, p01); - Interpolate(p1, p2, alpha, p12); - Interpolate(p2, p3, alpha, p23); - Interpolate(p01, p12, alpha, p012); - Interpolate(p12, p23, alpha, p123); - Interpolate(p012, p123, alpha, result); + Interpolate(p0, p1, t, p01); + Interpolate(p1, p2, t, p12); + Interpolate(p2, p3, t, p23); + Interpolate(p01, p12, t, p012); + Interpolate(p12, p23, t, p123); + Interpolate(p012, p123, t, result); } template<> - void Bezier(const Vector2& p0, const Vector2& p1, const Vector2& p2, const Vector2& p3, float alpha, Vector2& result) + void Bezier(const Vector2& p0, const Vector2& p1, const Vector2& p2, const Vector2& p3, float t, Vector2& result) { - const float u = 1.0f - alpha; - const float tt = alpha * alpha; + const float u = 1.0f - t; + const float tt = t * t; const float uu = u * u; const float uuu = uu * u; - const float ttt = tt * alpha; - result = uuu * p0 + 3 * uu * alpha * p1 + 3 * u * tt * p2 + ttt * p3; + const float ttt = tt * t; + result = uuu * p0 + 3 * uu * t * p1 + 3 * u * tt * p2 + ttt * p3; } template<> - void Bezier(const Vector3& p0, const Vector3& p1, const Vector3& p2, const Vector3& p3, float alpha, Vector3& result) + void Bezier(const Vector3& p0, const Vector3& p1, const Vector3& p2, const Vector3& p3, float t, Vector3& result) { - const float u = 1.0f - alpha; - const float tt = alpha * alpha; + const float u = 1.0f - t; + const float tt = t * t; const float uu = u * u; const float uuu = uu * u; - const float ttt = tt * alpha; - result = uuu * p0 + 3 * uu * alpha * p1 + 3 * u * tt * p2 + ttt * p3; + const float ttt = tt * t; + result = uuu * p0 + 3 * uu * t * p1 + 3 * u * tt * p2 + ttt * p3; } template<> - void Bezier(const Quaternion& p0, const Quaternion& p1, const Quaternion& p2, const Quaternion& p3, float alpha, Quaternion& result) + void Bezier(const Quaternion& p0, const Quaternion& p1, const Quaternion& p2, const Quaternion& p3, float t, Quaternion& result) { Quaternion p01, p12, p23, p012, p123; - Quaternion::Slerp(p0, p1, alpha, p01); - Quaternion::Slerp(p1, p2, alpha, p12); - Quaternion::Slerp(p2, p3, alpha, p23); - Quaternion::Slerp(p01, p12, alpha, p012); - Quaternion::Slerp(p12, p23, alpha, p123); - Quaternion::Slerp(p012, p123, alpha, result); + Quaternion::Slerp(p0, p1, t, p01); + Quaternion::Slerp(p1, p2, t, p12); + Quaternion::Slerp(p2, p3, t, p23); + Quaternion::Slerp(p01, p12, t, p012); + Quaternion::Slerp(p12, p23, t, p123); + Quaternion::Slerp(p012, p123, t, result); + } + + template<> + void Bezier(const Transform& p0, const Transform& p1, const Transform& p2, const Transform& p3, float t, Transform& result) + { + Bezier(p0.Translation, p1.Translation, p2.Translation, p3.Translation, t, result.Translation); + Bezier(p0.Orientation, p1.Orientation, p2.Orientation, p3.Orientation, t, result.Orientation); + Bezier(p0.Scale, p1.Scale, p2.Scale, p3.Scale, t, result.Scale); + } + + template + static void BezierFirstDerivative(const T& p0, const T& p1, const T& p2, const T& p3, float t, T& result) + { + const float u = 1.0f - t; + const float tt = t * t; + const float uu = u * u; + result = 3.0f * uu * (p1 - p0) + 6.0f * u * t * (p2 - p1) + 3.0f * tt * (p3 - p2); + } + + template<> + static void BezierFirstDerivative(const Quaternion& p0, const Quaternion& p1, const Quaternion& p2, const Quaternion& p3, float t, Quaternion& result) + { + Vector3 euler; + BezierFirstDerivative(p0.GetEuler(), p1.GetEuler(), p2.GetEuler(), p3.GetEuler(), t, euler); + result = Quaternion::Euler(euler); + } + + template<> + static void BezierFirstDerivative(const Transform& p0, const Transform& p1, const Transform& p2, const Transform& p3, float t, Transform& result) + { + BezierFirstDerivative(p0.Translation, p1.Translation, p2.Translation, p3.Translation, t, result.Translation); + BezierFirstDerivative(p0.Orientation, p1.Orientation, p2.Orientation, p3.Orientation, t, result.Orientation); + BezierFirstDerivative(p0.Scale, p1.Scale, p2.Scale, p3.Scale, t, result.Scale); } } diff --git a/Source/Engine/Animations/Curve.cs b/Source/Engine/Animations/Curve.cs index d9762d05f..3a8ddc100 100644 --- a/Source/Engine/Animations/Curve.cs +++ b/Source/Engine/Animations/Curve.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.InteropServices; namespace FlaxEngine { @@ -338,6 +339,7 @@ namespace FlaxEngine /// /// A single keyframe that can be injected into linear curve. /// + [StructLayout(LayoutKind.Sequential)] public struct Keyframe : IComparable, IComparable { /// @@ -586,6 +588,7 @@ namespace FlaxEngine /// /// A single keyframe that can be injected into Bezier curve. /// + [StructLayout(LayoutKind.Sequential)] public struct Keyframe : IComparable, IComparable { /// diff --git a/Source/Engine/Animations/Curve.h b/Source/Engine/Animations/Curve.h index 8261dea5b..be5c268ef 100644 --- a/Source/Engine/Animations/Curve.h +++ b/Source/Engine/Animations/Curve.h @@ -4,9 +4,7 @@ #include "AnimationUtils.h" #include "Engine/Core/Collections/Array.h" -#include "Engine/Core/Types/DataContainer.h" -#include "Engine/Serialization/ReadStream.h" -#include "Engine/Serialization/WriteStream.h" +#include "Engine/Core/Types/Span.h" // @formatter:off @@ -48,10 +46,20 @@ public: result = a.Value; } + FORCE_INLINE static void InterpolateFirstDerivative(const StepCurveKeyframe& a, const StepCurveKeyframe& b, float alpha, float length, T& result) + { + result = AnimationUtils::GetZero(); + } + FORCE_INLINE static void InterpolateKey(const StepCurveKeyframe& a, const StepCurveKeyframe& b, float alpha, float length, StepCurveKeyframe& result) { result = a; } + + bool operator==(const StepCurveKeyframe& other) const + { + return Math::NearEqual(Time, other.Time) && Math::NearEqual(Value, other.Value); + } } PACK_END(); /// @@ -92,11 +100,21 @@ public: AnimationUtils::Interpolate(a.Value, b.Value, alpha, result); } + FORCE_INLINE static void InterpolateFirstDerivative(const LinearCurveKeyframe& a, const LinearCurveKeyframe& b, float alpha, float length, T& result) + { + result = b.Value - a.Value; + } + FORCE_INLINE static void InterpolateKey(const LinearCurveKeyframe& a, const LinearCurveKeyframe& b, float alpha, float length, LinearCurveKeyframe& result) { result.Time = a.Time + (b.Time - a.Time) * alpha; AnimationUtils::Interpolate(a.Value, b.Value, alpha, result.Value); } + + bool operator==(const LinearCurveKeyframe& other) const + { + return Math::NearEqual(Time, other.Time) && Math::NearEqual(Value, other.Value); + } } PACK_END(); /// @@ -147,23 +165,31 @@ public: { T leftTangent = a.Value + a.TangentOut * length; T rightTangent = b.Value + b.TangentIn * length; + AnimationUtils::CubicHermite( a.Value, b.Value, leftTangent, rightTangent, alpha, result); + } - AnimationUtils::CubicHermite(alpha, a.Value, b.Value, leftTangent, rightTangent, result); + static void InterpolateFirstDerivative(const HermiteCurveKeyframe& a, const HermiteCurveKeyframe& b, float alpha, float length, T& result) + { + T leftTangent = a.Value + a.TangentOut * length; + T rightTangent = b.Value + b.TangentIn * length; + AnimationUtils::CubicHermiteFirstDerivative( a.Value, b.Value, leftTangent, rightTangent, alpha, result); } static void InterpolateKey(const HermiteCurveKeyframe& a, const HermiteCurveKeyframe& b, float alpha, float length, HermiteCurveKeyframe& result) { result.Time = a.Time + length * alpha; - T leftTangent = a.Value + a.TangentOut * length; T rightTangent = b.Value + b.TangentIn * length; - - AnimationUtils::CubicHermite(alpha, a.Value, b.Value, leftTangent, rightTangent, result.Value); - AnimationUtils::CubicHermiteD1(alpha, a.Value, b.Value, leftTangent, rightTangent, result.TangentIn); - + AnimationUtils::CubicHermite(a.Value, b.Value, leftTangent, rightTangent, alpha, result.Value); + AnimationUtils::CubicHermiteFirstDerivative(a.Value, b.Value, leftTangent, rightTangent, alpha, result.TangentIn); result.TangentIn /= length; result.TangentOut = result.TangentIn; } + + bool operator==(const HermiteCurveKeyframe& other) const + { + return Math::NearEqual(Time, other.Time) && Math::NearEqual(Value, other.Value) && Math::NearEqual(TangentIn, other.TangentIn) && Math::NearEqual(TangentOut, other.TangentOut); + } } PACK_END(); /// @@ -223,23 +249,32 @@ public: T leftTangent, rightTangent; AnimationUtils::GetTangent(a.Value, a.TangentOut, length, leftTangent); AnimationUtils::GetTangent(b.Value, b.TangentIn, length, rightTangent); - AnimationUtils::Bezier(a.Value, leftTangent, rightTangent, b.Value, alpha, result); } + static void InterpolateFirstDerivative(const BezierCurveKeyframe& a, const BezierCurveKeyframe& b, float alpha, float length, T& result) + { + T leftTangent, rightTangent; + AnimationUtils::GetTangent(a.Value, a.TangentOut, length, leftTangent); + AnimationUtils::GetTangent(b.Value, b.TangentIn, length, rightTangent); + AnimationUtils::BezierFirstDerivative(a.Value, leftTangent, rightTangent, b.Value, alpha, result); + } + static void InterpolateKey(const BezierCurveKeyframe& a, const BezierCurveKeyframe& b, float alpha, float length, BezierCurveKeyframe& result) { result.Time = a.Time + length * alpha; - T leftTangent, rightTangent; AnimationUtils::GetTangent(a.Value, a.TangentOut, length, leftTangent); AnimationUtils::GetTangent(b.Value, b.TangentIn, length, rightTangent); - AnimationUtils::Bezier(a.Value, leftTangent, rightTangent, b.Value, alpha, result.Value); - result.TangentIn = a.TangentOut; result.TangentOut = b.TangentIn; } + + bool operator==(const BezierCurveKeyframe& other) const + { + return Math::NearEqual(Time, other.Time) && Math::NearEqual(Value, other.Value) && Math::NearEqual(TangentIn, other.TangentIn) && Math::NearEqual(TangentOut, other.TangentOut); + } } PACK_END(); // @formatter:on @@ -252,7 +287,7 @@ class CurveBase { public: - typedef DataContainer KeyFrameData; + typedef Span KeyFrameData; protected: @@ -346,6 +381,48 @@ public: KeyFrame::Interpolate(leftKey, rightKey, t, length, result); } + /// + /// Evaluates the first derivative of the animation curve at the specified time (aka velocity). + /// + /// The keyframes data container. + /// The calculated first derivative from the curve at provided time. + /// The time to evaluate the curve at. + /// If true the curve will loop when it goes past the end or beginning. Otherwise the curve value will be clamped. + void EvaluateFirstDerivative(const KeyFrameData& data, T& result, float time, bool loop = true) const + { + const int32 count = data.Length(); + if (count == 0) + { + result = _default; + return; + } + + const float start = 0; + const float end = data[count - 1].Time; + AnimationUtils::WrapTime(time, start, end, loop); + + int32 leftKeyIdx; + int32 rightKeyIdx; + FindKeys(data, time, leftKeyIdx, rightKeyIdx); + + const KeyFrame& leftKey = data[leftKeyIdx]; + const KeyFrame& rightKey = data[rightKeyIdx]; + + if (leftKeyIdx == rightKeyIdx) + { + result = leftKey.Value; + return; + } + + const float length = rightKey.Time - leftKey.Time; + + // Scale from arbitrary range to [0, 1] + float t = Math::NearEqual(length, 0.0f) ? 0.0f : (time - leftKey.Time) / length; + + // Evaluate the derivative at the curve + KeyFrame::InterpolateFirstDerivative(leftKey, rightKey, t, length, result); + } + /// /// Evaluates the animation curve key at the specified time. /// @@ -562,10 +639,22 @@ public: /// If true the curve will loop when it goes past the end or beginning. Otherwise the curve value will be clamped. void Evaluate(T& result, float time, bool loop = true) const { - typename Base::KeyFrameData data(_keyframes); + typename Base::KeyFrameData data(_keyframes.Get(), _keyframes.Count()); Base::Evaluate(data, result, time, loop); } + /// + /// Evaluates the first derivative of the animation curve at the specified time (aka velocity). + /// + /// The calculated first derivative from the curve at provided time. + /// The time to evaluate the curve at. + /// If true the curve will loop when it goes past the end or beginning. Otherwise the curve value will be clamped. + void EvaluateFirstDerivative(T& result, float time, bool loop = true) const + { + typename Base::KeyFrameData data(_keyframes.Get(), _keyframes.Count()); + Base::EvaluateFirstDerivative(data, result, time, loop); + } + /// /// Evaluates the animation curve key at the specified time. /// @@ -574,7 +663,7 @@ public: /// If true the curve will loop when it goes past the end or beginning. Otherwise the curve value will be clamped. void EvaluateKey(KeyFrame& result, float time, bool loop = true) const { - typename Base::KeyFrameData data(_keyframes); + typename Base::KeyFrameData data(_keyframes.Get(), _keyframes.Count()); Base::EvaluateKey(data, result, time, loop); } @@ -595,7 +684,7 @@ public: return; } - typename Base::KeyFrameData data(_keyframes); + typename Base::KeyFrameData data(_keyframes.Get(), _keyframes.Count()); KeyFrame startValue, endValue; Base::EvaluateKey(data, startValue, start, false); Base::EvaluateKey(data, endValue, end, false); @@ -660,53 +749,26 @@ public: public: - /// - /// Serializes the curve data to the stream. - /// - /// The output stream. - void Serialize(WriteStream& stream) const + FORCE_INLINE KeyFrame& operator[](int32 index) { - // Version - if (_keyframes.IsEmpty()) - { - stream.WriteInt32(0); - return; - } - stream.WriteInt32(1); - - // TODO: support compression (serialize compression mode) - - // Raw keyframes data - stream.WriteInt32(_keyframes.Count()); - stream.WriteBytes(_keyframes.Get(), _keyframes.Count() * sizeof(KeyFrame)); + return _keyframes[index]; } - /// - /// Deserializes the curve data from the stream. - /// - /// The input stream. - bool Deserialize(ReadStream& stream) + FORCE_INLINE const KeyFrame& operator[](int32 index) const { - // Cleanup - _keyframes.Resize(0); + return _keyframes[index]; + } - // Version - int32 version; - stream.ReadInt32(&version); - if (version == 0) + bool operator==(const Curve& other) const + { + if (_keyframes.Count() != other._keyframes.Count()) return false; - if (version != 1) + for (int32 i = 0; i < _keyframes.Count(); i++) { - return true; + if (!(_keyframes[i] == other._keyframes[i])) + return false; } - - // Raw keyframes data - int32 keyframesCount; - stream.ReadInt32(&keyframesCount); - _keyframes.Resize(keyframesCount, false); - stream.ReadBytes(_keyframes.Get(), _keyframes.Count() * sizeof(KeyFrame)); - - return false; + return true; } }; diff --git a/Source/Engine/Animations/CurveSerialization.h b/Source/Engine/Animations/CurveSerialization.h new file mode 100644 index 000000000..4af89ceca --- /dev/null +++ b/Source/Engine/Animations/CurveSerialization.h @@ -0,0 +1,218 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Curve.h" +#include "Engine/Core/Types/DataContainer.h" +#include "Engine/Serialization/ReadStream.h" +#include "Engine/Serialization/WriteStream.h" +#include "Engine/Serialization/Serialization.h" + +// @formatter:off + +namespace Serialization +{ + // StepCurveKeyframe + + template + inline bool ShouldSerialize(const StepCurveKeyframe& v, const void* otherObj) + { + if (!otherObj) + return true; + const auto other = (const StepCurveKeyframe*)otherObj; + return !(v == *other); + } + template + inline void Serialize(ISerializable::SerializeStream& stream, const StepCurveKeyframe& v, const void* otherObj) + { + stream.StartObject(); + stream.JKEY("Time"); + Serialize(stream, v.Time, nullptr); + stream.JKEY("Value"); + Serialize(stream, v.Value, nullptr); + stream.EndObject(); + } + template + inline void Deserialize(ISerializable::DeserializeStream& stream, StepCurveKeyframe& v, ISerializeModifier* modifier) + { + DESERIALIZE_MEMBER(Time, v.Time); + DESERIALIZE_MEMBER(Value, v.Value); + } + + // LinearCurveKeyframe + + template + inline bool ShouldSerialize(const LinearCurveKeyframe& v, const void* otherObj) + { + if (!otherObj) + return true; + const auto other = (const LinearCurveKeyframe*)otherObj; + return !(v == *other); + } + template + inline void Serialize(ISerializable::SerializeStream& stream, const LinearCurveKeyframe& v, const void* otherObj) + { + stream.StartObject(); + stream.JKEY("Time"); + Serialize(stream, v.Time, nullptr); + stream.JKEY("Value"); + Serialize(stream, v.Value, nullptr); + stream.EndObject(); + } + template + inline void Deserialize(ISerializable::DeserializeStream& stream, LinearCurveKeyframe& v, ISerializeModifier* modifier) + { + DESERIALIZE_MEMBER(Time, v.Time); + DESERIALIZE_MEMBER(Value, v.Value); + } + + // HermiteCurveKeyframe + + template + inline bool ShouldSerialize(const HermiteCurveKeyframe& v, const void* otherObj) + { + if (!otherObj) + return true; + const auto other = (const HermiteCurveKeyframe*)otherObj; + return !(v == *other); + } + template + inline void Serialize(ISerializable::SerializeStream& stream, const HermiteCurveKeyframe& v, const void* otherObj) + { + stream.StartObject(); + stream.JKEY("Time"); + Serialize(stream, v.Time, nullptr); + stream.JKEY("Value"); + Serialize(stream, v.Value, nullptr); + stream.JKEY("TangentIn"); + Serialize(stream, v.TangentIn, nullptr); + stream.JKEY("TangentOut"); + Serialize(stream, v.TangentOut, nullptr); + stream.EndObject(); + } + template + inline void Deserialize(ISerializable::DeserializeStream& stream, HermiteCurveKeyframe& v, ISerializeModifier* modifier) + { + DESERIALIZE_MEMBER(Time, v.Time); + DESERIALIZE_MEMBER(Value, v.Value); + DESERIALIZE_MEMBER(TangentIn, v.TangentIn); + DESERIALIZE_MEMBER(TangentOut, v.TangentOut); + } + + // BezierCurveKeyframe + + template + inline bool ShouldSerialize(const BezierCurveKeyframe& v, const void* otherObj) + { + if (!otherObj) + return true; + const auto other = (const BezierCurveKeyframe*)otherObj; + return !(v == *other); + } + template + inline void Serialize(ISerializable::SerializeStream& stream, const BezierCurveKeyframe& v, const void* otherObj) + { + stream.StartObject(); + stream.JKEY("Time"); + Serialize(stream, v.Time, nullptr); + stream.JKEY("Value"); + Serialize(stream, v.Value, nullptr); + stream.JKEY("TangentIn"); + Serialize(stream, v.TangentIn, nullptr); + stream.JKEY("TangentOut"); + Serialize(stream, v.TangentOut, nullptr); + stream.EndObject(); + } + template + inline void Deserialize(ISerializable::DeserializeStream& stream, BezierCurveKeyframe& v, ISerializeModifier* modifier) + { + DESERIALIZE_MEMBER(Time, v.Time); + DESERIALIZE_MEMBER(Value, v.Value); + DESERIALIZE_MEMBER(TangentIn, v.TangentIn); + DESERIALIZE_MEMBER(TangentOut, v.TangentOut); + } + + // Curve + + template + inline bool ShouldSerialize(const Curve& v, const void* otherObj) + { + if (!otherObj) + return true; + const auto other = (const Curve*)otherObj; + return !(v == *other); + } + template + inline void Serialize(ISerializable::SerializeStream& stream, const Curve& v, const void* otherObj) + { + stream.StartObject(); + stream.JKEY("Keyframes"); + stream.StartArray(); + for (auto& k : v.GetKeyframes()) + Serialize(stream, k, nullptr); + stream.EndArray(); + stream.EndObject(); + } + template + inline void Deserialize(ISerializable::DeserializeStream& stream, Curve& v, ISerializeModifier* modifier) + { + if (!stream.IsObject()) + return; + const auto mKeyframes = SERIALIZE_FIND_MEMBER(stream, "Keyframes"); + if (mKeyframes != stream.MemberEnd()) + { + const auto& keyframesArray = mKeyframes->value.GetArray(); + auto& keyframes = v.GetKeyframes(); + keyframes.Resize(keyframesArray.Size()); + for (rapidjson::SizeType i = 0; i < keyframesArray.Size(); i++) + Deserialize(keyframesArray[i], keyframes[i], modifier); + } + } + + template + inline void Serialize(WriteStream& stream, const Curve& v) + { + auto& keyframes = v.GetKeyframes(); + + // Version + if (keyframes.IsEmpty()) + { + stream.WriteInt32(0); + return; + } + stream.WriteInt32(1); + + // TODO: support compression (serialize compression mode) + + // Raw keyframes data + stream.WriteInt32(keyframes.Count()); + stream.WriteBytes(keyframes.Get(), keyframes.Count() * sizeof(KeyFrame)); + } + + template + inline bool Deserialize(ReadStream& stream, Curve& v) + { + auto& keyframes = v.GetKeyframes(); + keyframes.Resize(0); + + // Version + int32 version; + stream.ReadInt32(&version); + if (version == 0) + return false; + if (version != 1) + { + return true; + } + + // Raw keyframes data + int32 keyframesCount; + stream.ReadInt32(&keyframesCount); + keyframes.Resize(keyframesCount, false); + stream.ReadBytes(keyframes.Get(), keyframes.Count() * sizeof(KeyFrame)); + + return false; + } +} + +// @formatter:on diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp index 8f43506d2..1a26318a1 100644 --- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp +++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp @@ -1489,11 +1489,11 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu case 2: value = transitionsData.Position / transitionsData.Length; break; - // Reaming Time + // Remaining Time case 3: value = transitionsData.Length - transitionsData.Position; break; - // Reaming Normalized Time + // Remaining Normalized Time case 4: value = 1.0f - (transitionsData.Position / transitionsData.Length); break; diff --git a/Source/Engine/Audio/Audio.cpp b/Source/Engine/Audio/Audio.cpp index 1dd0393b4..51f674e73 100644 --- a/Source/Engine/Audio/Audio.cpp +++ b/Source/Engine/Audio/Audio.cpp @@ -44,9 +44,14 @@ Array Audio::Devices; Action Audio::DevicesChanged; Action Audio::ActiveDeviceChanged; AudioBackend* AudioBackend::Instance = nullptr; -float MasterVolume = 1.0f; -float Volume = 1.0f; -int32 ActiveDeviceIndex = -1; + +namespace +{ + float MasterVolume = 1.0f; + float Volume = 1.0f; + int32 ActiveDeviceIndex = -1; + bool MuteOnFocusLoss = true; +} class AudioService : public EngineService { @@ -77,6 +82,11 @@ namespace } } +void AudioSettings::Apply() +{ + ::MuteOnFocusLoss = MuteOnFocusLoss; +} + AudioDevice* Audio::GetActiveDevice() { return &Devices[ActiveDeviceIndex]; @@ -161,7 +171,7 @@ void Audio::OnRemoveSource(AudioSource* source) bool AudioService::Init() { - const auto settings = AudioSettings::Instance(); + const auto settings = AudioSettings::Get(); const bool mute = CommandLine::Options.Mute.IsTrue() || settings->DisableAudio; // Pick a backend to use @@ -225,11 +235,9 @@ void AudioService::Update() { PROFILE_CPU(); - const auto settings = AudioSettings::Instance(); - // Update the master volume float masterVolume = MasterVolume; - if (settings->MuteOnFocusLoss && !Engine::HasFocus) + if (MuteOnFocusLoss && !Engine::HasFocus) { // Mute audio if app has no user focus masterVolume = 0.0f; diff --git a/Source/Engine/Audio/AudioClip.cpp b/Source/Engine/Audio/AudioClip.cpp index 4005e46b9..29dd2a153 100644 --- a/Source/Engine/Audio/AudioClip.cpp +++ b/Source/Engine/Audio/AudioClip.cpp @@ -125,7 +125,7 @@ void AudioClip::StreamingTask::OnEnd() { ASSERT(_asset->_streamingTask == this); _asset->_streamingTask = nullptr; - _asset.Unlink(); + _asset = nullptr; } _dataLock.Release(); diff --git a/Source/Engine/Audio/AudioSettings.h b/Source/Engine/Audio/AudioSettings.h index 2f73e3d1d..b5efe9524 100644 --- a/Source/Engine/Audio/AudioSettings.h +++ b/Source/Engine/Audio/AudioSettings.h @@ -8,35 +8,38 @@ /// /// Audio settings container. /// -/// -class AudioSettings : public Settings +API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API AudioSettings : public SettingsBase { +DECLARE_SCRIPTING_TYPE_MINIMAL(AudioSettings); public: /// /// If checked, audio playback will be disabled in build game. Can be used if game uses custom audio playback engine. /// + API_FIELD(Attributes="EditorOrder(0), DefaultValue(false), EditorDisplay(\"General\")") bool DisableAudio = false; /// /// The doppler effect factor. Scale for source and listener velocities. Default is 1. /// + API_FIELD(Attributes="EditorOrder(100), DefaultValue(1.0f), EditorDisplay(\"General\")") float DopplerFactor = 1.0f; /// /// True if mute all audio playback when game has no use focus. /// + API_FIELD(Attributes="EditorOrder(200), DefaultValue(true), EditorDisplay(\"General\", \"Mute On Focus Loss\")") bool MuteOnFocusLoss = true; public: - // [Settings] - void RestoreDefault() final override - { - DisableAudio = false; - DopplerFactor = 1.0f; - MuteOnFocusLoss = true; - } + /// + /// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use. + /// + static AudioSettings* Get(); + + // [SettingsBase] + void Apply() override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override { diff --git a/Source/Engine/Audio/AudioSource.h b/Source/Engine/Audio/AudioSource.h index ac15b5a4b..2b5b7e9d8 100644 --- a/Source/Engine/Audio/AudioSource.h +++ b/Source/Engine/Audio/AudioSource.h @@ -199,7 +199,7 @@ public: /// Gets the current state of the audio playback (playing/paused/stopped). /// /// The value. - API_PROPERTY() FORCE_INLINE States GetState() const + API_PROPERTY() FORCE_INLINE AudioSource::States GetState() const { return _state; } diff --git a/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp b/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp index e06ab35aa..89c8f3d02 100644 --- a/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp +++ b/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp @@ -878,7 +878,7 @@ bool AudioBackendOAL::Base_Init() // Init ALC::AL_EXT_float32 = ALC::IsExtensionSupported("AL_EXT_float32"); - SetDopplerFactor(AudioSettings::Instance()->DopplerFactor); + SetDopplerFactor(AudioSettings::Get()->DopplerFactor); ALC::RebuildContexts(true); Audio::SetActiveDeviceIndex(activeDeviceIndex); diff --git a/Source/Engine/Audio/XAudio2/AudioBackendXAudio2.cpp b/Source/Engine/Audio/XAudio2/AudioBackendXAudio2.cpp index a201253ac..aac3979ce 100644 --- a/Source/Engine/Audio/XAudio2/AudioBackendXAudio2.cpp +++ b/Source/Engine/Audio/XAudio2/AudioBackendXAudio2.cpp @@ -794,7 +794,7 @@ void AudioBackendXAudio2::Base_Update() } // Update dirty voices - const float dopplerFactor = AudioSettings::Instance()->DopplerFactor; + const float dopplerFactor = AudioSettings::Get()->DopplerFactor; X3DAUDIO_DSP_SETTINGS dsp = { 0 }; dsp.DstChannelCount = XAudio2::Channels; dsp.pMatrixCoefficients = XAudio2::MatrixCoefficients; diff --git a/Source/Engine/CSG/CSGMesh.Triangulate.cpp b/Source/Engine/CSG/CSGMesh.Triangulate.cpp index bb814653b..8a64f4042 100644 --- a/Source/Engine/CSG/CSGMesh.Triangulate.cpp +++ b/Source/Engine/CSG/CSGMesh.Triangulate.cpp @@ -22,7 +22,7 @@ bool CSG::Mesh::Triangulate(RawData& data, Array& cacheVB) const Array surfaceCacheVB(32); // Cache submeshes by material to lay them down - // key- brush index, value- direcotry for surafecs (key: surface index, value: list with start vertex per triangle) + // key- brush index, value- direcotry for surfaces (key: surface index, value: list with start vertex per triangle) Dictionary>> polygonsPerBrush(64); // Build index buffer @@ -115,8 +115,8 @@ bool CSG::Mesh::Triangulate(RawData& data, Array& cacheVB) const tangent.Normalize(); // Gram-Schmidt orthogonalize - Vector3 newTangentUnormalized = tangent - normal * Vector3::Dot(normal, tangent); - const float length = newTangentUnormalized.Length(); + Vector3 newTangentUnnormalized = tangent - normal * Vector3::Dot(normal, tangent); + const float length = newTangentUnnormalized.Length(); // Workaround to handle degenerated case if (Math::IsZero(length)) @@ -129,7 +129,7 @@ bool CSG::Mesh::Triangulate(RawData& data, Array& cacheVB) const } else { - tangent = newTangentUnormalized / length; + tangent = newTangentUnnormalized / length; bitangent.Normalize(); } @@ -217,12 +217,12 @@ bool CSG::Mesh::Triangulate(RawData& data, Array& cacheVB) const auto& vertex = cacheVB[triangleStartVertex + k]; Vector3 projected = Vector3::Project(vertex.Position, 0, 0, 1000, 1000, 0, 1, vp); - Vector2 projectecXY = Vector2(projected); + Vector2 projectedXY = Vector2(projected); - min = Vector2::Min(projectecXY, min); - max = Vector2::Max(projectecXY, max); + min = Vector2::Min(projectedXY, min); + max = Vector2::Max(projectedXY, max); - pointsCache.Add(projectecXY); + pointsCache.Add(projectedXY); } } diff --git a/Source/Engine/CSG/CSGMesh.cpp b/Source/Engine/CSG/CSGMesh.cpp index cc4897d3a..9a885a35d 100644 --- a/Source/Engine/CSG/CSGMesh.cpp +++ b/Source/Engine/CSG/CSGMesh.cpp @@ -38,7 +38,7 @@ void CSG::Mesh::PerformOperation(Mesh* other) { case Mode::Additive: { - // Check if both meshes do not intesect + // Check if both meshes do not intersect if (AABB::IsOutside(_bounds, other->GetBounds())) // TODO: test every sub bounds not whole _bounds { // Add vertices to the mesh without any additional calculations @@ -57,7 +57,7 @@ void CSG::Mesh::PerformOperation(Mesh* other) case Mode::Subtractive: { - // Check if both meshes do not intesect + // Check if both meshes do not intersect if (AABB::IsOutside(_bounds, other->GetBounds())) // TODO: test every sub bounds not whole _bounds { // Do nothing @@ -141,7 +141,7 @@ void CSG::Mesh::intersect(const Mesh* other, PolygonOperation insideOp, PolygonO // insideOp - operation for polygons being inside the other brush // outsideOp - operation for polygons being outside the other brush - // Prevent from redudant action + // Prevent from redundant action if (insideOp == Keep && outsideOp == Keep) return; @@ -180,7 +180,7 @@ void CSG::Mesh::intersectSubMesh(const Mesh* other, int32 subMeshIndex, PolygonO int32 startBrushSurface = brushMeta.StartSurfaceIndex; int32 endBrushSurface = startBrushSurface + brushMeta.SurfacesCount; - // Check every polygon (itereate fron end since we can ass new polygons and we don't want to process them) + // Check every polygon (iterate from end since we can ass new polygons and we don't want to process them) for (int32 i = _polygons.Count() - 1; i >= 0; i--) { auto& polygon = _polygons[i]; diff --git a/Source/Engine/Content/AssetReference.h b/Source/Engine/Content/AssetReference.h index c78aa5aef..658545d1a 100644 --- a/Source/Engine/Content/AssetReference.h +++ b/Source/Engine/Content/AssetReference.h @@ -78,14 +78,6 @@ public: return _asset ? _asset->GetOrCreateManagedInstance() : nullptr; } - /// - /// Clears the asset reference. - /// - FORCE_INLINE void Unlink() - { - OnSet(nullptr); - } - /// /// Gets the asset property value as string. /// @@ -135,7 +127,7 @@ protected: if (_asset == asset) { Unload(); - Unlink(); + OnSet(nullptr); } } }; diff --git a/Source/Engine/Content/Assets/Animation.cpp b/Source/Engine/Content/Assets/Animation.cpp index c6927530e..963ddaceb 100644 --- a/Source/Engine/Content/Assets/Animation.cpp +++ b/Source/Engine/Content/Assets/Animation.cpp @@ -5,6 +5,7 @@ #include "Engine/Core/Log.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Content/Factories/BinaryAssetFactory.h" +#include "Engine/Animations/CurveSerialization.h" #include "Engine/Serialization/MemoryReadStream.h" #if USE_EDITOR #include "Engine/Serialization/MemoryWriteStream.h" @@ -358,9 +359,9 @@ bool Animation::Save(const StringView& path) { auto& anim = Data.Channels[i]; stream.WriteString(anim.NodeName, 172); - anim.Position.Serialize(stream); - anim.Rotation.Serialize(stream); - anim.Scale.Serialize(stream); + Serialization::Serialize(stream, anim.Position); + Serialization::Serialize(stream, anim.Rotation); + Serialization::Serialize(stream, anim.Scale); } // Set data to the chunk asset @@ -442,9 +443,9 @@ Asset::LoadResult Animation::load() auto& anim = Data.Channels[i]; stream.ReadString(&anim.NodeName, 172); - bool failed = anim.Position.Deserialize(stream); - failed |= anim.Rotation.Deserialize(stream); - failed |= anim.Scale.Deserialize(stream); + bool failed = Serialization::Deserialize(stream, anim.Position); + failed |= Serialization::Deserialize(stream, anim.Rotation); + failed |= Serialization::Deserialize(stream, anim.Scale); if (failed) { diff --git a/Source/Engine/Content/Assets/Material.cpp b/Source/Engine/Content/Assets/Material.cpp index b40e1e9c0..4d1e5d283 100644 --- a/Source/Engine/Content/Assets/Material.cpp +++ b/Source/Engine/Content/Assets/Material.cpp @@ -61,9 +61,9 @@ bool Material::CanUseLightmap() const return _materialShader && _materialShader->CanUseLightmap(); } -bool Material::CanUseInstancing() const +bool Material::CanUseInstancing(InstancingHandler& handler) const { - return _materialShader && _materialShader->CanUseInstancing(); + return _materialShader && _materialShader->CanUseInstancing(handler); } void Material::Bind(BindParameters& params) @@ -390,14 +390,14 @@ void Material::InitCompilationOptions(ShaderCompilationOptions& options) // Prepare auto& info = _shaderHeader.Material.Info; - const bool isSurfaceOrTerrain = info.Domain == MaterialDomain::Surface || info.Domain == MaterialDomain::Terrain; + const bool isSurfaceOrTerrainOrDeformable = info.Domain == MaterialDomain::Surface || info.Domain == MaterialDomain::Terrain || info.Domain == MaterialDomain::Deformable; const bool useCustomData = info.ShadingModel == MaterialShadingModel::Subsurface || info.ShadingModel == MaterialShadingModel::Foliage; - const bool useForward = (info.Domain == MaterialDomain::Surface && info.BlendMode != MaterialBlendMode::Opaque) || info.Domain == MaterialDomain::Particle; + const bool useForward = ((info.Domain == MaterialDomain::Surface || info.Domain == MaterialDomain::Deformable) && info.BlendMode != MaterialBlendMode::Opaque) || info.Domain == MaterialDomain::Particle; const bool useTess = info.TessellationMode != TessellationMethod::None && - RenderTools::CanSupportTessellation(options.Profile) && isSurfaceOrTerrain; + RenderTools::CanSupportTessellation(options.Profile) && isSurfaceOrTerrainOrDeformable; const bool useDistortion = - (info.Domain == MaterialDomain::Surface || info.Domain == MaterialDomain::Particle) && + (info.Domain == MaterialDomain::Surface || info.Domain == MaterialDomain::Deformable || info.Domain == MaterialDomain::Particle) && info.BlendMode != MaterialBlendMode::Opaque && (info.UsageFlags & MaterialUsageFlags::UseRefraction) != 0 && (info.FeaturesFlags & MaterialFeaturesFlags::DisableDistortion) == 0; @@ -455,10 +455,10 @@ void Material::InitCompilationOptions(ShaderCompilationOptions& options) options.Macros.Add({ "IS_DECAL", Numbers[info.Domain == MaterialDomain::Decal ? 1 : 0] }); options.Macros.Add({ "IS_TERRAIN", Numbers[info.Domain == MaterialDomain::Terrain ? 1 : 0] }); options.Macros.Add({ "IS_PARTICLE", Numbers[info.Domain == MaterialDomain::Particle ? 1 : 0] }); + options.Macros.Add({ "IS_DEFORMABLE", Numbers[info.Domain == MaterialDomain::Deformable ? 1 : 0] }); options.Macros.Add({ "USE_FORWARD", Numbers[useForward ? 1 : 0] }); - options.Macros.Add({ "USE_DEFERRED", Numbers[isSurfaceOrTerrain && info.BlendMode == MaterialBlendMode::Opaque ? 1 : 0] }); + options.Macros.Add({ "USE_DEFERRED", Numbers[isSurfaceOrTerrainOrDeformable && info.BlendMode == MaterialBlendMode::Opaque ? 1 : 0] }); options.Macros.Add({ "USE_DISTORTION", Numbers[useDistortion ? 1 : 0] }); - options.Macros.Add({ "CAN_USE_LIGHTMAP", Numbers[isSurfaceOrTerrain ? 1 : 0] }); #endif } diff --git a/Source/Engine/Content/Assets/Material.h b/Source/Engine/Content/Assets/Material.h index bdaba7abb..0a9ad1dbc 100644 --- a/Source/Engine/Content/Assets/Material.h +++ b/Source/Engine/Content/Assets/Material.h @@ -48,7 +48,7 @@ public: bool IsReady() const override; DrawPass GetDrawModes() const override; bool CanUseLightmap() const override; - bool CanUseInstancing() const override; + bool CanUseInstancing(InstancingHandler& handler) const override; void Bind(BindParameters& params) override; // [ShaderAssetBase] diff --git a/Source/Engine/Content/Assets/MaterialInstance.cpp b/Source/Engine/Content/Assets/MaterialInstance.cpp index ace2fcee9..0678f9992 100644 --- a/Source/Engine/Content/Assets/MaterialInstance.cpp +++ b/Source/Engine/Content/Assets/MaterialInstance.cpp @@ -167,9 +167,9 @@ bool MaterialInstance::CanUseLightmap() const return _baseMaterial && _baseMaterial->CanUseLightmap(); } -bool MaterialInstance::CanUseInstancing() const +bool MaterialInstance::CanUseInstancing(InstancingHandler& handler) const { - return _baseMaterial && _baseMaterial->CanUseInstancing(); + return _baseMaterial && _baseMaterial->CanUseInstancing(handler); } void MaterialInstance::Bind(BindParameters& params) diff --git a/Source/Engine/Content/Assets/MaterialInstance.h b/Source/Engine/Content/Assets/MaterialInstance.h index 9fe92e39c..3cf74db03 100644 --- a/Source/Engine/Content/Assets/MaterialInstance.h +++ b/Source/Engine/Content/Assets/MaterialInstance.h @@ -64,7 +64,7 @@ public: bool IsReady() const override; DrawPass GetDrawModes() const override; bool CanUseLightmap() const override; - bool CanUseInstancing() const override; + bool CanUseInstancing(InstancingHandler& handler) const override; void Bind(BindParameters& params) override; protected: diff --git a/Source/Engine/Content/Assets/Model.cpp b/Source/Engine/Content/Assets/Model.cpp index 897e589a8..8cdd0601e 100644 --- a/Source/Engine/Content/Assets/Model.cpp +++ b/Source/Engine/Content/Assets/Model.cpp @@ -104,7 +104,7 @@ protected: { ASSERT(_asset->_streamingTask == this); _asset->_streamingTask = nullptr; - _asset.Unlink(); + _asset = nullptr; } _dataLock.Release(); diff --git a/Source/Engine/Content/Assets/ModelBase.h b/Source/Engine/Content/Assets/ModelBase.h index ebf05570d..89805f3c0 100644 --- a/Source/Engine/Content/Assets/ModelBase.h +++ b/Source/Engine/Content/Assets/ModelBase.h @@ -53,7 +53,7 @@ public: ScopeLock lock(Locker); const int32 prevCount = MaterialSlots.Count(); - MaterialSlots.Resize(slotsCount); + MaterialSlots.Resize(slotsCount, false); // Initialize slot names for (int32 i = prevCount; i < slotsCount; i++) diff --git a/Source/Engine/Content/Assets/SkeletonMask.cpp b/Source/Engine/Content/Assets/SkeletonMask.cpp index 4747e0e44..0742e2951 100644 --- a/Source/Engine/Content/Assets/SkeletonMask.cpp +++ b/Source/Engine/Content/Assets/SkeletonMask.cpp @@ -38,7 +38,7 @@ Asset::LoadResult SkeletonMask::load() void SkeletonMask::unload(bool isReloading) { - Skeleton.Unlink(); + Skeleton = nullptr; _maskedNodes.Resize(0); _mask.Resize(0); } diff --git a/Source/Engine/Content/Assets/SkinnedModel.cpp b/Source/Engine/Content/Assets/SkinnedModel.cpp index 5309cf6b9..dfccfec19 100644 --- a/Source/Engine/Content/Assets/SkinnedModel.cpp +++ b/Source/Engine/Content/Assets/SkinnedModel.cpp @@ -97,7 +97,7 @@ protected: { ASSERT(_asset->_streamingTask == this); _asset->_streamingTask = nullptr; - _asset.Unlink(); + _asset = nullptr; } _dataLock.Release(); diff --git a/Source/Engine/Content/Assets/VisualScript.cpp b/Source/Engine/Content/Assets/VisualScript.cpp index 65a75d1ef..da1b97687 100644 --- a/Source/Engine/Content/Assets/VisualScript.cpp +++ b/Source/Engine/Content/Assets/VisualScript.cpp @@ -6,6 +6,7 @@ #include "Engine/Content/Factories/BinaryAssetFactory.h" #include "Engine/Scripting/MException.h" #include "Engine/Scripting/Scripting.h" +#include "Engine/Scripting/Events.h" #include "Engine/Scripting/ManagedCLR/MClass.h" #include "Engine/Scripting/ManagedCLR/MMethod.h" #include "Engine/Scripting/ManagedCLR/MField.h" @@ -347,7 +348,7 @@ void VisualScriptExecutor::ProcessGroupParameters(Box* box, Node* node, Value& v const auto instanceParams = stack.Stack->Script->_instances.Find(stack.Stack->Instance->GetID()); if (param && instanceParams) { - value = instanceParams->Value[paramIndex]; + value = instanceParams->Value.Params[paramIndex]; } else { @@ -371,7 +372,7 @@ void VisualScriptExecutor::ProcessGroupParameters(Box* box, Node* node, Value& v const auto instanceParams = stack.Stack->Script->_instances.Find(stack.Stack->Instance->GetID()); if (param && instanceParams) { - instanceParams->Value[paramIndex] = tryGetValue(node->GetBox(1), 1, Value::Zero); + instanceParams->Value.Params[paramIndex] = tryGetValue(node->GetBox(1), 1, Value::Zero); } else { @@ -1004,6 +1005,125 @@ void VisualScriptExecutor::ProcessGroupFunction(Box* boxBase, Node* node, Value& if (returnedImpulse && returnedImpulse->HasConnection()) eatBox(node, returnedImpulse->FirstConnection()); break; + } + // Bind/Unbind + case 9: + case 10: + { + const bool bind = node->TypeID == 9; + auto& stack = ThreadStacks.Get(); + if (!stack.Stack->Instance) + { + // TODO: add support for binding to events in static Visual Script + LOG(Error, "Cannot bind to event in static Visual Script."); + PrintStack(LogType::Error); + break; + } + const auto object = stack.Stack->Instance; + + // Find method to bind + VisualScriptGraphNode* methodNode = nullptr; + const auto graph = stack.Stack && stack.Stack->Script ? &stack.Stack->Script->Graph : nullptr; + if (graph) + methodNode = graph->GetNode((uint32)node->Values[2]); + if (!methodNode) + { + LOG(Error, "Missing function handler to bind to the event."); + PrintStack(LogType::Error); + break; + } + VisualScript::Method* method = nullptr; + for (auto& m : stack.Stack->Script->_methods) + { + if (m.Node == methodNode) + { + method = &m; + break; + } + } + if (!method) + { + LOG(Error, "Missing method to bind to the event."); + PrintStack(LogType::Error); + break; + } + + // Find event + const StringView eventTypeName(node->Values[0]); + const StringView eventName(node->Values[1]); + const StringAsANSI<100> eventTypeNameAnsi(eventTypeName.Get(), eventTypeName.Length()); + const ScriptingTypeHandle eventType = Scripting::FindScriptingType(StringAnsiView(eventTypeNameAnsi.Get(), eventTypeName.Length())); + + // Find event binding callback + auto eventBinder = ScriptingEvents::EventsTable.TryGet(Pair(eventType, eventName)); + if (!eventBinder) + { + LOG(Error, "Cannot bind to missing event {0} from type {1}.", eventName, eventTypeName); + PrintStack(LogType::Error); + break; + } + + // Evaluate object instance + const auto box = node->GetBox(1); + Variant instance; + if (box->HasConnection()) + instance = eatBox(node, box->FirstConnection()); + else + instance.SetObject(object); + if (!instance.AsObject) + { + LOG(Error, "Cannot bind event to null object."); + PrintStack(LogType::Error); + break; + } + // TODO: check if instance is of event type (including inheritance) + + // Add Visual Script method to the event bindings table + const auto& type = object->GetType(); + Guid id; + if (Guid::Parse(type.Fullname, id)) + break; + if (const auto visualScript = (VisualScript*)Content::GetAsset(id)) + { + if (auto i = visualScript->GetScriptInstance(object)) + { + VisualScript::EventBinding* eventBinding = nullptr; + for (auto& b : i->EventBindings) + { + if (b.Type == eventType && b.Name == eventName) + { + eventBinding = &b; + break; + } + } + if (bind) + { + // Bind to the event + if (!eventBinding) + { + eventBinding = &i->EventBindings.AddOne(); + eventBinding->Type = eventType; + eventBinding->Name = eventName; + } + eventBinding->BindedMethods.Add(method); + if (eventBinding->BindedMethods.Count() == 1) + (*eventBinder)(instance.AsObject, object, true); + } + else if (eventBinding) + { + // Unbind from the event + if (eventBinding->BindedMethods.Count() == 1) + (*eventBinder)(instance.AsObject, object, false); + eventBinding->BindedMethods.Remove(method); + } + } + } + + // Call graph further + const auto returnedImpulse = &node->Boxes[2]; + if (returnedImpulse && returnedImpulse->HasConnection()) + eatBox(node, returnedImpulse->FirstConnection()); + break; } default: break; @@ -1290,11 +1410,11 @@ Asset::LoadResult VisualScript::load() // Hack vtable similarly to VisualScriptObjectSpawn ScriptingType& visualScriptType = (ScriptingType&)object->GetType(); - if (visualScriptType.Class.ScriptVTable) + if (visualScriptType.Script.ScriptVTable) { // Override object vtable with hacked one that has Visual Script functions calls - ASSERT(visualScriptType.Class.VTable); - *(void**)object = visualScriptType.Class.VTable; + ASSERT(visualScriptType.Script.VTable); + *(void**)object = visualScriptType.Script.VTable; } } const int32 oldCount = _oldParamsLayout.Count(); @@ -1304,7 +1424,7 @@ Asset::LoadResult VisualScript::load() // Update instanced data from previous format to the current graph parameters scheme for (auto& e : _instances) { - auto& instanceParams = e.Value; + auto& instanceParams = e.Value.Params; Array valuesCache(MoveTemp(instanceParams)); instanceParams.Resize(count); for (int32 i = 0; i < count; i++) @@ -1319,7 +1439,7 @@ Asset::LoadResult VisualScript::load() // Reset instances values to defaults for (auto& e : _instances) { - auto& instanceParams = e.Value; + auto& instanceParams = e.Value.Params; instanceParams.Resize(count); for (int32 i = 0; i < count; i++) instanceParams[i] = Graph.Parameters[i].Value; @@ -1361,10 +1481,10 @@ void VisualScript::unload(bool isReloading) if (_scriptingTypeHandle) { auto& type = VisualScriptingModule.Types[_scriptingTypeHandle.TypeIndex]; - if (type.Class.DefaultInstance) + if (type.Script.DefaultInstance) { - Delete(type.Class.DefaultInstance); - type.Class.DefaultInstance = nullptr; + Delete(type.Script.DefaultInstance); + type.Script.DefaultInstance = nullptr; } VisualScriptingModule.TypeNameToTypeIndex.RemoveValue(_scriptingTypeHandle.TypeIndex); VisualScriptingModule.Scripts[_scriptingTypeHandle.TypeIndex] = nullptr; @@ -1389,7 +1509,7 @@ void VisualScript::CacheScriptingType() { // Find first native base C++ class of this Visual Script class ScriptingTypeHandle nativeType = baseType; - while (nativeType && nativeType.GetType().Class.ScriptVTable) + while (nativeType && nativeType.GetType().Script.ScriptVTable) { nativeType = nativeType.GetType().GetBaseType(); } @@ -1421,13 +1541,17 @@ void VisualScript::CacheScriptingType() _scriptingTypeHandle = ScriptingTypeHandle(&binaryModule, typeIndex); binaryModule.Scripts.Add(this); -#if USE_EDITOR - // When first Visual Script gets loaded register for other modules unload to clear runtime execution cache + // Special initialization when the first Visual Script gets loaded if (typeIndex == 0) { +#if USE_EDITOR + // Register for other modules unload to clear runtime execution cache Scripting::ScriptsReloading.Bind(&binaryModule); - } #endif + + // Register for scripting events + ScriptingEvents::Event.Bind(VisualScriptingBinaryModule::OnEvent); + } } auto& type = _scriptingTypeHandle.Module->Types[_scriptingTypeHandle.TypeIndex]; type.ManagedClass = baseType.GetType().ManagedClass; @@ -1437,14 +1561,14 @@ void VisualScript::CacheScriptingType() for (ScriptingTypeHandle e = nativeType; e;) { const ScriptingType& eType = e.GetType(); - if (eType.Class.SetupScriptVTable) + if (eType.Script.SetupScriptVTable) { ASSERT(eType.ManagedClass); - eType.Class.SetupScriptVTable(eType.ManagedClass, type.Class.ScriptVTable, type.Class.ScriptVTableBase); + eType.Script.SetupScriptVTable(eType.ManagedClass, type.Script.ScriptVTable, type.Script.ScriptVTableBase); } e = eType.GetBaseType(); } - MMethod** scriptVTable = (MMethod**)type.Class.ScriptVTable; + MMethod** scriptVTable = (MMethod**)type.Script.ScriptVTable; while (scriptVTable && *scriptVTable) { const MMethod* referenceMethod = *scriptVTable; @@ -1498,12 +1622,12 @@ ScriptingObject* VisualScriptingBinaryModule::VisualScriptObjectSpawn(const Scri ScriptingType& visualScriptType = (ScriptingType&)params.Type.GetType(); ScriptingTypeHandle baseTypeHandle = visualScriptType.GetBaseType(); const ScriptingType* baseTypePtr = &baseTypeHandle.GetType(); - while (baseTypePtr->Class.Spawn == &VisualScriptObjectSpawn) + while (baseTypePtr->Script.Spawn == &VisualScriptObjectSpawn) { baseTypeHandle = baseTypePtr->GetBaseType(); baseTypePtr = &baseTypeHandle.GetType(); } - ScriptingObject* object = baseTypePtr->Class.Spawn(params); + ScriptingObject* object = baseTypePtr->Script.Spawn(params); if (!object) { return nullptr; @@ -1514,9 +1638,9 @@ ScriptingObject* VisualScriptingBinaryModule::VisualScriptObjectSpawn(const Scri // We create a custom vtable for the Visual Script objects that use a native class object with virtual functions overrides. // To make it easy to use in C++ we inject custom wrapper methods into C++ object vtable to execute Visual Script graph from them. // Because virtual member functions calls are C++ ABI and impl-defined this is quite hard. But works. - if (visualScriptType.Class.ScriptVTable) + if (visualScriptType.Script.ScriptVTable) { - if (!visualScriptType.Class.VTable) + if (!visualScriptType.Script.VTable) { // Duplicate vtable void** vtable = *(void***)object; @@ -1525,21 +1649,21 @@ ScriptingObject* VisualScriptingBinaryModule::VisualScriptObjectSpawn(const Scri while (vtable[entriesCount] && entriesCount < 200) entriesCount++; const int32 size = entriesCount * sizeof(void*); - visualScriptType.Class.VTable = (void**)((byte*)Platform::Allocate(prefixSize + size, 16) + prefixSize); - Platform::MemoryCopy((byte*)visualScriptType.Class.VTable - prefixSize, (byte*)vtable - prefixSize, prefixSize + size); + visualScriptType.Script.VTable = (void**)((byte*)Platform::Allocate(prefixSize + size, 16) + prefixSize); + Platform::MemoryCopy((byte*)visualScriptType.Script.VTable - prefixSize, (byte*)vtable - prefixSize, prefixSize + size); // Override vtable entries by the class for (ScriptingTypeHandle e = baseTypeHandle; e;) { const ScriptingType& eType = e.GetType(); - if (eType.Class.SetupScriptObjectVTable) - eType.Class.SetupScriptObjectVTable(visualScriptType.Class.ScriptVTable, visualScriptType.Class.ScriptVTableBase, visualScriptType.Class.VTable, entriesCount, 1); + if (eType.Script.SetupScriptObjectVTable) + eType.Script.SetupScriptObjectVTable(visualScriptType.Script.ScriptVTable, visualScriptType.Script.ScriptVTableBase, visualScriptType.Script.VTable, entriesCount, 1); e = eType.GetBaseType(); } } // Override object vtable with hacked one that has Visual Script functions calls - *(void**)object = visualScriptType.Class.VTable; + *(void**)object = visualScriptType.Script.VTable; } // Mark as custom scripting type @@ -1550,7 +1674,7 @@ ScriptingObject* VisualScriptingBinaryModule::VisualScriptObjectSpawn(const Scri VisualScript* visualScript = VisualScriptingModule.Scripts[params.Type.TypeIndex]; // Initialize instance data - auto& instanceParams = visualScript->_instances[object->GetID()]; + auto& instanceParams = visualScript->_instances[object->GetID()].Params; instanceParams.Resize(visualScript->Graph.Parameters.Count()); for (int32 i = 0; i < instanceParams.Count(); i++) instanceParams[i] = visualScript->Graph.Parameters[i].Value; @@ -1573,10 +1697,10 @@ void VisualScriptingBinaryModule::OnScriptsReloading() if (script->_scriptingTypeHandle) { auto& type = VisualScriptingModule.Types[script->_scriptingTypeHandle.TypeIndex]; - if (type.Class.DefaultInstance) + if (type.Script.DefaultInstance) { - Delete(type.Class.DefaultInstance); - type.Class.DefaultInstance = nullptr; + Delete(type.Script.DefaultInstance); + type.Script.DefaultInstance = nullptr; } VisualScriptingModule.TypeNameToTypeIndex.RemoveValue(script->_scriptingTypeHandle.TypeIndex); script->_scriptingTypeHandleCached = script->_scriptingTypeHandle; @@ -1608,6 +1732,56 @@ void VisualScriptingBinaryModule::OnScriptsReloading() #endif +void VisualScriptingBinaryModule::OnEvent(ScriptingObject* object, Span& parameters, const ScriptingTypeHandle& eventType, const StringView& eventName) +{ + if (object) + { + // Object event + const auto& type = object->GetType(); + Guid id; + if (Guid::Parse(type.Fullname, id)) + return; + if (const auto visualScript = (VisualScript*)Content::GetAsset(id)) + { + if (auto instance = visualScript->GetScriptInstance(object)) + { + for (auto& b : instance->EventBindings) + { + if (b.Type != eventType || b.Name != eventName) + continue; + for (auto& m : b.BindedMethods) + { + VisualScripting::Invoke(m, object, parameters); + } + } + } + } + } + else + { + // Static event + for (auto& asset : Content::GetAssetsRaw()) + { + if (const auto visualScript = ScriptingObject::Cast(asset.Value)) + { + for (auto& e : visualScript->_instances) + { + auto instance = &e.Value; + for (auto& b : instance->EventBindings) + { + if (b.Type != eventType || b.Name != eventName) + continue; + for (auto& m : b.BindedMethods) + { + VisualScripting::Invoke(m, object, parameters); + } + } + } + } + } + } +} + const StringAnsi& VisualScriptingBinaryModule::GetName() const { return _name; @@ -1702,7 +1876,7 @@ bool VisualScriptingBinaryModule::GetFieldValue(void* field, const Variant& inst LOG(Error, "Missing parameters for the object instance."); return true; } - result = instanceParams->Value[vsFiled->Index]; + result = instanceParams->Value.Params[vsFiled->Index]; return false; } @@ -1721,7 +1895,7 @@ bool VisualScriptingBinaryModule::SetFieldValue(void* field, const Variant& inst LOG(Error, "Missing parameters for the object instance."); return true; } - instanceParams->Value[vsFiled->Index] = value; + instanceParams->Value.Params[vsFiled->Index] = value; return false; } @@ -1735,7 +1909,7 @@ void VisualScriptingBinaryModule::SerializeObject(JsonWriter& stream, ScriptingO const auto instanceParams = asset->_instances.Find(object->GetID()); if (instanceParams) { - auto& params = instanceParams->Value; + auto& params = instanceParams->Value.Params; if (otherObj) { // Serialize parameters diff @@ -1746,7 +1920,7 @@ void VisualScriptingBinaryModule::SerializeObject(JsonWriter& stream, ScriptingO { auto& param = asset->Graph.Parameters[paramIndex]; auto& value = params[paramIndex]; - auto& otherValue = otherParams->Value[paramIndex]; + auto& otherValue = otherParams->Value.Params[paramIndex]; if (value != otherValue) { param.Identifier.ToString(idName, Guid::FormatType::N); @@ -1798,7 +1972,7 @@ void VisualScriptingBinaryModule::DeserializeObject(ISerializable::DeserializeSt if (instanceParams) { // Deserialize all parameters - auto& params = instanceParams->Value; + auto& params = instanceParams->Value.Params; for (auto i = stream.MemberBegin(); i != stream.MemberEnd(); ++i) { StringAnsiView idNameAnsi(i->name.GetString(), i->name.GetStringLength()); @@ -1862,7 +2036,12 @@ ScriptingTypeHandle VisualScript::GetScriptingType() ScriptingObject* VisualScript::CreateInstance() { const auto scriptingTypeHandle = GetScriptingType(); - return scriptingTypeHandle ? scriptingTypeHandle.GetType().Class.Spawn(ScriptingObjectSpawnParams(Guid::New(), scriptingTypeHandle)) : nullptr; + return scriptingTypeHandle ? scriptingTypeHandle.GetType().Script.Spawn(ScriptingObjectSpawnParams(Guid::New(), scriptingTypeHandle)) : nullptr; +} + +VisualScript::Instance* VisualScript::GetScriptInstance(ScriptingObject* instance) const +{ + return instance ? _instances.TryGet(instance->GetID()) : nullptr; } Variant VisualScript::GetScriptInstanceParameterValue(const StringView& name, ScriptingObject* instance) const @@ -1874,7 +2053,7 @@ Variant VisualScript::GetScriptInstanceParameterValue(const StringView& name, Sc { const auto instanceParams = _instances.Find(instance->GetID()); if (instanceParams) - return instanceParams->Value[paramIndex]; + return instanceParams->Value.Params[paramIndex]; LOG(Error, "Failed to access Visual Script parameter {1} for {0}.", instance->ToString(), name); return Graph.Parameters[paramIndex].Value; } @@ -1893,7 +2072,7 @@ void VisualScript::SetScriptInstanceParameterValue(const StringView& name, Scrip const auto instanceParams = _instances.Find(instance->GetID()); if (instanceParams) { - instanceParams->Value[paramIndex] = value; + instanceParams->Value.Params[paramIndex] = value; return; } LOG(Error, "Failed to access Visual Script parameter {1} for {0}.", instance->ToString(), name); @@ -1913,7 +2092,7 @@ void VisualScript::SetScriptInstanceParameterValue(const StringView& name, Scrip const auto instanceParams = _instances.Find(instance->GetID()); if (instanceParams) { - instanceParams->Value[paramIndex] = MoveTemp(value); + instanceParams->Value.Params[paramIndex] = MoveTemp(value); return; } } diff --git a/Source/Engine/Content/Assets/VisualScript.h b/Source/Engine/Content/Assets/VisualScript.h index 0a7f34b1c..cf8dccce3 100644 --- a/Source/Engine/Content/Assets/VisualScript.h +++ b/Source/Engine/Content/Assets/VisualScript.h @@ -125,9 +125,22 @@ public: StringAnsi Name; }; + struct EventBinding + { + ScriptingTypeHandle Type; + String Name; + Array> BindedMethods; + }; + + struct Instance + { + Array Params; + Array EventBindings; + }; + private: - Dictionary> _instances; + Dictionary _instances; ScriptingTypeHandle _scriptingTypeHandle; ScriptingTypeHandle _scriptingTypeHandleCached; StringAnsiView _typename; @@ -179,6 +192,13 @@ public: /// The created instance or null if failed. API_FUNCTION() ScriptingObject* CreateInstance(); + /// + /// Gets the Visual Script instance data. + /// + /// The object instance. + /// The data or invalid instance (not VS or missing). + Instance* GetScriptInstance(ScriptingObject* instance) const; + /// /// Gets the value of the Visual Script parameter of the given instance. /// @@ -307,6 +327,7 @@ private: #if USE_EDITOR void OnScriptsReloading(); #endif + static void OnEvent(ScriptingObject* object, Span& parameters, const ScriptingTypeHandle& eventType, const StringView& eventName); public: diff --git a/Source/Engine/Content/BinaryAsset.cpp b/Source/Engine/Content/BinaryAsset.cpp index 7160cb287..1dbc672f2 100644 --- a/Source/Engine/Content/BinaryAsset.cpp +++ b/Source/Engine/Content/BinaryAsset.cpp @@ -491,7 +491,7 @@ protected: void OnEnd() override { _dataLock.Release(); - _asset.Unlink(); + _asset = nullptr; // Base ContentLoadTask::OnEnd(); diff --git a/Source/Engine/Content/Content.cpp b/Source/Engine/Content/Content.cpp index c56c2e658..63cb5ffc4 100644 --- a/Source/Engine/Content/Content.cpp +++ b/Source/Engine/Content/Content.cpp @@ -876,7 +876,7 @@ Asset* Content::LoadAsync(const Guid& id, const ScriptingTypeHandle& type) if (result) { // Validate type - if (IsAssetTypeIdInvalid(type, result->GetTypeHandle())) + if (IsAssetTypeIdInvalid(type, result->GetTypeHandle()) && !result->Is(type)) { LOG(Warning, "Different loaded asset type! Asset: \'{0}\'. Expected type: {1}", result->ToString(), type.ToString()); return nullptr; @@ -949,15 +949,6 @@ Asset* Content::load(const Guid& id, const ScriptingTypeHandle& type, AssetInfo& return nullptr; } - // Validate type - const StringAsANSI<> typeNameStd(assetInfo.TypeName.Get(), assetInfo.TypeName.Length()); - const auto assetType = Scripting::FindScriptingType(StringAnsiView(typeNameStd.Get(), assetInfo.TypeName.Length())); - if (IsAssetTypeIdInvalid(type, assetType)) - { - LOG(Error, "Different loaded asset type! Asset: '{0}'. Expected type: {1}", assetInfo.ToString(), type.ToString()); - return nullptr; - } - #endif // Find asset factory based in its type @@ -976,6 +967,18 @@ Asset* Content::load(const Guid& id, const ScriptingTypeHandle& type, AssetInfo& return nullptr; } +#if ASSETS_LOADING_EXTRA_VERIFICATION + + // Validate type + if (IsAssetTypeIdInvalid(type, result->GetTypeHandle()) && !result->Is(type)) + { + LOG(Error, "Different loaded asset type! Asset: '{0}'. Expected type: {1}", assetInfo.ToString(), type.ToString()); + result->DeleteObject(); + return nullptr; + } + +#endif + // Register asset { AssetsLocker.Lock(); diff --git a/Source/Engine/Content/JsonAsset.cpp b/Source/Engine/Content/JsonAsset.cpp index 3612a88b8..821c43f87 100644 --- a/Source/Engine/Content/JsonAsset.cpp +++ b/Source/Engine/Content/JsonAsset.cpp @@ -10,7 +10,10 @@ #include "Engine/Core/Log.h" #include "Engine/Serialization/JsonTools.h" #include "Engine/Content/Factories/JsonAssetFactory.h" +#include "Engine/Core/Cache.h" #include "Engine/Debug/Exceptions/JsonParseException.h" +#include "Engine/Scripting/Scripting.h" +#include "Engine/Utilities/StringConverter.h" JsonAssetBase::JsonAssetBase(const SpawnParams& params, const AssetInfo* info) : Asset(params, info) @@ -175,53 +178,55 @@ void JsonAssetBase::onRename(const StringView& newPath) REGISTER_JSON_ASSET(JsonAsset, "FlaxEngine.JsonAsset"); -//////////////////////////////////////////////////////////////////////////////////// - -#include "Engine/Physics/PhysicalMaterial.h" - -// Unmanaged json asset types that are serialized to JsonAsset and should be created by auto by asset. -// This allows to reuse JsonAsset without creating dedicated asset types. It has been designed for lightweight resources. - -typedef ISerializable* (*UnmanagedJsonInstanceCreator)(); - -template -ISerializable* Create() -{ - return New(); -} - -Dictionary UnmanagedTypes(32); - -void InitUnmanagedJsonTypes() -{ - ASSERT(UnmanagedTypes.IsEmpty()); - - // Key: managed class typename, Value: unmanaged instance spawner function - UnmanagedTypes[TEXT("FlaxEngine.PhysicalMaterial")] = &Create; -} - -//////////////////////////////////////////////////////////////////////////////////// - JsonAsset::JsonAsset(const SpawnParams& params, const AssetInfo* info) : JsonAssetBase(params, info) , Instance(nullptr) { - if (UnmanagedTypes.IsEmpty()) - InitUnmanagedJsonTypes(); } Asset::LoadResult JsonAsset::loadAsset() { // Base auto result = JsonAssetBase::loadAsset(); - if (result != LoadResult::Ok) + if (result != LoadResult::Ok || IsInternalType()) return result; - UnmanagedJsonInstanceCreator instanceSpawner = nullptr; - if (UnmanagedTypes.TryGet(DataTypeName, instanceSpawner)) + // Try to scripting type for this data + const StringAsANSI<> dataTypeNameAnsi(DataTypeName.Get(), DataTypeName.Length()); + const auto typeHandle = Scripting::FindScriptingType(StringAnsiView(dataTypeNameAnsi.Get(), DataTypeName.Length())); + if (typeHandle) { - Instance = instanceSpawner(); - Instance->Deserialize(*Data, nullptr); + auto& type = typeHandle.GetType(); + switch (type.Type) + { + case ScriptingTypes::Class: + { + // Ensure that object can deserialized + const ScriptingType::InterfaceImplementation* interfaces = type.GetInterface(&ISerializable::TypeInitializer); + if (!interfaces) + { + LOG(Warning, "Cannot deserialize {0} from Json Asset because it doesn't implement ISerializable interface.", type.ToString()); + break; + } + + // Allocate object + const auto instance = Allocator::Allocate(type.Size); + if (!instance) + return LoadResult::Failed; + Instance = instance; + InstanceType = typeHandle; + _dtor = type.Class.Dtor; + type.Class.Ctor(instance); + + // Deserialize object + auto modifier = Cache::ISerializeModifier.Get(); + modifier->EngineBuild = DataEngineBuild; + ((ISerializable*)((byte*)instance + interfaces->VTableOffset))->Deserialize(*Data, modifier.Value); + // TODO: delete object when containing BinaryModule gets unloaded + break; + } + default: ; + } } return result; @@ -232,5 +237,12 @@ void JsonAsset::unload(bool isReloading) // Base JsonAssetBase::unload(isReloading); - SAFE_DELETE(Instance); + if (Instance) + { + InstanceType = ScriptingTypeHandle(); + _dtor(Instance); + Allocator::Free(Instance); + Instance = nullptr; + _dtor = nullptr; + } } diff --git a/Source/Engine/Content/JsonAsset.h b/Source/Engine/Content/JsonAsset.h index ce617938d..d865e8b96 100644 --- a/Source/Engine/Content/JsonAsset.h +++ b/Source/Engine/Content/JsonAsset.h @@ -75,16 +75,23 @@ protected: /// Generic type of Json-format asset. It provides the managed representation of this resource data so it can be accessed via C# API. /// /// -API_CLASS(NoSpawn) class JsonAsset : public JsonAssetBase +API_CLASS(NoSpawn) class FLAXENGINE_API JsonAsset : public JsonAssetBase { DECLARE_ASSET_HEADER(JsonAsset); +private: + ScriptingType::Dtor _dtor; public: + /// + /// The scripting type of the deserialized unmanaged object instance (e.g. PhysicalMaterial). + /// + ScriptingTypeHandle InstanceType; + /// /// The deserialized unmanaged object instance (e.g. PhysicalMaterial). /// - ISerializable* Instance; + void* Instance; protected: diff --git a/Source/Engine/Content/Loading/ContentLoadingManager.cpp b/Source/Engine/Content/Loading/ContentLoadingManager.cpp index ac2dd8257..6e6ff7a65 100644 --- a/Source/Engine/Content/Loading/ContentLoadingManager.cpp +++ b/Source/Engine/Content/Loading/ContentLoadingManager.cpp @@ -208,7 +208,7 @@ void ContentLoadingManagerService::Dispose() MainThread = nullptr; ThisThread = nullptr; - // Cancel all reaming tasks (no chance to execute them) + // Cancel all remaining tasks (no chance to execute them) Tasks.CancelAll(); } diff --git a/Source/Engine/Content/Loading/Tasks/LoadAssetDataTask.h b/Source/Engine/Content/Loading/Tasks/LoadAssetDataTask.h index 9d97a439f..5d552b996 100644 --- a/Source/Engine/Content/Loading/Tasks/LoadAssetDataTask.h +++ b/Source/Engine/Content/Loading/Tasks/LoadAssetDataTask.h @@ -82,7 +82,7 @@ protected: void OnEnd() override { _dataLock.Release(); - _asset.Unlink(); + _asset = nullptr; // Base ContentLoadTask::OnEnd(); diff --git a/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h b/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h index 7eed30399..7a7ea285f 100644 --- a/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h +++ b/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h @@ -68,7 +68,7 @@ protected: void OnEnd() override { - _asset.Unlink(); + _asset = nullptr; // Base ContentLoadTask::OnEnd(); diff --git a/Source/Engine/Content/Utilities/IESLoader.cpp b/Source/Engine/Content/Utilities/IESLoader.cpp index 0ef7c320a..271ab3d5e 100644 --- a/Source/Engine/Content/Utilities/IESLoader.cpp +++ b/Source/Engine/Content/Utilities/IESLoader.cpp @@ -240,7 +240,7 @@ float IESLoader::ExtractInR16(Array& output) float result = 0.0f; for (uint32 i = 0; i < hAnglesCount; i++) result += InterpolateBilinear(static_cast(i), v); - *out++ = ConvertFloatToHalf(invMaxValue * result / (float)hAnglesCount); + *out++ = Float16Compressor::Compress(invMaxValue * result / (float)hAnglesCount); } } diff --git a/Source/Engine/Content/WeakAssetReference.h b/Source/Engine/Content/WeakAssetReference.h index 9ed81b521..76f6b1fff 100644 --- a/Source/Engine/Content/WeakAssetReference.h +++ b/Source/Engine/Content/WeakAssetReference.h @@ -66,14 +66,6 @@ public: return _asset ? _asset->GetOrCreateManagedInstance() : nullptr; } - /// - /// Clears the asset reference. - /// - FORCE_INLINE void Unlink() - { - OnSet(nullptr); - } - /// /// Gets the asset property value as string. /// @@ -103,7 +95,8 @@ protected: { ASSERT(_asset == asset); Unload(); - Unlink(); + asset->OnUnloaded.Unbind(this); + asset = nullptr; } }; diff --git a/Source/Engine/ContentExporters/ExportModel.cpp b/Source/Engine/ContentExporters/ExportModel.cpp index efc13419b..7fdd011f9 100644 --- a/Source/Engine/ContentExporters/ExportModel.cpp +++ b/Source/Engine/ContentExporters/ExportModel.cpp @@ -76,7 +76,7 @@ ExportAssetResult AssetExporters::ExportModel(ExportAssetContext& context) for (uint32 i = 0; i < vertices; i++) { auto v = vb1[i].TexCoord; - output->WriteTextFormatted("vt {0} {1}\n", ConvertHalfToFloat(v.X), ConvertHalfToFloat(v.Y)); + output->WriteTextFormatted("vt {0} {1}\n", Float16Compressor::Decompress(v.X), Float16Compressor::Decompress(v.Y)); } output->WriteChar('\n'); @@ -181,7 +181,7 @@ ExportAssetResult AssetExporters::ExportSkinnedModel(ExportAssetContext& context for (uint32 i = 0; i < vertices; i++) { auto v = vb0[i].TexCoord; - output->WriteTextFormatted("vt {0} {1}\n", ConvertHalfToFloat(v.X), ConvertHalfToFloat(v.Y)); + output->WriteTextFormatted("vt {0} {1}\n", Float16Compressor::Decompress(v.X), Float16Compressor::Decompress(v.Y)); } output->WriteChar('\n'); diff --git a/Source/Engine/ContentImporters/AssetsImportingManager.cpp b/Source/Engine/ContentImporters/AssetsImportingManager.cpp index 3a39bd64b..f1d048ca5 100644 --- a/Source/Engine/ContentImporters/AssetsImportingManager.cpp +++ b/Source/Engine/ContentImporters/AssetsImportingManager.cpp @@ -178,7 +178,7 @@ void CreateAssetContext::ApplyChanges() // Move file if (FileSystem::MoveFile(TargetAssetPath, OutputPath, true)) { - LOG(Warning, "Cannot move imported file to the destination path."); + LOG(Warning, "Cannot move imported file {0} to the destination path {1}.", OutputPath, TargetAssetPath); _applyChangesResult = CreateAssetResult::CannotSaveFile; return; } diff --git a/Source/Engine/Core/Collections/Dictionary.h b/Source/Engine/Core/Collections/Dictionary.h index e61cfc4d9..99bad0bef 100644 --- a/Source/Engine/Core/Collections/Dictionary.h +++ b/Source/Engine/Core/Collections/Dictionary.h @@ -13,7 +13,6 @@ template API_CLASS(InBuild) class Dictionary { friend Dictionary; - public: /// @@ -23,94 +22,73 @@ public: { friend Dictionary; - public: - enum State : byte { - Empty, - Deleted, - Occupied, + Empty = 0, + Deleted = 1, + Occupied = 2, }; public: - /// The key. KeyType Key; /// The value. ValueType Value; private: - State _state; - public: - - Bucket() - : _state(Empty) - { - } - - ~Bucket() - { - } - - public: - void Free() { + if (_state == Occupied) + { + Memory::DestructItem(&Key); + Memory::DestructItem(&Value); + } _state = Empty; } void Delete() { _state = Deleted; + Memory::DestructItem(&Key); + Memory::DestructItem(&Value); } template void Occupy(const KeyComparableType& key) { - Key = key; + Memory::ConstructItems(&Key, &key, 1); + Memory::ConstructItem(&Value); _state = Occupied; } template void Occupy(const KeyComparableType& key, const ValueType& value) { - Key = key; - Value = value; + Memory::ConstructItems(&Key, &key, 1); + Memory::ConstructItems(&Value, &value, 1); _state = Occupied; } template void Occupy(const KeyComparableType& key, ValueType&& value) { - Key = key; - Value = MoveTemp(value); + Memory::ConstructItems(&Key, &key, 1); + Memory::MoveItems(&Value, &value, 1); _state = Occupied; } - public: - FORCE_INLINE bool IsEmpty() const { return _state == Empty; } - FORCE_INLINE bool IsNotEmpty() const - { - return _state != Empty; - } - FORCE_INLINE bool IsDeleted() const { return _state == Deleted; } - FORCE_INLINE bool IsNotDeleted() const - { - return _state != Deleted; - } - FORCE_INLINE bool IsOccupied() const { return _state == Occupied; @@ -182,7 +160,6 @@ public: /// /// Gets the amount of the elements in the collection. /// - /// The amount of elements in the collection. FORCE_INLINE int32 Count() const { return _elementsCount; @@ -191,7 +168,6 @@ public: /// /// Gets the amount of the elements that can be contained by the collection. /// - /// The capacity of the collection. FORCE_INLINE int32 Capacity() const { return _tableSize; @@ -200,7 +176,6 @@ public: /// /// Returns true if collection is empty. /// - /// True if is empty, otherwise false. FORCE_INLINE bool IsEmpty() const { return _elementsCount == 0; @@ -209,7 +184,6 @@ public: /// /// Returns true if collection has one or more elements. /// - /// True if isn't empty, otherwise false. FORCE_INLINE bool HasItems() const { return _elementsCount != 0; @@ -218,12 +192,11 @@ public: public: /// - /// The dictionary collection iterator. + /// The Dictionary collection iterator. /// struct Iterator { friend Dictionary; - private: Dictionary& _collection; @@ -254,7 +227,6 @@ public: /// /// Checks if iterator is in the end of the collection. /// - /// True if is in the end, otherwise false. FORCE_INLINE bool IsEnd() const { return _index == _collection._tableSize; @@ -263,7 +235,6 @@ public: /// /// Checks if iterator is not in the end of the collection. /// - /// True if is not in the end, otherwise false. FORCE_INLINE bool IsNotEnd() const { return _index != _collection._tableSize; @@ -286,7 +257,7 @@ public: return _index >= 0 && _index < _collection._tableSize; } - FORCE_INLINE bool operator !() const + FORCE_INLINE bool operator!() const { return !(bool)*this; } @@ -316,7 +287,7 @@ public: return *this; } - Iterator operator++(int) + Iterator operator++(int) const { Iterator i = *this; ++i; @@ -335,7 +306,7 @@ public: return *this; } - Iterator operator--(int) + Iterator operator--(int) const { Iterator i = *this; --i; @@ -428,7 +399,6 @@ public: if (pos.ObjectIndex == -1) return false; - result = _table[pos.ObjectIndex].Value; return true; } @@ -449,7 +419,6 @@ public: if (pos.ObjectIndex == -1) return nullptr; - return &_table[pos.ObjectIndex].Value; } @@ -463,11 +432,8 @@ public: if (_table) { // Free all buckets - // Note: this will not clear allocated objects space! for (int32 i = 0; i < _tableSize; i++) - { _table[i].Free(); - } _elementsCount = _deletedCount = 0; } } @@ -492,16 +458,14 @@ public: /// Enables preserving collection contents during resizing. void SetCapacity(int32 capacity, bool preserveContents = true) { - // Validate input - ASSERT(capacity >= 0); - // Check if capacity won't change if (capacity == Capacity()) return; // Cache previous state + ASSERT(capacity >= 0); Bucket* oldTable = _table; - int32 oldTableSize = _tableSize; + const int32 oldTableSize = _tableSize; // Clear elements counters const int32 oldElementsCount = _elementsCount; @@ -523,10 +487,10 @@ public: } // Allocate new table - _table = NewArray(capacity); + _table = (Bucket*)Allocator::Allocate(capacity * sizeof(Bucket)); _tableSize = capacity; - - // Check if preserve content + for (int32 i = 0; i < capacity; i++) + _table[i]._state = Bucket::Empty; if (oldElementsCount != 0 && preserveContents) { // Try to preserve all pairs in the collection @@ -548,7 +512,9 @@ public: // Delete old table if (oldTable) { - DeleteArray(oldTable, oldTableSize); + for (int32 i = 0; i < oldTableSize; i++) + oldTable[i].Free(); + Allocator::Free(oldTable); } } @@ -571,11 +537,10 @@ public: if (Capacity() >= minCapacity) return; - // TODO: improve this, better collection growing and shrinking on remove - int32 num = Capacity() == 0 ? DICTIONARY_DEFAULT_CAPACITY : Capacity() * 2; - if (num < minCapacity) - num = minCapacity; - SetCapacity(num); + int32 capacity = Capacity() == 0 ? DICTIONARY_DEFAULT_CAPACITY : Capacity() * 2; + if (capacity < minCapacity) + capacity = minCapacity; + SetCapacity(capacity); } /// @@ -676,7 +641,6 @@ public: _deletedCount++; return true; } - return false; } @@ -733,9 +697,7 @@ public: for (int32 i = 0; i < _tableSize; i++) { if (_table[i].IsOccupied() && _table[i].Key == key) - { return Iterator(*this, i); - } } } return End(); @@ -754,7 +716,6 @@ public: FindPositionResult pos; FindPosition(key, pos); - return pos.ObjectIndex != -1; } @@ -809,12 +770,9 @@ public: void Clone(const Dictionary& other) { Clear(); - SetCapacity(other.Capacity(), false); - for (auto i = other.Begin(); i != other.End(); ++i) Add(i); - ASSERT(Count() == other.Count()); ASSERT(Capacity() == other.Capacity()); } @@ -909,14 +867,13 @@ protected: void FindPosition(const KeyComparableType& key, FindPositionResult& result) const { ASSERT(_table); - const int32 tableSizeMinusOne = _tableSize - 1; int32 bucketIndex = GetHash(key) & tableSizeMinusOne; int32 insertPos = -1; - int32 numChecks = 0; + int32 checksCount = 0; result.FreeSlotIndex = -1; - while (numChecks < _tableSize) + while (checksCount < _tableSize) { // Empty bucket if (_table[bucketIndex].IsEmpty()) @@ -941,8 +898,8 @@ protected: return; } - numChecks++; - bucketIndex = (bucketIndex + DICTIONARY_PROB_FUNC(_tableSize, numChecks)) & tableSizeMinusOne; + checksCount++; + bucketIndex = (bucketIndex + DICTIONARY_PROB_FUNC(_tableSize, checksCount)) & tableSizeMinusOne; } result.ObjectIndex = -1; diff --git a/Source/Engine/Core/Config.h b/Source/Engine/Core/Config.h index 0a9a10b3d..0628374f2 100644 --- a/Source/Engine/Core/Config.h +++ b/Source/Engine/Core/Config.h @@ -44,6 +44,7 @@ // Scripting API defines (see C++ scripting documentation for more info) #define API_ENUM(...) #define API_CLASS(...) +#define API_INTERFACE(...) #define API_STRUCT(...) #define API_FUNCTION(...) #define API_PROPERTY(...) @@ -52,3 +53,4 @@ #define API_PARAM(...) #define API_INJECT_CPP_CODE(...) #define API_AUTO_SERIALIZATION(...) public: void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; +#define DECLARE_SCRIPTING_TYPE_MINIMAL(type) public: friend class type##Internal; static struct ScriptingTypeInitializer TypeInitializer; diff --git a/Source/Engine/Core/Config/BuildSettings.h b/Source/Engine/Core/Config/BuildSettings.h index d0ae40e0f..529171639 100644 --- a/Source/Engine/Core/Config/BuildSettings.h +++ b/Source/Engine/Core/Config/BuildSettings.h @@ -10,72 +10,73 @@ /// /// The game building rendering settings. /// -/// -class BuildSettings : public Settings +API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API BuildSettings : public SettingsBase { +DECLARE_SCRIPTING_TYPE_MINIMAL(BuildSettings); public: /// /// The maximum amount of assets to include into a single assets package. Asset packages will split into several packages if need to. /// - int32 MaxAssetsPerPackage = 1024; + API_FIELD(Attributes="EditorOrder(10), DefaultValue(4096), Limit(1, 32, ushort.MaxValue), EditorDisplay(\"General\", \"Max assets per package\")") + int32 MaxAssetsPerPackage = 4096; /// /// The maximum size of the single assets package (in megabytes). Asset packages will split into several packages if need to. /// + API_FIELD(Attributes="EditorOrder(20), DefaultValue(1024), Limit(1, 16, ushort.MaxValue), EditorDisplay(\"General\", \"Max package size (in MB)\")") int32 MaxPackageSizeMB = 1024; /// /// The game content cooking keycode. Use the same value for a game and DLC packages to support loading them by the build game. Use 0 to randomize it during building. /// + API_FIELD(Attributes="EditorOrder(30), DefaultValue(0), Limit(1, 16, ushort.MaxValue), EditorDisplay(\"General\")") int32 ContentKey = 0; /// /// If checked, the builds produced by the Game Cooker will be treated as for final game distribution (eg. for game store upload). Builds done this way cannot be tested on console devkits (eg. Xbox One, Xbox Scarlett). /// + API_FIELD(Attributes="EditorOrder(40), DefaultValue(false), EditorDisplay(\"General\")") bool ForDistribution = false; /// /// If checked, the output build files won't be packaged for the destination platform. Useful when debugging build from local PC. /// + API_FIELD(Attributes="EditorOrder(50), DefaultValue(false), EditorDisplay(\"General\")") bool SkipPackaging = false; /// /// The list of additional assets to include into build (into root assets set). /// + API_FIELD(Attributes="EditorOrder(1000), EditorDisplay(\"Additional Data\")") Array> AdditionalAssets; /// /// The list of additional folders with assets to include into build (into root assets set). Paths relative to the project directory (or absolute). /// + API_FIELD(Attributes="EditorOrder(1010), EditorDisplay(\"Additional Data\")") Array AdditionalAssetFolders; /// /// Disables shaders compiler optimizations in cooked game. Can be used to debug shaders on a target platform or to speed up the shaders compilation time. /// + API_FIELD(Attributes="EditorOrder(2000), DefaultValue(false), EditorDisplay(\"Content\", \"Shaders No Optimize\")") bool ShadersNoOptimize = false; /// /// Enables shader debug data generation for shaders in cooked game (depends on the target platform rendering backend). /// + API_FIELD(Attributes="EditorOrder(2010), DefaultValue(false), EditorDisplay(\"Content\")") bool ShadersGenerateDebugData = false; public: - // [Settings] - void RestoreDefault() final override - { - MaxAssetsPerPackage = 1024; - MaxPackageSizeMB = 1024; - ContentKey = 0; - ForDistribution = false; - SkipPackaging = false; - AdditionalAssets.Clear(); - AdditionalAssetFolders.Clear(); - ShadersNoOptimize = false; - ShadersGenerateDebugData = false; - } + /// + /// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use. + /// + static BuildSettings* Get(); + // [SettingsBase] void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override { DESERIALIZE(MaxAssetsPerPackage); diff --git a/Source/Engine/Core/Config/GameSettings.cpp b/Source/Engine/Core/Config/GameSettings.cpp index 38e036de6..5ea356d61 100644 --- a/Source/Engine/Core/Config/GameSettings.cpp +++ b/Source/Engine/Core/Config/GameSettings.cpp @@ -2,6 +2,7 @@ #include "GameSettings.h" #include "Engine/Serialization/JsonTools.h" +#include "Engine/Scripting/ScriptingType.h" #include "Engine/Physics/PhysicsSettings.h" #include "Engine/Core/Log.h" #include "LayersTagsSettings.h" @@ -17,30 +18,6 @@ #include "Engine/Content/AssetReference.h" #include "Engine/Engine/EngineService.h" #include "Engine/Engine/Globals.h" -#include "Engine/Engine/Time.h" - -String GameSettings::ProductName; -String GameSettings::CompanyName; -String GameSettings::CopyrightNotice; -Guid GameSettings::Icon; -Guid GameSettings::FirstScene; -bool GameSettings::NoSplashScreen = false; -Guid GameSettings::SplashScreen; -Dictionary GameSettings::CustomSettings; - -Array SettingsBase::Containers(32); - -#if USE_EDITOR -extern void LoadPlatformSettingsEditor(ISerializable::DeserializeStream& data); -#endif - -void TimeSettings::Apply() -{ - Time::UpdateFPS = UpdateFPS; - Time::PhysicsFPS = PhysicsFPS; - Time::DrawFPS = DrawFPS; - Time::TimeScale = TimeScale; -} class GameSettingsService : public EngineService { @@ -49,9 +26,6 @@ public: GameSettingsService() : EngineService(TEXT("GameSettings"), -70) { - GameSettings::Icon = Guid::Empty; - GameSettings::FirstScene = Guid::Empty; - GameSettings::SplashScreen = Guid::Empty; } bool Init() override @@ -60,62 +34,141 @@ public: } }; +IMPLEMENT_SETTINGS_GETTER(BuildSettings, GameCooking); +IMPLEMENT_SETTINGS_GETTER(GraphicsSettings, Graphics); +IMPLEMENT_SETTINGS_GETTER(LayersAndTagsSettings, LayersAndTags); +IMPLEMENT_SETTINGS_GETTER(TimeSettings, Time); +IMPLEMENT_SETTINGS_GETTER(AudioSettings, Audio); +IMPLEMENT_SETTINGS_GETTER(PhysicsSettings, Physics); +IMPLEMENT_SETTINGS_GETTER(InputSettings, Input); + +#if !USE_EDITOR +#if PLATFORM_WINDOWS +IMPLEMENT_SETTINGS_GETTER(WindowsPlatformSettings, WindowsPlatform); +#elif PLATFORM_UWP || PLATFORM_XBOX_ONE +IMPLEMENT_SETTINGS_GETTER(UWPPlatformSettings, UWPPlatform); +#elif PLATFORM_LINUX +IMPLEMENT_SETTINGS_GETTER(LinuxPlatformSettings, LinuxPlatform); +#elif PLATFORM_PS4 +IMPLEMENT_SETTINGS_GETTER(PS4PlatformSettings, PS4Platform); +#elif PLATFORM_XBOX_SCARLETT +IMPLEMENT_SETTINGS_GETTER(XboxScarlettPlatformSettings, XboxScarlettPlatform); +#elif PLATFORM_ANDROID +IMPLEMENT_SETTINGS_GETTER(AndroidPlatformSettings, AndroidPlatform); +#else +#error Unknown platform +#endif +#endif + GameSettingsService GameSettingsServiceInstance; +AssetReference GameSettingsAsset; + +GameSettings* GameSettings::Get() +{ + if (!GameSettingsAsset) + { + // Load root game settings asset. + // It may be missing in editor during dev but must be ready in the build game. + const auto assetPath = Globals::ProjectContentFolder / TEXT("GameSettings.json"); + GameSettingsAsset = Content::LoadAsync(assetPath); + if (GameSettingsAsset == nullptr) + { + LOG(Error, "Missing game settings asset."); + return nullptr; + } + if (GameSettingsAsset->WaitForLoaded()) + { + return nullptr; + } + if (GameSettingsAsset->InstanceType != GameSettings::TypeInitializer) + { + LOG(Error, "Invalid game settings asset data type."); + return nullptr; + } + } + auto asset = GameSettingsAsset.Get(); + if (asset && asset->WaitForLoaded()) + asset = nullptr; + return asset ? (GameSettings*)asset->Instance : nullptr; +} bool GameSettings::Load() { + // Load main settings asset + auto settings = Get(); + if (!settings) + { + return true; + } + + // Preload all settings assets +#define PRELOAD_SETTINGS(type) \ + { \ + if (settings->type) \ + { \ + Content::LoadAsync(settings->type); \ + } \ + else \ + { \ + LOG(Warning, "Missing {0} settings", TEXT(#type)); \ + } \ + } + PRELOAD_SETTINGS(Time); + PRELOAD_SETTINGS(Audio); + PRELOAD_SETTINGS(LayersAndTags); + PRELOAD_SETTINGS(Physics); + PRELOAD_SETTINGS(Input); + PRELOAD_SETTINGS(Graphics); + PRELOAD_SETTINGS(Navigation); + PRELOAD_SETTINGS(GameCooking); +#undef PRELOAD_SETTINGS + + // Apply the game settings to the engine + settings->Apply(); + + return false; +} + +void GameSettings::Apply() +{ + // TODO: impl this +#define APPLY_SETTINGS(type) \ + { \ + type* obj = type::Get(); \ + if (obj) \ + { \ + obj->Apply(); \ + } \ + else \ + { \ + LOG(Warning, "Missing {0} settings", TEXT(#type)); \ + } \ + } + APPLY_SETTINGS(TimeSettings); + APPLY_SETTINGS(AudioSettings); + APPLY_SETTINGS(LayersAndTagsSettings); + APPLY_SETTINGS(PhysicsSettings); + APPLY_SETTINGS(InputSettings); + APPLY_SETTINGS(GraphicsSettings); + APPLY_SETTINGS(NavigationSettings); + APPLY_SETTINGS(BuildSettings); + APPLY_SETTINGS(PlatformSettings); +#undef APPLY_SETTINGS +} + +void GameSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) +{ + // Load properties + ProductName = JsonTools::GetString(stream, "ProductName"); + CompanyName = JsonTools::GetString(stream, "CompanyName"); + CopyrightNotice = JsonTools::GetString(stream, "CopyrightNotice"); + Icon = JsonTools::GetGuid(stream, "Icon"); + FirstScene = JsonTools::GetGuid(stream, "FirstScene"); + NoSplashScreen = JsonTools::GetBool(stream, "NoSplashScreen", NoSplashScreen); + SplashScreen = JsonTools::GetGuid(stream, "SplashScreen"); CustomSettings.Clear(); - -#if USE_EDITOR -#define END_POINT(msg) LOG(Warning, msg " Using default values."); return false -#else -#define END_POINT(msg) LOG(Fatal, msg); return true -#endif - -#define LOAD_SETTINGS(nodeName, settingsType) \ - { \ - Guid id = JsonTools::GetGuid(data, nodeName); \ - if (id.IsValid()) \ - { \ - AssetReference subAsset = Content::LoadAsync(id); \ - if (subAsset && !subAsset->WaitForLoaded()) \ - { \ - settingsType::Instance()->Deserialize(*subAsset->Data, nullptr); \ - settingsType::Instance()->Apply(); \ - } \ - else \ - { LOG(Warning, "Cannot load " nodeName " settings"); } \ - } \ - else \ - { LOG(Warning, "Missing " nodeName " settings"); } \ - } - - // Load root game settings asset. - // It may be missing in editor during dev but must be ready in the build game. - const auto assetPath = Globals::ProjectContentFolder / TEXT("GameSettings.json"); - AssetReference asset = Content::LoadAsync(assetPath); - if (asset == nullptr) - { - END_POINT("Missing game settings asset."); - } - if (asset->WaitForLoaded() - || asset->DataTypeName != TEXT("FlaxEditor.Content.Settings.GameSettings") - || asset->Data == nullptr) - { - END_POINT("Cannot load game settings asset."); - } - auto& data = *asset->Data; - - // Load settings - ProductName = JsonTools::GetString(data, "ProductName"); - CompanyName = JsonTools::GetString(data, "CompanyName"); - CopyrightNotice = JsonTools::GetString(data, "CopyrightNotice"); - Icon = JsonTools::GetGuid(data, "Icon"); - FirstScene = JsonTools::GetGuid(data, "FirstScene"); - NoSplashScreen = JsonTools::GetBool(data, "NoSplashScreen", NoSplashScreen); - SplashScreen = JsonTools::GetGuid(data, "SplashScreen"); - const auto customSettings = data.FindMember("CustomSettings"); - if (customSettings != data.MemberEnd()) + const auto customSettings = stream.FindMember("CustomSettings"); + if (customSettings != stream.MemberEnd()) { auto& items = customSettings->value; for (auto it = items.MemberBegin(); it != items.MemberEnd(); ++it) @@ -129,39 +182,21 @@ bool GameSettings::Load() } } - // Load child settings - LOAD_SETTINGS("Time", TimeSettings); - LOAD_SETTINGS("Physics", PhysicsSettings); - LOAD_SETTINGS("LayersAndTags", LayersAndTagsSettings); - LOAD_SETTINGS("Graphics", GraphicsSettings); - LOAD_SETTINGS("GameCooking", BuildSettings); - LOAD_SETTINGS("Input", InputSettings); - LOAD_SETTINGS("Audio", AudioSettings); - LOAD_SETTINGS("Navigation", NavigationSettings); + // Settings containers + DESERIALIZE(Time); + DESERIALIZE(Audio); + DESERIALIZE(LayersAndTags); + DESERIALIZE(Physics); + DESERIALIZE(Input); + DESERIALIZE(Graphics); + DESERIALIZE(Navigation); + DESERIALIZE(GameCooking); - // Load platform settings -#if PLATFORM_WINDOWS - LOAD_SETTINGS("WindowsPlatform", WindowsPlatformSettings); -#endif -#if PLATFORM_UWP - LOAD_SETTINGS("UWPPlatform", UWPPlatformSettings); -#endif -#if PLATFORM_LINUX - LOAD_SETTINGS("LinuxPlatform", LinuxPlatformSettings); -#endif -#if PLATFORM_PS4 - LOAD_SETTINGS("PS4Platform", PS4PlatformSettings); -#endif -#if PLATFORM_XBOX_SCARLETT - LOAD_SETTINGS("XboxScarlettPlatform", XboxScarlettPlatformSettings); -#endif -#if PLATFORM_ANDROID - LOAD_SETTINGS("AndroidPlatform", AndroidPlatformSettings); -#endif -#if USE_EDITOR - LoadPlatformSettingsEditor(data); -#endif - - return false; -#undef END_POINT + // Per-platform settings containers + DESERIALIZE(WindowsPlatform); + DESERIALIZE(UWPPlatform); + DESERIALIZE(LinuxPlatform); + DESERIALIZE(PS4Platform); + DESERIALIZE(XboxScarlettPlatform); + DESERIALIZE(AndroidPlatform); } diff --git a/Source/Engine/Core/Config/GameSettings.h b/Source/Engine/Core/Config/GameSettings.h index 6870d8c22..16e1fe7b5 100644 --- a/Source/Engine/Core/Config/GameSettings.h +++ b/Source/Engine/Core/Config/GameSettings.h @@ -2,60 +2,96 @@ #pragma once +#include "Settings.h" #include "Engine/Core/Types/Guid.h" #include "Engine/Core/Types/String.h" #include "Engine/Core/Collections/Dictionary.h" /// -/// Main engine configuration service. Loads and applies game configuration. +/// The main game engine configuration service. Loads and applies game configuration. /// -class GameSettings +API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API GameSettings : public SettingsBase { +DECLARE_SCRIPTING_TYPE_MINIMAL(GameSettings); public: /// /// The product full name. /// - static String ProductName; + API_FIELD(Attributes="EditorOrder(0), EditorDisplay(\"General\")") + String ProductName; /// /// The company full name. /// - static String CompanyName; + API_FIELD(Attributes="EditorOrder(10), EditorDisplay(\"General\")") + String CompanyName; /// /// The copyright note used for content signing (eg. source code header). /// - static String CopyrightNotice; + API_FIELD(Attributes="EditorOrder(15), EditorDisplay(\"General\")") + String CopyrightNotice; /// /// The default application icon. /// - static Guid Icon; + Guid Icon = Guid::Empty; /// /// Reference to the first scene to load on a game startup. /// - static Guid FirstScene; + Guid FirstScene = Guid::Empty; /// /// True if skip showing splash screen image on the game startup. /// - static bool NoSplashScreen; + bool NoSplashScreen = false; /// /// Reference to the splash screen image to show on a game startup. /// - static Guid SplashScreen; + Guid SplashScreen = Guid::Empty; /// /// The custom settings to use with a game. Can be specified by the user to define game-specific options and be used by the external plugins (used as key-value pair). /// - static Dictionary CustomSettings; + Dictionary CustomSettings; + +public: + + // Settings containers + Guid Time; + Guid Audio; + Guid LayersAndTags; + Guid Physics; + Guid Input; + Guid Graphics; + Guid Navigation; + Guid GameCooking; + + // Per-platform settings containers + Guid WindowsPlatform; + Guid UWPPlatform; + Guid LinuxPlatform; + Guid PS4Platform; + Guid XboxScarlettPlatform; + Guid AndroidPlatform; + +public: + + /// + /// Gets the instance of the game settings asset (null if missing). Object returned by this method is always loaded with valid data to use. + /// + static GameSettings* Get(); /// /// Loads the game settings (including other settings such as Physics, Input, etc.). /// /// True if failed, otherwise false. static bool Load(); + + // [SettingsBase] + void Apply() override; + void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override; }; diff --git a/Source/Engine/Core/Config/GraphicsSettings.h b/Source/Engine/Core/Config/GraphicsSettings.h index b13972cc7..17c342e89 100644 --- a/Source/Engine/Core/Config/GraphicsSettings.h +++ b/Source/Engine/Core/Config/GraphicsSettings.h @@ -9,68 +9,68 @@ /// /// Graphics rendering settings. /// -/// -class GraphicsSettings : public Settings +API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API GraphicsSettings : public SettingsBase { +DECLARE_SCRIPTING_TYPE_MINIMAL(GraphicsSettings); public: /// /// Enables rendering synchronization with the refresh rate of the display device to avoid "tearing" artifacts. /// + API_FIELD(Attributes="EditorOrder(20), DefaultValue(false), EditorDisplay(\"General\", \"Use V-Sync\")") bool UseVSync = false; /// /// Anti Aliasing quality setting. /// + API_FIELD(Attributes="EditorOrder(1000), DefaultValue(Quality.Medium), EditorDisplay(\"Quality\", \"AA Quality\")") Quality AAQuality = Quality::Medium; /// /// Screen Space Reflections quality setting. /// + API_FIELD(Attributes="EditorOrder(1100), DefaultValue(Quality.Medium), EditorDisplay(\"Quality\", \"SSR Quality\")") Quality SSRQuality = Quality::Medium; /// /// Screen Space Ambient Occlusion quality setting. /// + API_FIELD(Attributes="EditorOrder(1200), DefaultValue(Quality.Medium), EditorDisplay(\"Quality\", \"SSAO Quality\")") Quality SSAOQuality = Quality::Medium; /// /// Volumetric Fog quality setting. /// + API_FIELD(Attributes="EditorOrder(1250), DefaultValue(Quality.High), EditorDisplay(\"Quality\")") Quality VolumetricFogQuality = Quality::High; /// /// The shadows quality. /// + API_FIELD(Attributes="EditorOrder(1300), DefaultValue(Quality.Medium), EditorDisplay(\"Quality\")") Quality ShadowsQuality = Quality::Medium; /// /// The shadow maps quality (textures resolution). /// + API_FIELD(Attributes="EditorOrder(1310), DefaultValue(Quality.Medium), EditorDisplay(\"Quality\")") Quality ShadowMapsQuality = Quality::Medium; /// /// Enables cascades splits blending for directional light shadows. /// + API_FIELD(Attributes="EditorOrder(1320), DefaultValue(false), EditorDisplay(\"Quality\", \"Allow CSM Blending\")") bool AllowCSMBlending = false; public: - // [Settings] + /// + /// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use. + /// + static GraphicsSettings* Get(); + + // [SettingsBase] void Apply() override; - - void RestoreDefault() final override - { - UseVSync = false; - AAQuality = Quality::Medium; - SSRQuality = Quality::Medium; - SSAOQuality = Quality::Medium; - VolumetricFogQuality = Quality::High; - ShadowsQuality = Quality::Medium; - ShadowMapsQuality = Quality::Medium; - AllowCSMBlending = false; - } - void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override { DESERIALIZE(UseVSync); diff --git a/Source/Engine/Core/Config/LayersTagsSettings.h b/Source/Engine/Core/Config/LayersTagsSettings.h index 4ed9394fb..945a24fbf 100644 --- a/Source/Engine/Core/Config/LayersTagsSettings.h +++ b/Source/Engine/Core/Config/LayersTagsSettings.h @@ -3,13 +3,14 @@ #pragma once #include "Engine/Core/Config/Settings.h" +#include "Engine/Serialization/Json.h" /// /// Layers and objects tags settings. /// -/// -class LayersAndTagsSettings : public Settings +API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API LayersAndTagsSettings : public SettingsBase { +DECLARE_SCRIPTING_TYPE_MINIMAL(LayersAndTagsSettings); public: /// @@ -25,43 +26,11 @@ public: public: /// - /// Gets or adds the tag (returns the tag index). + /// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use. /// - /// The tag. - /// The tag index. - int32 GetOrAddTag(const String& tag) - { - int32 index = Tags.Find(tag); - if (index == INVALID_INDEX) - { - index = Tags.Count(); - Tags.Add(tag); - } - return index; - } + static LayersAndTagsSettings* Get(); - /// - /// Gets the amount of non empty layer names (from the beginning, trims the last ones). - /// - /// The layers count. - int32 GetNonEmptyLayerNamesCount() const - { - int32 result = 31; - while (result >= 0 && Layers[result].IsEmpty()) - result--; - return result + 1; - } - -public: - - // [Settings] - void RestoreDefault() override - { - Tags.Clear(); - for (int32 i = 0; i < 32; i++) - Layers[i].Clear(); - } - + // [SettingsBase] void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override { const auto tags = stream.FindMember("Tags"); diff --git a/Source/Engine/Core/Config/Settings.h b/Source/Engine/Core/Config/Settings.h index 0380c96b3..2822f4a6a 100644 --- a/Source/Engine/Core/Config/Settings.h +++ b/Source/Engine/Core/Config/Settings.h @@ -2,81 +2,46 @@ #pragma once -#include "Engine/Core/Singleton.h" -#include "Engine/Core/Collections/Array.h" #include "Engine/Serialization/ISerializable.h" /// -/// Base class for all global settings containers for the engine. Helps to apply, store and expose properties to c#. +/// Base class for all global settings containers for the engine. Helps to apply, store and expose properties to engine/game. /// -class SettingsBase +API_CLASS(Abstract) class FLAXENGINE_API SettingsBase : public ISerializable { +DECLARE_SCRIPTING_TYPE_MINIMAL(SettingsBase); public: /// - /// The settings containers. - /// - static Array Containers; - - /// - /// Restores the default settings for all the registered containers. - /// - static void RestoreDefaultAll() - { - for (int32 i = 0; i < Containers.Count(); i++) - Containers[i]->RestoreDefault(); - } - -private: - - // Disable copy/move - SettingsBase(const SettingsBase&) = delete; - SettingsBase& operator=(const SettingsBase&) = delete; - -protected: - - SettingsBase() - { - Containers.Add(this); - } - -public: - - virtual ~SettingsBase() = default; - -public: - - typedef ISerializable::DeserializeStream DeserializeStream; - - /// - /// Applies the settings to the target services. + /// Applies the settings to the target system. /// virtual void Apply() { } - /// - /// Restores the default settings. - /// - virtual void RestoreDefault() = 0; +public: - /// - /// Deserializes the settings container. - /// - /// The input data stream. - /// The deserialization modifier object. Always valid. - virtual void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) = 0; -}; - -/// -/// Base class for all global settings containers for the engine. Helps to apply, store and expose properties to c#. -/// -template -class Settings : public SettingsBase, public Singleton -{ -protected: - - Settings() + // [ISerializable] + void Serialize(SerializeStream& stream, const void* otherObj) override { + // Not supported (Editor C# edits settings data) } }; + +// Helper utility define for settings getter implementation code +#define IMPLEMENT_SETTINGS_GETTER(type, field) \ + type* type::Get() \ + { \ + static type DefaultInstance; \ + type* result = &DefaultInstance; \ + const auto gameSettings = GameSettings::Get(); \ + if (gameSettings) \ + { \ + const auto asset = Content::Load(gameSettings->field); \ + if (asset && asset->Instance && asset->InstanceType == type::TypeInitializer) \ + { \ + result = static_cast(asset->Instance); \ + } \ + } \ + return result; \ + } diff --git a/Source/Engine/Core/Config/TimeSettings.h b/Source/Engine/Core/Config/TimeSettings.h index 8ac7ae741..7e383380f 100644 --- a/Source/Engine/Core/Config/TimeSettings.h +++ b/Source/Engine/Core/Config/TimeSettings.h @@ -3,61 +3,53 @@ #pragma once #include "Engine/Core/Config/Settings.h" -#include "Engine/Serialization/Serialization.h" /// /// Time and game simulation settings container. /// -/// -class TimeSettings : public Settings +API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API TimeSettings : public SettingsBase { +DECLARE_SCRIPTING_TYPE_MINIMAL(TimeSettings); public: /// /// The target amount of the game logic updates per second (script updates frequency). /// + API_FIELD(Attributes="EditorOrder(1), DefaultValue(30.0f), Limit(0, 1000), EditorDisplay(\"General\", \"Update FPS\")") float UpdateFPS = 30.0f; /// /// The target amount of the physics simulation updates per second (also fixed updates frequency). /// + API_FIELD(Attributes="EditorOrder(2), DefaultValue(60.0f), Limit(0, 1000), EditorDisplay(\"General\", \"Physics FPS\")") float PhysicsFPS = 60.0f; /// /// The target amount of the frames rendered per second (actual game FPS). /// + API_FIELD(Attributes="EditorOrder(3), DefaultValue(60.0f), Limit(0, 1000), EditorDisplay(\"General\", \"Draw FPS\")") float DrawFPS = 60.0f; /// /// The game time scale factor. Default is 1. /// + API_FIELD(Attributes="EditorOrder(10), DefaultValue(1.0f), Limit(0, 1000.0f, 0.1f), EditorDisplay(\"General\")") float TimeScale = 1.0f; /// /// The maximum allowed delta time (in seconds) for the game logic update step. /// - float MaxUpdateDeltaTime = (1.0f / 10.0f); + API_FIELD(Attributes="EditorOrder(20), DefaultValue(0.1f), Limit(0.1f, 1000.0f, 0.01f), EditorDisplay(\"General\")") + float MaxUpdateDeltaTime = 0.1f; public: - // [Settings] + /// + /// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use. + /// + static TimeSettings* Get(); + + // [SettingsBase] void Apply() override; - - void RestoreDefault() override - { - UpdateFPS = 30.0f; - PhysicsFPS = 60.0f; - DrawFPS = 60.0f; - TimeScale = 1.0f; - MaxUpdateDeltaTime = 1.0f / 10.0f; - } - - void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override - { - DESERIALIZE(UpdateFPS); - DESERIALIZE(PhysicsFPS); - DESERIALIZE(DrawFPS); - DESERIALIZE(TimeScale); - DESERIALIZE(MaxUpdateDeltaTime); - } + void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override; }; diff --git a/Source/Engine/Core/ISerializable.h b/Source/Engine/Core/ISerializable.h new file mode 100644 index 000000000..cfc5e1976 --- /dev/null +++ b/Source/Engine/Core/ISerializable.h @@ -0,0 +1,60 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Serialization/JsonFwd.h" +#include "Engine/Core/Compiler.h" +#include "Engine/Core/Config.h" + +class JsonWriter; +class ISerializeModifier; + +/// +/// Interface for objects that can be serialized/deserialized to/from JSON format. +/// +API_INTERFACE() class FLAXENGINE_API ISerializable +{ +DECLARE_SCRIPTING_TYPE_MINIMAL(ISerializable); +public: + + typedef rapidjson_flax::Document SerializeDocument; + + /// + /// Serialization output stream + /// + typedef rapidjson_flax::Value DeserializeStream; + + /// + /// Serialization input stream + /// + typedef JsonWriter SerializeStream; + +public: + + /// + /// Finalizes an instance of the class. + /// + virtual ~ISerializable() = default; + + /// + /// Serialize object to the output stream compared to the values of the other object instance (eg. default class object). If other object is null then serialize all properties. + /// + /// The output stream. + /// The instance of the object to compare with and serialize only the modified properties. If null, then serialize all properties. + virtual void Serialize(SerializeStream& stream, const void* otherObj) = 0; + + /// + /// Deserialize object from the input stream + /// + /// The input stream. + /// The deserialization modifier object. Always valid. + virtual void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) = 0; + + /// + /// Deserialize object from the input stream child member. Won't deserialize it if member is missing. + /// + /// The input stream. + /// The input stream member to lookup. + /// The deserialization modifier object. Always valid. + void DeserializeIfExists(DeserializeStream& stream, const char* memberName, ISerializeModifier* modifier); +}; diff --git a/Source/Engine/Core/Log.cpp b/Source/Engine/Core/Log.cpp index 2213672c9..cb73a79e1 100644 --- a/Source/Engine/Core/Log.cpp +++ b/Source/Engine/Core/Log.cpp @@ -51,18 +51,18 @@ bool Log::Logger::Init() { // Check if there are any files to delete const int32 maxLogFiles = 20; - int32 reaming = oldLogs.Count() - maxLogFiles + 1; - if (reaming > 0) + int32 remaining = oldLogs.Count() - maxLogFiles + 1; + if (remaining > 0) { Sorting::QuickSort(oldLogs.Get(), oldLogs.Count()); // Delete the oldest logs int32 i = 0; - while (reaming > 0) + while (remaining > 0) { FileSystem::DeleteFile(oldLogs[i++]); filesDeleted++; - reaming--; + remaining--; } } } diff --git a/Source/Engine/Core/Math/AABB.h b/Source/Engine/Core/Math/AABB.h index 39e8a5bf6..5853d6597 100644 --- a/Source/Engine/Core/Math/AABB.h +++ b/Source/Engine/Core/Math/AABB.h @@ -8,7 +8,7 @@ /// /// Integer axis aligned bounding box /// -struct AABB +struct FLAXENGINE_API AABB { public: diff --git a/Source/Engine/Core/Math/BoundingFrustum.h b/Source/Engine/Core/Math/BoundingFrustum.h index b8b16c412..9052521fc 100644 --- a/Source/Engine/Core/Math/BoundingFrustum.h +++ b/Source/Engine/Core/Math/BoundingFrustum.h @@ -10,7 +10,7 @@ /// /// Defines a frustum which can be used in frustum culling, zoom to Extents (zoom to fit) operations, (matrix, frustum, camera) interchange, and many kind of intersection testing. /// -API_STRUCT(InBuild) struct BoundingFrustum +API_STRUCT(InBuild) struct FLAXENGINE_API BoundingFrustum { private: diff --git a/Source/Engine/Core/Math/CollisionsHelper.cpp b/Source/Engine/Core/Math/CollisionsHelper.cpp index 48e7472d1..d485e5fba 100644 --- a/Source/Engine/Core/Math/CollisionsHelper.cpp +++ b/Source/Engine/Core/Math/CollisionsHelper.cpp @@ -15,7 +15,7 @@ void CollisionsHelper::ClosestPointPointLine(const Vector2& point, const Vector2 Vector2 n = p1 - p0; const float length = n.Length(); - if (length < 1e-10) + if (length < 1e-10f) { // Both points are the same, just give any result = p0; @@ -24,7 +24,7 @@ void CollisionsHelper::ClosestPointPointLine(const Vector2& point, const Vector2 n /= length; const float dot = Vector2::Dot(n, p); - if (dot <= 0.0) + if (dot <= 0.0f) { // Before first point result = p0; @@ -41,6 +41,45 @@ void CollisionsHelper::ClosestPointPointLine(const Vector2& point, const Vector2 } } +Vector2 CollisionsHelper::ClosestPointPointLine(const Vector2& point, const Vector2& p0, const Vector2& p1) +{ + Vector2 result; + ClosestPointPointLine(point, p0, p1, result); + return result; +} + +void CollisionsHelper::ClosestPointPointLine(const Vector3& point, const Vector3& p0, const Vector3& p1, Vector3& result) +{ + const Vector3 p = point - p0; + Vector3 n = p1 - p0; + const float length = n.Length(); + if (length < 1e-10f) + { + result = p0; + return; + } + n /= length; + const float dot = Vector3::Dot(n, p); + if (dot <= 0.0f) + { + result = p0; + return; + } + if (dot >= length) + { + result = p1; + return; + } + result = p0 + n * dot; +} + +Vector3 CollisionsHelper::ClosestPointPointLine(const Vector3& point, const Vector3& p0, const Vector3& p1) +{ + Vector3 result; + ClosestPointPointLine(point, p0, p1, result); + return result; +} + void CollisionsHelper::ClosestPointPointTriangle(const Vector3& point, const Vector3& vertex1, const Vector3& vertex2, const Vector3& vertex3, Vector3& result) { // Source: Real-Time Collision Detection by Christer Ericson @@ -101,6 +140,13 @@ void CollisionsHelper::ClosestPointPointTriangle(const Vector3& point, const Vec result = vertex1 + ab * v2 + ac * w2; //= u*vertex1 + v*vertex2 + w*vertex3, u = va * denom = 1.0f - v - w } +Vector3 CollisionsHelper::ClosestPointPointTriangle(const Vector3& point, const Vector3& vertex1, const Vector3& vertex2, const Vector3& vertex3) +{ + Vector3 result; + ClosestPointPointTriangle(point, vertex1, vertex2, vertex3, result); + return result; +} + void CollisionsHelper::ClosestPointPlanePoint(const Plane& plane, const Vector3& point, Vector3& result) { // Source: Real-Time Collision Detection by Christer Ericson @@ -112,6 +158,13 @@ void CollisionsHelper::ClosestPointPlanePoint(const Plane& plane, const Vector3& result = point - t * plane.Normal; } +Vector3 CollisionsHelper::ClosestPointPlanePoint(const Plane& plane, const Vector3& point) +{ + Vector3 result; + ClosestPointPlanePoint(plane, point, result); + return result; +} + void CollisionsHelper::ClosestPointBoxPoint(const BoundingBox& box, const Vector3& point, Vector3& result) { // Source: Real-Time Collision Detection by Christer Ericson @@ -122,6 +175,13 @@ void CollisionsHelper::ClosestPointBoxPoint(const BoundingBox& box, const Vector Vector3::Min(temp, box.Maximum, result); } +Vector3 CollisionsHelper::ClosestPointBoxPoint(const BoundingBox& box, const Vector3& point) +{ + Vector3 result; + ClosestPointBoxPoint(box, point, result); + return result; +} + void CollisionsHelper::ClosestPointRectanglePoint(const Rectangle& rect, const Vector2& point, Vector2& result) { Vector2 temp, end; @@ -130,6 +190,13 @@ void CollisionsHelper::ClosestPointRectanglePoint(const Rectangle& rect, const V Vector2::Min(temp, end, result); } +Vector2 CollisionsHelper::ClosestPointRectanglePoint(const Rectangle& rect, const Vector2& point) +{ + Vector2 result; + ClosestPointRectanglePoint(rect, point, result); + return result; +} + void CollisionsHelper::ClosestPointSpherePoint(const BoundingSphere& sphere, const Vector3& point, Vector3& result) { // Source: Jorgy343 @@ -147,6 +214,13 @@ void CollisionsHelper::ClosestPointSpherePoint(const BoundingSphere& sphere, con result += sphere.Center; } +Vector3 CollisionsHelper::ClosestPointSpherePoint(const BoundingSphere& sphere, const Vector3& point) +{ + Vector3 result; + ClosestPointSpherePoint(sphere, point, result); + return result; +} + void CollisionsHelper::ClosestPointSphereSphere(const BoundingSphere& sphere1, const BoundingSphere& sphere2, Vector3& result) { // Source: Jorgy343 @@ -164,6 +238,13 @@ void CollisionsHelper::ClosestPointSphereSphere(const BoundingSphere& sphere1, c result += sphere1.Center; } +Vector3 CollisionsHelper::ClosestPointSphereSphere(const BoundingSphere& sphere1, const BoundingSphere& sphere2) +{ + Vector3 result; + ClosestPointSphereSphere(sphere1, sphere2, result); + return result; +} + float CollisionsHelper::DistancePlanePoint(const Plane& plane, const Vector3& point) { // Source: Real-Time Collision Detection by Christer Ericson @@ -821,7 +902,6 @@ bool CollisionsHelper::RayIntersectsBox(const Ray& ray, const BoundingBox& box, d = Math::Abs(size.Z - Math::Abs(localPoint.Z)); if (d < dMin) { - dMin = d; normal = Vector3(0, 0, Math::Sign(localPoint.Z)); } @@ -990,15 +1070,11 @@ PlaneIntersectionType CollisionsHelper::PlaneIntersectsBox(const Plane& plane, c min.Z = plane.Normal.Z >= 0.0f ? box.Maximum.Z : box.Minimum.Z; float distance = Vector3::Dot(plane.Normal, max); - if (distance + plane.D > Plane::DistanceEpsilon) return PlaneIntersectionType::Front; - distance = Vector3::Dot(plane.Normal, min); - if (distance + plane.D < Plane::DistanceEpsilon) return PlaneIntersectionType::Back; - return PlaneIntersectionType::Intersecting; } @@ -1012,10 +1088,8 @@ PlaneIntersectionType CollisionsHelper::PlaneIntersectsSphere(const Plane& plane if (distance > sphere.Radius) return PlaneIntersectionType::Front; - if (distance < -sphere.Radius) return PlaneIntersectionType::Back; - return PlaneIntersectionType::Intersecting; } @@ -1023,13 +1097,10 @@ bool CollisionsHelper::BoxIntersectsBox(const BoundingBox& box1, const BoundingB { if (box1.Minimum.X > box2.Maximum.X || box2.Minimum.X > box1.Maximum.X) return false; - if (box1.Minimum.Y > box2.Maximum.Y || box2.Minimum.Y > box1.Maximum.Y) return false; - if (box1.Minimum.Z > box2.Maximum.Z || box2.Minimum.Z > box1.Maximum.Z) return false; - return true; } diff --git a/Source/Engine/Core/Math/CollisionsHelper.h b/Source/Engine/Core/Math/CollisionsHelper.h index 33631a682..4bebf2492 100644 --- a/Source/Engine/Core/Math/CollisionsHelper.h +++ b/Source/Engine/Core/Math/CollisionsHelper.h @@ -72,6 +72,33 @@ public: /// When the method completes, contains the closest point between the two objects. static void ClosestPointPointLine(const Vector2& point, const Vector2& p0, const Vector2& p1, Vector2& result); + /// + /// Determines the closest point between a point and a line. + /// + /// The point to test. + /// The line first point. + /// The line second point. + /// The closest point between the two objects. + static Vector2 ClosestPointPointLine(const Vector2& point, const Vector2& p0, const Vector2& p1); + + /// + /// Determines the closest point between a point and a line. + /// + /// The point to test. + /// The line first point. + /// The line second point. + /// When the method completes, contains the closest point between the two objects. + static void ClosestPointPointLine(const Vector3& point, const Vector3& p0, const Vector3& p1, Vector3& result); + + /// + /// Determines the closest point between a point and a line. + /// + /// The point to test. + /// The line first point. + /// The line second point. + /// The closest point between the two objects. + static Vector3 ClosestPointPointLine(const Vector3& point, const Vector3& p0, const Vector3& p1); + /// /// Determines the closest point between a point and a triangle. /// @@ -82,6 +109,16 @@ public: /// When the method completes, contains the closest point between the two objects. static void ClosestPointPointTriangle(const Vector3& point, const Vector3& vertex1, const Vector3& vertex2, const Vector3& vertex3, Vector3& result); + /// + /// Determines the closest point between a point and a triangle. + /// + /// The point to test. + /// The first vertex to test. + /// The second vertex to test. + /// The third vertex to test. + /// The closest point between the two objects. + static Vector3 ClosestPointPointTriangle(const Vector3& point, const Vector3& vertex1, const Vector3& vertex2, const Vector3& vertex3); + /// /// Determines the closest point between a and a point. /// @@ -90,6 +127,14 @@ public: /// When the method completes, contains the closest point between the two objects. static void ClosestPointPlanePoint(const Plane& plane, const Vector3& point, Vector3& result); + /// + /// Determines the closest point between a and a point. + /// + /// The plane to test. + /// The point to test. + /// The closest point between the two objects. + static Vector3 ClosestPointPlanePoint(const Plane& plane, const Vector3& point); + /// /// Determines the closest point between a and a point. /// @@ -98,6 +143,14 @@ public: /// When the method completes, contains the closest point between the two objects. static void ClosestPointBoxPoint(const BoundingBox& box, const Vector3& point, Vector3& result); + /// + /// Determines the closest point between a and a point. + /// + /// The box to test. + /// The point to test. + /// The closest point between the two objects. + static Vector3 ClosestPointBoxPoint(const BoundingBox& box, const Vector3& point); + /// /// Determines the closest point between a and a point. /// @@ -106,6 +159,14 @@ public: /// When the method completes, contains the closest point between the two objects. static void ClosestPointRectanglePoint(const Rectangle& rect, const Vector2& point, Vector2& result); + /// + /// Determines the closest point between a and a point. + /// + /// The rectangle to test. + /// The point to test. + /// The closest point between the two objects. + static Vector2 ClosestPointRectanglePoint(const Rectangle& rect, const Vector2& point); + /// /// Determines the closest point between a and a point. /// @@ -114,6 +175,14 @@ public: /// When the method completes, contains the closest point between the two objects; or, if the point is directly in the center of the sphere, contains . static void ClosestPointSpherePoint(const BoundingSphere& sphere, const Vector3& point, Vector3& result); + /// + /// Determines the closest point between a and a point. + /// + /// The sphere to test. + /// The point to test. + /// The closest point between the two objects; or, if the point is directly in the center of the sphere, contains . + static Vector3 ClosestPointSpherePoint(const BoundingSphere& sphere, const Vector3& point); + /// /// Determines the closest point between a and a . /// @@ -127,6 +196,19 @@ public: /// static void ClosestPointSphereSphere(const BoundingSphere& sphere1, const BoundingSphere& sphere2, Vector3& result); + /// + /// Determines the closest point between a and a . + /// + /// The first sphere to test. + /// The second sphere to test. + /// The closest point between the two objects; or, if the point is directly in the center of the sphere, contains . + /// + /// If the two spheres are overlapping, but not directly on top of each other, the closest point + /// is the 'closest' point of intersection. This can also be considered is the deepest point of + /// intersection. + /// + static Vector3 ClosestPointSphereSphere(const BoundingSphere& sphere1, const BoundingSphere& sphere2); + /// /// Determines the distance between a and a point. /// diff --git a/Source/Engine/Core/Math/Color.cpp b/Source/Engine/Core/Math/Color.cpp index 168db71a5..5a00a52e5 100644 --- a/Source/Engine/Core/Math/Color.cpp +++ b/Source/Engine/Core/Math/Color.cpp @@ -137,6 +137,26 @@ String Color::ToHexString() const return String(result, 6); } +bool Color::IsTransparent() const +{ + return Math::IsZero(R + G + B + A); +} + +bool Color::HasOpacity() const +{ + return !Math::IsOne(A); +} + +bool Color::NearEqual(const Color& a, const Color& b) +{ + return Math::NearEqual(a.R, b.R) && Math::NearEqual(a.G, b.G) && Math::NearEqual(a.B, b.B) && Math::NearEqual(a.A, b.A); +} + +bool Color::NearEqual(const Color& a, const Color& b, float epsilon) +{ + return Math::NearEqual(a.R, b.R, epsilon) && Math::NearEqual(a.G, b.G, epsilon) && Math::NearEqual(a.B, b.B, epsilon) && Math::NearEqual(a.A, b.A, epsilon); +} + Vector3 Color::ToVector3() const { return Vector3(R, G, B); @@ -158,6 +178,21 @@ Vector3 Color::ToHSV() const return Vector3(hue, saturation, value); } +void Color::Lerp(const Color& start, const Color& end, float amount, Color& result) +{ + result.R = Math::Lerp(start.R, end.R, amount); + result.G = Math::Lerp(start.G, end.G, amount); + result.B = Math::Lerp(start.B, end.B, amount); + result.A = Math::Lerp(start.A, end.A, amount); +} + +Color Color::Lerp(const Color& start, const Color& end, float amount) +{ + Color result; + Lerp(start, end, amount, result); + return result; +} + Color Color::LinearToSrgb(const Color& linear) { #define LINEAR_TO_SRGB(value) value < 0.00313067f ? value * 12.92f : Math::Pow(value, (1.0f / 2.4f)) * 1.055f - 0.055f diff --git a/Source/Engine/Core/Math/Color.cs b/Source/Engine/Core/Math/Color.cs index 5aee4c428..96376092e 100644 --- a/Source/Engine/Core/Math/Color.cs +++ b/Source/Engine/Core/Math/Color.cs @@ -280,7 +280,7 @@ namespace FlaxEngine /// /// The hexadecimal string. /// Output value. - /// True if value has benn parsed, otherwise false. + /// True if value has been parsed, otherwise false. public static bool TryParseHex(string hexString, out Color value) { value = Black; @@ -716,7 +716,7 @@ namespace FlaxEngine } /// - /// Converts the color to HSV color space (returned as vector). + /// Gets Hue[0-360], Saturation[0-1] and Value[0-1] from RGB color. /// /// The HSV color. public Vector3 ToHSV() @@ -743,51 +743,6 @@ namespace FlaxEngine return new Vector3(hue, saturation, value); } - /// - /// Convert color from the RGB color space to HSV color space. - /// - /// Color of the RGB. - /// The output Hue. - /// The output Saturation. - /// The output Value. - public static void RGBToHSV(Color rgbColor, out float h, out float s, out float v) - { - if ((rgbColor.B > rgbColor.G) && (rgbColor.B > rgbColor.R)) - RGBToHSVHelper(4f, rgbColor.B, rgbColor.R, rgbColor.G, out h, out s, out v); - else if (rgbColor.G <= rgbColor.R) - RGBToHSVHelper(0f, rgbColor.R, rgbColor.G, rgbColor.B, out h, out s, out v); - else - RGBToHSVHelper(2f, rgbColor.G, rgbColor.B, rgbColor.R, out h, out s, out v); - } - - private static void RGBToHSVHelper(float offset, float dominantcolor, float colorone, float colortwo, out float h, out float s, out float v) - { - v = dominantcolor; - if (Mathf.IsZero(v)) - { - s = 0f; - h = 0f; - } - else - { - var single = colorone <= colortwo ? colorone : colortwo; - float vv = v - single; - if (Mathf.IsZero(vv)) - { - s = 0f; - h = offset + (colorone - colortwo); - } - else - { - s = vv / v; - h = offset + (colorone - colortwo) / vv; - } - h = h / 6f; - if (h < 0f) - h = h + 1f; - } - } - /// /// Adjusts the contrast of a color. /// diff --git a/Source/Engine/Core/Math/Color.h b/Source/Engine/Core/Math/Color.h index d11097d5c..5a7986517 100644 --- a/Source/Engine/Core/Math/Color.h +++ b/Source/Engine/Core/Math/Color.h @@ -166,7 +166,7 @@ public: /// Creates RGB color from Hue[0-360], Saturation[0-1] and Value[0-1] packed to XYZ vector. /// /// The HSV color. - /// The alpha value. Default is 1. + /// The alpha value. Default is 1. /// The RGB color. static Color FromHSV(const Vector3& hsv, float alpha = 1.0f); @@ -264,28 +264,15 @@ public: } // Returns true if color is fully transparent (all components are equal zero). - bool IsTransparent() const - { - return Math::IsZero(R + G + B + A); - } + bool IsTransparent() const; // Returns true if color has opacity channel in use (different from 1). - bool HasOpacity() const - { - return !Math::IsOne(A); - } + bool HasOpacity() const; public: - static bool NearEqual(const Color& a, const Color& b) - { - return Math::NearEqual(a.R, b.R) && Math::NearEqual(a.G, b.G) & Math::NearEqual(a.B, b.B) && Math::NearEqual(a.A, b.A); - } - - static bool NearEqual(const Color& a, const Color& b, float epsilon) - { - return Math::NearEqual(a.R, b.R, epsilon) && Math::NearEqual(a.G, b.G, epsilon) & Math::NearEqual(a.B, b.B, epsilon) && Math::NearEqual(a.A, b.A, epsilon); - } + static bool NearEqual(const Color& a, const Color& b); + static bool NearEqual(const Color& a, const Color& b, float epsilon); public: @@ -296,7 +283,7 @@ public: Vector4 ToVector4() const; /// - /// Gets Hue[0-1], Saturation[0-1] and Value[0-360] from RGB color. + /// Gets Hue[0-360], Saturation[0-1] and Value[0-1] from RGB color. /// /// HSV color Vector3 ToHSV() const; @@ -308,13 +295,7 @@ public: /// The end color. /// The value between 0 and 1 indicating the weight of interpolation. /// When the method completes, contains the linear interpolation of the two colors. - static void Lerp(const Color& start, const Color& end, float amount, Color& result) - { - result.R = Math::Lerp(start.R, end.R, amount); - result.G = Math::Lerp(start.G, end.G, amount); - result.B = Math::Lerp(start.B, end.B, amount); - result.A = Math::Lerp(start.A, end.A, amount); - } + static void Lerp(const Color& start, const Color& end, float amount, Color& result); /// /// Performs a linear interpolation between two colors. @@ -323,12 +304,7 @@ public: /// The end color. /// The value between 0 and 1 indicating the weight of interpolation. /// The linear interpolation of the two colors. - static Color Lerp(const Color& start, const Color& end, float amount) - { - Color result; - Lerp(start, end, amount, result); - return result; - } + static Color Lerp(const Color& start, const Color& end, float amount); // Converts a [0.0, 1.0] linear value into a [0.0, 1.0] sRGB value. static Color LinearToSrgb(const Color& linear); @@ -530,6 +506,14 @@ inline Color operator*(float a, const Color& b) return b * a; } +namespace Math +{ + FORCE_INLINE static bool NearEqual(const Color& a, const Color& b) + { + return Color::NearEqual(a, b); + } +} + template<> struct TIsPODType { diff --git a/Source/Engine/Core/Math/Color32.h b/Source/Engine/Core/Math/Color32.h index 89f64cc35..f848f6f73 100644 --- a/Source/Engine/Core/Math/Color32.h +++ b/Source/Engine/Core/Math/Color32.h @@ -231,6 +231,14 @@ inline Color32 operator*(float a, const Color32& b) return b * a; } +namespace Math +{ + FORCE_INLINE static bool NearEqual(const Color32& a, const Color32& b) + { + return a == b; + } +} + template<> struct TIsPODType { diff --git a/Source/Engine/Core/Math/Half.cpp b/Source/Engine/Core/Math/Half.cpp index 47bf66802..0de29c030 100644 --- a/Source/Engine/Core/Math/Half.cpp +++ b/Source/Engine/Core/Math/Half.cpp @@ -18,81 +18,81 @@ Half4 Half4::Zero(0, 0, 0, 0); Half2::Half2(const Vector2& v) { - X = ConvertFloatToHalf(v.X); - Y = ConvertFloatToHalf(v.Y); + X = Float16Compressor::Compress(v.X); + Y = Float16Compressor::Compress(v.Y); } Vector2 Half2::ToVector2() const { return Vector2( - ConvertHalfToFloat(X), - ConvertHalfToFloat(Y) + Float16Compressor::Decompress(X), + Float16Compressor::Decompress(Y) ); } Half3::Half3(const Vector3& v) { - X = ConvertFloatToHalf(v.X); - Y = ConvertFloatToHalf(v.Y); - Z = ConvertFloatToHalf(v.Z); + X = Float16Compressor::Compress(v.X); + Y = Float16Compressor::Compress(v.Y); + Z = Float16Compressor::Compress(v.Z); } Vector3 Half3::ToVector3() const { return Vector3( - ConvertHalfToFloat(X), - ConvertHalfToFloat(Y), - ConvertHalfToFloat(Z) + Float16Compressor::Decompress(X), + Float16Compressor::Decompress(Y), + Float16Compressor::Decompress(Z) ); } Half4::Half4(const Vector4& v) { - X = ConvertFloatToHalf(v.X); - Y = ConvertFloatToHalf(v.Y); - Z = ConvertFloatToHalf(v.Z); - W = ConvertFloatToHalf(v.W); + X = Float16Compressor::Compress(v.X); + Y = Float16Compressor::Compress(v.Y); + Z = Float16Compressor::Compress(v.Z); + W = Float16Compressor::Compress(v.W); } Half4::Half4(const Color& c) { - X = ConvertFloatToHalf(c.R); - Y = ConvertFloatToHalf(c.G); - Z = ConvertFloatToHalf(c.B); - W = ConvertFloatToHalf(c.A); + X = Float16Compressor::Compress(c.R); + Y = Float16Compressor::Compress(c.G); + Z = Float16Compressor::Compress(c.B); + W = Float16Compressor::Compress(c.A); } Half4::Half4(const Rectangle& rect) { - X = ConvertFloatToHalf(rect.Location.X); - Y = ConvertFloatToHalf(rect.Location.Y); - Z = ConvertFloatToHalf(rect.Size.X); - W = ConvertFloatToHalf(rect.Size.Y); + X = Float16Compressor::Compress(rect.Location.X); + Y = Float16Compressor::Compress(rect.Location.Y); + Z = Float16Compressor::Compress(rect.Size.X); + W = Float16Compressor::Compress(rect.Size.Y); } Vector2 Half4::ToVector2() const { return Vector2( - ConvertHalfToFloat(X), - ConvertHalfToFloat(Y) + Float16Compressor::Decompress(X), + Float16Compressor::Decompress(Y) ); } Vector3 Half4::ToVector3() const { return Vector3( - ConvertHalfToFloat(X), - ConvertHalfToFloat(Y), - ConvertHalfToFloat(Z) + Float16Compressor::Decompress(X), + Float16Compressor::Decompress(Y), + Float16Compressor::Decompress(Z) ); } Vector4 Half4::ToVector4() const { return Vector4( - ConvertHalfToFloat(X), - ConvertHalfToFloat(Y), - ConvertHalfToFloat(Z), - ConvertHalfToFloat(W) + Float16Compressor::Decompress(X), + Float16Compressor::Decompress(Y), + Float16Compressor::Decompress(Z), + Float16Compressor::Decompress(W) ); } diff --git a/Source/Engine/Core/Math/Half.h b/Source/Engine/Core/Math/Half.h index 19059ad0c..e0773b1d4 100644 --- a/Source/Engine/Core/Math/Half.h +++ b/Source/Engine/Core/Math/Half.h @@ -11,8 +11,14 @@ typedef uint16 Half; #define USE_SSE_HALF_CONVERSION 0 -class Float16Compressor +/// +/// Utility for packing/unpacking floating point value from single precision (32 bit) to half precision (16 bit). +/// +class FLAXENGINE_API Float16Compressor { + // Reference: + // http://www.cs.cmu.edu/~jinlianw/third_party/float16_compressor.hpp + union Bits { float f; @@ -22,24 +28,19 @@ class Float16Compressor static const int shift = 13; static const int shiftSign = 16; - static const int32 infN = 0x7F800000; // flt32 infinity static const int32 maxN = 0x477FE000; // max flt16 normal as a flt32 static const int32 minN = 0x38800000; // min flt16 normal as a flt32 static const int32 signN = 0x80000000; // flt32 sign bit - static const int32 infC = infN >> shift; static const int32 nanN = (infC + 1) << shift; // minimum flt16 nan as a flt32 static const int32 maxC = maxN >> shift; static const int32 minC = minN >> shift; static const int32 signC = signN >> shiftSign; // flt16 sign bit - static const int32 mulN = 0x52000000; // (1 << 23) / minN static const int32 mulC = 0x33800000; // minN / (1 << (23 - shift)) - static const int32 subC = 0x003FF; // max flt32 subnormal down shifted static const int32 norC = 0x00400; // min flt32 normal down shifted - static const int32 maxD = infC - maxC - 1; static const int32 minD = minC - subC - 1; @@ -48,9 +49,9 @@ public: static Half Compress(const float value) { #if USE_SSE_HALF_CONVERSION - __m128 V1 = _mm_set_ss(value); - __m128i V2 = _mm_cvtps_ph(V1, 0); - return static_cast(_mm_cvtsi128_si32(V2)); + __m128 value1 = _mm_set_ss(value); + __m128i value2 = _mm_cvtps_ph(value1, 0); + return static_cast(_mm_cvtsi128_si32(value2)); #else Bits v, s; v.f = value; @@ -72,9 +73,9 @@ public: static float Decompress(const Half value) { #if USE_SSE_HALF_CONVERSION - __m128i V1 = _mm_cvtsi32_si128(static_cast(value)); - __m128 V2 = _mm_cvtph_ps(V1); - return _mm_cvtss_f32(V2); + __m128i value1 = _mm_cvtsi32_si128(static_cast(value)); + __m128 value2 = _mm_cvtph_ps(value1); + return _mm_cvtss_f32(value2); #else Bits v; v.ui = value; @@ -95,20 +96,10 @@ public: } }; -inline float ConvertHalfToFloat(const Half value) -{ - return Float16Compressor::Decompress(value); -} - -inline Half ConvertFloatToHalf(const float value) -{ - return Float16Compressor::Compress(value); -} - /// /// Defines a two component vector, using half precision floating point coordinates. /// -struct Half2 +struct FLAXENGINE_API Half2 { public: @@ -145,8 +136,8 @@ public: /// Y component Half2(float x, float y) { - X = ConvertFloatToHalf(x); - Y = ConvertFloatToHalf(y); + X = Float16Compressor::Compress(x); + Y = Float16Compressor::Compress(y); } /// @@ -167,7 +158,7 @@ public: /// /// Defines a three component vector, using half precision floating point coordinates. /// -struct Half3 +struct FLAXENGINE_API Half3 { public: @@ -201,9 +192,9 @@ public: Half3(const float x, const float y, const float z) { - X = ConvertFloatToHalf(x); - Y = ConvertFloatToHalf(y); - Z = ConvertFloatToHalf(z); + X = Float16Compressor::Compress(x); + Y = Float16Compressor::Compress(y); + Z = Float16Compressor::Compress(z); } Half3(const Vector3& v); @@ -216,7 +207,7 @@ public: /// /// Defines a four component vector, using half precision floating point coordinates. /// -struct Half4 +struct FLAXENGINE_API Half4 { public: @@ -255,31 +246,27 @@ public: Half4(const float x, const float y, const float z) { - X = ConvertFloatToHalf(x); - Y = ConvertFloatToHalf(y); - Z = ConvertFloatToHalf(z); + X = Float16Compressor::Compress(x); + Y = Float16Compressor::Compress(y); + Z = Float16Compressor::Compress(z); W = 0; } Half4(const float x, const float y, const float z, const float w) { - X = ConvertFloatToHalf(x); - Y = ConvertFloatToHalf(y); - Z = ConvertFloatToHalf(z); - W = ConvertFloatToHalf(w); + X = Float16Compressor::Compress(x); + Y = Float16Compressor::Compress(y); + Z = Float16Compressor::Compress(z); + W = Float16Compressor::Compress(w); } explicit Half4(const Vector4& v); - explicit Half4(const Color& c); - explicit Half4(const Rectangle& rect); public: Vector2 ToVector2() const; - Vector3 ToVector3() const; - Vector4 ToVector4() const; }; diff --git a/Source/Engine/Core/Math/Int2.h b/Source/Engine/Core/Math/Int2.h new file mode 100644 index 000000000..a6038d1b0 --- /dev/null +++ b/Source/Engine/Core/Math/Int2.h @@ -0,0 +1,279 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Math.h" +#include "Engine/Core/Formatting.h" +#include "Engine/Core/Templates.h" + +struct Vector2; +struct Vector3; +struct Vector4; + +/// +/// Two-components vector (32 bit integer type). +/// +API_STRUCT(InBuild) struct FLAXENGINE_API Int2 +{ +public: + + union + { + struct + { + // X component + int32 X; + + // Y component + int32 Y; + }; + + // Raw values + int32 Raw[2]; + }; + +public: + + // Vector with all components equal 0 + static const Int2 Zero; + + // Vector with all components equal 1 + static const Int2 One; + +public: + + /// + /// Empty constructor. + /// + Int2() + { + } + + // Init + // @param xy Value to assign to the all components + Int2(int32 xy) + : X(xy) + , Y(xy) + { + } + + // Init + // @param x X component value + // @param y Y component value + Int2(int32 x, int32 y) + : X(x) + , Y(y) + { + } + + // Init + // @param v Vector to use X and Y components + explicit Int2(const Vector2& v); + +public: + + String ToString() const; + +public: + + // Arithmetic operators with Int2 + + Int2 operator+(const Int2& b) const + { + return Add(*this, b); + } + + Int2 operator-(const Int2& b) const + { + return Subtract(*this, b); + } + + Int2 operator*(const Int2& b) const + { + return Multiply(*this, b); + } + + Int2 operator/(const Int2& b) const + { + return Divide(*this, b); + } + + Int2 operator-() const + { + return Int2(-X, -Y); + } + + // op= operators with Int2 + + Int2& operator+=(const Int2& b) + { + *this = Add(*this, b); + return *this; + } + + Int2& operator-=(const Int2& b) + { + *this = Subtract(*this, b); + return *this; + } + + Int2& operator*=(const Int2& b) + { + *this = Multiply(*this, b); + return *this; + } + + Int2& operator/=(const Int2& b) + { + *this = Divide(*this, b); + return *this; + } + + // Arithmetic operators with int32 + + Int2 operator+(int32 b) const + { + return Add(*this, b); + } + + Int2 operator-(int32 b) const + { + return Subtract(*this, b); + } + + Int2 operator*(int32 b) const + { + return Multiply(*this, b); + } + + Int2 operator/(int32 b) const + { + return Divide(*this, b); + } + + // op= operators with int32 + + Int2& operator+=(int32 b) + { + *this = Add(*this, b); + return *this; + } + + Int2& operator-=(int32 b) + { + *this = Subtract(*this, b); + return *this; + } + + Int2& operator*=(int32 b) + { + *this = Multiply(*this, b); + return *this; + } + + Int2& operator/=(int32 b) + { + *this = Divide(*this, b); + return *this; + } + + // Comparison operators + + bool operator==(const Int2& b) const + { + return X == b.X && Y == b.Y; + } + + bool operator!=(const Int2& b) const + { + return X != b.X || Y != b.Y; + } + + bool operator>(const Int2& b) const + { + return X > b.X && Y > b.Y; + } + + bool operator>=(const Int2& b) const + { + return X >= b.X && Y >= b.Y; + } + + bool operator<(const Int2& b) const + { + return X < b.X && Y < b.Y; + } + + bool operator<=(const Int2& b) const + { + return X <= b.X && Y <= b.Y; + } + +public: + + static void Add(const Int2& a, const Int2& b, Int2* result) + { + result->X = a.X + b.X; + result->Y = a.Y + b.Y; + } + + static Int2 Add(const Int2& a, const Int2& b) + { + Int2 result; + Add(a, b, &result); + return result; + } + + static void Subtract(const Int2& a, const Int2& b, Int2* result) + { + result->X = a.X - b.X; + result->Y = a.Y - b.Y; + } + + static Int2 Subtract(const Int2& a, const Int2& b) + { + Int2 result; + Subtract(a, b, &result); + return result; + } + + static Int2 Multiply(const Int2& a, const Int2& b) + { + return Int2(a.X * b.X, a.Y * b.Y); + } + + static Int2 Multiply(const Int2& a, int32 b) + { + return Int2(a.X * b, a.Y * b); + } + + static Int2 Divide(const Int2& a, const Int2& b) + { + return Int2(a.X / b.X, a.Y / b.Y); + } + + static Int2 Divide(const Int2& a, int32 b) + { + return Int2(a.X / b, a.Y / b); + } + + // Creates vector from minimum components of two vectors + static Int2 Min(const Int2& a, const Int2& b) + { + return Int2(a.X < b.X ? a.X : b.X, a.Y < b.Y ? a.Y : b.Y); + } + + // Creates vector from maximum components of two vectors + static Int2 Max(const Int2& a, const Int2& b) + { + return Int2(a.X > b.X ? a.X : b.X, a.Y > b.Y ? a.Y : b.Y); + } +}; + +template<> +struct TIsPODType +{ + enum { Value = true }; +}; + +DEFINE_DEFAULT_FORMATTING(Int2, "X:{0} Y:{1}", v.X, v.Y); diff --git a/Source/Engine/Core/Math/Int3.h b/Source/Engine/Core/Math/Int3.h new file mode 100644 index 000000000..6886acc6b --- /dev/null +++ b/Source/Engine/Core/Math/Int3.h @@ -0,0 +1,128 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Math.h" +#include "Engine/Core/Formatting.h" +#include "Engine/Core/Templates.h" + +struct Vector2; +struct Vector3; +struct Vector4; + +/// +/// Three-components vector (32 bit integer type). +/// +API_STRUCT(InBuild) struct FLAXENGINE_API Int3 +{ +public: + + union + { + struct + { + // X component + int32 X; + + // Y component + int32 Y; + + // Y component + int32 Z; + }; + + // Raw values + int32 Raw[3]; + }; + +public: + + // Vector with all components equal 0 + static const Int3 Zero; + + // Vector with all components equal 1 + static const Int3 One; + +public: + + /// + /// Empty constructor. + /// + Int3() + { + } + + // Init + // @param xy Value to assign to the all components + Int3(int32 xyz) + : X(xyz) + , Y(xyz) + , Z(xyz) + { + } + + // Init + // @param x X component value + // @param y Y component value + // @param z Z component value + Int3(int32 x, int32 y, int32 z) + : X(x) + , Y(y) + , Z(z) + { + } + + // Init + // @param v Vector to use X, Y and Z components + explicit Int3(const Vector3& v); + +public: + + String ToString() const; + +public: + + // Returns a vector containing the largest components of the specified vectors + // @param a The first source vector + // @param b The second source vector + // @param result When the method completes, contains an new vector composed of the largest components of the source vectors + static Int3 Max(const Int3& a, const Int3& b) + { + return Int3(a.X > b.X ? a.X : b.X, a.Y > b.Y ? a.Y : b.Y, a.Z > b.Z ? a.Z : b.Z); + } + + // Returns a vector containing the smallest components of the specified vectors + // @param a The first source vector + // @param b The second source vector + // @param result When the method completes, contains an new vector composed of the smallest components of the source vectors + static Int3 Min(const Int3& a, const Int3& b) + { + return Int3(a.X < b.X ? a.X : b.X, a.Y < b.Y ? a.Y : b.Y, a.Z < b.Z ? a.Z : b.Z); + } + + // Returns a vector containing the largest components of the specified vectors + // @param a The first source vector + // @param b The second source vector + // @param result When the method completes, contains an new vector composed of the largest components of the source vectors + static void Max(const Int3& a, const Int3& b, Int3* result) + { + *result = Int3(a.X > b.X ? a.X : b.X, a.Y > b.Y ? a.Y : b.Y, a.Z > b.Z ? a.Z : b.Z); + } + + // Returns a vector containing the smallest components of the specified vectors + // @param a The first source vector + // @param b The second source vector + // @param result When the method completes, contains an new vector composed of the smallest components of the source vectors + static void Min(const Int3& a, const Int3& b, Int3* result) + { + *result = Int3(a.X < b.X ? a.X : b.X, a.Y < b.Y ? a.Y : b.Y, a.Z < b.Z ? a.Z : b.Z); + } +}; + +template<> +struct TIsPODType +{ + enum { Value = true }; +}; + +DEFINE_DEFAULT_FORMATTING(Int3, "X:{0} Y:{1} Z:{2}", v.X, v.Y, v.Z); diff --git a/Source/Engine/Core/Math/Int4.h b/Source/Engine/Core/Math/Int4.h new file mode 100644 index 000000000..c8e9eebd8 --- /dev/null +++ b/Source/Engine/Core/Math/Int4.h @@ -0,0 +1,134 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Math.h" +#include "Engine/Core/Formatting.h" +#include "Engine/Core/Templates.h" + +struct Vector2; +struct Vector3; +struct Vector4; + +/// +/// Four-components vector (32 bit integer type). +/// +API_STRUCT(InBuild) struct FLAXENGINE_API Int4 +{ +public: + + union + { + struct + { + // X component + int32 X; + + // Y component + int32 Y; + + // Z component + int32 Z; + + // W component + int32 W; + }; + + // Raw values + int32 Raw[4]; + }; + +public: + + // Vector with all components equal 0 + static const Int4 Zero; + + // Vector with all components equal 1 + static const Int4 One; + +public: + + /// + /// Empty constructor. + /// + Int4() + { + } + + // Init + // @param xy Value to assign to the all components + Int4(int32 xyzw) + : X(xyzw) + , Y(xyzw) + , Z(xyzw) + , W(xyzw) + { + } + + // Init + // @param x X component value + // @param y Y component value + // @param z Z component value + // @param w W component value + Int4(int32 x, int32 y, int32 z, int32 w) + : X(x) + , Y(y) + , Z(z) + , W(w) + { + } + + // Init + // @param v Vector to use X, Y, Z and W components + explicit Int4(const Vector4& v); + +public: + + String ToString() const; + +public: + + /// + /// Returns average arithmetic of all the components + /// + /// Average arithmetic of all the components + float AverageArithmetic() const + { + return (X + Y + Z + W) * 0.25f; + } + + /// + /// Gets sum of all vector components values + /// + /// Sum of X, Y, Z and W + int32 SumValues() const + { + return X + Y + Z + W; + } + + /// + /// Returns minimum value of all the components + /// + /// Minimum value + int32 MinValue() const + { + return Math::Min(X, Y, Z, W); + } + + /// + /// Returns maximum value of all the components + /// + /// Maximum value + int32 MaxValue() const + { + return Math::Max(X, Y, Z, W); + } +}; + +template<> +struct TIsPODType +{ + enum { Value = true }; +}; + +DEFINE_DEFAULT_FORMATTING(Int4, "X:{0} Y:{1} Z:{2} W:{3}", v.X, v.Y, v.Z, v.W); diff --git a/Source/Engine/Core/Math/Math.cpp b/Source/Engine/Core/Math/Math.cpp index 4075bcbda..d0642d7ca 100644 --- a/Source/Engine/Core/Math/Math.cpp +++ b/Source/Engine/Core/Math/Math.cpp @@ -5,8 +5,8 @@ void Math::SinCos(float angle, float& sine, float& cosine) { - sine = sin(angle); - cosine = cos(angle); + sine = Math::Sin(angle); + cosine = Math::Cos(angle); } uint32 Math::FloorLog2(uint32 value) diff --git a/Source/Engine/Core/Math/Mathf.cs b/Source/Engine/Core/Math/Mathf.cs index 7286573bf..529bd6ab0 100644 --- a/Source/Engine/Core/Math/Mathf.cs +++ b/Source/Engine/Core/Math/Mathf.cs @@ -36,6 +36,11 @@ namespace FlaxEngine /// public const float PiOverFour = (float)(Math.PI / 4); + /// + /// A value specifying the golden mean + /// + public const float GoldenRatio = 1.6180339887f; + /// /// Returns the absolute value of f. /// diff --git a/Source/Engine/Core/Math/Matrix3x3.h b/Source/Engine/Core/Math/Matrix3x3.h index 3cf690259..b28be5437 100644 --- a/Source/Engine/Core/Math/Matrix3x3.h +++ b/Source/Engine/Core/Math/Matrix3x3.h @@ -9,7 +9,7 @@ /// /// Represents a 3x3 mathematical matrix. /// -API_STRUCT(InBuild) struct Matrix3x3 +API_STRUCT(InBuild) struct FLAXENGINE_API Matrix3x3 { public: diff --git a/Source/Engine/Core/Math/Matrix3x4.h b/Source/Engine/Core/Math/Matrix3x4.h new file mode 100644 index 000000000..49f6cc1ff --- /dev/null +++ b/Source/Engine/Core/Math/Matrix3x4.h @@ -0,0 +1,55 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Matrix.h" + +/// +/// Helper matrix for optimized float3x4 package of transformation matrices. +/// +struct FLAXENGINE_API Matrix3x4 +{ + float M[3][4]; + + void SetMatrix(const Matrix& m) + { + const float* src = m.Raw; + float* dst = &M[0][0]; + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + dst[3] = src[3]; + dst[4] = src[4]; + dst[5] = src[5]; + dst[6] = src[6]; + dst[7] = src[7]; + dst[8] = src[8]; + dst[9] = src[9]; + dst[10] = src[10]; + dst[11] = src[11]; + } + + void SetMatrixTranspose(const Matrix& m) + { + const float* src = m.Raw; + float* dst = &M[0][0]; + dst[0] = src[0]; + dst[1] = src[4]; + dst[2] = src[8]; + dst[3] = src[12]; + dst[4] = src[1]; + dst[5] = src[5]; + dst[6] = src[9]; + dst[7] = src[13]; + dst[8] = src[2]; + dst[9] = src[6]; + dst[10] = src[10]; + dst[11] = src[14]; + } +}; + +template<> +struct TIsPODType +{ + enum { Value = true }; +}; diff --git a/Source/Engine/Core/Math/OrientedBoundingBox.h b/Source/Engine/Core/Math/OrientedBoundingBox.h index 03256633d..d19108f84 100644 --- a/Source/Engine/Core/Math/OrientedBoundingBox.h +++ b/Source/Engine/Core/Math/OrientedBoundingBox.h @@ -8,7 +8,7 @@ #include "CollisionsHelper.h" // Oriented Bounding Box (OBB) is a rectangular block, much like an AABB (Bounding Box) but with an arbitrary orientation in 3D space. -API_STRUCT(InBuild) struct OrientedBoundingBox +API_STRUCT(InBuild) struct FLAXENGINE_API OrientedBoundingBox { public: diff --git a/Source/Engine/Core/Math/Packed.h b/Source/Engine/Core/Math/Packed.h index ec666bf61..4f41dac9c 100644 --- a/Source/Engine/Core/Math/Packed.h +++ b/Source/Engine/Core/Math/Packed.h @@ -13,7 +13,7 @@ typedef Half Float16; /// /// Packed vector, layout: R:10 bytes, G:10 bytes, B:10 bytes, A:2 bytes, all values are stored as floats in range [0;1]. /// -struct Float1010102 +struct FLAXENGINE_API Float1010102 { union { @@ -64,7 +64,7 @@ public: }; // The 3D vector is packed into 32 bits with 11/11/10 bits per floating-point component. -struct FloatR11G11B10 +struct FLAXENGINE_API FloatR11G11B10 { union { @@ -118,7 +118,7 @@ public: Vector3 ToVector3() const; }; -struct RG16UNorm +struct FLAXENGINE_API RG16UNorm { uint16 X, Y; @@ -131,7 +131,7 @@ struct RG16UNorm Vector2 ToVector2() const; }; -struct RGBA16UNorm +struct FLAXENGINE_API RGBA16UNorm { uint16 X, Y, Z, W; diff --git a/Source/Engine/Core/Math/Plane.h b/Source/Engine/Core/Math/Plane.h index 2ca34258f..2fbe7438c 100644 --- a/Source/Engine/Core/Math/Plane.h +++ b/Source/Engine/Core/Math/Plane.h @@ -8,7 +8,7 @@ /// /// Represents a plane in three dimensional space. /// -API_STRUCT() struct Plane +API_STRUCT() struct FLAXENGINE_API Plane { DECLARE_SCRIPTING_TYPE_MINIMAL(Plane); public: diff --git a/Source/Engine/Core/Math/Quaternion.cs b/Source/Engine/Core/Math/Quaternion.cs index bc86fa2c9..938bf48b1 100644 --- a/Source/Engine/Core/Math/Quaternion.cs +++ b/Source/Engine/Core/Math/Quaternion.cs @@ -324,6 +324,14 @@ namespace FlaxEngine Z = -Z; } + /// + /// Gets the conjugated quaternion. + /// + public Quaternion Conjugated() + { + return new Quaternion(-X, -Y, -Z, W); + } + /// /// Conjugates and renormalizes the quaternion. /// @@ -627,7 +635,7 @@ namespace FlaxEngine public static float AngleBetween(Quaternion a, Quaternion b) { float num = Dot(a, b); - return num > 0.99999999f ? 0 : Mathf.Acos(Mathf.Min(Mathf.Abs(num), 1f)) * 2f * 57.29578f; + return num > 0.9999999f ? 0 : Mathf.Acos(Mathf.Min(Mathf.Abs(num), 1f)) * 2f * 57.29578f; } /// @@ -1470,7 +1478,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Quaternion left, Quaternion right) { - return Dot(ref left, ref right) > 0.99999999f; + return Dot(ref left, ref right) > 0.9999999f; } /// @@ -1485,7 +1493,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Quaternion left, Quaternion right) { - return Dot(ref left, ref right) <= 0.99999999f; + return Dot(ref left, ref right) <= 0.9999999f; } /// @@ -1597,7 +1605,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(ref Quaternion other) { - //return Dot(ref this, ref other) > 0.99999999f; + //return Dot(ref this, ref other) > 0.9999999f; return Mathf.NearEqual(other.X, X) && Mathf.NearEqual(other.Y, Y) && Mathf.NearEqual(other.Z, Z) && Mathf.NearEqual(other.W, W); } diff --git a/Source/Engine/Core/Math/Quaternion.h b/Source/Engine/Core/Math/Quaternion.h index 54ea49540..d3eeb4f32 100644 --- a/Source/Engine/Core/Math/Quaternion.h +++ b/Source/Engine/Core/Math/Quaternion.h @@ -391,7 +391,7 @@ public: /// true if the specified is equal to this instance; otherwise, false. FORCE_INLINE bool operator==(const Quaternion& other) const { - return Dot(*this, other) > 0.99999999f; + return Dot(*this, other) > 0.9999999f; } /// @@ -414,7 +414,7 @@ public: /// true if the specified structures are equal; otherwise, false. static bool NearEqual(const Quaternion& a, const Quaternion& b) { - return Dot(a, b) > 0.99999999f; + return Dot(a, b) > 0.9999999f; } /// @@ -474,7 +474,7 @@ public: static float AngleBetween(const Quaternion& a, const Quaternion& b) { const float dot = Dot(a, b); - return dot > 0.99999999f ? 0 : Math::Acos(Math::Min(Math::Abs(dot), 1.0f)) * 2.0f * 57.29578f; + return dot > 0.9999999f ? 0 : Math::Acos(Math::Min(Math::Abs(dot), 1.0f)) * 2.0f * 57.29578f; } // Adds two quaternions diff --git a/Source/Engine/Core/Math/Ray.h b/Source/Engine/Core/Math/Ray.h index 56d06a21e..f125cba97 100644 --- a/Source/Engine/Core/Math/Ray.h +++ b/Source/Engine/Core/Math/Ray.h @@ -11,7 +11,7 @@ struct Viewport; /// /// Represents a three dimensional line based on a point in space and a direction. /// -API_STRUCT() struct Ray +API_STRUCT() struct FLAXENGINE_API Ray { DECLARE_SCRIPTING_TYPE_MINIMAL(Ray); public: diff --git a/Source/Engine/Core/Math/Rectangle.h b/Source/Engine/Core/Math/Rectangle.h index 305ae70e1..f2dcbd20a 100644 --- a/Source/Engine/Core/Math/Rectangle.h +++ b/Source/Engine/Core/Math/Rectangle.h @@ -10,7 +10,6 @@ API_STRUCT() struct FLAXENGINE_API Rectangle { DECLARE_SCRIPTING_TYPE_MINIMAL(Rectangle); -public: /// /// The empty rectangle. diff --git a/Source/Engine/Core/Math/Transform.cpp b/Source/Engine/Core/Math/Transform.cpp index 5ac71779b..35f616236 100644 --- a/Source/Engine/Core/Math/Transform.cpp +++ b/Source/Engine/Core/Math/Transform.cpp @@ -43,37 +43,42 @@ void Transform::GetWorld(Matrix& result) const Transform Transform::Add(const Vector3& translation) const { Transform result; - result.Orientation = Orientation; result.Scale = Scale; Vector3::Add(Translation, translation, result.Translation); - return result; } Transform Transform::Add(const Transform& other) const { Transform result; - Quaternion::Multiply(Orientation, other.Orientation, result.Orientation); result.Orientation.Normalize(); Vector3::Multiply(Scale, other.Scale, result.Scale); Vector3::Add(Translation, other.Translation, result.Translation); + return result; +} +Transform Transform::Subtract(const Transform& other) const +{ + Transform result; + Vector3::Subtract(Translation, other.Translation, result.Translation); + const Quaternion invRotation = other.Orientation.Conjugated(); + Quaternion::Multiply(Orientation, invRotation, result.Orientation); + result.Orientation.Normalize(); + Vector3::Divide(Scale, other.Scale, result.Scale); return result; } Transform Transform::LocalToWorld(const Transform& other) const { Transform result; - Quaternion::Multiply(Orientation, other.Orientation, result.Orientation); result.Orientation.Normalize(); Vector3::Multiply(Scale, other.Scale, result.Scale); Vector3 tmp = other.Translation * Scale; Vector3::Transform(tmp, Orientation, tmp); Vector3::Add(tmp, Translation, result.Translation); - return result; } @@ -94,6 +99,13 @@ Vector3 Transform::LocalToWorld(const Vector3& point) const return result + Translation; } +Vector3 Transform::LocalToWorldVector(const Vector3& vector) const +{ + Vector3 result = vector * Scale; + Vector3::Transform(result, Orientation, result); + return result; +} + void Transform::LocalToWorld(const Vector3& point, Vector3& result) const { Vector3 tmp = point * Scale; @@ -171,6 +183,24 @@ Vector3 Transform::WorldToLocal(const Vector3& point) const return result * invScale; } +Vector3 Transform::WorldToLocalVector(const Vector3& vector) const +{ + Vector3 invScale = Scale; + if (invScale.X != 0.0f) + invScale.X = 1.0f / invScale.X; + if (invScale.Y != 0.0f) + invScale.Y = 1.0f / invScale.Y; + if (invScale.Z != 0.0f) + invScale.Z = 1.0f / invScale.Z; + + const Quaternion invRotation = Orientation.Conjugated(); + + Vector3 result; + Vector3::Transform(vector, invRotation, result); + + return result * invScale; +} + void Transform::WorldToLocal(const Vector3* points, int32 pointsCount, Vector3* result) const { Vector3 invScale = Scale; diff --git a/Source/Engine/Core/Math/Transform.cs b/Source/Engine/Core/Math/Transform.cs index 21ed705ff..129ddf92a 100644 --- a/Source/Engine/Core/Math/Transform.cs +++ b/Source/Engine/Core/Math/Transform.cs @@ -169,6 +169,37 @@ namespace FlaxEngine Matrix.Transformation(ref Scale, ref Orientation, ref Translation, out result); } + /// + /// Adds two transforms. + /// + /// The first transform to add. + /// The second transform to add. + /// The sum of the two transforms. + public static Transform Add(Transform left, Transform right) + { + Transform result; + Quaternion.Multiply(ref left.Orientation, ref right.Orientation, out result.Orientation); + Vector3.Multiply(ref left.Scale, ref right.Scale, out result.Scale); + Vector3.Add(ref left.Translation, ref right.Translation, out result.Translation); + return result; + } + + /// + /// Subtracts two transforms. + /// + /// The first transform to subtract from. + /// The second transform to subtract. + /// The difference of the two transforms. + public static Transform Subtract(Transform left, Transform right) + { + Transform result; + Vector3.Subtract(ref left.Translation, ref right.Translation, out result.Translation); + Quaternion invRotation = right.Orientation.Conjugated(); + Quaternion.Multiply(ref left.Orientation, ref invRotation, out result.Orientation); + Vector3.Divide(ref left.Scale, ref right.Scale, out result.Scale); + return result; + } + /// /// Perform transformation of the given transform in local space /// @@ -176,12 +207,10 @@ namespace FlaxEngine /// World space transform public Transform LocalToWorld(Transform other) { - Transform result = new Transform(Vector3.Zero); - + Transform result; Quaternion.Multiply(ref Orientation, ref other.Orientation, out result.Orientation); Vector3.Multiply(ref Scale, ref other.Scale, out result.Scale); result.Translation = LocalToWorld(other.Translation); - return result; } @@ -197,6 +226,18 @@ namespace FlaxEngine return point + Translation; } + /// + /// Performs transformation of the given vector in local space to the world space of this transform. + /// + /// The local space vector. + /// The world space vector. + public Vector3 LocalToWorldVector(Vector3 vector) + { + vector *= Scale; + Vector3.Transform(ref vector, ref Orientation, out vector); + return vector; + } + /// /// Perform transformation of the given points in local space /// @@ -217,7 +258,6 @@ namespace FlaxEngine /// Local space transform public Transform WorldToLocal(Transform other) { - Transform result = new Transform(Vector3.Zero); Vector3 invScale = Scale; if (invScale.X != 0.0f) invScale.X = 1.0f / invScale.X; @@ -226,6 +266,7 @@ namespace FlaxEngine if (invScale.Z != 0.0f) invScale.Z = 1.0f / invScale.Z; + Transform result; result.Orientation = Orientation; result.Orientation.Invert(); Quaternion.Multiply(ref result.Orientation, ref other.Orientation, out result.Orientation); @@ -259,6 +300,29 @@ namespace FlaxEngine return result * invScale; } + /// + /// Perform transformation of the given vector in world space + /// + /// World space vector + /// Local space vector + public Vector3 WorldToLocalVector(Vector3 vector) + { + Vector3 invScale = Scale; + if (invScale.X != 0.0f) + invScale.X = 1.0f / invScale.X; + if (invScale.Y != 0.0f) + invScale.Y = 1.0f / invScale.Y; + if (invScale.Z != 0.0f) + invScale.Z = 1.0f / invScale.Z; + + Quaternion invRotation = Orientation; + invRotation.Invert(); + + Vector3.Transform(ref vector, ref invRotation, out var result); + + return result * invScale; + } + /// /// Perform transformation of the given points in world space /// @@ -369,6 +433,28 @@ namespace FlaxEngine return !left.Equals(ref right); } + /// + /// Adds two transformations. + /// + /// The first transform to add. + /// The second transform to add. + /// The sum of the two transformations. + public static Transform operator +(Transform left, Transform right) + { + return Add(left, right); + } + + /// + /// Subtracts two transformations. + /// + /// The first transform to subtract from. + /// The second transform to subtract. + /// The difference of the two transformations. + public static Transform operator -(Transform left, Transform right) + { + return Subtract(left, right); + } + /// /// Returns a that represents this instance. /// diff --git a/Source/Engine/Core/Math/Transform.h b/Source/Engine/Core/Math/Transform.h index 3b8cf71b7..069a49992 100644 --- a/Source/Engine/Core/Math/Transform.h +++ b/Source/Engine/Core/Math/Transform.h @@ -11,10 +11,9 @@ struct Matrix; /// /// Describes transformation in a 3D space. /// -API_STRUCT() struct Transform +API_STRUCT() struct FLAXENGINE_API Transform { DECLARE_SCRIPTING_TYPE_MINIMAL(Transform); -public: /// /// The translation vector of the transform. @@ -164,6 +163,13 @@ public: /// The sum of two transformations. Transform Add(const Transform& other) const; + /// + /// Subtracts transformation from this transform. + /// + /// The other transformation. + /// The different of two transformations. + Transform Subtract(const Transform& other) const; + /// /// Performs transformation of the given transform in local space to the world space of this transform. /// @@ -185,6 +191,13 @@ public: /// The world space point. Vector3 LocalToWorld(const Vector3& point) const; + /// + /// Performs transformation of the given vector in local space to the world space of this transform. + /// + /// The local space vector. + /// The world space vector. + Vector3 LocalToWorldVector(const Vector3& vector) const; + /// /// Performs transformation of the given point in local space to the world space of this transform. /// @@ -221,6 +234,13 @@ public: /// The local space point. Vector3 WorldToLocal(const Vector3& point) const; + /// + /// Performs transformation of the given vector in world space to the local space of this transform. + /// + /// The world space vector. + /// The local space vector. + Vector3 WorldToLocalVector(const Vector3& vector) const; + /// /// Performs transformation of the given points in world space to the local space of this transform. /// @@ -241,6 +261,11 @@ public: return Add(other); } + FORCE_INLINE Transform operator-(const Transform& other) const + { + return Subtract(other); + } + FORCE_INLINE Transform operator+(const Vector3& other) const { return Add(other); @@ -294,6 +319,14 @@ public: static void Lerp(const Transform& t1, const Transform& t2, float amount, Transform& result); }; +namespace Math +{ + FORCE_INLINE static bool NearEqual(const Transform& a, const Transform& b) + { + return Transform::NearEqual(a, b); + } +} + template<> struct TIsPODType { diff --git a/Source/Engine/Core/Math/Triangle.h b/Source/Engine/Core/Math/Triangle.h index 03f91ece3..b0f7ea09d 100644 --- a/Source/Engine/Core/Math/Triangle.h +++ b/Source/Engine/Core/Math/Triangle.h @@ -8,7 +8,7 @@ /// /// Represents a three dimensional triangle. /// -struct Triangle +struct FLAXENGINE_API Triangle { public: diff --git a/Source/Engine/Core/Math/Vector2.cpp b/Source/Engine/Core/Math/Vector2.cpp index 6a0468033..c23e79d19 100644 --- a/Source/Engine/Core/Math/Vector2.cpp +++ b/Source/Engine/Core/Math/Vector2.cpp @@ -4,8 +4,8 @@ #include "Vector3.h" #include "Vector4.h" #include "Color.h" +#include "Int2.h" #include "../Types/String.h" -#include "VectorInt.h" static_assert(sizeof(Vector2) == 8, "Invalid Vector2 type size."); diff --git a/Source/Engine/Core/Math/Vector3.cpp b/Source/Engine/Core/Math/Vector3.cpp index e3ff349ba..f66bbcbbd 100644 --- a/Source/Engine/Core/Math/Vector3.cpp +++ b/Source/Engine/Core/Math/Vector3.cpp @@ -6,7 +6,7 @@ #include "Color.h" #include "Quaternion.h" #include "Matrix.h" -#include "VectorInt.h" +#include "Int3.h" #include "../Types/String.h" static_assert(sizeof(Vector3) == 12, "Invalid Vector3 type size."); diff --git a/Source/Engine/Core/Math/Vector3.cs b/Source/Engine/Core/Math/Vector3.cs index db27cf8ed..a57fcb224 100644 --- a/Source/Engine/Core/Math/Vector3.cs +++ b/Source/Engine/Core/Math/Vector3.cs @@ -1221,7 +1221,7 @@ namespace FlaxEngine float dot = Mathf.Clamp(Dot(from.Normalized, to.Normalized), -1F, 1F); if (Mathf.Abs(dot) > (1F - Mathf.Epsilon)) return dot > 0F ? 0F : 180F; - return Mathf.Acos(dot) * Mathf.DegreesToRadians; + return Mathf.Acos(dot) * Mathf.RadiansToDegrees; } /// diff --git a/Source/Engine/Core/Math/Vector4.cpp b/Source/Engine/Core/Math/Vector4.cpp index 3caaab985..34fa27aac 100644 --- a/Source/Engine/Core/Math/Vector4.cpp +++ b/Source/Engine/Core/Math/Vector4.cpp @@ -6,7 +6,7 @@ #include "Color.h" #include "Matrix.h" #include "Rectangle.h" -#include "VectorInt.h" +#include "Int4.h" #include "../Types/String.h" static_assert(sizeof(Vector4) == 16, "Invalid Vector4 type size."); diff --git a/Source/Engine/Core/Math/Vector4.h b/Source/Engine/Core/Math/Vector4.h index adca955fc..c7771aa67 100644 --- a/Source/Engine/Core/Math/Vector4.h +++ b/Source/Engine/Core/Math/Vector4.h @@ -379,7 +379,7 @@ public: static bool NearEqual(const Vector4& a, const Vector4& b) { - return Math::NearEqual(a.X, b.X) && Math::NearEqual(a.Y, b.Y) & Math::NearEqual(a.Z, b.Z) && Math::NearEqual(a.W, b.W); + return Math::NearEqual(a.X, b.X) && Math::NearEqual(a.Y, b.Y) && Math::NearEqual(a.Z, b.Z) && Math::NearEqual(a.W, b.W); } static bool NearEqual(const Vector4& a, const Vector4& b, float epsilon) diff --git a/Source/Engine/Core/Math/VectorInt.h b/Source/Engine/Core/Math/VectorInt.h index 80804c39a..d9611d615 100644 --- a/Source/Engine/Core/Math/VectorInt.h +++ b/Source/Engine/Core/Math/VectorInt.h @@ -2,518 +2,6 @@ #pragma once -#include "Math.h" -#include "Engine/Core/Formatting.h" -#include "Engine/Core/Templates.h" - -struct Vector2; -struct Vector3; -struct Vector4; - -/// -/// Two-components vector (32 bit integer type). -/// -API_STRUCT(InBuild) struct Int2 -{ -public: - - union - { - struct - { - // X component - int32 X; - - // Y component - int32 Y; - }; - - // Raw values - int32 Raw[2]; - }; - -public: - - // Vector with all components equal 0 - static const Int2 Zero; - - // Vector with all components equal 1 - static const Int2 One; - -public: - - /// - /// Empty constructor. - /// - Int2() - { - } - - // Init - // @param xy Value to assign to the all components - Int2(int32 xy) - : X(xy) - , Y(xy) - { - } - - // Init - // @param x X component value - // @param y Y component value - Int2(int32 x, int32 y) - : X(x) - , Y(y) - { - } - - // Init - // @param v Vector to use X and Y components - explicit Int2(const Vector2& v); - -public: - - String ToString() const; - -public: - - // Arithmetic operators with Int2 - - Int2 operator+(const Int2& b) const - { - return Add(*this, b); - } - - Int2 operator-(const Int2& b) const - { - return Subtract(*this, b); - } - - Int2 operator*(const Int2& b) const - { - return Multiply(*this, b); - } - - Int2 operator/(const Int2& b) const - { - return Divide(*this, b); - } - - Int2 operator-() const - { - return Int2(-X, -Y); - } - - // op= operators with Int2 - - Int2& operator+=(const Int2& b) - { - *this = Add(*this, b); - return *this; - } - - Int2& operator-=(const Int2& b) - { - *this = Subtract(*this, b); - return *this; - } - - Int2& operator*=(const Int2& b) - { - *this = Multiply(*this, b); - return *this; - } - - Int2& operator/=(const Int2& b) - { - *this = Divide(*this, b); - return *this; - } - - // Arithmetic operators with int32 - - Int2 operator+(int32 b) const - { - return Add(*this, b); - } - - Int2 operator-(int32 b) const - { - return Subtract(*this, b); - } - - Int2 operator*(int32 b) const - { - return Multiply(*this, b); - } - - Int2 operator/(int32 b) const - { - return Divide(*this, b); - } - - // op= operators with int32 - - Int2& operator+=(int32 b) - { - *this = Add(*this, b); - return *this; - } - - Int2& operator-=(int32 b) - { - *this = Subtract(*this, b); - return *this; - } - - Int2& operator*=(int32 b) - { - *this = Multiply(*this, b); - return *this; - } - - Int2& operator/=(int32 b) - { - *this = Divide(*this, b); - return *this; - } - - // Comparison operators - - bool operator==(const Int2& b) const - { - return X == b.X && Y == b.Y; - } - - bool operator!=(const Int2& b) const - { - return X != b.X || Y != b.Y; - } - - bool operator>(const Int2& b) const - { - return X > b.X && Y > b.Y; - } - - bool operator>=(const Int2& b) const - { - return X >= b.X && Y >= b.Y; - } - - bool operator<(const Int2& b) const - { - return X < b.X && Y < b.Y; - } - - bool operator<=(const Int2& b) const - { - return X <= b.X && Y <= b.Y; - } - -public: - - static void Add(const Int2& a, const Int2& b, Int2* result) - { - result->X = a.X + b.X; - result->Y = a.Y + b.Y; - } - - static Int2 Add(const Int2& a, const Int2& b) - { - Int2 result; - Add(a, b, &result); - return result; - } - - static void Subtract(const Int2& a, const Int2& b, Int2* result) - { - result->X = a.X - b.X; - result->Y = a.Y - b.Y; - } - - static Int2 Subtract(const Int2& a, const Int2& b) - { - Int2 result; - Subtract(a, b, &result); - return result; - } - - static Int2 Multiply(const Int2& a, const Int2& b) - { - return Int2(a.X * b.X, a.Y * b.Y); - } - - static Int2 Multiply(const Int2& a, int32 b) - { - return Int2(a.X * b, a.Y * b); - } - - static Int2 Divide(const Int2& a, const Int2& b) - { - return Int2(a.X / b.X, a.Y / b.Y); - } - - static Int2 Divide(const Int2& a, int32 b) - { - return Int2(a.X / b, a.Y / b); - } - - // Creates vector from minimum components of two vectors - static Int2 Min(const Int2& a, const Int2& b) - { - return Int2(a.X < b.X ? a.X : b.X, a.Y < b.Y ? a.Y : b.Y); - } - - // Creates vector from maximum components of two vectors - static Int2 Max(const Int2& a, const Int2& b) - { - return Int2(a.X > b.X ? a.X : b.X, a.Y > b.Y ? a.Y : b.Y); - } -}; - -/// -/// Three-components vector (32 bit integer type). -/// -API_STRUCT(InBuild) struct Int3 -{ -public: - - union - { - struct - { - // X component - int32 X; - - // Y component - int32 Y; - - // Y component - int32 Z; - }; - - // Raw values - int32 Raw[3]; - }; - -public: - - // Vector with all components equal 0 - static const Int3 Zero; - - // Vector with all components equal 1 - static const Int3 One; - -public: - - /// - /// Empty constructor. - /// - Int3() - { - } - - // Init - // @param xy Value to assign to the all components - Int3(int32 xyz) - : X(xyz) - , Y(xyz) - , Z(xyz) - { - } - - // Init - // @param x X component value - // @param y Y component value - // @param z Z component value - Int3(int32 x, int32 y, int32 z) - : X(x) - , Y(y) - , Z(z) - { - } - - // Init - // @param v Vector to use X, Y and Z components - explicit Int3(const Vector3& v); - -public: - - String ToString() const; - -public: - - // Returns a vector containing the largest components of the specified vectors - // @param a The first source vector - // @param b The second source vector - // @param result When the method completes, contains an new vector composed of the largest components of the source vectors - static Int3 Max(const Int3& a, const Int3& b) - { - return Int3(a.X > b.X ? a.X : b.X, a.Y > b.Y ? a.Y : b.Y, a.Z > b.Z ? a.Z : b.Z); - } - - // Returns a vector containing the smallest components of the specified vectors - // @param a The first source vector - // @param b The second source vector - // @param result When the method completes, contains an new vector composed of the smallest components of the source vectors - static Int3 Min(const Int3& a, const Int3& b) - { - return Int3(a.X < b.X ? a.X : b.X, a.Y < b.Y ? a.Y : b.Y, a.Z < b.Z ? a.Z : b.Z); - } - - // Returns a vector containing the largest components of the specified vectors - // @param a The first source vector - // @param b The second source vector - // @param result When the method completes, contains an new vector composed of the largest components of the source vectors - static void Max(const Int3& a, const Int3& b, Int3* result) - { - *result = Int3(a.X > b.X ? a.X : b.X, a.Y > b.Y ? a.Y : b.Y, a.Z > b.Z ? a.Z : b.Z); - } - - // Returns a vector containing the smallest components of the specified vectors - // @param a The first source vector - // @param b The second source vector - // @param result When the method completes, contains an new vector composed of the smallest components of the source vectors - static void Min(const Int3& a, const Int3& b, Int3* result) - { - *result = Int3(a.X < b.X ? a.X : b.X, a.Y < b.Y ? a.Y : b.Y, a.Z < b.Z ? a.Z : b.Z); - } -}; - -/// -/// Four-components vector (32 bit integer type). -/// -API_STRUCT(InBuild) struct Int4 -{ -public: - - union - { - struct - { - // X component - int32 X; - - // Y component - int32 Y; - - // Z component - int32 Z; - - // W component - int32 W; - }; - - // Raw values - int32 Raw[4]; - }; - -public: - - // Vector with all components equal 0 - static const Int4 Zero; - - // Vector with all components equal 1 - static const Int4 One; - -public: - - /// - /// Empty constructor. - /// - Int4() - { - } - - // Init - // @param xy Value to assign to the all components - Int4(int32 xyzw) - : X(xyzw) - , Y(xyzw) - , Z(xyzw) - , W(xyzw) - { - } - - // Init - // @param x X component value - // @param y Y component value - // @param z Z component value - // @param w W component value - Int4(int32 x, int32 y, int32 z, int32 w) - : X(x) - , Y(y) - , Z(z) - , W(w) - { - } - - // Init - // @param v Vector to use X, Y, Z and W components - explicit Int4(const Vector4& v); - -public: - - String ToString() const; - -public: - - /// - /// Returns average arithmetic of all the components - /// - /// Average arithmetic of all the components - float AverageArithmetic() const - { - return (X + Y + Z + W) * 0.25f; - } - - /// - /// Gets sum of all vector components values - /// - /// Sum of X, Y, Z and W - int32 SumValues() const - { - return X + Y + Z + W; - } - - /// - /// Returns minimum value of all the components - /// - /// Minimum value - int32 MinValue() const - { - return Math::Min(X, Y, Z, W); - } - - /// - /// Returns maximum value of all the components - /// - /// Maximum value - int32 MaxValue() const - { - return Math::Max(X, Y, Z, W); - } -}; - -template<> -struct TIsPODType -{ - enum { Value = true }; -}; - -template<> -struct TIsPODType -{ - enum { Value = true }; -}; - -template<> -struct TIsPODType -{ - enum { Value = true }; -}; - -DEFINE_DEFAULT_FORMATTING(Int2, "X:{0} Y:{1}", v.X, v.Y); - -DEFINE_DEFAULT_FORMATTING(Int3, "X:{0} Y:{1} Z:{2}", v.X, v.Y, v.Z); - -DEFINE_DEFAULT_FORMATTING(Int4, "X:{0} Y:{1} Z:{2} W:{3}", v.X, v.Y, v.Z, v.W); +#include "Int2.h" +#include "Int3.h" +#include "Int4.h" diff --git a/Source/Engine/Core/Math/Viewport.h b/Source/Engine/Core/Math/Viewport.h index 0b5871f2e..e30d085d3 100644 --- a/Source/Engine/Core/Math/Viewport.h +++ b/Source/Engine/Core/Math/Viewport.h @@ -10,7 +10,7 @@ struct Matrix; struct Rectangle; // Describes the viewport dimensions. -API_STRUCT(InBuild) struct Viewport +API_STRUCT(InBuild) struct FLAXENGINE_API Viewport { public: diff --git a/Source/Engine/Core/ObjectsRemovalService.cpp b/Source/Engine/Core/ObjectsRemovalService.cpp index a36265646..f9d26f808 100644 --- a/Source/Engine/Core/ObjectsRemovalService.cpp +++ b/Source/Engine/Core/ObjectsRemovalService.cpp @@ -193,7 +193,7 @@ void ObjectsRemovalServiceService::Dispose() // Collect new objects ObjectsRemovalService::Flush(); - // Delete all reaming objects + // Delete all remaining objects { ScopeLock lock(PoolLocker); for (auto i = Pool.Begin(); i.IsNotEnd(); ++i) diff --git a/Source/Engine/Core/Types/BaseTypes.h b/Source/Engine/Core/Types/BaseTypes.h index c8cf21f99..b57f139ff 100644 --- a/Source/Engine/Core/Types/BaseTypes.h +++ b/Source/Engine/Core/Types/BaseTypes.h @@ -108,7 +108,5 @@ class Dictionary; inline T& operator&= (T& a, T b) { return (T&)((int&)a &= (int)b); } \ inline T& operator^= (T& a, T b) { return (T&)((int&)a ^= (int)b); } -#define DECLARE_SCRIPTING_TYPE_MINIMAL(type) \ - public: \ - friend class type##Internal; \ - static struct ScriptingTypeInitializer TypeInitializer; +// Returns byte offset from the object pointer in vtable to the begin of the given inherited type implementation +#define VTABLE_OFFSET(type, baseType) (((intptr)static_cast((type*)1))-1) diff --git a/Source/Engine/Core/Types/DataContainer.h b/Source/Engine/Core/Types/DataContainer.h index 954ff24e2..8b504e58f 100644 --- a/Source/Engine/Core/Types/DataContainer.h +++ b/Source/Engine/Core/Types/DataContainer.h @@ -324,9 +324,7 @@ public: void Append(T* data, int32 length) { if (length <= 0) - { return; - } if (Base::Length() == 0) { Copy(data, length); @@ -337,16 +335,13 @@ public: const auto prevLength = Base::_length; Base::_length = prevLength + length; - Base::_data = Allocator::Allocate(Base::_length * sizeof(T)); + Base::_data = (T*)Allocator::Allocate(Base::_length * sizeof(T)); Platform::MemoryCopy(Base::_data, prev, prevLength * sizeof(T)); Platform::MemoryCopy(Base::_data + prevLength * sizeof(T), data, length * sizeof(T)); if (_isAllocated && prev) - { Allocator::Free(prev); - } - _isAllocated = true; } diff --git a/Source/Engine/Core/Types/String.h b/Source/Engine/Core/Types/String.h index 4d1ef4e7f..cf21a1236 100644 --- a/Source/Engine/Core/Types/String.h +++ b/Source/Engine/Core/Types/String.h @@ -220,7 +220,7 @@ public: /// The index of the found substring or -1 if not found. int32 Find(const T* subStr, StringSearchCase searchCase = StringSearchCase::CaseSensitive, int32 startPosition = -1) const { - if (subStr == nullptr) + if (subStr == nullptr || !_data) return -1; const T* start = _data; if (startPosition != -1) @@ -241,7 +241,7 @@ public: int32 FindLast(const T* subStr, StringSearchCase searchCase = StringSearchCase::CaseSensitive, int32 startPosition = -1) const { const int32 subStrLen = StringUtils::Length(subStr); - if (subStrLen == 0) + if (subStrLen == 0 || !_data) return -1; if (startPosition == -1) startPosition = Length(); diff --git a/Source/Engine/Core/Types/Variant.cpp b/Source/Engine/Core/Types/Variant.cpp index 62c791875..db71c2f77 100644 --- a/Source/Engine/Core/Types/Variant.cpp +++ b/Source/Engine/Core/Types/Variant.cpp @@ -2612,7 +2612,8 @@ Variant Variant::Lerp(const Variant& a, const Variant& b, float alpha) void Variant::AllocStructure() { - const ScriptingTypeHandle typeHandle = Scripting::FindScriptingType(StringAnsiView(Type.TypeName)); + const StringAnsiView typeName(Type.TypeName); + const ScriptingTypeHandle typeHandle = Scripting::FindScriptingType(typeName); if (typeHandle) { const ScriptingType& type = typeHandle.GetType(); @@ -2620,8 +2621,26 @@ void Variant::AllocStructure() AsBlob.Data = Allocator::Allocate(AsBlob.Length); type.Struct.Ctor(AsBlob.Data); } + else if (typeName == "System.Byte") + { + // Hack for byte + AsBlob.Length = 1; + AsBlob.Data = Allocator::Allocate(AsBlob.Length); + *((byte*)AsBlob.Data) = 0; + } + else if (typeName == "System.Int16" || typeName == "System.UInt16") + { + // Hack for 16bit int + AsBlob.Length = 2; + AsBlob.Data = Allocator::Allocate(AsBlob.Length); + *((int16*)AsBlob.Data) = 0; + } else { + if (typeName.Length() != 0) + { + LOG(Warning, "Missing scripting type \'{0}\'", String(typeName.Get())); + } AsBlob.Data = nullptr; AsBlob.Length = 0; } @@ -2637,6 +2656,10 @@ void Variant::CopyStructure(void* src) auto& type = typeHandle.GetType(); type.Struct.Copy(AsBlob.Data, src); } + else + { + Platform::MemoryCopy(AsBlob.Data, src, AsBlob.Length); + } } } diff --git a/Source/Engine/Debug/DebugDraw.cpp b/Source/Engine/Debug/DebugDraw.cpp index b5e4b8540..032fc0960 100644 --- a/Source/Engine/Debug/DebugDraw.cpp +++ b/Source/Engine/Debug/DebugDraw.cpp @@ -19,6 +19,7 @@ #include "Engine/Graphics/RenderBuffers.h" #include "Engine/Animations/AnimationUtils.h" #include "Engine/Profiler/Profiler.h" +#include "Engine/Debug/DebugLog.h" // Debug draw service configuration #define DEBUG_DRAW_INITIAL_VB_CAPACITY (4 * 1024) @@ -130,6 +131,8 @@ struct DebugDrawData Array OneFrameLines; Array DefaultTriangles; Array OneFrameTriangles; + Array DefaultWireTriangles; + Array OneFrameWireTriangles; inline int32 Count() const { @@ -143,7 +146,7 @@ struct DebugDrawData inline int32 TrianglesCount() const { - return DefaultTriangles.Count() + OneFrameTriangles.Count(); + return DefaultTriangles.Count() + OneFrameTriangles.Count() + DefaultWireTriangles.Count() + OneFrameWireTriangles.Count(); } inline void Add(const DebugLine& l) @@ -162,13 +165,23 @@ struct DebugDrawData OneFrameTriangles.Add(t); } + inline void AddWire(const DebugTriangle& t) + { + if (t.TimeLeft > 0) + DefaultWireTriangles.Add(t); + else + OneFrameWireTriangles.Add(t); + } + inline void Update(float deltaTime) { UpdateList(deltaTime, DefaultLines); UpdateList(deltaTime, DefaultTriangles); + UpdateList(deltaTime, DefaultWireTriangles); OneFrameLines.Clear(); OneFrameTriangles.Clear(); + OneFrameWireTriangles.Clear(); } inline void Clear() @@ -177,6 +190,8 @@ struct DebugDrawData OneFrameLines.Clear(); DefaultTriangles.Clear(); OneFrameTriangles.Clear(); + DefaultWireTriangles.Clear(); + OneFrameWireTriangles.Clear(); } inline void Release() @@ -185,16 +200,20 @@ struct DebugDrawData OneFrameLines.Resize(0); DefaultTriangles.Resize(0); OneFrameTriangles.Resize(0); + DefaultWireTriangles.Resize(0); + OneFrameWireTriangles.Resize(0); } }; DebugDrawData DebugDrawDefault; DebugDrawData DebugDrawDepthTest; AssetReference DebugDrawShader; -PsData DebugDrawPsWireDefault; -PsData DebugDrawPsWireDepthTest; -PsData DebugDrawPsDefault; -PsData DebugDrawPsDepthTest; +PsData DebugDrawPsLinesDefault; +PsData DebugDrawPsLinesDepthTest; +PsData DebugDrawPsWireTrianglesDefault; +PsData DebugDrawPsWireTrianglesDepthTest; +PsData DebugDrawPsTrianglesDefault; +PsData DebugDrawPsTrianglesDepthTest; DynamicVertexBuffer* DebugDrawVB = nullptr; Vector3 SphereCache[DEBUG_DRAW_SPHERE_VERTICES]; Vector3 CircleCache[DEBUG_DRAW_CIRCLE_VERTICES]; @@ -453,16 +472,21 @@ void DebugDrawService::Update() // Default desc.PS = shader->GetPS("PS"); desc.PrimitiveTopologyType = PrimitiveTopologyType::Line; - failed |= DebugDrawPsWireDefault.Create(desc); + failed |= DebugDrawPsLinesDefault.Create(desc); desc.PrimitiveTopologyType = PrimitiveTopologyType::Triangle; - failed |= DebugDrawPsDefault.Create(desc); + failed |= DebugDrawPsTrianglesDefault.Create(desc); + desc.Wireframe = true; + failed |= DebugDrawPsWireTrianglesDefault.Create(desc); // Depth Test + desc.Wireframe = false; desc.PS = shader->GetPS("PS_DepthTest"); desc.PrimitiveTopologyType = PrimitiveTopologyType::Line; - failed |= DebugDrawPsWireDepthTest.Create(desc); + failed |= DebugDrawPsLinesDepthTest.Create(desc); desc.PrimitiveTopologyType = PrimitiveTopologyType::Triangle; - failed |= DebugDrawPsDepthTest.Create(desc); + failed |= DebugDrawPsTrianglesDepthTest.Create(desc); + desc.Wireframe = true; + failed |= DebugDrawPsWireTrianglesDepthTest.Create(desc); if (failed) { @@ -482,12 +506,14 @@ void DebugDrawService::Dispose() // Release resources SphereTriangleCache.Resize(0); - DebugDrawPsWireDefault.Release(); - DebugDrawPsWireDepthTest.Release(); - DebugDrawPsDepthTest.Release(); - DebugDrawPsDepthTest.Release(); + DebugDrawPsLinesDefault.Release(); + DebugDrawPsLinesDepthTest.Release(); + DebugDrawPsWireTrianglesDefault.Release(); + DebugDrawPsWireTrianglesDepthTest.Release(); + DebugDrawPsTrianglesDefault.Release(); + DebugDrawPsTrianglesDepthTest.Release(); SAFE_DELETE(DebugDrawVB); - DebugDrawShader.Unlink(); + DebugDrawShader = nullptr; } void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTextureView* depthBuffer, bool enableDepthTest) @@ -495,9 +521,9 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe PROFILE_GPU_CPU("Debug Draw"); // Ensure to have shader loaded and any lines to render - const int32 DebugDrawDepthTestCount = DebugDrawDepthTest.Count(); - const int32 DebugDrawDefaultCount = DebugDrawDefault.Count(); - if (DebugDrawShader == nullptr || !DebugDrawShader->IsLoaded() || DebugDrawDepthTestCount + DebugDrawDefaultCount == 0) + const int32 debugDrawDepthTestCount = DebugDrawDepthTest.Count(); + const int32 debugDrawDefaultCount = DebugDrawDefault.Count(); + if (DebugDrawShader == nullptr || !DebugDrawShader->IsLoaded() || debugDrawDepthTestCount + debugDrawDefaultCount == 0) return; if (renderContext.Buffers == nullptr || !DebugDrawVB) return; @@ -517,6 +543,8 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe const DebugDrawCall defaultLines = WriteLists(vertexCounter, DebugDrawDefault.DefaultLines, DebugDrawDefault.OneFrameLines); const DebugDrawCall depthTestTriangles = WriteLists(vertexCounter, DebugDrawDepthTest.DefaultTriangles, DebugDrawDepthTest.OneFrameTriangles); const DebugDrawCall defaultTriangles = WriteLists(vertexCounter, DebugDrawDefault.DefaultTriangles, DebugDrawDefault.OneFrameTriangles); + const DebugDrawCall depthTestWireTriangles = WriteLists(vertexCounter, DebugDrawDepthTest.DefaultWireTriangles, DebugDrawDepthTest.OneFrameWireTriangles); + const DebugDrawCall defaultWireTriangles = WriteLists(vertexCounter, DebugDrawDefault.DefaultWireTriangles, DebugDrawDefault.OneFrameWireTriangles); DebugDrawVB->Flush(context); #if COMPILE_WITH_PROFILER ProfilerCPU::EndEvent(updateBufferProfileKey); @@ -536,7 +564,7 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe #define DRAW(drawCall) if (drawCall.VertexCount) // Draw with depth test - if (depthTestLines.VertexCount + depthTestTriangles.VertexCount > 0) + if (depthTestLines.VertexCount + depthTestTriangles.VertexCount + depthTestWireTriangles.VertexCount > 0) { if (data.EnableDepthTest) context->BindSR(0, renderContext.Buffers->DepthBuffer); @@ -547,16 +575,25 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe // Lines if (depthTestLines.VertexCount) { - auto state = data.EnableDepthTest ? &DebugDrawPsWireDepthTest : &DebugDrawPsWireDefault; + auto state = data.EnableDepthTest ? &DebugDrawPsLinesDepthTest : &DebugDrawPsLinesDefault; context->SetState(state->Get(enableDepthWrite, true)); context->BindVB(ToSpan(&vb, 1)); context->Draw(depthTestLines.StartVertex, depthTestLines.VertexCount); } + + // Wire Triangles + if (depthTestWireTriangles.VertexCount) + { + auto state = data.EnableDepthTest ? &DebugDrawPsWireTrianglesDepthTest : &DebugDrawPsWireTrianglesDefault; + context->SetState(state->Get(enableDepthWrite, true)); + context->BindVB(ToSpan(&vb, 1)); + context->Draw(depthTestWireTriangles.StartVertex, depthTestWireTriangles.VertexCount); + } // Triangles if (depthTestTriangles.VertexCount) { - auto state = data.EnableDepthTest ? &DebugDrawPsDepthTest : &DebugDrawPsDefault; + auto state = data.EnableDepthTest ? &DebugDrawPsTrianglesDepthTest : &DebugDrawPsTrianglesDefault; context->SetState(state->Get(enableDepthWrite, true)); context->BindVB(ToSpan(&vb, 1)); context->Draw(depthTestTriangles.StartVertex, depthTestTriangles.VertexCount); @@ -567,22 +604,30 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe } // Draw without depth - if (defaultLines.VertexCount + defaultTriangles.VertexCount > 0) + if (defaultLines.VertexCount + defaultTriangles.VertexCount + defaultWireTriangles.VertexCount > 0) { context->SetRenderTarget(target); // Lines if (defaultLines.VertexCount) { - context->SetState(DebugDrawPsWireDefault.Get(false, false)); + context->SetState(DebugDrawPsLinesDefault.Get(false, false)); context->BindVB(ToSpan(&vb, 1)); context->Draw(defaultLines.StartVertex, defaultLines.VertexCount); } + // Wire Triangles + if (defaultWireTriangles.VertexCount) + { + context->SetState(DebugDrawPsWireTrianglesDefault.Get(false, false)); + context->BindVB(ToSpan(&vb, 1)); + context->Draw(defaultWireTriangles.StartVertex, defaultWireTriangles.VertexCount); + } + // Triangles if (defaultTriangles.VertexCount) { - context->SetState(DebugDrawPsDefault.Get(false, false)); + context->SetState(DebugDrawPsTrianglesDefault.Get(false, false)); context->BindVB(ToSpan(&vb, 1)); context->Draw(defaultTriangles.StartVertex, defaultTriangles.VertexCount); } @@ -606,6 +651,7 @@ namespace void DebugDraw::DrawActors(Actor** selectedActors, int32 selectedActorsCount) { + PROFILE_CPU(); if (selectedActors) { for (int32 i = 0; i < selectedActorsCount; i++) @@ -635,7 +681,11 @@ void DebugDraw::DrawLine(const Vector3& start, const Vector3& end, const Color& void DebugDraw::DrawLines(const Span& lines, const Matrix& transform, const Color& color, float duration, bool depthTest) { - ASSERT(lines.Length() % 2 == 0); + if (lines.Length() % 2 != 0) + { + DebugLog::ThrowException("Cannot draw debug lines with uneven amount of items in array"); + return; + } // Create draw call entry DebugLine l = { Vector3::Zero, Vector3::Zero, Color32(color), duration }; @@ -643,10 +693,12 @@ void DebugDraw::DrawLines(const Span& lines, const Matrix& transform, c // Add lines const Vector3* p = lines.Get(); Array* list; - if (depthTest) + + if (depthTest) list = duration > 0 ? &DebugDrawDepthTest.DefaultLines : &DebugDrawDepthTest.OneFrameLines; else list = duration > 0 ? &DebugDrawDefault.DefaultLines : &DebugDrawDefault.OneFrameLines; + list->EnsureCapacity(list->Count() + lines.Length()); for (int32 i = 0; i < lines.Length(); i += 2) { @@ -656,16 +708,6 @@ void DebugDraw::DrawLines(const Span& lines, const Matrix& transform, c } } -static Vector3 GetPoint(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t) -{ - const float oneMinusT = 1.0f - t; - return - oneMinusT * oneMinusT * oneMinusT * p0 + - 3.0f * oneMinusT * oneMinusT * t * p1 + - 3.0f * oneMinusT * t * t * p2 + - t * t * t * p3; -} - void DebugDraw::DrawBezier(const Vector3& p1, const Vector3& p2, const Vector3& p3, const Vector3& p4, const Color& color, float duration, bool depthTest) { // Create draw call entry @@ -682,13 +724,13 @@ void DebugDraw::DrawBezier(const Vector3& p1, const Vector3& p2, const Vector3& const Vector3 d3 = p4 - p3; const float len = d1.Length() + d2.Length() + d3.Length(); const int32 segmentCount = Math::Clamp(Math::CeilToInt(len * 0.05f), 1, 100); - const float segmentCountInv = 1.0f / segmentCount; + const float segmentCountInv = 1.0f / (float)segmentCount; list->EnsureCapacity(list->Count() + segmentCount + 2); // Draw segmented curve for (int32 i = 0; i <= segmentCount; i++) { - const float t = i * segmentCountInv; + const float t = (float)i * segmentCountInv; AnimationUtils::Bezier(p1, p2, p3, p4, t, l.End); list->Add(l); l.Start = l.End; @@ -884,18 +926,22 @@ void DebugDraw::DrawTriangles(const Span& vertices, const Color& color, list = duration > 0 ? &DebugDrawDepthTest.DefaultTriangles : &DebugDrawDepthTest.OneFrameTriangles; else list = duration > 0 ? &DebugDrawDefault.DefaultTriangles : &DebugDrawDefault.OneFrameTriangles; - list->EnsureCapacity(list->Count() + vertices.Length()); + list->EnsureCapacity(list->Count() + vertices.Length() / 3); - for (int32 i = 0; i < vertices.Length() * 3;) + for (int32 i = 0; i < vertices.Length();) { t.V0 = vertices[i++]; t.V1 = vertices[i++]; t.V2 = vertices[i++]; - list->Add(t); } } +void DebugDraw::DrawTriangles(const Array& vertices, const Color& color, float duration, bool depthTest) +{ + DrawTriangles(Span(vertices.Get(), vertices.Count()), color, duration, depthTest); +} + void DebugDraw::DrawTriangles(const Span& vertices, const Span& indices, const Color& color, float duration, bool depthTest) { ASSERT(indices.Length() % 3 == 0); @@ -909,18 +955,80 @@ void DebugDraw::DrawTriangles(const Span& vertices, const Span& list = duration > 0 ? &DebugDrawDepthTest.DefaultTriangles : &DebugDrawDepthTest.OneFrameTriangles; else list = duration > 0 ? &DebugDrawDefault.DefaultTriangles : &DebugDrawDefault.OneFrameTriangles; - list->EnsureCapacity(list->Count() + indices.Length()); + list->EnsureCapacity(list->Count() + indices.Length() / 3); - for (int32 i = 0; i < indices.Length() * 3;) + for (int32 i = 0; i < indices.Length();) { t.V0 = vertices[indices[i++]]; t.V1 = vertices[indices[i++]]; t.V2 = vertices[indices[i++]]; - list->Add(t); } } +void DebugDraw::DrawTriangles(const Array& vertices, const Array& indices, const Color& color, float duration, bool depthTest) +{ + DrawTriangles(Span(vertices.Get(), vertices.Count()), Span(indices.Get(), indices.Count()), color, duration, depthTest); +} + +void DebugDraw::DrawWireTriangles(const Span& vertices, const Color& color, float duration, bool depthTest) +{ + ASSERT(vertices.Length() % 3 == 0); + + DebugTriangle t; + t.Color = Color32(color); + t.TimeLeft = duration; + + Array* list; + if (depthTest) + list = duration > 0 ? &DebugDrawDepthTest.DefaultWireTriangles : &DebugDrawDepthTest.OneFrameWireTriangles; + else + list = duration > 0 ? &DebugDrawDefault.DefaultWireTriangles : &DebugDrawDefault.OneFrameWireTriangles; + list->EnsureCapacity(list->Count() + vertices.Length() / 3); + + for (int32 i = 0; i < vertices.Length();) + { + t.V0 = vertices[i++]; + t.V1 = vertices[i++]; + t.V2 = vertices[i++]; + list->Add(t); + } +} + +void DebugDraw::DrawWireTriangles(const Array& vertices, const Color& color, float duration, bool depthTest) +{ + DrawWireTriangles(Span(vertices.Get(), vertices.Count()), color, duration, depthTest); +} + +void DebugDraw::DrawWireTriangles(const Span& vertices, const Span& indices, const Color& color, float duration, bool depthTest) +{ + ASSERT(indices.Length() % 3 == 0); + + DebugTriangle t; + t.Color = Color32(color); + t.TimeLeft = duration; + + Array* list; + if (depthTest) + list = duration > 0 ? &DebugDrawDepthTest.DefaultWireTriangles : &DebugDrawDepthTest.OneFrameWireTriangles; + else + list = duration > 0 ? &DebugDrawDefault.DefaultWireTriangles : &DebugDrawDefault.OneFrameWireTriangles; + list->EnsureCapacity(list->Count() + indices.Length() / 3); + + for (int32 i = 0; i < indices.Length();) + { + t.V0 = vertices[indices[i++]]; + t.V1 = vertices[indices[i++]]; + t.V2 = vertices[indices[i++]]; + list->Add(t); + } +} + +void DebugDraw::DrawWireTriangles(const Array& vertices, const Array& indices, const Color& color, float duration, bool depthTest) +{ + DrawWireTriangles(Span(vertices.Get(), vertices.Count()), Span(indices.Get(), indices.Count()), color, duration, depthTest); +} + void DebugDraw::DrawWireTube(const Vector3& position, const Quaternion& orientation, float radius, float length, const Color& color, float duration, bool depthTest) { // Check if has no length (just sphere) diff --git a/Source/Engine/Debug/DebugDraw.h b/Source/Engine/Debug/DebugDraw.h index 6027c6697..69c3d4fed 100644 --- a/Source/Engine/Debug/DebugDraw.h +++ b/Source/Engine/Debug/DebugDraw.h @@ -113,6 +113,15 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(DebugDraw); /// If set to true depth test will be performed, otherwise depth will be ignored. API_FUNCTION() static void DrawTriangles(const Span& vertices, const Color& color, float duration = 0.0f, bool depthTest = true); + /// + /// Draws the triangles. + /// + /// The triangle vertices list (must have multiple of 3 elements). + /// The color. + /// The duration (in seconds). Use 0 to draw it only once. + /// If set to true depth test will be performed, otherwise depth will be ignored. + static void DrawTriangles(const Array& vertices, const Color& color, float duration = 0.0f, bool depthTest = true); + /// /// Draws the triangles using the given index buffer. /// @@ -123,6 +132,54 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(DebugDraw); /// If set to true depth test will be performed, otherwise depth will be ignored. API_FUNCTION() static void DrawTriangles(const Span& vertices, const Span& indices, const Color& color, float duration = 0.0f, bool depthTest = true); + /// + /// Draws the triangles using the given index buffer. + /// + /// The triangle vertices list. + /// The triangle indices list (must have multiple of 3 elements). + /// The color. + /// The duration (in seconds). Use 0 to draw it only once. + /// If set to true depth test will be performed, otherwise depth will be ignored. + static void DrawTriangles(const Array& vertices, const Array& indices, const Color& color, float duration = 0.0f, bool depthTest = true); + + /// + /// Draws the wireframe triangles. + /// + /// The triangle vertices list (must have multiple of 3 elements). + /// The color. + /// The duration (in seconds). Use 0 to draw it only once. + /// If set to true depth test will be performed, otherwise depth will be ignored. + API_FUNCTION() static void DrawWireTriangles(const Span& vertices, const Color& color, float duration = 0.0f, bool depthTest = true); + + /// + /// Draws the wireframe triangles. + /// + /// The triangle vertices list (must have multiple of 3 elements). + /// The color. + /// The duration (in seconds). Use 0 to draw it only once. + /// If set to true depth test will be performed, otherwise depth will be ignored. + static void DrawWireTriangles(const Array& vertices, const Color& color, float duration = 0.0f, bool depthTest = true); + + /// + /// Draws the wireframe triangles using the given index buffer. + /// + /// The triangle vertices list. + /// The triangle indices list (must have multiple of 3 elements). + /// The color. + /// The duration (in seconds). Use 0 to draw it only once. + /// If set to true depth test will be performed, otherwise depth will be ignored. + API_FUNCTION() static void DrawWireTriangles(const Span& vertices, const Span& indices, const Color& color, float duration = 0.0f, bool depthTest = true); + + /// + /// Draws the wireframe triangles using the given index buffer. + /// + /// The triangle vertices list. + /// The triangle indices list (must have multiple of 3 elements). + /// The color. + /// The duration (in seconds). Use 0 to draw it only once. + /// If set to true depth test will be performed, otherwise depth will be ignored. + static void DrawWireTriangles(const Array& vertices, const Array& indices, const Color& color, float duration = 0.0f, bool depthTest = true); + /// /// Draws the wireframe box. /// @@ -232,6 +289,8 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(DebugDraw); #define DEBUG_DRAW_SPHERE(sphere, color, duration, depthTest) DebugDraw::DrawSphere(sphere, color, duration, depthTest) #define DEBUG_DRAW_BOX(box, color, duration, depthTest) DebugDraw::DrawBox(box, color, duration, depthTest) #define DEBUG_DRAW_WIRE_TRIANGLE(v0, v1, v2, color, duration, depthTest) DebugDraw::DrawWireTriangle(v0, v1, v2, color, duration, depthTest) +#define DEBUG_DRAW_WIRE_TRIANGLES(vertices, color, duration, depthTest) DebugDraw::DrawWireTriangles(vertices, color, duration, depthTest) +#define DEBUG_DRAW_WIRE_TRIANGLES_EX(vertices, indices, color, duration, depthTest) DebugDraw::DrawWireTriangles(vertices, indices, color, duration, depthTest) #define DEBUG_DRAW_WIRE_BOX(box, color, duration, depthTest) DebugDraw::DrawWireBox(box, color, duration, depthTest) #define DEBUG_DRAW_WIRE_FRUSTUM(frustum, color, duration, depthTest) DebugDraw::DrawWireFrustum(frustum, color, duration, depthTest) #define DEBUG_DRAW_WIRE_SPHERE(sphere, color, duration, depthTest) DebugDraw::DrawWireSphere(sphere, color, duration, depthTest) @@ -251,6 +310,8 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(DebugDraw); #define DEBUG_DRAW_SPHERE(sphere, color, duration, depthTest) #define DEBUG_DRAW_BOX(box, color, duration, depthTest) #define DEBUG_DRAW_WIRE_TRIANGLE(v0, v1, v2, color, duration, depthTest) +#define DEBUG_DRAW_WIRE_TRIANGLES(vertices, color, duration, depthTest) +#define DEBUG_DRAW_WIRE_TRIANGLES_EX(vertices, indices, color, duration, depthTest) #define DEBUG_DRAW_WIRE_BOX(box, color, duration, depthTest) #define DEBUG_DRAW_WIRE_FRUSTUM(frustum, color, duration, depthTest) #define DEBUG_DRAW_WIRE_SPHERE(sphere, color, duration, depthTest) diff --git a/Source/Engine/Debug/Exception.h b/Source/Engine/Debug/Exception.h index 481c78589..5bef7aa55 100644 --- a/Source/Engine/Debug/Exception.h +++ b/Source/Engine/Debug/Exception.h @@ -36,7 +36,7 @@ namespace Log /// /// Additional information that help describe error Exception(const String& additionalInfo) - : Exception(TEXT("An exception has occured."), additionalInfo) + : Exception(TEXT("An exception has occurred."), additionalInfo) { } diff --git a/Source/Engine/Engine/Base/GameBase.cpp b/Source/Engine/Engine/Base/GameBase.cpp index 6c02ce1d1..62e187e7d 100644 --- a/Source/Engine/Engine/Base/GameBase.cpp +++ b/Source/Engine/Engine/Base/GameBase.cpp @@ -119,7 +119,10 @@ bool GameBase::Init() } // Preload first scene asset data - GameBaseImpl::FirstScene = GameSettings::FirstScene; + const auto gameSettings = GameSettings::Get(); + if (!gameSettings) + return true; + GameBaseImpl::FirstScene = gameSettings->FirstScene; return false; } @@ -256,13 +259,13 @@ void GameBaseImpl::OnSplashScreenEnd() { // Hide splash screen SplashScreenTime = 0; - SplashScreen.Unlink(); + SplashScreen = nullptr; MainRenderTask::Instance->PostRender.Unbind(&OnPostRender); // Load the first scene LOG(Info, "Loading the first scene"); - FirstScene.Unlink(); - const auto sceneId = GameSettings::FirstScene; + const auto sceneId = FirstScene ? FirstScene.GetID() : Guid::Empty; + FirstScene = nullptr; if (Level::LoadSceneAsync(sceneId)) { LOG(Fatal, "Cannot load the first scene."); diff --git a/Source/Engine/Engine/CommandLine.cpp b/Source/Engine/Engine/CommandLine.cpp index 707f99de1..edfc9c268 100644 --- a/Source/Engine/Engine/CommandLine.cpp +++ b/Source/Engine/Engine/CommandLine.cpp @@ -133,6 +133,7 @@ bool CommandLine::Parse(const Char* cmdLine) PARSE_BOOL_SWITCH("-genprojectfiles ", GenProjectFiles); PARSE_ARG_SWITCH("-build ", Build); PARSE_BOOL_SWITCH("-skipcompile ", SkipCompile); + PARSE_BOOL_SWITCH("-shaderdebug ", ShaderDebug); #endif diff --git a/Source/Engine/Engine/CommandLine.h b/Source/Engine/Engine/CommandLine.h index 065135b48..278620bf5 100644 --- a/Source/Engine/Engine/CommandLine.h +++ b/Source/Engine/Engine/CommandLine.h @@ -150,6 +150,11 @@ public: /// Nullable SkipCompile; + /// + /// -shaderdebug (enables debugging data generation for shaders and disables shader compiler optimizations) + /// + Nullable ShaderDebug; + #endif }; diff --git a/Source/Engine/Engine/Engine.cpp b/Source/Engine/Engine/Engine.cpp index ef1a26e60..a4c1408ad 100644 --- a/Source/Engine/Engine/Engine.cpp +++ b/Source/Engine/Engine/Engine.cpp @@ -51,6 +51,9 @@ namespace EngineImpl { bool IsReady = false; +#if !USE_EDITOR + bool RunInBackground = false; +#endif String CommandLine = nullptr; int32 Fps = 0, FpsAccumulatedFrames = 0; double FpsAccumulated = 0.0; @@ -133,6 +136,9 @@ int32 Engine::Main(const Char* cmdLine) Platform::BeforeRun(); EngineImpl::InitMainWindow(); Application::BeforeRun(); +#if !USE_EDITOR && (PLATFORM_WINDOWS || PLATFORM_LINUX) + EngineImpl::RunInBackground = PlatformSettings::Get()->RunInBackground; +#endif Log::Logger::WriteFloor(); LOG_FLUSH(); Time::OnBeforeRun(); @@ -279,11 +285,7 @@ void Engine::OnUpdate() bool isGameRunning = true; if (mainWindow && !mainWindow->IsFocused()) { -#if PLATFORM_WINDOWS || PLATFORM_LINUX - isGameRunning = PlatformSettings::Instance()->RunInBackground; -#else - isGameRunning = false; -#endif + isGameRunning = EngineImpl::RunInBackground; } Time::SetGamePaused(!isGameRunning); #endif @@ -393,8 +395,11 @@ const String& Engine::GetCommandLine() JsonAsset* Engine::GetCustomSettings(const StringView& key) { + const auto settings = GameSettings::Get(); + if (!settings) + return nullptr; Guid assetId = Guid::Empty; - GameSettings::CustomSettings.TryGet(key, assetId); + settings->CustomSettings.TryGet(key, assetId); return Content::LoadAsync(assetId); } @@ -463,7 +468,7 @@ void Engine::OnExit() LOG_FLUSH(); - // Kill all reaming threads + // Kill all remaining threads ThreadRegistry::KillEmAll(); // Cleanup diff --git a/Source/Engine/Engine/ISceneObject.cs b/Source/Engine/Engine/ISceneObject.cs deleted file mode 100644 index 2b5ae7706..000000000 --- a/Source/Engine/Engine/ISceneObject.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -using System; - -namespace FlaxEngine -{ - /// - /// Interface for scene objects that unifies various properties used across actors and scripts. - /// - public interface ISceneObject - { - /// - /// Gets the scene object which contains this object. - /// - Scene Scene { get; } - - /// - /// Gets a value indicating whether this object has a valid linkage to the prefab asset. - /// - bool HasPrefabLink { get; } - - /// - /// Gets the prefab asset ID. Empty if no prefab link exists. - /// - Guid PrefabID { get; } - - /// - /// Gets the ID of the object within a object that is used for synchronization with this object. Empty if no prefab link exists. - /// - Guid PrefabObjectID { get; } - - /// - /// Breaks the prefab linkage for this object (including all children). - /// - void BreakPrefabLink(); - } -} diff --git a/Source/Engine/Engine/ITransformable.cs b/Source/Engine/Engine/ITransformable.cs deleted file mode 100644 index a8bbd910c..000000000 --- a/Source/Engine/Engine/ITransformable.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -namespace FlaxEngine -{ - /// - /// Interface for objects that can be transformed. - /// - public interface ITransformable - { - /// - /// Gets or sets the transform. - /// - Transform Transform { get; set; } - } -} diff --git a/Source/Engine/Engine/Linux/LinuxGame.cpp b/Source/Engine/Engine/Linux/LinuxGame.cpp index 43bfe4a6e..1f59a66a9 100644 --- a/Source/Engine/Engine/Linux/LinuxGame.cpp +++ b/Source/Engine/Engine/Linux/LinuxGame.cpp @@ -16,7 +16,7 @@ void LinuxGame::InitMainWindowSettings(CreateWindowSettings& settings) { // TODO: restore window size and fullscreen mode from the cached local settings saved after previous session - const auto platformSettings = LinuxPlatformSettings::Instance(); + const auto platformSettings = LinuxPlatformSettings::Get(); auto windowMode = platformSettings->WindowMode; // Use command line switches @@ -54,7 +54,7 @@ void LinuxGame::InitMainWindowSettings(CreateWindowSettings& settings) bool LinuxGame::Init() { - const auto platformSettings = LinuxPlatformSettings::Instance(); + const auto platformSettings = LinuxPlatformSettings::Get(); // Create mutex if need to if (platformSettings->ForceSingleInstance) diff --git a/Source/Engine/Engine/PhysicalMaterial.cs b/Source/Engine/Engine/PhysicalMaterial.cs deleted file mode 100644 index 3136c8d8a..000000000 --- a/Source/Engine/Engine/PhysicalMaterial.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -namespace FlaxEngine -{ - /// - /// Physical materials are used to define the response of a physical object when interacting dynamically with the world. - /// - public sealed class PhysicalMaterial - { - /// - /// The friction value of surface, controls how easily things can slide on this surface. - /// - [EditorOrder(0), Limit(0), EditorDisplay("Physical Material"), Tooltip("The friction value of surface, controls how easily things can slide on this surface.")] - public float Friction = 0.7f; - - /// - /// The friction combine mode, controls how friction is computed for multiple materials. - /// - [EditorOrder(1), EditorDisplay("Physical Material"), Tooltip("The friction combine mode, controls how friction is computed for multiple materials.")] - public PhysicsCombineMode FrictionCombineMode = PhysicsCombineMode.Average; - - /// - /// If set we will use the FrictionCombineMode of this material, instead of the FrictionCombineMode found in the Physics settings. - /// - [HideInEditor] - public bool OverrideFrictionCombineMode = false; - - /// - /// The restitution or 'bounciness' of this surface, between 0 (no bounce) and 1 (outgoing velocity is same as incoming). - /// - [EditorOrder(3), Range(0, 1), EditorDisplay("Physical Material"), Tooltip("The restitution or \'bounciness\' of this surface, between 0 (no bounce) and 1 (outgoing velocity is same as incoming).")] - public float Restitution = 0.3f; - - /// - /// The restitution combine mode, controls how restitution is computed for multiple materials. - /// - [EditorOrder(4), EditorDisplay("Physical Material"), Tooltip("The restitution combine mode, controls how restitution is computed for multiple materials.")] - public PhysicsCombineMode RestitutionCombineMode = PhysicsCombineMode.Average; - - /// - /// If set we will use the RestitutionCombineMode of this material, instead of the RestitutionCombineMode found in the Physics settings. - /// - [HideInEditor] - public bool OverrideRestitutionCombineMode = false; - } -} diff --git a/Source/Engine/Engine/Time.cpp b/Source/Engine/Engine/Time.cpp index 1afe7e05a..f26f4f759 100644 --- a/Source/Engine/Engine/Time.cpp +++ b/Source/Engine/Engine/Time.cpp @@ -3,17 +3,19 @@ #include "Time.h" #include "EngineService.h" #include "Engine/Core/Math/Math.h" -#include "Engine/Core/Config/TimeSettings.h" #include "Engine/Platform/Platform.h" -#include "Engine/Physics/PhysicsSettings.h" +#include "Engine/Core/Config/TimeSettings.h" +#include "Engine/Serialization/Serialization.h" namespace { bool FixedDeltaTimeEnable; float FixedDeltaTimeValue; + float MaxUpdateDeltaTime = 0.1f; } bool Time::_gamePaused = false; +float Time::_physicsMaxDeltaTime = 0.1f; DateTime Time::StartupTime; float Time::UpdateFPS = 30.0f; float Time::PhysicsFPS = 60.0f; @@ -43,6 +45,24 @@ public: TimeService TimeServiceInstance; +void TimeSettings::Apply() +{ + Time::UpdateFPS = UpdateFPS; + Time::PhysicsFPS = PhysicsFPS; + Time::DrawFPS = DrawFPS; + Time::TimeScale = TimeScale; + ::MaxUpdateDeltaTime = MaxUpdateDeltaTime; +} + +void TimeSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) +{ + DESERIALIZE(UpdateFPS); + DESERIALIZE(PhysicsFPS); + DESERIALIZE(DrawFPS); + DESERIALIZE(TimeScale); + DESERIALIZE(MaxUpdateDeltaTime); +} + void Time::TickData::OnBeforeRun(float targetFps, double currentTime) { Time = UnscaledTime = TimeSpan::Zero(); @@ -219,7 +239,7 @@ void Time::OnBeforeRun() bool Time::OnBeginUpdate() { - if (Update.OnTickBegin(UpdateFPS, TimeSettings::Instance()->MaxUpdateDeltaTime)) + if (Update.OnTickBegin(UpdateFPS, MaxUpdateDeltaTime)) { Current = &Update; return true; @@ -229,7 +249,7 @@ bool Time::OnBeginUpdate() bool Time::OnBeginPhysics() { - if (Physics.OnTickBegin(PhysicsFPS, PhysicsSettings::Instance()->MaxDeltaTime)) + if (Physics.OnTickBegin(PhysicsFPS, _physicsMaxDeltaTime)) { Current = &Physics; return true; diff --git a/Source/Engine/Engine/Time.h b/Source/Engine/Engine/Time.h index 0e8c25806..0d44afa22 100644 --- a/Source/Engine/Engine/Time.h +++ b/Source/Engine/Engine/Time.h @@ -15,6 +15,7 @@ API_CLASS(Static) class FLAXENGINE_API Time DECLARE_SCRIPTING_TYPE_NO_SPAWN(Time); friend class Engine; friend class TimeService; + friend class PhysicsSettings; public: /// @@ -100,6 +101,7 @@ public: private: static bool _gamePaused; + static float _physicsMaxDeltaTime; public: diff --git a/Source/Engine/Engine/Windows/WindowsGame.cpp b/Source/Engine/Engine/Windows/WindowsGame.cpp index da82cc7fc..4b7496481 100644 --- a/Source/Engine/Engine/Windows/WindowsGame.cpp +++ b/Source/Engine/Engine/Windows/WindowsGame.cpp @@ -11,7 +11,7 @@ void WindowsGame::InitMainWindowSettings(CreateWindowSettings& settings) { // TODO: restore window size and fullscreen mode from the cached local settings saved after previous session - const auto platformSettings = WindowsPlatformSettings::Instance(); + const auto platformSettings = WindowsPlatformSettings::Get(); auto windowMode = platformSettings->WindowMode; // Use command line switches @@ -45,7 +45,7 @@ void WindowsGame::InitMainWindowSettings(CreateWindowSettings& settings) bool WindowsGame::Init() { - const auto platformSettings = WindowsPlatformSettings::Instance(); + const auto platformSettings = WindowsPlatformSettings::Get(); // Create mutex if need to if (platformSettings->ForceSingleInstance) diff --git a/Source/Engine/Foliage/Foliage.cpp b/Source/Engine/Foliage/Foliage.cpp index dcfd50212..c38191adf 100644 --- a/Source/Engine/Foliage/Foliage.cpp +++ b/Source/Engine/Foliage/Foliage.cpp @@ -201,7 +201,7 @@ void Foliage::RemoveFoliageType(int32 index) FoliageTypes[i].Index--; } auto& item = FoliageTypes[index]; - item.Model.Unlink(); + item.Model = nullptr; item.Entries.Release(); FoliageTypes.RemoveAtKeepOrder(index); diff --git a/Source/Engine/Graphics/Enums.h b/Source/Engine/Graphics/Enums.h index 20836b300..8aa88756a 100644 --- a/Source/Engine/Graphics/Enums.h +++ b/Source/Engine/Graphics/Enums.h @@ -985,7 +985,7 @@ API_ENUM() enum class TessellationMethod enum class ShaderFlags : uint32 { /// - /// The default set fo flags. + /// The default set for flags. /// Default = 0, diff --git a/Source/Engine/Graphics/GPUBuffer.h b/Source/Engine/Graphics/GPUBuffer.h index 84611419e..97be261a4 100644 --- a/Source/Engine/Graphics/GPUBuffer.h +++ b/Source/Engine/Graphics/GPUBuffer.h @@ -215,7 +215,7 @@ public: /// Gets a CPU pointer to the resource by mapping its contents. Denies the GPU access to that resource. /// /// The map operation mode. - /// The pointer ot the mapped CPU buffer with resource data or null if failed. + /// The pointer of the mapped CPU buffer with resource data or null if failed. API_FUNCTION() virtual void* Map(GPUResourceMapMode mode) = 0; /// diff --git a/Source/Engine/Graphics/GPUDevice.cpp b/Source/Engine/Graphics/GPUDevice.cpp index a5195ff50..51d92cd3f 100644 --- a/Source/Engine/Graphics/GPUDevice.cpp +++ b/Source/Engine/Graphics/GPUDevice.cpp @@ -13,11 +13,12 @@ #include "Engine/Platform/Windows/WindowsWindow.h" #include "Engine/Render2D/Render2D.h" #include "Engine/Engine/CommandLine.h" +#include "Engine/Engine/Engine.h" #include "Engine/Engine/EngineService.h" #include "Engine/Profiler/Profiler.h" #include "Engine/Renderer/RenderList.h" -#include "Engine/Engine/Engine.h" #include "Engine/Core/Utilities.h" +#include "Engine/Scripting/SoftObjectReference.h" GPUPipelineState* GPUPipelineState::Spawn(const SpawnParams& params) { @@ -127,6 +128,7 @@ struct GPUDevice::PrivateData GPUPipelineState* PS_Clear = nullptr; GPUBuffer* FullscreenTriangleVB = nullptr; AssetReference DefaultMaterial; + SoftObjectReference DefaultDeformableMaterial; AssetReference DefaultNormalMap; AssetReference DefaultWhiteTexture; AssetReference DefaultBlackTexture; @@ -206,6 +208,7 @@ bool GPUDevice::LoadContent() _res->DefaultMaterial = Content::LoadAsyncInternal(TEXT("Engine/DefaultMaterial")); if (_res->DefaultMaterial == nullptr) return true; + _res->DefaultDeformableMaterial = Guid(0x639e12c0, 0x42d34bae, 0x89dd8b81, 0x7e1efc2d); // Load default normal map _res->DefaultNormalMap = Content::LoadAsyncInternal(TEXT("Engine/Textures/NormalTexture")); @@ -230,16 +233,17 @@ void GPUDevice::preDispose() RenderTargetPool::Flush(); // Release resources - _res->DefaultMaterial.Unlink(); - _res->DefaultNormalMap.Unlink(); - _res->DefaultWhiteTexture.Unlink(); - _res->DefaultBlackTexture.Unlink(); + _res->DefaultMaterial = nullptr; + _res->DefaultDeformableMaterial = nullptr; + _res->DefaultNormalMap = nullptr; + _res->DefaultWhiteTexture = nullptr; + _res->DefaultBlackTexture = nullptr; SAFE_DELETE_GPU_RESOURCE(_res->PS_CopyLinear); SAFE_DELETE_GPU_RESOURCE(_res->PS_Clear); SAFE_DELETE_GPU_RESOURCE(_res->FullscreenTriangleVB); // Release GPU resources memory and unlink from device - // Note: after that noe GPU resources should be used/created, only deleted + // Note: after that no GPU resources should be used/created, only deleted Resources.OnDeviceDispose(); } @@ -380,6 +384,11 @@ MaterialBase* GPUDevice::GetDefaultMaterial() const return _res->DefaultMaterial; } +MaterialBase* GPUDevice::GetDefaultDeformableMaterial() const +{ + return _res->DefaultDeformableMaterial.Get(); +} + GPUTexture* GPUDevice::GetDefaultNormalMap() const { return _res->DefaultNormalMap ? _res->DefaultNormalMap->GetTexture() : nullptr; diff --git a/Source/Engine/Graphics/GPUDevice.h b/Source/Engine/Graphics/GPUDevice.h index 0b5fb46b9..e1deb16cc 100644 --- a/Source/Engine/Graphics/GPUDevice.h +++ b/Source/Engine/Graphics/GPUDevice.h @@ -234,6 +234,11 @@ public: /// MaterialBase* GetDefaultMaterial() const; + /// + /// Gets the default material (Deformable domain). + /// + MaterialBase* GetDefaultDeformableMaterial() const; + /// /// Gets the default normal map texture. /// diff --git a/Source/Engine/Graphics/GPULimits.h b/Source/Engine/Graphics/GPULimits.h index 485f5d866..b50714fcd 100644 --- a/Source/Engine/Graphics/GPULimits.h +++ b/Source/Engine/Graphics/GPULimits.h @@ -174,10 +174,10 @@ API_ENUM(Attributes="Flags") enum class FormatSupport : int32 DECLARE_ENUM_OPERATORS(FormatSupport); // Helper macro to check if given format does not support a given set of feature flags -#define FORMAT_FEATURES_ARE_NOT_SUPPORTED(formatSupport, formatSupportFlags) ((formatSupport & formatSupportFlags) != static_cast(formatSupportFlags)) +#define FORMAT_FEATURES_ARE_NOT_SUPPORTED(formatSupport, formatSupportFlags) ((formatSupport & (formatSupportFlags)) != static_cast(formatSupportFlags)) // Helper macro to check if given format does support a given set of feature flags -#define FORMAT_FEATURES_ARE_SUPPORTED(formatSupport, formatSupportFlags) ((formatSupport & formatSupportFlags) == static_cast(formatSupportFlags)) +#define FORMAT_FEATURES_ARE_SUPPORTED(formatSupport, formatSupportFlags) ((formatSupport & (formatSupportFlags)) == static_cast(formatSupportFlags)) /// /// The features exposed for a particular format. diff --git a/Source/Engine/Graphics/GPUResourceProperty.h b/Source/Engine/Graphics/GPUResourceProperty.h index 5ad31439d..49e3c7b9b 100644 --- a/Source/Engine/Graphics/GPUResourceProperty.h +++ b/Source/Engine/Graphics/GPUResourceProperty.h @@ -75,12 +75,8 @@ public: GPUResourceProperty& operator=(const GPUResourceProperty& other) { - // Protect against invalid self-assignment if (this != &other) - { Set(other.Get()); - } - return *this; } @@ -158,7 +154,6 @@ public: /// Value to assign void Set(T* value) { - // Check if value will change if (_resource != value) { // Remove reference from the old one @@ -179,7 +174,6 @@ public: /// void Unlink() { - // Check if value will change if (_resource) { // Remove reference from the old one @@ -194,10 +188,7 @@ private: { if (_resource) { - // Unlink _resource = nullptr; - - // Fire event OnUnload(this); } } diff --git a/Source/Engine/Graphics/GPUSwapChain.cpp b/Source/Engine/Graphics/GPUSwapChain.cpp index 347fa6e46..68b1486ad 100644 --- a/Source/Engine/Graphics/GPUSwapChain.cpp +++ b/Source/Engine/Graphics/GPUSwapChain.cpp @@ -39,7 +39,7 @@ Task* GPUSwapChain::DownloadDataAsync(TextureData& result) { if (_downloadTask) { - LOG(Warning, "Can download window backuffer data ony once at the time."); + LOG(Warning, "Can download window backuffer data only once at the time."); return nullptr; } diff --git a/Source/Engine/Graphics/Graphics.cpp b/Source/Engine/Graphics/Graphics.cpp index 21b506ef1..69170dca0 100644 --- a/Source/Engine/Graphics/Graphics.cpp +++ b/Source/Engine/Graphics/Graphics.cpp @@ -49,6 +49,9 @@ GraphicsService GraphicsServiceInstance; void Graphics::DisposeDevice() { + // Clean any danging pointer to last task (might stay if engine is disposing after crash) + GPUDevice::Instance->CurrentTask = nullptr; + if (GPUDevice::Instance) { GPUDevice::Instance->Dispose(); diff --git a/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp b/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp index c5166a088..d8d68e912 100644 --- a/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp @@ -37,14 +37,16 @@ void DecalMaterialShader::Bind(BindParameters& params) auto context = params.GPUContext; auto& view = params.RenderContext.View; auto& drawCall = *params.FirstDrawCall; - const auto cb0 = _shader->GetCB(0); - const bool hasCb0 = cb0->GetSize() != 0; + byte* cb = _cbData.Get(); + auto materialData = reinterpret_cast(cb); + cb += sizeof(DecalMaterialShaderData); + int32 srv = 0; const bool isCameraInside = OrientedBoundingBox(Vector3::Half, params.FirstDrawCall->World).Contains(view.Position) == ContainmentType::Contains; // Setup parameters MaterialParameter::BindMeta bindMeta; bindMeta.Context = context; - bindMeta.Buffer0 = hasCb0 ? _cb0Data.Get() + sizeof(DecalMaterialShaderData) : nullptr; + bindMeta.Constants = cb; bindMeta.Input = nullptr; bindMeta.Buffers = nullptr; bindMeta.CanSampleDepth = true; @@ -54,11 +56,8 @@ void DecalMaterialShader::Bind(BindParameters& params) // Decals use depth buffer to draw on top of the objects context->BindSR(0, GET_TEXTURE_VIEW_SAFE(params.RenderContext.Buffers->DepthBuffer)); - // Setup material constants data - if (hasCb0) + // Setup material constants { - const auto materialData = reinterpret_cast(_cb0Data.Get()); - Matrix::Transpose(view.Frustum.GetMatrix(), materialData->ViewProjectionMatrix); Matrix::Transpose(drawCall.World, materialData->WorldMatrix); Matrix::Transpose(view.View, materialData->ViewMatrix); @@ -85,10 +84,10 @@ void DecalMaterialShader::Bind(BindParameters& params) } // Bind constants - if (hasCb0) + if (_cb) { - context->UpdateCB(cb0, _cb0Data.Get()); - context->BindCB(0, cb0); + context->UpdateCB(_cb, _cbData.Get()); + context->BindCB(0, _cb); } // Bind pipeline diff --git a/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp b/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp index 049484459..babfaf830 100644 --- a/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp @@ -1,10 +1,12 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "DeferredMaterialShader.h" +#include "MaterialShaderFeatures.h" #include "MaterialParams.h" #include "Engine/Graphics/RenderBuffers.h" #include "Engine/Graphics/RenderView.h" #include "Engine/Renderer/DrawCall.h" +#include "Engine/Renderer/RenderList.h" #include "Engine/Level/Scene/Lightmap.h" #include "Engine/Graphics/GPUContext.h" #include "Engine/Graphics/Shaders/GPUConstantBuffer.h" @@ -27,7 +29,6 @@ PACK_STRUCT(struct DeferredMaterialShaderData { float TimeParam; Vector4 ViewInfo; Vector4 ScreenSize; - Rectangle LightmapArea; Vector3 WorldInvScale; float WorldDeterminantSign; Vector2 Dummy0; @@ -48,8 +49,9 @@ bool DeferredMaterialShader::CanUseLightmap() const return true; } -bool DeferredMaterialShader::CanUseInstancing() const +bool DeferredMaterialShader::CanUseInstancing(InstancingHandler& handler) const { + handler = { SurfaceDrawCallHandler::GetHash, SurfaceDrawCallHandler::CanBatch, SurfaceDrawCallHandler::WriteDrawCall, }; return true; } @@ -59,90 +61,71 @@ void DeferredMaterialShader::Bind(BindParameters& params) auto context = params.GPUContext; auto& view = params.RenderContext.View; auto& drawCall = *params.FirstDrawCall; - const auto cb0 = _shader->GetCB(0); - const bool hasCb0 = cb0 && cb0->GetSize() != 0; + byte* cb = _cbData.Get(); + auto materialData = reinterpret_cast(cb); + cb += sizeof(DeferredMaterialShaderData); + int32 srv = 2; + + // Setup features + const bool useLightmap = _info.BlendMode == MaterialBlendMode::Opaque && LightmapFeature::Bind(params, cb, srv); // Setup parameters MaterialParameter::BindMeta bindMeta; bindMeta.Context = context; - bindMeta.Buffer0 = hasCb0 ? _cb0Data.Get() + sizeof(DeferredMaterialShaderData) : nullptr; + bindMeta.Constants = cb; bindMeta.Input = nullptr; bindMeta.Buffers = nullptr; bindMeta.CanSampleDepth = false; bindMeta.CanSampleGBuffer = false; MaterialParams::Bind(params.ParamsLink, bindMeta); - // Setup material constants data - auto materialData = reinterpret_cast(_cb0Data.Get()); - if (hasCb0) + // Setup material constants { Matrix::Transpose(view.Frustum.GetMatrix(), materialData->ViewProjectionMatrix); Matrix::Transpose(drawCall.World, materialData->WorldMatrix); Matrix::Transpose(view.View, materialData->ViewMatrix); - Matrix::Transpose(drawCall.PrevWorld, materialData->PrevWorldMatrix); + Matrix::Transpose(drawCall.Surface.PrevWorld, materialData->PrevWorldMatrix); Matrix::Transpose(view.PrevViewProjection, materialData->PrevViewProjectionMatrix); - materialData->ViewPos = view.Position; materialData->ViewFar = view.Far; materialData->ViewDir = view.Direction; materialData->TimeParam = Time::Draw.UnscaledTime.GetTotalSeconds(); materialData->ViewInfo = view.ViewInfo; materialData->ScreenSize = view.ScreenSize; - - // Extract per axis scales from LocalToWorld transform const float scaleX = Vector3(drawCall.World.M11, drawCall.World.M12, drawCall.World.M13).Length(); const float scaleY = Vector3(drawCall.World.M21, drawCall.World.M22, drawCall.World.M23).Length(); const float scaleZ = Vector3(drawCall.World.M31, drawCall.World.M32, drawCall.World.M33).Length(); - const Vector3 worldInvScale = Vector3( + materialData->WorldInvScale = Vector3( scaleX > 0.00001f ? 1.0f / scaleX : 0.0f, scaleY > 0.00001f ? 1.0f / scaleY : 0.0f, scaleZ > 0.00001f ? 1.0f / scaleZ : 0.0f); - - materialData->WorldInvScale = worldInvScale; materialData->WorldDeterminantSign = drawCall.WorldDeterminantSign; - materialData->LODDitherFactor = drawCall.LODDitherFactor; + materialData->LODDitherFactor = drawCall.Surface.LODDitherFactor; materialData->PerInstanceRandom = drawCall.PerInstanceRandom; materialData->TemporalAAJitter = view.TemporalAAJitter; - materialData->GeometrySize = drawCall.GeometrySize; - } - const bool useLightmap = view.Flags & ViewFlags::GI -#if USE_EDITOR - && EnableLightmapsUsage -#endif - && drawCall.Lightmap != nullptr; - if (useLightmap) - { - // Bind lightmap textures - GPUTexture *lightmap0, *lightmap1, *lightmap2; - drawCall.Lightmap->GetTextures(&lightmap0, &lightmap1, &lightmap2); - context->BindSR(0, lightmap0); - context->BindSR(1, lightmap1); - context->BindSR(2, lightmap2); - - // Set lightmap data - materialData->LightmapArea = drawCall.LightmapUVsArea; + materialData->GeometrySize = drawCall.Surface.GeometrySize; } // Check if is using mesh skinning - const bool useSkinning = drawCall.Skinning != nullptr; + const bool useSkinning = drawCall.Surface.Skinning != nullptr; bool perBoneMotionBlur = false; if (useSkinning) { // Bind skinning buffer - ASSERT(drawCall.Skinning->IsReady()); - context->BindSR(0, drawCall.Skinning->BoneMatrices->View()); - if (drawCall.Skinning->PrevBoneMatrices && drawCall.Skinning->PrevBoneMatrices->IsAllocated()) + ASSERT(drawCall.Surface.Skinning->IsReady()); + context->BindSR(0, drawCall.Surface.Skinning->BoneMatrices->View()); + if (drawCall.Surface.Skinning->PrevBoneMatrices && drawCall.Surface.Skinning->PrevBoneMatrices->IsAllocated()) { - context->BindSR(1, drawCall.Skinning->PrevBoneMatrices->View()); + context->BindSR(1, drawCall.Surface.Skinning->PrevBoneMatrices->View()); perBoneMotionBlur = true; } } // Bind constants - if (hasCb0) + if (_cb) { - context->UpdateCB(cb0, _cb0Data.Get()); - context->BindCB(0, cb0); + context->UpdateCB(_cb, _cbData.Get()); + context->BindCB(0, _cb); } // Select pipeline state based on current pass and render mode @@ -152,7 +135,7 @@ void DeferredMaterialShader::Bind(BindParameters& params) if (IsRunningRadiancePass) cullMode = CullMode::TwoSided; #endif - if (cullMode != CullMode::TwoSided && drawCall.IsNegativeScale()) + if (cullMode != CullMode::TwoSided && drawCall.WorldDeterminantSign < 0) { // Invert culling when scale is negative if (cullMode == CullMode::Normal) @@ -200,7 +183,7 @@ bool DeferredMaterialShader::Load() psDesc.VS = _shader->GetVS("VS", 1); _cacheInstanced.Default.Init(psDesc); - // GBuffer Pass with lightmap (use pixel shader permutation for USE_LIGHTMAP=1) + // GBuffer Pass with lightmap (pixel shader permutation for USE_LIGHTMAP=1) psDesc.VS = _shader->GetVS("VS"); psDesc.PS = _shader->GetPS("PS_GBuffer", 1); _cache.DefaultLightmap.Init(psDesc); @@ -216,21 +199,16 @@ bool DeferredMaterialShader::Load() psDesc.DepthWriteEnable = false; psDesc.DepthTestEnable = true; psDesc.DepthFunc = ComparisonFunc::LessEqual; - if (useTess) - { - psDesc.HS = _shader->GetHS("HS", 1); - psDesc.DS = _shader->GetDS("DS", 1); - } - psDesc.VS = _shader->GetVS("VS", 2); + psDesc.VS = _shader->GetVS("VS"); psDesc.PS = _shader->GetPS("PS_MotionVectors"); _cache.MotionVectors.Init(psDesc); // Motion Vectors pass with skinning - psDesc.VS = _shader->GetVS("VS_Skinned", 1); + psDesc.VS = _shader->GetVS("VS_Skinned"); _cache.MotionVectorsSkinned.Init(psDesc); // Motion Vectors pass with skinning (with per-bone motion blur) - psDesc.VS = _shader->GetVS("VS_Skinned", 2); + psDesc.VS = _shader->GetVS("VS_Skinned", 1); _cache.MotionVectorsSkinnedPerBone.Init(psDesc); // Depth Pass diff --git a/Source/Engine/Graphics/Materials/DeferredMaterialShader.h b/Source/Engine/Graphics/Materials/DeferredMaterialShader.h index f8df968c9..537547c6a 100644 --- a/Source/Engine/Graphics/Materials/DeferredMaterialShader.h +++ b/Source/Engine/Graphics/Materials/DeferredMaterialShader.h @@ -66,7 +66,7 @@ public: // [MaterialShader] DrawPass GetDrawModes() const override; bool CanUseLightmap() const override; - bool CanUseInstancing() const override; + bool CanUseInstancing(InstancingHandler& handler) const override; void Bind(BindParameters& params) override; void Unload() override; diff --git a/Source/Engine/Graphics/Materials/DeformableMaterialShader.cpp b/Source/Engine/Graphics/Materials/DeformableMaterialShader.cpp new file mode 100644 index 000000000..dafe13d46 --- /dev/null +++ b/Source/Engine/Graphics/Materials/DeformableMaterialShader.cpp @@ -0,0 +1,188 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#include "DeformableMaterialShader.h" +#include "MaterialShaderFeatures.h" +#include "MaterialParams.h" +#include "Engine/Graphics/RenderBuffers.h" +#include "Engine/Graphics/RenderView.h" +#include "Engine/Renderer/DrawCall.h" +#include "Engine/Renderer/RenderList.h" +#include "Engine/Graphics/GPUContext.h" +#include "Engine/Graphics/Shaders/GPUConstantBuffer.h" +#include "Engine/Graphics/GPUDevice.h" +#include "Engine/Graphics/Shaders/GPUShader.h" +#include "Engine/Graphics/GPULimits.h" +#include "Engine/Engine/Time.h" +#include "Engine/Graphics/RenderTask.h" + +PACK_STRUCT(struct DeformableMaterialShaderData { + Matrix ViewProjectionMatrix; + Matrix WorldMatrix; + Matrix LocalMatrix; + Matrix ViewMatrix; + Vector3 ViewPos; + float ViewFar; + Vector3 ViewDir; + float TimeParam; + Vector4 ViewInfo; + Vector4 ScreenSize; + Vector3 Dummy0; + float WorldDeterminantSign; + float MeshMinZ; + float Segment; + float ChunksPerSegment; + float PerInstanceRandom; + Vector4 TemporalAAJitter; + Vector3 GeometrySize; + float MeshMaxZ; + }); + +DrawPass DeformableMaterialShader::GetDrawModes() const +{ + return _drawModes; +} + +void DeformableMaterialShader::Bind(BindParameters& params) +{ + // Prepare + auto context = params.GPUContext; + auto& view = params.RenderContext.View; + auto& drawCall = *params.FirstDrawCall; + byte* cb = _cbData.Get(); + auto materialData = reinterpret_cast(cb); + cb += sizeof(DeformableMaterialShaderData); + int32 srv = 1; + + // Setup features + if (_info.BlendMode != MaterialBlendMode::Opaque) + ForwardShadingFeature::Bind(params, cb, srv); + + // Setup parameters + MaterialParameter::BindMeta bindMeta; + bindMeta.Context = context; + bindMeta.Constants = cb; + bindMeta.Input = nullptr; + bindMeta.Buffers = nullptr; + bindMeta.CanSampleDepth = false; + bindMeta.CanSampleGBuffer = false; + MaterialParams::Bind(params.ParamsLink, bindMeta); + + // Setup material constants + { + Matrix::Transpose(view.Frustum.GetMatrix(), materialData->ViewProjectionMatrix); + Matrix::Transpose(drawCall.World, materialData->WorldMatrix); + Matrix::Transpose(drawCall.Deformable.LocalMatrix, materialData->LocalMatrix); + Matrix::Transpose(view.View, materialData->ViewMatrix); + materialData->ViewPos = view.Position; + materialData->ViewFar = view.Far; + materialData->ViewDir = view.Direction; + materialData->TimeParam = Time::Draw.UnscaledTime.GetTotalSeconds(); + materialData->ViewInfo = view.ViewInfo; + materialData->ScreenSize = view.ScreenSize; + materialData->WorldDeterminantSign = drawCall.WorldDeterminantSign; + materialData->Segment = drawCall.Deformable.Segment; + materialData->ChunksPerSegment = drawCall.Deformable.ChunksPerSegment; + materialData->MeshMinZ = drawCall.Deformable.MeshMinZ; + materialData->MeshMaxZ = drawCall.Deformable.MeshMaxZ; + materialData->PerInstanceRandom = drawCall.PerInstanceRandom; + materialData->TemporalAAJitter = view.TemporalAAJitter; + materialData->GeometrySize = drawCall.Deformable.GeometrySize; + } + + // Bind spline deformation buffer + context->BindSR(0, drawCall.Deformable.SplineDeformation->View()); + + // Bind constants + if (_cb) + { + context->UpdateCB(_cb, _cbData.Get()); + context->BindCB(0, _cb); + } + + // Select pipeline state based on current pass and render mode + const bool wireframe = (_info.FeaturesFlags & MaterialFeaturesFlags::Wireframe) != 0 || view.Mode == ViewMode::Wireframe; + CullMode cullMode = view.Pass == DrawPass::Depth ? CullMode::TwoSided : _info.CullMode; + if (cullMode != CullMode::TwoSided && drawCall.WorldDeterminantSign < 0) + { + // Invert culling when scale is negative + if (cullMode == CullMode::Normal) + cullMode = CullMode::Inverted; + else + cullMode = CullMode::Normal; + } + PipelineStateCache* psCache = _cache.GetPS(view.Pass); + ASSERT(psCache); + GPUPipelineState* state = psCache->GetPS(cullMode, wireframe); + + // Bind pipeline + context->SetState(state); +} + +void DeformableMaterialShader::Unload() +{ + // Base + MaterialShader::Unload(); + + _cache.Release(); +} + +bool DeformableMaterialShader::Load() +{ + _drawModes = DrawPass::Depth; + auto psDesc = GPUPipelineState::Description::Default; + psDesc.DepthTestEnable = (_info.FeaturesFlags & MaterialFeaturesFlags::DisableDepthTest) == 0; + psDesc.DepthWriteEnable = (_info.FeaturesFlags & MaterialFeaturesFlags::DisableDepthWrite) == 0; + + // Check if use tessellation (both material and runtime supports it) + const bool useTess = _info.TessellationMode != TessellationMethod::None && GPUDevice::Instance->Limits.HasTessellation; + if (useTess) + { + psDesc.HS = _shader->GetHS("HS"); + psDesc.DS = _shader->GetDS("DS"); + } + + if (_info.BlendMode == MaterialBlendMode::Opaque) + { + _drawModes = DrawPass::GBuffer; + + // GBuffer Pass + psDesc.VS = _shader->GetVS("VS_SplineModel"); + psDesc.PS = _shader->GetPS("PS_GBuffer"); + _cache.Default.Init(psDesc); + } + else + { + _drawModes = DrawPass::Forward; + + // Forward Pass + psDesc.VS = _shader->GetVS("VS_SplineModel"); + psDesc.PS = _shader->GetPS("PS_Forward"); + psDesc.DepthWriteEnable = false; + psDesc.BlendMode = BlendingMode::AlphaBlend; + switch (_info.BlendMode) + { + case MaterialBlendMode::Transparent: + psDesc.BlendMode = BlendingMode::AlphaBlend; + break; + case MaterialBlendMode::Additive: + psDesc.BlendMode = BlendingMode::Additive; + break; + case MaterialBlendMode::Multiply: + psDesc.BlendMode = BlendingMode::Multiply; + break; + } + _cache.Default.Init(psDesc); + } + + // Depth Pass + psDesc.CullMode = CullMode::TwoSided; + psDesc.DepthClipEnable = false; + psDesc.DepthWriteEnable = true; + psDesc.DepthTestEnable = true; + psDesc.DepthFunc = ComparisonFunc::Less; + psDesc.HS = nullptr; + psDesc.DS = nullptr; + _cache.Depth.Init(psDesc); + + return false; +} diff --git a/Source/Engine/Graphics/Materials/DeformableMaterialShader.h b/Source/Engine/Graphics/Materials/DeformableMaterialShader.h new file mode 100644 index 000000000..f2c796a23 --- /dev/null +++ b/Source/Engine/Graphics/Materials/DeformableMaterialShader.h @@ -0,0 +1,63 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "MaterialShader.h" + +/// +/// Represents material that can be used to render objects that can be deformed. +/// +class DeformableMaterialShader : public MaterialShader +{ +private: + + struct Cache + { + PipelineStateCache Default; + PipelineStateCache Depth; + + FORCE_INLINE PipelineStateCache* GetPS(const DrawPass pass) + { + switch (pass) + { + case DrawPass::Depth: + return &Depth; + case DrawPass::GBuffer: + case DrawPass::Forward: + return &Default; + default: + return nullptr; + } + } + + FORCE_INLINE void Release() + { + Default.Release(); + Depth.Release(); + } + }; + +private: + + Cache _cache; + DrawPass _drawModes = DrawPass::None; + +public: + + DeformableMaterialShader(const String& name) + : MaterialShader(name) + { + } + +public: + + // [MaterialShader] + DrawPass GetDrawModes() const override; + void Bind(BindParameters& params) override; + void Unload() override; + +protected: + + // [MaterialShader] + bool Load() override; +}; diff --git a/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp b/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp index 863af34e1..2ea17c9cd 100644 --- a/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp @@ -1,15 +1,18 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "ForwardMaterialShader.h" +#include "MaterialShaderFeatures.h" #include "MaterialParams.h" #include "Engine/Engine/Time.h" +#include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/GPULimits.h" -#include "Engine/Graphics/Models/SkinnedMeshDrawData.h" #include "Engine/Graphics/RenderView.h" -#include "Engine/Level/Actors/EnvironmentProbe.h" -#include "Engine/Renderer/DepthOfFieldPass.h" +#include "Engine/Graphics/RenderTask.h" +#include "Engine/Graphics/Models/SkinnedMeshDrawData.h" +#include "Engine/Graphics/Shaders/GPUConstantBuffer.h" +#include "Engine/Graphics/Shaders/GPUShader.h" #include "Engine/Renderer/DrawCall.h" -#include "Engine/Renderer/ShadowsPass.h" +#include "Engine/Renderer/RenderList.h" #if USE_EDITOR #include "Engine/Renderer/Lightmaps.h" #endif @@ -28,34 +31,24 @@ PACK_STRUCT(struct ForwardMaterialShaderData { float TimeParam; Vector4 ViewInfo; Vector4 ScreenSize; - Rectangle LightmapArea; Vector3 WorldInvScale; float WorldDeterminantSign; Vector2 Dummy0; float LODDitherFactor; float PerInstanceRandom; + Vector4 TemporalAAJitter; Vector3 GeometrySize; float Dummy1; }); -PACK_STRUCT(struct ForwardMaterialShaderLightingData { - LightData DirectionalLight; - LightShadowData DirectionalLightShadow; - LightData SkyLight; - ProbeData EnvironmentProbe; - ExponentialHeightFogData ExponentialHeightFog; - Vector3 Dummy2; - uint32 LocalLightsCount; - LightData LocalLights[MAX_LOCAL_LIGHTS]; - }); - DrawPass ForwardMaterialShader::GetDrawModes() const { return _drawModes; } -bool ForwardMaterialShader::CanUseInstancing() const +bool ForwardMaterialShader::CanUseInstancing(InstancingHandler& handler) const { + handler = { SurfaceDrawCallHandler::GetHash, SurfaceDrawCallHandler::CanBatch, SurfaceDrawCallHandler::WriteDrawCall, }; return true; } @@ -64,17 +57,19 @@ void ForwardMaterialShader::Bind(BindParameters& params) // Prepare auto context = params.GPUContext; auto& view = params.RenderContext.View; - auto cache = params.RenderContext.List; auto& drawCall = *params.FirstDrawCall; - const auto cb0 = _shader->GetCB(0); - const bool hasCb0 = cb0 && cb0->GetSize() != 0; - const auto cb1 = _shader->GetCB(1); - const bool hasCb1 = cb1 && cb1->GetSize() != 0; + byte* cb = _cbData.Get(); + auto materialData = reinterpret_cast(cb); + cb += sizeof(ForwardMaterialShaderData); + int32 srv = 2; + + // Setup features + ForwardShadingFeature::Bind(params, cb, srv); // Setup parameters MaterialParameter::BindMeta bindMeta; bindMeta.Context = context; - bindMeta.Buffer0 = hasCb0 ? _cb0Data.Get() + sizeof(ForwardMaterialShaderData) : nullptr; + bindMeta.Constants = cb; bindMeta.Input = nullptr; // forward pass materials cannot sample scene color for now bindMeta.Buffers = params.RenderContext.Buffers; bindMeta.CanSampleDepth = GPUDevice::Instance->Limits.HasReadOnlyDepth; @@ -82,164 +77,45 @@ void ForwardMaterialShader::Bind(BindParameters& params) MaterialParams::Bind(params.ParamsLink, bindMeta); // Check if is using mesh skinning - const bool useSkinning = drawCall.Skinning != nullptr; + const bool useSkinning = drawCall.Surface.Skinning != nullptr; if (useSkinning) { // Bind skinning buffer - ASSERT(drawCall.Skinning->IsReady()); - context->BindSR(0, drawCall.Skinning->BoneMatrices->View()); + ASSERT(drawCall.Surface.Skinning->IsReady()); + context->BindSR(0, drawCall.Surface.Skinning->BoneMatrices->View()); } - // Setup material constants data - const auto materialData = reinterpret_cast(_cb0Data.Get()); - if (hasCb0) + // Setup material constants { Matrix::Transpose(view.Frustum.GetMatrix(), materialData->ViewProjectionMatrix); Matrix::Transpose(drawCall.World, materialData->WorldMatrix); Matrix::Transpose(view.View, materialData->ViewMatrix); - Matrix::Transpose(drawCall.PrevWorld, materialData->PrevWorldMatrix); + Matrix::Transpose(drawCall.Surface.PrevWorld, materialData->PrevWorldMatrix); Matrix::Transpose(view.PrevViewProjection, materialData->PrevViewProjectionMatrix); - materialData->ViewPos = view.Position; materialData->ViewFar = view.Far; materialData->ViewDir = view.Direction; materialData->TimeParam = Time::Draw.UnscaledTime.GetTotalSeconds(); materialData->ViewInfo = view.ViewInfo; materialData->ScreenSize = view.ScreenSize; - - // Extract per axis scales from LocalToWorld transform const float scaleX = Vector3(drawCall.World.M11, drawCall.World.M12, drawCall.World.M13).Length(); const float scaleY = Vector3(drawCall.World.M21, drawCall.World.M22, drawCall.World.M23).Length(); const float scaleZ = Vector3(drawCall.World.M31, drawCall.World.M32, drawCall.World.M33).Length(); - const Vector3 worldInvScale = Vector3( + materialData->WorldInvScale = Vector3( scaleX > 0.00001f ? 1.0f / scaleX : 0.0f, scaleY > 0.00001f ? 1.0f / scaleY : 0.0f, scaleZ > 0.00001f ? 1.0f / scaleZ : 0.0f); - - materialData->WorldInvScale = worldInvScale; materialData->WorldDeterminantSign = drawCall.WorldDeterminantSign; - materialData->LODDitherFactor = drawCall.LODDitherFactor; + materialData->LODDitherFactor = drawCall.Surface.LODDitherFactor; materialData->PerInstanceRandom = drawCall.PerInstanceRandom; - materialData->GeometrySize = drawCall.GeometrySize; - } - - // Setup lighting constants data - if (hasCb1) - { - auto& lightingData = *reinterpret_cast(_cb1Data.Get()); - const int32 envProbeShaderRegisterIndex = 0; - const int32 skyLightShaderRegisterIndex = 1; - const int32 dirLightShaderRegisterIndex = 2; - - // Set fog input - if (cache->Fog) - { - cache->Fog->GetExponentialHeightFogData(view, lightingData.ExponentialHeightFog); - } - else - { - lightingData.ExponentialHeightFog.FogMinOpacity = 1.0f; - lightingData.ExponentialHeightFog.ApplyDirectionalInscattering = 0.0f; - } - - // Set directional light input - if (cache->DirectionalLights.HasItems()) - { - const auto& dirLight = cache->DirectionalLights.First(); - const auto shadowPass = ShadowsPass::Instance(); - const bool useShadow = shadowPass->LastDirLightIndex == 0; - if (useShadow) - { - lightingData.DirectionalLightShadow = shadowPass->LastDirLight; - context->BindSR(dirLightShaderRegisterIndex, shadowPass->LastDirLightShadowMap); - } - else - { - context->UnBindSR(dirLightShaderRegisterIndex); - } - dirLight.SetupLightData(&lightingData.DirectionalLight, view, useShadow); - } - else - { - lightingData.DirectionalLight.Color = Vector3::Zero; - lightingData.DirectionalLight.CastShadows = 0.0f; - context->UnBindSR(dirLightShaderRegisterIndex); - } - - // Set sky light - if (cache->SkyLights.HasItems()) - { - auto& skyLight = cache->SkyLights.First(); - skyLight.SetupLightData(&lightingData.SkyLight, view, false); - const auto texture = skyLight.Image ? skyLight.Image->GetTexture() : nullptr; - context->BindSR(skyLightShaderRegisterIndex, GET_TEXTURE_VIEW_SAFE(texture)); - } - else - { - Platform::MemoryClear(&lightingData.SkyLight, sizeof(lightingData.SkyLight)); - context->UnBindSR(skyLightShaderRegisterIndex); - } - - // Set reflection probe data - EnvironmentProbe* probe = nullptr; - // TODO: optimize env probe searching for a transparent material - use spatial cache for renderer to find it - for (int32 i = 0; i < cache->EnvironmentProbes.Count(); i++) - { - const auto p = cache->EnvironmentProbes[i]; - if (p->GetSphere().Contains(drawCall.World.GetTranslation()) != ContainmentType::Disjoint) - { - probe = p; - break; - } - } - if (probe && probe->GetProbe()) - { - probe->SetupProbeData(&lightingData.EnvironmentProbe); - const auto texture = probe->GetProbe()->GetTexture(); - context->BindSR(envProbeShaderRegisterIndex, GET_TEXTURE_VIEW_SAFE(texture)); - } - else - { - lightingData.EnvironmentProbe.Data1 = Vector4::Zero; - context->UnBindSR(envProbeShaderRegisterIndex); - } - - // Set local lights - lightingData.LocalLightsCount = 0; - for (int32 i = 0; i < cache->PointLights.Count(); i++) - { - const auto& light = cache->PointLights[i]; - if (BoundingSphere(light.Position, light.Radius).Contains(drawCall.World.GetTranslation()) != ContainmentType::Disjoint) - { - light.SetupLightData(&lightingData.LocalLights[lightingData.LocalLightsCount], view, false); - lightingData.LocalLightsCount++; - if (lightingData.LocalLightsCount == MAX_LOCAL_LIGHTS) - break; - } - } - for (int32 i = 0; i < cache->SpotLights.Count(); i++) - { - const auto& light = cache->SpotLights[i]; - if (BoundingSphere(light.Position, light.Radius).Contains(drawCall.World.GetTranslation()) != ContainmentType::Disjoint) - { - light.SetupLightData(&lightingData.LocalLights[lightingData.LocalLightsCount], view, false); - lightingData.LocalLightsCount++; - if (lightingData.LocalLightsCount == MAX_LOCAL_LIGHTS) - break; - } - } + materialData->GeometrySize = drawCall.Surface.GeometrySize; } // Bind constants - if (hasCb0) + if (_cb) { - context->UpdateCB(cb0, _cb0Data.Get()); - context->BindCB(0, cb0); - } - if (hasCb1) - { - context->UpdateCB(cb1, _cb1Data.Get()); - context->BindCB(1, cb1); + context->UpdateCB(_cb, _cbData.Get()); + context->BindCB(0, _cb); } // Select pipeline state based on current pass and render mode @@ -249,7 +125,7 @@ void ForwardMaterialShader::Bind(BindParameters& params) if (IsRunningRadiancePass) cullMode = CullMode::TwoSided; #endif - if (cullMode != CullMode::TwoSided && drawCall.IsNegativeScale()) + if (cullMode != CullMode::TwoSided && drawCall.WorldDeterminantSign < 0) { // Invert culling when scale is negative if (cullMode == CullMode::Normal) diff --git a/Source/Engine/Graphics/Materials/ForwardMaterialShader.h b/Source/Engine/Graphics/Materials/ForwardMaterialShader.h index d39fcea54..bcba48259 100644 --- a/Source/Engine/Graphics/Materials/ForwardMaterialShader.h +++ b/Source/Engine/Graphics/Materials/ForwardMaterialShader.h @@ -67,7 +67,7 @@ public: // [MaterialShader] DrawPass GetDrawModes() const override; - bool CanUseInstancing() const override; + bool CanUseInstancing(InstancingHandler& handler) const override; void Bind(BindParameters& params) override; void Unload() override; diff --git a/Source/Engine/Graphics/Materials/GUIMaterialShader.cpp b/Source/Engine/Graphics/Materials/GUIMaterialShader.cpp index 4b079dd54..769d36557 100644 --- a/Source/Engine/Graphics/Materials/GUIMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/GUIMaterialShader.cpp @@ -28,26 +28,24 @@ void GUIMaterialShader::Bind(BindParameters& params) { // Prepare auto context = params.GPUContext; - auto& view = params.RenderContext.View; - const auto cb0 = _shader->GetCB(0); - const bool hasCb0 = cb0->GetSize() != 0; + byte* cb = _cbData.Get(); + auto materialData = reinterpret_cast(cb); + cb += sizeof(GUIMaterialShaderData); + int32 srv = 0; const auto ps = context->IsDepthBufferBinded() ? _cache.Depth : _cache.NoDepth; // Setup parameters MaterialParameter::BindMeta bindMeta; bindMeta.Context = context; - bindMeta.Buffer0 = hasCb0 ? _cb0Data.Get() + sizeof(GUIMaterialShaderData) : nullptr; + bindMeta.Constants = cb; bindMeta.Input = nullptr; bindMeta.Buffers = nullptr; bindMeta.CanSampleDepth = false; bindMeta.CanSampleGBuffer = false; MaterialParams::Bind(params.ParamsLink, bindMeta); - // Setup material constants data - if (hasCb0) + // Setup material constants { - auto materialData = reinterpret_cast(_cb0Data.Get()); - const auto viewProjectionMatrix = (Matrix*)params.CustomData; Matrix::Transpose(*viewProjectionMatrix, materialData->ViewProjectionMatrix); Matrix::Transpose(Matrix::Identity, materialData->WorldMatrix); @@ -62,10 +60,10 @@ void GUIMaterialShader::Bind(BindParameters& params) } // Bind constants - if (hasCb0) + if (_cb) { - context->UpdateCB(cb0, _cb0Data.Get()); - context->BindCB(0, cb0); + context->UpdateCB(_cb, _cbData.Get()); + context->BindCB(0, _cb); } // Bind pipeline diff --git a/Source/Engine/Graphics/Materials/IMaterial.h b/Source/Engine/Graphics/Materials/IMaterial.h index 8d5590354..4d61656e8 100644 --- a/Source/Engine/Graphics/Materials/IMaterial.h +++ b/Source/Engine/Graphics/Materials/IMaterial.h @@ -2,7 +2,6 @@ #pragma once -#include "Engine/Threading/Task.h" #include "MaterialInfo.h" struct MaterialParamsLink; @@ -30,7 +29,6 @@ public: /// /// Determines whether material is a surface shader. /// - /// true if material is surface shader; otherwise, false. FORCE_INLINE bool IsSurface() const { return GetInfo().Domain == MaterialDomain::Surface; @@ -39,7 +37,6 @@ public: /// /// Determines whether material is a post fx. /// - /// true if material is post fx; otherwise, false. FORCE_INLINE bool IsPostFx() const { return GetInfo().Domain == MaterialDomain::PostProcess; @@ -48,7 +45,6 @@ public: /// /// Determines whether material is a decal. /// - /// true if material is decal; otherwise, false. FORCE_INLINE bool IsDecal() const { return GetInfo().Domain == MaterialDomain::Decal; @@ -57,7 +53,6 @@ public: /// /// Determines whether material is a GUI shader. /// - /// true if material is GUI shader; otherwise, false. FORCE_INLINE bool IsGUI() const { return GetInfo().Domain == MaterialDomain::GUI; @@ -66,7 +61,6 @@ public: /// /// Determines whether material is a terrain shader. /// - /// true if material is terrain shader; otherwise, false. FORCE_INLINE bool IsTerrain() const { return GetInfo().Domain == MaterialDomain::Terrain; @@ -75,28 +69,17 @@ public: /// /// Determines whether material is a particle shader. /// - /// true if material is particle shader; otherwise, false. FORCE_INLINE bool IsParticle() const { return GetInfo().Domain == MaterialDomain::Particle; } /// - /// Checks if material needs vertex color vertex buffer data for rendering. + /// Determines whether material is a deformable shader. /// - /// True if mesh renderer have to provide vertex color buffer to render that material - FORCE_INLINE bool RequireVertexColor() const + FORCE_INLINE bool IsDeformable() const { - return (GetInfo().UsageFlags & MaterialUsageFlags::UseVertexColor) != 0; - } - - /// - /// Checks if material supports dithered LOD transitions. - /// - /// True if material supports dithered LOD transitions, otherwise false. - FORCE_INLINE bool IsDitheredLODTransition() const - { - return (GetInfo().FeaturesFlags & MaterialFeaturesFlags::DitheredLODTransition) != 0; + return GetInfo().Domain == MaterialDomain::Deformable; } /// @@ -108,7 +91,7 @@ public: /// /// Gets the mask of render passes supported by this material. /// - /// The drw passes supported by this material. + /// The draw passes supported by this material. virtual DrawPass GetDrawModes() const { return DrawPass::None; @@ -123,11 +106,22 @@ public: return false; } + /// + /// The instancing handling used to hash, batch and write draw calls. + /// + struct InstancingHandler + { + void (*GetHash)(const DrawCall& drawCall, int32& batchKey); + bool (*CanBatch)(const DrawCall& a, const DrawCall& b); + void (*WriteDrawCall)(struct InstanceData* instanceData, const DrawCall& drawCall); + }; + /// /// Returns true if material can use draw calls instancing. /// + /// The output data for the instancing handling used to hash, batch and write draw calls. Valid only when function returns true. /// True if can use instancing, otherwise false. - virtual bool CanUseInstancing() const + virtual bool CanUseInstancing(InstancingHandler& handler) const { return false; } diff --git a/Source/Engine/Graphics/Materials/MaterialInfo.h b/Source/Engine/Graphics/Materials/MaterialInfo.h index 50b4c1e35..862f8f221 100644 --- a/Source/Engine/Graphics/Materials/MaterialInfo.h +++ b/Source/Engine/Graphics/Materials/MaterialInfo.h @@ -39,6 +39,11 @@ API_ENUM() enum class MaterialDomain : byte /// The particle shader. Can be used only with particles geometry (sprites, trails and ribbons). Supports reading particle data on a GPU. /// Particle = 5, + + /// + /// The deformable shader. Can be used only with objects that can be deformed (spline models). + /// + Deformable = 6, }; /// diff --git a/Source/Engine/Graphics/Materials/MaterialParams.cpp b/Source/Engine/Graphics/Materials/MaterialParams.cpp index 1f1d1bac6..ea70d4587 100644 --- a/Source/Engine/Graphics/Materials/MaterialParams.cpp +++ b/Source/Engine/Graphics/Materials/MaterialParams.cpp @@ -230,36 +230,28 @@ void MaterialParameter::Bind(BindMeta& meta) const switch (_type) { case MaterialParameterType::Bool: - if (meta.Buffer0) - *((int32*)(meta.Buffer0 + _offset)) = _asBool; + *((int32*)(meta.Constants + _offset)) = _asBool; break; case MaterialParameterType::Integer: - if (meta.Buffer0) - *((int32*)(meta.Buffer0 + _offset)) = _asInteger; + *((int32*)(meta.Constants + _offset)) = _asInteger; break; case MaterialParameterType::Float: - if (meta.Buffer0) - *((float*)(meta.Buffer0 + _offset)) = _asFloat; + *((float*)(meta.Constants + _offset)) = _asFloat; break; case MaterialParameterType::Vector2: - if (meta.Buffer0) - *((Vector2*)(meta.Buffer0 + _offset)) = _asVector2; + *((Vector2*)(meta.Constants + _offset)) = _asVector2; break; case MaterialParameterType::Vector3: - if (meta.Buffer0) - *((Vector3*)(meta.Buffer0 + _offset)) = _asVector3; + *((Vector3*)(meta.Constants + _offset)) = _asVector3; break; case MaterialParameterType::Vector4: - if (meta.Buffer0) - *((Vector4*)(meta.Buffer0 + _offset)) = _asVector4; + *((Vector4*)(meta.Constants + _offset)) = _asVector4; break; case MaterialParameterType::Color: - if (meta.Buffer0) - *((Color*)(meta.Buffer0 + _offset)) = _asColor; + *((Color*)(meta.Constants + _offset)) = _asColor; break; case MaterialParameterType::Matrix: - if (meta.Buffer0) - Matrix::Transpose(_asMatrix, *(Matrix*)(meta.Buffer0 + _offset)); + Matrix::Transpose(_asMatrix, *(Matrix*)(meta.Constants + _offset)); break; case MaterialParameterType::NormalMap: { @@ -336,11 +328,10 @@ void MaterialParameter::Bind(BindMeta& meta) const break; } case MaterialParameterType::ChannelMask: - if (meta.Buffer0) - *((Vector4*)(meta.Buffer0 + _offset)) = Vector4(_asInteger == 0, _asInteger == 1, _asInteger == 2, _asInteger == 3); + *((Vector4*)(meta.Constants + _offset)) = Vector4(_asInteger == 0, _asInteger == 1, _asInteger == 2, _asInteger == 3); break; case MaterialParameterType::GameplayGlobal: - if (meta.Buffer0 && _asAsset) + if (_asAsset) { const auto e = _asAsset.As()->Variables.TryGet(_name); if (e) @@ -348,26 +339,26 @@ void MaterialParameter::Bind(BindMeta& meta) const switch (e->Value.Type.Type) { case VariantType::Bool: - *((bool*)(meta.Buffer0 + _offset)) = e->Value.AsBool; + *((bool*)(meta.Constants + _offset)) = e->Value.AsBool; break; case VariantType::Int: - *((int32*)(meta.Buffer0 + _offset)) = e->Value.AsInt; + *((int32*)(meta.Constants + _offset)) = e->Value.AsInt; break; case VariantType::Uint: - *((uint32*)(meta.Buffer0 + _offset)) = e->Value.AsUint; + *((uint32*)(meta.Constants + _offset)) = e->Value.AsUint; break; case VariantType::Float: - *((float*)(meta.Buffer0 + _offset)) = e->Value.AsFloat; + *((float*)(meta.Constants + _offset)) = e->Value.AsFloat; break; case VariantType::Vector2: - *((Vector2*)(meta.Buffer0 + _offset)) = e->Value.AsVector2(); + *((Vector2*)(meta.Constants + _offset)) = e->Value.AsVector2(); break; case VariantType::Vector3: - *((Vector3*)(meta.Buffer0 + _offset)) = e->Value.AsVector3(); + *((Vector3*)(meta.Constants + _offset)) = e->Value.AsVector3(); break; case VariantType::Vector4: case VariantType::Color: - *((Vector4*)(meta.Buffer0 + _offset)) = e->Value.AsVector4(); + *((Vector4*)(meta.Constants + _offset)) = e->Value.AsVector4(); break; default: ; } diff --git a/Source/Engine/Graphics/Materials/MaterialParams.h b/Source/Engine/Graphics/Materials/MaterialParams.h index 997b18d34..ca48dc143 100644 --- a/Source/Engine/Graphics/Materials/MaterialParams.h +++ b/Source/Engine/Graphics/Materials/MaterialParams.h @@ -309,9 +309,9 @@ public: GPUContext* Context; /// - /// The pointer to the first constants buffer in memory. + /// The pointer to the constants buffer in the memory. /// - byte* Buffer0; + byte* Constants; /// /// The input scene color. It's optional and used in forward/postFx rendering. diff --git a/Source/Engine/Graphics/Materials/MaterialShader.cpp b/Source/Engine/Graphics/Materials/MaterialShader.cpp index 478992959..caac3b5f2 100644 --- a/Source/Engine/Graphics/Materials/MaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/MaterialShader.cpp @@ -14,18 +14,15 @@ #include "GUIMaterialShader.h" #include "TerrainMaterialShader.h" #include "ParticleMaterialShader.h" +#include "DeformableMaterialShader.h" -GPUPipelineState* MaterialShader::PipelineStateCache::GetPS(CullMode mode, bool wireframe) +GPUPipelineState* MaterialShader::PipelineStateCache::InitPS(CullMode mode, bool wireframe) { - const int32 index = static_cast(mode) + (wireframe ? 3 : 0); - if (PS[index]) - return PS[index]; - Desc.CullMode = mode; Desc.Wireframe = wireframe; - PS[index] = GPUDevice::Instance->CreatePipelineState(); - PS[index]->Init(Desc); - return PS[index]; + auto ps = GPUDevice::Instance->CreatePipelineState(); + ps->Init(Desc); + return ps; } MaterialShader::MaterialShader(const String& name) @@ -65,6 +62,9 @@ MaterialShader* MaterialShader::Create(const String& name, MemoryReadStream& sha case MaterialDomain::Particle: material = New(name); break; + case MaterialDomain::Deformable: + material = New(name); + break; default: LOG(Fatal, "Unknown material type."); return nullptr; @@ -138,15 +138,17 @@ bool MaterialShader::Load(MemoryReadStream& shaderCacheStream, const MaterialInf } // Init memory for a constant buffer - const auto cb0 = _shader->GetCB(0); - if (cb0) + _cb = _shader->GetCB(0); + if (_cb) { - _cb0Data.Resize(cb0->GetSize(), false); - } - const auto cb1 = _shader->GetCB(1); - if (cb1) - { - _cb1Data.Resize(cb1->GetSize(), false); + int32 cbSize = _cb->GetSize(); + if (cbSize == 0) + { + // Handle unused constant buffer (eg. postFx returning solid color) + cbSize = 1024; + _cb = nullptr; + } + _cbData.Resize(cbSize, false); } // Initialize the material based on type (create pipeline states and setup) @@ -162,7 +164,7 @@ bool MaterialShader::Load(MemoryReadStream& shaderCacheStream, const MaterialInf void MaterialShader::Unload() { _isLoaded = false; - _cb0Data.Resize(0, false); - _cb1Data.Resize(0, false); + _cb = nullptr; + _cbData.Resize(0, false); _shader->ReleaseGPU(); } diff --git a/Source/Engine/Graphics/Materials/MaterialShader.h b/Source/Engine/Graphics/Materials/MaterialShader.h index 58ba297fa..8c49389a7 100644 --- a/Source/Engine/Graphics/Materials/MaterialShader.h +++ b/Source/Engine/Graphics/Materials/MaterialShader.h @@ -9,10 +9,11 @@ /// /// Current materials shader version. /// -#define MATERIAL_GRAPH_VERSION 146 +#define MATERIAL_GRAPH_VERSION 148 class Material; class GPUShader; +class GPUConstantBuffer; class MemoryReadStream; /// @@ -37,7 +38,16 @@ protected: Desc = desc; } - GPUPipelineState* GetPS(CullMode mode, bool wireframe); + GPUPipelineState* GetPS(CullMode mode, bool wireframe) + { + const int32 index = static_cast(mode) + (wireframe ? 3 : 0); + auto ps = PS[index]; + if (!ps) + PS[index] = ps = InitPS(mode, wireframe); + return ps; + } + + GPUPipelineState* InitPS(CullMode mode, bool wireframe); void Release() { @@ -52,8 +62,8 @@ protected: bool _isLoaded; GPUShader* _shader; - Array _cb0Data; - Array _cb1Data; + GPUConstantBuffer* _cb; + Array _cbData; MaterialInfo _info; protected: @@ -90,10 +100,8 @@ public: /// The created and loaded material or null if failed. static MaterialShader* CreateDummy(MemoryReadStream& shaderCacheStream, const MaterialInfo& info); -public: - /// - /// Clear loaded data + /// Clears the loaded data. /// virtual void Unload(); diff --git a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp new file mode 100644 index 000000000..abc28a0de --- /dev/null +++ b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp @@ -0,0 +1,188 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#include "MaterialShaderFeatures.h" +#include "Engine/Graphics/RenderTask.h" +#include "Engine/Renderer/RenderList.h" +#include "Engine/Renderer/ShadowsPass.h" +#if USE_EDITOR +#include "Engine/Renderer/Lightmaps.h" +#endif +#include "Engine/Level/Scene/Lightmap.h" +#include "Engine/Level/Actors/EnvironmentProbe.h" + +void ForwardShadingFeature::Bind(MaterialShader::BindParameters& params, byte*& cb, int32& srv) +{ + auto context = params.GPUContext; + auto cache = params.RenderContext.List; + auto& view = params.RenderContext.View; + auto& drawCall = *params.FirstDrawCall; + auto& data = *(Data*)cb; + const int32 envProbeShaderRegisterIndex = srv + 0; + const int32 skyLightShaderRegisterIndex = srv + 1; + const int32 dirLightShaderRegisterIndex = srv + 2; + + // Set fog input + if (cache->Fog) + { + cache->Fog->GetExponentialHeightFogData(view, data.ExponentialHeightFog); + } + else + { + data.ExponentialHeightFog.FogMinOpacity = 1.0f; + data.ExponentialHeightFog.ApplyDirectionalInscattering = 0.0f; + } + + // Set directional light input + if (cache->DirectionalLights.HasItems()) + { + const auto& dirLight = cache->DirectionalLights.First(); + const auto shadowPass = ShadowsPass::Instance(); + const bool useShadow = shadowPass->LastDirLightIndex == 0; + if (useShadow) + { + data.DirectionalLightShadow = shadowPass->LastDirLight; + context->BindSR(dirLightShaderRegisterIndex, shadowPass->LastDirLightShadowMap); + } + else + { + context->UnBindSR(dirLightShaderRegisterIndex); + } + dirLight.SetupLightData(&data.DirectionalLight, view, useShadow); + } + else + { + data.DirectionalLight.Color = Vector3::Zero; + data.DirectionalLight.CastShadows = 0.0f; + context->UnBindSR(dirLightShaderRegisterIndex); + } + + // Set sky light + if (cache->SkyLights.HasItems()) + { + auto& skyLight = cache->SkyLights.First(); + skyLight.SetupLightData(&data.SkyLight, view, false); + const auto texture = skyLight.Image ? skyLight.Image->GetTexture() : nullptr; + context->BindSR(skyLightShaderRegisterIndex, GET_TEXTURE_VIEW_SAFE(texture)); + } + else + { + Platform::MemoryClear(&data.SkyLight, sizeof(data.SkyLight)); + context->UnBindSR(skyLightShaderRegisterIndex); + } + + // Set reflection probe data + EnvironmentProbe* probe = nullptr; + // TODO: optimize env probe searching for a transparent material - use spatial cache for renderer to find it + for (int32 i = 0; i < cache->EnvironmentProbes.Count(); i++) + { + const auto p = cache->EnvironmentProbes[i]; + if (p->GetSphere().Contains(drawCall.World.GetTranslation()) != ContainmentType::Disjoint) + { + probe = p; + break; + } + } + if (probe && probe->GetProbe()) + { + probe->SetupProbeData(&data.EnvironmentProbe); + const auto texture = probe->GetProbe()->GetTexture(); + context->BindSR(envProbeShaderRegisterIndex, GET_TEXTURE_VIEW_SAFE(texture)); + } + else + { + data.EnvironmentProbe.Data1 = Vector4::Zero; + context->UnBindSR(envProbeShaderRegisterIndex); + } + + // Set local lights + data.LocalLightsCount = 0; + for (int32 i = 0; i < cache->PointLights.Count(); i++) + { + const auto& light = cache->PointLights[i]; + if (BoundingSphere(light.Position, light.Radius).Contains(drawCall.World.GetTranslation()) != ContainmentType::Disjoint) + { + light.SetupLightData(&data.LocalLights[data.LocalLightsCount], view, false); + data.LocalLightsCount++; + if (data.LocalLightsCount == MaxLocalLights) + break; + } + } + for (int32 i = 0; i < cache->SpotLights.Count(); i++) + { + const auto& light = cache->SpotLights[i]; + if (BoundingSphere(light.Position, light.Radius).Contains(drawCall.World.GetTranslation()) != ContainmentType::Disjoint) + { + light.SetupLightData(&data.LocalLights[data.LocalLightsCount], view, false); + data.LocalLightsCount++; + if (data.LocalLightsCount == MaxLocalLights) + break; + } + } + + cb += sizeof(Data); + srv += SRVs; +} + +bool LightmapFeature::Bind(MaterialShader::BindParameters& params, byte*& cb, int32& srv) +{ + auto context = params.GPUContext; + auto& view = params.RenderContext.View; + auto& drawCall = *params.FirstDrawCall; + auto& data = *(Data*)cb; + + const bool useLightmap = view.Flags & ViewFlags::GI +#if USE_EDITOR + && EnableLightmapsUsage +#endif + && drawCall.Surface.Lightmap != nullptr; + if (useLightmap) + { + // Bind lightmap textures + GPUTexture *lightmap0, *lightmap1, *lightmap2; + drawCall.Features.Lightmap->GetTextures(&lightmap0, &lightmap1, &lightmap2); + context->BindSR(srv + 0, lightmap0); + context->BindSR(srv + 1, lightmap1); + context->BindSR(srv + 2, lightmap2); + + // Set lightmap data + data.LightmapArea = drawCall.Features.LightmapUVsArea; + } + + srv += SRVs; + cb += sizeof(Data); + return useLightmap; +} + +#if USE_EDITOR + +void ForwardShadingFeature::Generate(GeneratorData& data) +{ + data.Template = TEXT("Features/ForwardShading.hlsl"); +} + +void DeferredShadingFeature::Generate(GeneratorData& data) +{ + data.Template = TEXT("Features/DeferredShading.hlsl"); +} + +void TessellationFeature::Generate(GeneratorData& data) +{ + data.Template = TEXT("Features/Tessellation.hlsl"); +} + +void LightmapFeature::Generate(GeneratorData& data) +{ + data.Template = TEXT("Features/Lightmap.hlsl"); +} + +void DistortionFeature::Generate(GeneratorData& data) +{ + data.Template = TEXT("Features/Distortion.hlsl"); +} + +void MotionVectorsFeature::Generate(GeneratorData& data) +{ + data.Template = TEXT("Features/MotionVectors.hlsl"); +} + +#endif diff --git a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.h b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.h new file mode 100644 index 000000000..a70d9ada8 --- /dev/null +++ b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.h @@ -0,0 +1,90 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "MaterialShader.h" +#include "Engine/Core/Math/Rectangle.h" + +// Material shader features are plugin-based functionalities that are reusable between different material domains. +struct MaterialShaderFeature +{ +#if USE_EDITOR + struct GeneratorData + { + const Char* Template; + }; +#endif; +}; + +// Material shader feature that add support for Forward shading inside the material shader. +struct ForwardShadingFeature : MaterialShaderFeature +{ + enum { MaxLocalLights = 4 }; + + enum { SRVs = 3 }; + + PACK_STRUCT(struct Data + { + LightData DirectionalLight; + LightShadowData DirectionalLightShadow; + LightData SkyLight; + ProbeData EnvironmentProbe; + ExponentialHeightFogData ExponentialHeightFog; + Vector3 Dummy2; + uint32 LocalLightsCount; + LightData LocalLights[MaxLocalLights]; + }); + + static void Bind(MaterialShader::BindParameters& params, byte*& cb, int32& srv); +#if USE_EDITOR + static void Generate(GeneratorData& data); +#endif +}; + +// Material shader feature that add support for Deferred shading inside the material shader. +struct DeferredShadingFeature : MaterialShaderFeature +{ +#if USE_EDITOR + static void Generate(GeneratorData& data); +#endif +}; + +// Material shader feature that adds geometry hardware tessellation (using Hull and Domain shaders). +struct TessellationFeature : MaterialShaderFeature +{ +#if USE_EDITOR + static void Generate(GeneratorData& data); +#endif +}; + +// Material shader feature that adds lightmap sampling feature. +struct LightmapFeature : MaterialShaderFeature +{ + enum { SRVs = 3 }; + + PACK_STRUCT(struct Data + { + Rectangle LightmapArea; + }); + + static bool Bind(MaterialShader::BindParameters& params, byte*& cb, int32& srv); +#if USE_EDITOR + static void Generate(GeneratorData& data); +#endif +}; + +// Material shader feature that adds distortion vectors rendering pass. +struct DistortionFeature : MaterialShaderFeature +{ +#if USE_EDITOR + static void Generate(GeneratorData& data); +#endif +}; + +// Material shader feature that adds motion vectors rendering pass. +struct MotionVectorsFeature : MaterialShaderFeature +{ +#if USE_EDITOR + static void Generate(GeneratorData& data); +#endif +}; diff --git a/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp b/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp index 8c1c065ab..7a718ceb7 100644 --- a/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp @@ -1,20 +1,19 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "ParticleMaterialShader.h" +#include "MaterialShaderFeatures.h" #include "MaterialParams.h" -#include "Engine/Renderer/DrawCall.h" -#include "Engine/Renderer/ShadowsPass.h" -#include "Engine/Graphics/RenderView.h" -#include "Engine/Renderer/RenderList.h" -#include "Engine/Graphics/GPUContext.h" -#include "Engine/Graphics/Shaders/GPUConstantBuffer.h" #include "Engine/Engine/Time.h" +#include "Engine/Renderer/DrawCall.h" +#include "Engine/Renderer/RenderList.h" +#include "Engine/Graphics/RenderView.h" +#include "Engine/Graphics/GPUContext.h" #include "Engine/Graphics/GPUDevice.h" -#include "Engine/Graphics/Shaders/GPUShader.h" #include "Engine/Graphics/GPULimits.h" +#include "Engine/Graphics/RenderTask.h" +#include "Engine/Graphics/Shaders/GPUShader.h" +#include "Engine/Graphics/Shaders/GPUConstantBuffer.h" #include "Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.h" -#include "Engine/Content/Assets/CubeTexture.h" -#include "Engine/Level/Actors/EnvironmentProbe.h" #define MAX_LOCAL_LIGHTS 4 @@ -49,17 +48,6 @@ PACK_STRUCT(struct ParticleMaterialShaderData { Matrix WorldMatrixInverseTransposed; }); -PACK_STRUCT(struct ParticleMaterialShaderLightingData { - LightData DirectionalLight; - LightShadowData DirectionalLightShadow; - LightData SkyLight; - ProbeData EnvironmentProbe; - ExponentialHeightFogData ExponentialHeightFog; - Vector3 Dummy1; - uint32 LocalLightsCount; - LightData LocalLights[MAX_LOCAL_LIGHTS]; - }); - DrawPass ParticleMaterialShader::GetDrawModes() const { return _drawModes; @@ -70,43 +58,42 @@ void ParticleMaterialShader::Bind(BindParameters& params) // Prepare auto context = params.GPUContext; auto& view = params.RenderContext.View; - auto cache = params.RenderContext.List; auto& drawCall = *params.FirstDrawCall; - const auto cb0 = _shader->GetCB(0); - const bool hasCb0 = cb0->GetSize() != 0; - const auto cb1 = _shader->GetCB(1); - const bool hasCb1 = cb1->GetSize() != 0; - const uint32 sortedIndicesOffset = drawCall.Module->SortedIndicesOffset; + const uint32 sortedIndicesOffset = drawCall.Particle.Module->SortedIndicesOffset; + byte* cb = _cbData.Get(); + auto materialData = reinterpret_cast(cb); + cb += sizeof(ParticleMaterialShaderData); + int32 srv = 2; + + // Setup features + ForwardShadingFeature::Bind(params, cb, srv); // Setup parameters MaterialParameter::BindMeta bindMeta; bindMeta.Context = context; - bindMeta.Buffer0 = hasCb0 ? _cb0Data.Get() + sizeof(ParticleMaterialShaderData) : nullptr; + bindMeta.Constants = cb; bindMeta.Input = nullptr; bindMeta.Buffers = params.RenderContext.Buffers; bindMeta.CanSampleDepth = GPUDevice::Instance->Limits.HasReadOnlyDepth; bindMeta.CanSampleGBuffer = true; MaterialParams::Bind(params.ParamsLink, bindMeta); - // Setup particles data and attributes binding info + // Setup particles data + context->BindSR(0, drawCall.Particle.Particles->GPU.Buffer->View()); + context->BindSR(1, drawCall.Particle.Particles->GPU.SortedIndices ? drawCall.Particle.Particles->GPU.SortedIndices->View() : nullptr); + + // Setup particles attributes binding info { - context->BindSR(0, drawCall.Particles->GPU.Buffer->View()); - if (drawCall.Particles->GPU.SortedIndices) - context->BindSR(1, drawCall.Particles->GPU.SortedIndices->View()); - - if (hasCb0) + const auto& p = *params.ParamsLink->This; + for (int32 i = 0; i < p.Count(); i++) { - const auto& p = *params.ParamsLink->This; - for (int32 i = 0; i < p.Count(); i++) + const auto& param = p.At(i); + if (param.GetParameterType() == MaterialParameterType::Integer && param.GetName().StartsWith(TEXT("Particle."))) { - const auto& param = p.At(i); - if (param.GetParameterType() == MaterialParameterType::Integer && param.GetName().StartsWith(TEXT("Particle."))) - { - auto name = StringView(param.GetName().Get() + 9); + auto name = StringView(param.GetName().Get() + 9); - const int32 offset = drawCall.Particles->Layout->FindAttributeOffset(name); - *((int32*)(bindMeta.Buffer0 + param.GetBindOffset())) = offset; - } + const int32 offset = drawCall.Particle.Particles->Layout->FindAttributeOffset(name); + *((int32*)(bindMeta.Constants + param.GetBindOffset())) = offset; } } } @@ -115,7 +102,7 @@ void ParticleMaterialShader::Bind(BindParameters& params) const bool wireframe = (_info.FeaturesFlags & MaterialFeaturesFlags::Wireframe) != 0 || view.Mode == ViewMode::Wireframe; CullMode cullMode = view.Pass == DrawPass::Depth ? CullMode::TwoSided : _info.CullMode; PipelineStateCache* psCache = nullptr; - switch (drawCall.Module->TypeID) + switch (drawCall.Particle.Module->TypeID) { // Sprite Rendering case 400: @@ -138,24 +125,19 @@ void ParticleMaterialShader::Bind(BindParameters& params) static StringView ParticleRibbonTwist(TEXT("RibbonTwist")); static StringView ParticleRibbonFacingVector(TEXT("RibbonFacingVector")); - if (hasCb0) - { - const auto materialData = reinterpret_cast(_cb0Data.Get()); + materialData->RibbonWidthOffset = drawCall.Particle.Particles->Layout->FindAttributeOffset(ParticleRibbonWidth, ParticleAttribute::ValueTypes::Float, -1); + materialData->RibbonTwistOffset = drawCall.Particle.Particles->Layout->FindAttributeOffset(ParticleRibbonTwist, ParticleAttribute::ValueTypes::Float, -1); + materialData->RibbonFacingVectorOffset = drawCall.Particle.Particles->Layout->FindAttributeOffset(ParticleRibbonFacingVector, ParticleAttribute::ValueTypes::Vector3, -1); - materialData->RibbonWidthOffset = drawCall.Particles->Layout->FindAttributeOffset(ParticleRibbonWidth, ParticleAttribute::ValueTypes::Float, -1); - materialData->RibbonTwistOffset = drawCall.Particles->Layout->FindAttributeOffset(ParticleRibbonTwist, ParticleAttribute::ValueTypes::Float, -1); - materialData->RibbonFacingVectorOffset = drawCall.Particles->Layout->FindAttributeOffset(ParticleRibbonFacingVector, ParticleAttribute::ValueTypes::Vector3, -1); + materialData->RibbonUVTilingDistance = drawCall.Particle.Ribbon.UVTilingDistance; + materialData->RibbonUVScale.X = drawCall.Particle.Ribbon.UVScaleX; + materialData->RibbonUVScale.Y = drawCall.Particle.Ribbon.UVScaleY; + materialData->RibbonUVOffset.X = drawCall.Particle.Ribbon.UVOffsetX; + materialData->RibbonUVOffset.Y = drawCall.Particle.Ribbon.UVOffsetY; + materialData->RibbonSegmentCount = drawCall.Particle.Ribbon.SegmentCount; - materialData->RibbonUVTilingDistance = drawCall.Ribbon.UVTilingDistance; - materialData->RibbonUVScale.X = drawCall.Ribbon.UVScaleX; - materialData->RibbonUVScale.Y = drawCall.Ribbon.UVScaleY; - materialData->RibbonUVOffset.X = drawCall.Ribbon.UVOffsetX; - materialData->RibbonUVOffset.Y = drawCall.Ribbon.UVOffsetY; - materialData->RibbonSegmentCount = drawCall.Ribbon.SegmentCount; - } - - if (drawCall.Ribbon.SegmentDistances) - context->BindSR(1, drawCall.Ribbon.SegmentDistances->View()); + if (drawCall.Particle.Ribbon.SegmentDistances) + context->BindSR(1, drawCall.Particle.Ribbon.SegmentDistances->View()); break; } @@ -163,11 +145,8 @@ void ParticleMaterialShader::Bind(BindParameters& params) ASSERT(psCache); GPUPipelineState* state = psCache->GetPS(cullMode, wireframe); - // Setup material constants data - if (hasCb0) + // Setup material constants { - const auto materialData = reinterpret_cast(_cb0Data.Get()); - static StringView ParticlePosition(TEXT("Position")); static StringView ParticleSpriteSize(TEXT("SpriteSize")); static StringView ParticleSpriteFacingMode(TEXT("SpriteFacingMode")); @@ -186,137 +165,25 @@ void ParticleMaterialShader::Bind(BindParameters& params) materialData->TimeParam = Time::Draw.UnscaledTime.GetTotalSeconds(); materialData->ViewInfo = view.ViewInfo; materialData->ScreenSize = view.ScreenSize; - materialData->SortedIndicesOffset = drawCall.Particles->GPU.SortedIndices && params.RenderContext.View.Pass != DrawPass::Depth ? sortedIndicesOffset : 0xFFFFFFFF; + materialData->SortedIndicesOffset = drawCall.Particle.Particles->GPU.SortedIndices && params.RenderContext.View.Pass != DrawPass::Depth ? sortedIndicesOffset : 0xFFFFFFFF; materialData->PerInstanceRandom = drawCall.PerInstanceRandom; - materialData->ParticleStride = drawCall.Particles->Stride; - materialData->PositionOffset = drawCall.Particles->Layout->FindAttributeOffset(ParticlePosition, ParticleAttribute::ValueTypes::Vector3); - materialData->SpriteSizeOffset = drawCall.Particles->Layout->FindAttributeOffset(ParticleSpriteSize, ParticleAttribute::ValueTypes::Vector2); - materialData->SpriteFacingModeOffset = drawCall.Particles->Layout->FindAttributeOffset(ParticleSpriteFacingMode, ParticleAttribute::ValueTypes::Int, -1); - materialData->SpriteFacingVectorOffset = drawCall.Particles->Layout->FindAttributeOffset(ParticleSpriteFacingVector, ParticleAttribute::ValueTypes::Vector3); - materialData->VelocityOffset = drawCall.Particles->Layout->FindAttributeOffset(ParticleVelocityOffset, ParticleAttribute::ValueTypes::Vector3); - materialData->RotationOffset = drawCall.Particles->Layout->FindAttributeOffset(ParticleRotationOffset, ParticleAttribute::ValueTypes::Vector3, -1); - materialData->ScaleOffset = drawCall.Particles->Layout->FindAttributeOffset(ParticleScaleOffset, ParticleAttribute::ValueTypes::Vector3, -1); - materialData->ModelFacingModeOffset = drawCall.Particles->Layout->FindAttributeOffset(ParticleModelFacingModeOffset, ParticleAttribute::ValueTypes::Int, -1); + materialData->ParticleStride = drawCall.Particle.Particles->Stride; + materialData->PositionOffset = drawCall.Particle.Particles->Layout->FindAttributeOffset(ParticlePosition, ParticleAttribute::ValueTypes::Vector3); + materialData->SpriteSizeOffset = drawCall.Particle.Particles->Layout->FindAttributeOffset(ParticleSpriteSize, ParticleAttribute::ValueTypes::Vector2); + materialData->SpriteFacingModeOffset = drawCall.Particle.Particles->Layout->FindAttributeOffset(ParticleSpriteFacingMode, ParticleAttribute::ValueTypes::Int, -1); + materialData->SpriteFacingVectorOffset = drawCall.Particle.Particles->Layout->FindAttributeOffset(ParticleSpriteFacingVector, ParticleAttribute::ValueTypes::Vector3); + materialData->VelocityOffset = drawCall.Particle.Particles->Layout->FindAttributeOffset(ParticleVelocityOffset, ParticleAttribute::ValueTypes::Vector3); + materialData->RotationOffset = drawCall.Particle.Particles->Layout->FindAttributeOffset(ParticleRotationOffset, ParticleAttribute::ValueTypes::Vector3, -1); + materialData->ScaleOffset = drawCall.Particle.Particles->Layout->FindAttributeOffset(ParticleScaleOffset, ParticleAttribute::ValueTypes::Vector3, -1); + materialData->ModelFacingModeOffset = drawCall.Particle.Particles->Layout->FindAttributeOffset(ParticleModelFacingModeOffset, ParticleAttribute::ValueTypes::Int, -1); Matrix::Invert(drawCall.World, materialData->WorldMatrixInverseTransposed); } - // Setup lighting constants data - if (hasCb1) - { - auto& lightingData = *reinterpret_cast(_cb1Data.Get()); - const int32 envProbeShaderRegisterIndex = 2; - const int32 skyLightShaderRegisterIndex = 3; - const int32 dirLightShaderRegisterIndex = 4; - - // Set fog input - if (cache->Fog) - { - cache->Fog->GetExponentialHeightFogData(view, lightingData.ExponentialHeightFog); - } - else - { - lightingData.ExponentialHeightFog.FogMinOpacity = 1.0f; - lightingData.ExponentialHeightFog.ApplyDirectionalInscattering = 0.0f; - } - - // Set directional light input - if (cache->DirectionalLights.HasItems()) - { - const auto& dirLight = cache->DirectionalLights.First(); - const auto shadowPass = ShadowsPass::Instance(); - const bool useShadow = shadowPass->LastDirLightIndex == 0; - if (useShadow) - { - lightingData.DirectionalLightShadow = shadowPass->LastDirLight; - context->BindSR(dirLightShaderRegisterIndex, shadowPass->LastDirLightShadowMap); - } - else - { - context->UnBindSR(dirLightShaderRegisterIndex); - } - dirLight.SetupLightData(&lightingData.DirectionalLight, view, useShadow); - } - else - { - lightingData.DirectionalLight.Color = Vector3::Zero; - lightingData.DirectionalLight.CastShadows = 0.0f; - context->UnBindSR(dirLightShaderRegisterIndex); - } - - // Set sky light - if (cache->SkyLights.HasItems()) - { - auto& skyLight = cache->SkyLights.Last(); - skyLight.SetupLightData(&lightingData.SkyLight, view, false); - const auto texture = skyLight.Image ? skyLight.Image->GetTexture() : nullptr; - context->BindSR(skyLightShaderRegisterIndex, texture); - } - else - { - Platform::MemoryClear(&lightingData.SkyLight, sizeof(lightingData.SkyLight)); - context->UnBindSR(skyLightShaderRegisterIndex); - } - - // Set reflection probe data - EnvironmentProbe* probe = nullptr; - // TODO: optimize env probe searching for a transparent material - use spatial cache for renderer to find it - for (int32 i = 0; i < cache->EnvironmentProbes.Count(); i++) - { - const auto p = cache->EnvironmentProbes[i]; - if (p->GetSphere().Contains(drawCall.World.GetTranslation()) != ContainmentType::Disjoint) - { - probe = p; - break; - } - } - if (probe && probe->GetProbe()) - { - probe->SetupProbeData(&lightingData.EnvironmentProbe); - const auto texture = probe->GetProbe()->GetTexture(); - context->BindSR(envProbeShaderRegisterIndex, texture); - } - else - { - lightingData.EnvironmentProbe.Data1 = Vector4::Zero; - context->UnBindSR(envProbeShaderRegisterIndex); - } - - // Set local lights - lightingData.LocalLightsCount = 0; - for (int32 i = 0; i < cache->PointLights.Count(); i++) - { - const auto& light = cache->PointLights[i]; - if (BoundingSphere(light.Position, light.Radius).Contains(drawCall.World.GetTranslation()) != ContainmentType::Disjoint) - { - light.SetupLightData(&lightingData.LocalLights[lightingData.LocalLightsCount], view, false); - lightingData.LocalLightsCount++; - if (lightingData.LocalLightsCount == MAX_LOCAL_LIGHTS) - break; - } - } - for (int32 i = 0; i < cache->SpotLights.Count(); i++) - { - const auto& light = cache->SpotLights[i]; - if (BoundingSphere(light.Position, light.Radius).Contains(drawCall.World.GetTranslation()) != ContainmentType::Disjoint) - { - light.SetupLightData(&lightingData.LocalLights[lightingData.LocalLightsCount], view, false); - lightingData.LocalLightsCount++; - if (lightingData.LocalLightsCount == MAX_LOCAL_LIGHTS) - break; - } - } - } - // Bind constants - if (hasCb0) + if (_cb) { - context->UpdateCB(cb0, _cb0Data.Get()); - context->BindCB(0, cb0); - } - if (hasCb1) - { - context->UpdateCB(cb1, _cb1Data.Get()); - context->BindCB(1, cb1); + context->UpdateCB(_cb, _cbData.Get()); + context->BindCB(0, _cb); } // Bind pipeline diff --git a/Source/Engine/Graphics/Materials/PostFxMaterialShader.cpp b/Source/Engine/Graphics/Materials/PostFxMaterialShader.cpp index 5c2fb1df0..a55455173 100644 --- a/Source/Engine/Graphics/Materials/PostFxMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/PostFxMaterialShader.cpp @@ -26,25 +26,23 @@ void PostFxMaterialShader::Bind(BindParameters& params) // Prepare auto context = params.GPUContext; auto& view = params.RenderContext.View; - auto& drawCall = *params.FirstDrawCall; - const auto cb0 = _shader->GetCB(0); - const bool hasCb0 = cb0->GetSize() != 0; + byte* cb = _cbData.Get(); + auto materialData = reinterpret_cast(cb); + cb += sizeof(PostFxMaterialShaderData); + int32 srv = 0; // Setup parameters MaterialParameter::BindMeta bindMeta; bindMeta.Context = context; - bindMeta.Buffer0 = hasCb0 ? _cb0Data.Get() + sizeof(PostFxMaterialShaderData) : nullptr; + bindMeta.Constants = cb; bindMeta.Input = params.Input; bindMeta.Buffers = params.RenderContext.Buffers; bindMeta.CanSampleDepth = true; bindMeta.CanSampleGBuffer = true; MaterialParams::Bind(params.ParamsLink, bindMeta); - // Setup material constants data - if (hasCb0) + // Setup material constants { - const auto materialData = reinterpret_cast(_cb0Data.Get()); - Matrix::Transpose(view.View, materialData->ViewMatrix); materialData->ViewPos = view.Position; materialData->ViewFar = view.Far; @@ -56,10 +54,10 @@ void PostFxMaterialShader::Bind(BindParameters& params) } // Bind constants - if (hasCb0) + if (_cb) { - context->UpdateCB(cb0, _cb0Data.Get()); - context->BindCB(0, cb0); + context->UpdateCB(_cb, _cbData.Get()); + context->BindCB(0, _cb); } // Bind pipeline diff --git a/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp b/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp index 212e27d87..ec3c038a5 100644 --- a/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "TerrainMaterialShader.h" +#include "MaterialShaderFeatures.h" #include "MaterialParams.h" #include "Engine/Engine/Time.h" #include "Engine/Graphics/GPUContext.h" @@ -25,7 +26,6 @@ PACK_STRUCT(struct TerrainMaterialShaderData { float TimeParam; Vector4 ViewInfo; Vector4 ScreenSize; - Rectangle LightmapArea; Vector3 WorldInvScale; float WorldDeterminantSign; float PerInstanceRandom; @@ -54,84 +54,65 @@ void TerrainMaterialShader::Bind(BindParameters& params) auto context = params.GPUContext; auto& view = params.RenderContext.View; auto& drawCall = *params.FirstDrawCall; - const auto cb0 = _shader->GetCB(0); - const bool hasCb0 = cb0->GetSize() != 0; + byte* cb = _cbData.Get(); + auto materialData = reinterpret_cast(cb); + cb += sizeof(TerrainMaterialShaderData); + int32 srv = 3; + + // Setup features + const bool useLightmap = LightmapFeature::Bind(params, cb, srv); // Setup parameters MaterialParameter::BindMeta bindMeta; bindMeta.Context = context; - bindMeta.Buffer0 = hasCb0 ? _cb0Data.Get() + sizeof(TerrainMaterialShaderData) : nullptr; + bindMeta.Constants = cb; bindMeta.Input = nullptr; bindMeta.Buffers = nullptr; bindMeta.CanSampleDepth = false; bindMeta.CanSampleGBuffer = false; MaterialParams::Bind(params.ParamsLink, bindMeta); - // Setup material constants data - auto data = reinterpret_cast(_cb0Data.Get()); - if (hasCb0) + // Setup material constants { - Matrix::Transpose(view.Frustum.GetMatrix(), data->ViewProjectionMatrix); - Matrix::Transpose(drawCall.World, data->WorldMatrix); - Matrix::Transpose(view.View, data->ViewMatrix); - - data->ViewPos = view.Position; - data->ViewFar = view.Far; - data->ViewDir = view.Direction; - data->TimeParam = Time::Draw.UnscaledTime.GetTotalSeconds(); - data->ViewInfo = view.ViewInfo; - data->ScreenSize = view.ScreenSize; - - // Extract per axis scales from LocalToWorld transform + Matrix::Transpose(view.Frustum.GetMatrix(), materialData->ViewProjectionMatrix); + Matrix::Transpose(drawCall.World, materialData->WorldMatrix); + Matrix::Transpose(view.View, materialData->ViewMatrix); + materialData->ViewPos = view.Position; + materialData->ViewFar = view.Far; + materialData->ViewDir = view.Direction; + materialData->TimeParam = Time::Draw.UnscaledTime.GetTotalSeconds(); + materialData->ViewInfo = view.ViewInfo; + materialData->ScreenSize = view.ScreenSize; const float scaleX = Vector3(drawCall.World.M11, drawCall.World.M12, drawCall.World.M13).Length(); const float scaleY = Vector3(drawCall.World.M21, drawCall.World.M22, drawCall.World.M23).Length(); const float scaleZ = Vector3(drawCall.World.M31, drawCall.World.M32, drawCall.World.M33).Length(); - data->WorldInvScale = Vector3( + materialData->WorldInvScale = Vector3( scaleX > 0.00001f ? 1.0f / scaleX : 0.0f, scaleY > 0.00001f ? 1.0f / scaleY : 0.0f, scaleZ > 0.00001f ? 1.0f / scaleZ : 0.0f); - - data->WorldDeterminantSign = drawCall.WorldDeterminantSign; - data->PerInstanceRandom = drawCall.PerInstanceRandom; - data->CurrentLOD = drawCall.TerrainData.CurrentLOD; - data->ChunkSizeNextLOD = drawCall.TerrainData.ChunkSizeNextLOD; - data->TerrainChunkSizeLOD0 = drawCall.TerrainData.TerrainChunkSizeLOD0; - data->HeightmapUVScaleBias = drawCall.TerrainData.HeightmapUVScaleBias; - data->NeighborLOD = drawCall.TerrainData.NeighborLOD; - data->OffsetUV = drawCall.TerrainData.OffsetUV; - } - const bool useLightmap = view.Flags & ViewFlags::GI -#if USE_EDITOR - && EnableLightmapsUsage -#endif - && view.Pass == DrawPass::GBuffer - && drawCall.Lightmap != nullptr; - if (useLightmap) - { - // Bind lightmap textures - GPUTexture *lightmap0, *lightmap1, *lightmap2; - drawCall.Lightmap->GetTextures(&lightmap0, &lightmap1, &lightmap2); - context->BindSR(0, lightmap0); - context->BindSR(1, lightmap1); - context->BindSR(2, lightmap2); - - // Set lightmap data - data->LightmapArea = drawCall.LightmapUVsArea; + materialData->WorldDeterminantSign = drawCall.WorldDeterminantSign; + materialData->PerInstanceRandom = drawCall.PerInstanceRandom; + materialData->CurrentLOD = drawCall.Terrain.CurrentLOD; + materialData->ChunkSizeNextLOD = drawCall.Terrain.ChunkSizeNextLOD; + materialData->TerrainChunkSizeLOD0 = drawCall.Terrain.TerrainChunkSizeLOD0; + materialData->HeightmapUVScaleBias = drawCall.Terrain.HeightmapUVScaleBias; + materialData->NeighborLOD = drawCall.Terrain.NeighborLOD; + materialData->OffsetUV = drawCall.Terrain.OffsetUV; } // Bind terrain textures - const auto heightmap = drawCall.TerrainData.Patch->Heightmap.Get()->GetTexture(); - const auto splatmap0 = drawCall.TerrainData.Patch->Splatmap[0] ? drawCall.TerrainData.Patch->Splatmap[0]->GetTexture() : nullptr; - const auto splatmap1 = drawCall.TerrainData.Patch->Splatmap[1] ? drawCall.TerrainData.Patch->Splatmap[1]->GetTexture() : nullptr; - context->BindSR(3, heightmap); - context->BindSR(4, splatmap0); - context->BindSR(5, splatmap1); + const auto heightmap = drawCall.Terrain.Patch->Heightmap->GetTexture(); + const auto splatmap0 = drawCall.Terrain.Patch->Splatmap[0] ? drawCall.Terrain.Patch->Splatmap[0]->GetTexture() : nullptr; + const auto splatmap1 = drawCall.Terrain.Patch->Splatmap[1] ? drawCall.Terrain.Patch->Splatmap[1]->GetTexture() : nullptr; + context->BindSR(0, heightmap); + context->BindSR(1, splatmap0); + context->BindSR(2, splatmap1); // Bind constants - if (hasCb0) + if (_cb) { - context->UpdateCB(cb0, _cb0Data.Get()); - context->BindCB(0, cb0); + context->UpdateCB(_cb, _cbData.Get()); + context->BindCB(0, _cb); } // Select pipeline state based on current pass and render mode @@ -141,7 +122,7 @@ void TerrainMaterialShader::Bind(BindParameters& params) if (IsRunningRadiancePass) cullMode = CullMode::TwoSided; #endif - if (cullMode != CullMode::TwoSided && drawCall.IsNegativeScale()) + if (cullMode != CullMode::TwoSided && drawCall.WorldDeterminantSign < 0) { // Invert culling when scale is negative if (cullMode == CullMode::Normal) diff --git a/Source/Engine/Graphics/Mesh.cs b/Source/Engine/Graphics/Mesh.cs index f639bff8f..f96b4ce63 100644 --- a/Source/Engine/Graphics/Mesh.cs +++ b/Source/Engine/Graphics/Mesh.cs @@ -117,7 +117,7 @@ namespace FlaxEngine /// Mesh data will be cached and uploaded to the GPU with a delay. /// /// The mesh vertices positions. Cannot be null. - /// The mesh index buffer (triangles). Uses 32-bit stride buffer. Cannot be null. + /// The mesh index buffer (clockwise triangles). Uses 32-bit stride buffer. Cannot be null. /// The normal vectors (per vertex). /// The normal vectors (per vertex). Use null to compute them from normal vectors. /// The texture coordinates (per vertex). @@ -163,7 +163,7 @@ namespace FlaxEngine /// Mesh data will be cached and uploaded to the GPU with a delay. /// /// The mesh vertices positions. Cannot be null. - /// The mesh index buffer (triangles). Uses 32-bit stride buffer. Cannot be null. + /// The mesh index buffer (clockwise triangles). Uses 32-bit stride buffer. Cannot be null. /// The normal vectors (per vertex). /// The normal vectors (per vertex). Use null to compute them from normal vectors. /// The texture coordinates (per vertex). @@ -210,7 +210,7 @@ namespace FlaxEngine /// Mesh data will be cached and uploaded to the GPU with a delay. /// /// The mesh vertices positions. Cannot be null. - /// The mesh index buffer (triangles). Uses 16-bit stride buffer. Cannot be null. + /// The mesh index buffer (clockwise triangles). Uses 16-bit stride buffer. Cannot be null. /// The normal vectors (per vertex). /// The tangent vectors (per vertex). Use null to compute them from normal vectors. /// The texture coordinates (per vertex). @@ -257,7 +257,7 @@ namespace FlaxEngine /// Mesh data will be cached and uploaded to the GPU with a delay. /// /// The mesh vertices positions. Cannot be null. - /// The mesh index buffer (triangles). Uses 16-bit stride buffer. Cannot be null. + /// The mesh index buffer (clockwise triangles). Uses 16-bit stride buffer. Cannot be null. /// The normal vectors (per vertex). /// The tangent vectors (per vertex). Use null to compute them from normal vectors. /// The texture coordinates (per vertex). @@ -435,7 +435,7 @@ namespace FlaxEngine /// Downloads the third vertex buffer that contains mesh vertices data. To download data from GPU set to true and call this method from the thread other than main thread (see ). /// /// - /// If mesh has no vertex colors (stored in vertex buffer 2) the the returned value is null. + /// If mesh has no vertex colors (stored in vertex buffer 2) the returned value is null. /// /// If set to true the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data. /// The gathered data or null if mesh has no vertex colors. diff --git a/Source/Engine/Graphics/Models/Mesh.cpp b/Source/Engine/Graphics/Models/Mesh.cpp index 0ba51b2d7..8285883ae 100644 --- a/Source/Engine/Graphics/Models/Mesh.cpp +++ b/Source/Engine/Graphics/Models/Mesh.cpp @@ -10,6 +10,127 @@ #include "Engine/Serialization/MemoryReadStream.h" #include +namespace +{ + template + bool UpdateMesh(Mesh* mesh, uint32 vertexCount, uint32 triangleCount, Vector3* vertices, IndexType* triangles, Vector3* normals, Vector3* tangents, Vector2* uvs, Color32* colors) + { + auto model = mesh->GetModel(); + CHECK_RETURN(model && model->IsVirtual(), true); + CHECK_RETURN(triangles && vertices, true); + + // Pack mesh data into vertex buffers + Array vb1; + Array vb2; + vb1.Resize(vertexCount); + if (normals) + { + if (tangents) + { + for (uint32 i = 0; i < vertexCount; i++) + { + const Vector3 normal = normals[i]; + const Vector3 tangent = tangents[i]; + + // Calculate bitangent sign + Vector3 bitangent = Vector3::Normalize(Vector3::Cross(normal, tangent)); + byte sign = static_cast(Vector3::Dot(Vector3::Cross(bitangent, normal), tangent) < 0.0f ? 1 : 0); + + // Set tangent frame + vb1[i].Tangent = Float1010102(tangent * 0.5f + 0.5f, sign); + vb1[i].Normal = Float1010102(normal * 0.5f + 0.5f, 0); + } + } + else + { + for (uint32 i = 0; i < vertexCount; i++) + { + const Vector3 normal = normals[i]; + + // Calculate tangent + Vector3 c1 = Vector3::Cross(normal, Vector3::UnitZ); + Vector3 c2 = Vector3::Cross(normal, Vector3::UnitY); + Vector3 tangent; + if (c1.LengthSquared() > c2.LengthSquared()) + tangent = c1; + else + tangent = c2; + + // Calculate bitangent sign + Vector3 bitangent = Vector3::Normalize(Vector3::Cross(normal, tangent)); + byte sign = static_cast(Vector3::Dot(Vector3::Cross(bitangent, normal), tangent) < 0.0f ? 1 : 0); + + // Set tangent frame + vb1[i].Tangent = Float1010102(tangent * 0.5f + 0.5f, sign); + vb1[i].Normal = Float1010102(normal * 0.5f + 0.5f, 0); + } + } + } + else + { + // Set default tangent frame + const auto n = Float1010102(Vector3::UnitZ); + const auto t = Float1010102(Vector3::UnitX); + for (uint32 i = 0; i < vertexCount; i++) + { + vb1[i].Normal = n; + vb1[i].Tangent = t; + } + } + if (uvs) + { + for (uint32 i = 0; i < vertexCount; i++) + vb1[i].TexCoord = Half2(uvs[i]); + } + else + { + auto v = Half2(0, 0); + for (uint32 i = 0; i < vertexCount; i++) + vb1[i].TexCoord = v; + } + { + auto v = Half2(0, 0); + for (uint32 i = 0; i < vertexCount; i++) + vb1[i].LightmapUVs = v; + } + if (colors) + { + vb2.Resize(vertexCount); + for (uint32 i = 0; i < vertexCount; i++) + vb2[i].Color = colors[i]; + } + + return mesh->UpdateMesh(vertexCount, triangleCount, (VB0ElementType*)vertices, vb1.Get(), vb2.HasItems() ? vb2.Get() : nullptr, triangles); + } + + template + bool UpdateMesh(Mesh* mesh, uint32 vertexCount, uint32 triangleCount, MonoArray* verticesObj, MonoArray* trianglesObj, MonoArray* normalsObj, MonoArray* tangentsObj, MonoArray* uvObj, MonoArray* colorsObj) + { + ASSERT((uint32)mono_array_length(verticesObj) >= vertexCount); + ASSERT((uint32)mono_array_length(trianglesObj) / 3 >= triangleCount); + auto vertices = (Vector3*)(void*)mono_array_addr_with_size(verticesObj, sizeof(Vector3), 0); + auto triangles = (IndexType*)(void*)mono_array_addr_with_size(trianglesObj, sizeof(IndexType), 0); + const auto normals = normalsObj ? (Vector3*)(void*)mono_array_addr_with_size(normalsObj, sizeof(Vector3), 0) : nullptr; + const auto tangents = tangentsObj ? (Vector3*)(void*)mono_array_addr_with_size(tangentsObj, sizeof(Vector3), 0) : nullptr; + const auto uvs = uvObj ? (Vector2*)(void*)mono_array_addr_with_size(uvObj, sizeof(Vector2), 0) : nullptr; + const auto colors = colorsObj ? (Color32*)(void*)mono_array_addr_with_size(colorsObj, sizeof(Color32), 0) : nullptr; + return UpdateMesh(mesh, vertexCount, triangleCount, vertices, triangles, normals, tangents, uvs, colors); + } + + template + bool UpdateTriangles(Mesh* mesh, int32 triangleCount, MonoArray* trianglesObj) + { + const auto model = mesh->GetModel(); + ASSERT(model && model->IsVirtual() && trianglesObj); + + // Get buffer data + ASSERT((int32)mono_array_length(trianglesObj) / 3 >= triangleCount); + auto ib = (IndexType*)(void*)mono_array_addr_with_size(trianglesObj, sizeof(IndexType), 0); + + return mesh->UpdateTriangles(triangleCount, ib); + } +} + bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0ElementType* vb0, VB1ElementType* vb1, VB2ElementType* vb2, void* ib, bool use16BitIndices) { Unload(); @@ -31,6 +152,16 @@ bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0ElementType* return failed; } +bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, Vector3* vertices, uint16* triangles, Vector3* normals, Vector3* tangents, Vector2* uvs, Color32* colors) +{ + return ::UpdateMesh(this, vertexCount, triangleCount, vertices, triangles, normals, tangents, uvs, colors); +} + +bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, Vector3* vertices, uint32* triangles, Vector3* normals, Vector3* tangents, Vector2* uvs, Color32* colors) +{ + return ::UpdateMesh(this, vertexCount, triangleCount, vertices, triangles, normals, tangents, uvs, colors); +} + bool Mesh::UpdateTriangles(uint32 triangleCount, void* ib, bool use16BitIndices) { // Cache data @@ -219,7 +350,7 @@ bool Mesh::Intersects(const Ray& ray, const Matrix& world, float& distance, Vect #endif } -void Mesh::GetDrawCallGeometry(DrawCall& drawCall) +void Mesh::GetDrawCallGeometry(DrawCall& drawCall) const { drawCall.Geometry.IndexBuffer = _indexBuffer; drawCall.Geometry.VertexBuffers[0] = _vertexBuffers[0]; @@ -228,8 +359,8 @@ void Mesh::GetDrawCallGeometry(DrawCall& drawCall) drawCall.Geometry.VertexBuffersOffsets[0] = 0; drawCall.Geometry.VertexBuffersOffsets[1] = 0; drawCall.Geometry.VertexBuffersOffsets[2] = 0; - drawCall.Geometry.StartIndex = 0; - drawCall.Geometry.IndicesCount = _triangles * 3; + drawCall.Draw.StartIndex = 0; + drawCall.Draw.IndicesCount = _triangles * 3; } void Mesh::Render(GPUContext* context) const @@ -255,22 +386,20 @@ void Mesh::Draw(const RenderContext& renderContext, MaterialBase* material, cons drawCall.Geometry.VertexBuffersOffsets[0] = 0; drawCall.Geometry.VertexBuffersOffsets[1] = 0; drawCall.Geometry.VertexBuffersOffsets[2] = 0; - drawCall.Geometry.StartIndex = 0; - drawCall.Geometry.IndicesCount = _triangles * 3; + drawCall.Draw.StartIndex = 0; + drawCall.Draw.IndicesCount = _triangles * 3; drawCall.InstanceCount = 1; - drawCall.IndirectArgsBuffer = nullptr; - drawCall.IndirectArgsOffset = 0; drawCall.Material = material; drawCall.World = world; - drawCall.PrevWorld = world; drawCall.ObjectPosition = drawCall.World.GetTranslation(); - drawCall.GeometrySize = _box.GetSize(); - drawCall.Lightmap = nullptr; - drawCall.LightmapUVsArea = Rectangle::Empty; - drawCall.Skinning = nullptr; + drawCall.Surface.GeometrySize = _box.GetSize(); + drawCall.Surface.PrevWorld = world; + drawCall.Surface.Lightmap = nullptr; + drawCall.Surface.LightmapUVsArea = Rectangle::Empty; + drawCall.Surface.Skinning = nullptr; + drawCall.Surface.LODDitherFactor = 0.0f; drawCall.WorldDeterminantSign = Math::FloatSelect(world.RotDeterminant(), 1, -1); drawCall.PerInstanceRandom = 0.0f; - drawCall.LODDitherFactor = 0.0f; renderContext.List->AddDrawCall(DrawPass::Default, flags, drawCall, receiveDecals); } @@ -318,22 +447,20 @@ void Mesh::Draw(const RenderContext& renderContext, const DrawInfo& info, float drawCall.Geometry.VertexBuffers[2] = info.VertexColors[_lodIndex]; drawCall.Geometry.VertexBuffersOffsets[2] = vertexOffset * sizeof(VB2ElementType); } - drawCall.Geometry.StartIndex = 0; - drawCall.Geometry.IndicesCount = _triangles * 3; + drawCall.Draw.StartIndex = 0; + drawCall.Draw.IndicesCount = _triangles * 3; drawCall.InstanceCount = 1; - drawCall.IndirectArgsBuffer = nullptr; - drawCall.IndirectArgsOffset = 0; drawCall.Material = material; drawCall.World = *info.World; - drawCall.PrevWorld = info.DrawState->PrevWorld; drawCall.ObjectPosition = drawCall.World.GetTranslation(); - drawCall.GeometrySize = _box.GetSize(); - drawCall.Lightmap = info.Flags & StaticFlags::Lightmap ? info.Lightmap : nullptr; - drawCall.LightmapUVsArea = info.LightmapUVs ? *info.LightmapUVs : Rectangle::Empty; - drawCall.Skinning = nullptr; + drawCall.Surface.GeometrySize = _box.GetSize(); + drawCall.Surface.PrevWorld = info.DrawState->PrevWorld; + drawCall.Surface.Lightmap = info.Flags & StaticFlags::Lightmap ? info.Lightmap : nullptr; + drawCall.Surface.LightmapUVsArea = info.LightmapUVs ? *info.LightmapUVs : Rectangle::Empty; + drawCall.Surface.Skinning = nullptr; + drawCall.Surface.LODDitherFactor = lodDitherFactor; drawCall.WorldDeterminantSign = Math::FloatSelect(drawCall.World.RotDeterminant(), 1, -1); drawCall.PerInstanceRandom = info.PerInstanceRandom; - drawCall.LODDitherFactor = lodDitherFactor; renderContext.List->AddDrawCall(drawModes, info.Flags, drawCall, entry.ReceiveDecals); } @@ -384,108 +511,9 @@ ScriptingObject* Mesh::GetParentModel() return _model; } -template -bool UpdateMesh(Mesh* mesh, uint32 vertexCount, uint32 triangleCount, MonoArray* verticesObj, MonoArray* trianglesObj, MonoArray* normalsObj, MonoArray* tangentsObj, MonoArray* uvObj, MonoArray* colorsObj) -{ - auto model = mesh->GetModel(); - ASSERT(model && model->IsVirtual() && verticesObj && trianglesObj); - - // Get buffers data - ASSERT((uint32)mono_array_length(verticesObj) >= vertexCount); - ASSERT((uint32)mono_array_length(trianglesObj) / 3 >= triangleCount); - auto vb0 = (Vector3*)(void*)mono_array_addr_with_size(verticesObj, sizeof(Vector3), 0); - auto ib = (IndexType*)(void*)mono_array_addr_with_size(trianglesObj, sizeof(IndexType), 0); - Array vb1; - Array vb2; - vb1.Resize(vertexCount); - if (normalsObj) - { - const auto normals = (Vector3*)(void*)mono_array_addr_with_size(normalsObj, sizeof(Vector3), 0); - if (tangentsObj) - { - const auto tangents = (Vector3*)(void*)mono_array_addr_with_size(tangentsObj, sizeof(Vector3), 0); - for (uint32 i = 0; i < vertexCount; i++) - { - // Peek normal and tangent - const Vector3 normal = normals[i]; - const Vector3 tangent = tangents[i]; - - // Calculate bitangent sign - Vector3 bitangent = Vector3::Normalize(Vector3::Cross(normal, tangent)); - byte sign = static_cast(Vector3::Dot(Vector3::Cross(bitangent, normal), tangent) < 0.0f ? 1 : 0); - - // Set tangent frame - vb1[i].Tangent = Float1010102(tangent * 0.5f + 0.5f, sign); - vb1[i].Normal = Float1010102(normal * 0.5f + 0.5f, 0); - } - } - else - { - for (uint32 i = 0; i < vertexCount; i++) - { - // Peek normal - const Vector3 normal = normals[i]; - - // Calculate tangent - Vector3 c1 = Vector3::Cross(normal, Vector3::UnitZ); - Vector3 c2 = Vector3::Cross(normal, Vector3::UnitY); - Vector3 tangent; - if (c1.LengthSquared() > c2.LengthSquared()) - tangent = c1; - else - tangent = c2; - - // Calculate bitangent sign - Vector3 bitangent = Vector3::Normalize(Vector3::Cross(normal, tangent)); - byte sign = static_cast(Vector3::Dot(Vector3::Cross(bitangent, normal), tangent) < 0.0f ? 1 : 0); - - // Set tangent frame - vb1[i].Tangent = Float1010102(tangent * 0.5f + 0.5f, sign); - vb1[i].Normal = Float1010102(normal * 0.5f + 0.5f, 0); - } - } - } - else - { - const auto n = Float1010102(Vector3::UnitZ); - const auto t = Float1010102(Vector3::UnitX); - for (uint32 i = 0; i < vertexCount; i++) - { - vb1[i].Normal = n; - vb1[i].Tangent = t; - } - } - if (uvObj) - { - const auto uvs = (Vector2*)(void*)mono_array_addr_with_size(uvObj, sizeof(Vector2), 0); - for (uint32 i = 0; i < vertexCount; i++) - vb1[i].TexCoord = Half2(uvs[i]); - } - else - { - auto v = Half2(0, 0); - for (uint32 i = 0; i < vertexCount; i++) - vb1[i].TexCoord = v; - } - { - auto v = Half2(0, 0); - for (uint32 i = 0; i < vertexCount; i++) - vb1[i].LightmapUVs = v; - } - if (colorsObj) - { - vb2.Resize(vertexCount); - const auto colors = (Color32*)(void*)mono_array_addr_with_size(colorsObj, sizeof(Color32), 0); - for (uint32 i = 0; i < vertexCount; i++) - vb2[i].Color = colors[i]; - } - - return mesh->UpdateMesh(vertexCount, triangleCount, (VB0ElementType*)vb0, vb1.Get(), vb2.HasItems() ? vb2.Get() : nullptr, ib); -} - bool Mesh::UpdateMeshInt(int32 vertexCount, int32 triangleCount, MonoArray* verticesObj, MonoArray* trianglesObj, MonoArray* normalsObj, MonoArray* tangentsObj, MonoArray* uvObj, MonoArray* colorsObj) { - return ::UpdateMesh(this, (uint32)vertexCount, (uint32)triangleCount, verticesObj, trianglesObj, normalsObj, tangentsObj, uvObj, colorsObj); + return ::UpdateMesh(this, (uint32)vertexCount, (uint32)triangleCount, verticesObj, trianglesObj, normalsObj, tangentsObj, uvObj, colorsObj); } bool Mesh::UpdateMeshUShort(int32 vertexCount, int32 triangleCount, MonoArray* verticesObj, MonoArray* trianglesObj, MonoArray* normalsObj, MonoArray* tangentsObj, MonoArray* uvObj, MonoArray* colorsObj) @@ -493,22 +521,9 @@ bool Mesh::UpdateMeshUShort(int32 vertexCount, int32 triangleCount, MonoArray* v return ::UpdateMesh(this, (uint32)vertexCount, (uint32)triangleCount, verticesObj, trianglesObj, normalsObj, tangentsObj, uvObj, colorsObj); } -template -bool UpdateTriangles(Mesh* mesh, int32 triangleCount, MonoArray* trianglesObj) -{ - auto model = mesh->GetModel(); - ASSERT(model && model->IsVirtual() && trianglesObj); - - // Get buffer data - ASSERT((int32)mono_array_length(trianglesObj) / 3 >= triangleCount); - auto ib = (IndexType*)(void*)mono_array_addr_with_size(trianglesObj, sizeof(IndexType), 0); - - return mesh->UpdateTriangles(triangleCount, ib); -} - bool Mesh::UpdateTrianglesInt(int32 triangleCount, MonoArray* trianglesObj) { - return ::UpdateTriangles(this, triangleCount, trianglesObj); + return ::UpdateTriangles(this, triangleCount, trianglesObj); } bool Mesh::UpdateTrianglesUShort(int32 triangleCount, MonoArray* trianglesObj) diff --git a/Source/Engine/Graphics/Models/Mesh.h b/Source/Engine/Graphics/Models/Mesh.h index c8311a1f3..94d4979a3 100644 --- a/Source/Engine/Graphics/Models/Mesh.h +++ b/Source/Engine/Graphics/Models/Mesh.h @@ -216,9 +216,9 @@ public: /// The first vertex buffer data. /// The second vertex buffer data. /// The third vertex buffer data. - /// The index buffer. + /// The index buffer in clockwise order. /// True if failed, otherwise false. - FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0ElementType* vb0, VB1ElementType* vb1, VB2ElementType* vb2, int32* ib) + FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0ElementType* vb0, VB1ElementType* vb1, VB2ElementType* vb2, uint32* ib) { return UpdateMesh(vertexCount, triangleCount, vb0, vb1, vb2, ib, false); } @@ -231,7 +231,7 @@ public: /// The first vertex buffer data. /// The second vertex buffer data. /// The third vertex buffer data. - /// The index buffer. + /// The index buffer in clockwise order. /// True if failed, otherwise false. FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0ElementType* vb0, VB1ElementType* vb1, VB2ElementType* vb2, uint16* ib) { @@ -240,17 +240,51 @@ public: /// /// Updates the model mesh (used by the virtual models created with Init rather than Load). + /// Can be used only for virtual assets (see and ). + /// Mesh data will be cached and uploaded to the GPU with a delay. /// /// The amount of vertices in the vertex buffer. /// The amount of triangles in the index buffer. /// The first vertex buffer data. /// The second vertex buffer data. /// The third vertex buffer data. - /// The index buffer. + /// The index buffer in clockwise order. /// True if index buffer uses 16-bit index buffer, otherwise 32-bit. /// True if failed, otherwise false. bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0ElementType* vb0, VB1ElementType* vb1, VB2ElementType* vb2, void* ib, bool use16BitIndices); + /// + /// Updates the model mesh (used by the virtual models created with Init rather than Load). + /// Can be used only for virtual assets (see and ). + /// Mesh data will be cached and uploaded to the GPU with a delay. + /// + /// The amount of vertices in the vertex buffer. + /// The amount of triangles in the index buffer. + /// The mesh vertices positions. Cannot be null. + /// The mesh index buffer (clockwise triangles). Uses 32-bit stride buffer. Cannot be null. + /// The normal vectors (per vertex). + /// The normal vectors (per vertex). Use null to compute them from normal vectors. + /// The texture coordinates (per vertex). + /// The vertex colors (per vertex). + /// True if failed, otherwise false. + bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, Vector3* vertices, uint16* triangles, Vector3* normals = nullptr, Vector3* tangents = nullptr, Vector2* uvs = nullptr, Color32* colors = nullptr); + + /// + /// Updates the model mesh (used by the virtual models created with Init rather than Load). + /// Can be used only for virtual assets (see and ). + /// Mesh data will be cached and uploaded to the GPU with a delay. + /// + /// The amount of vertices in the vertex buffer. + /// The amount of triangles in the index buffer. + /// The mesh vertices positions. Cannot be null. + /// The mesh index buffer (clockwise triangles). Uses 32-bit stride buffer. Cannot be null. + /// The normal vectors (per vertex). + /// The normal vectors (per vertex). Use null to compute them from normal vectors. + /// The texture coordinates (per vertex). + /// The vertex colors (per vertex). + /// True if failed, otherwise false. + bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, Vector3* vertices, uint32* triangles, Vector3* normals = nullptr, Vector3* tangents = nullptr, Vector2* uvs = nullptr, Color32* colors = nullptr); + public: /// @@ -259,7 +293,7 @@ public: /// The amount of triangles in the index buffer. /// The index buffer. /// True if failed, otherwise false. - FORCE_INLINE bool UpdateTriangles(uint32 triangleCount, int32* ib) + FORCE_INLINE bool UpdateTriangles(uint32 triangleCount, uint32* ib) { return UpdateTriangles(triangleCount, ib, false); } @@ -343,7 +377,7 @@ public: /// Gets the draw call geometry for this mesh. Sets the index and vertex buffers. /// /// The draw call. - void GetDrawCallGeometry(DrawCall& drawCall); + void GetDrawCallGeometry(DrawCall& drawCall) const; /// /// Model instance drawing packed data. diff --git a/Source/Engine/Graphics/Models/ModelData.Tool.cpp b/Source/Engine/Graphics/Models/ModelData.Tool.cpp index db36d7d4f..a1b02ff69 100644 --- a/Source/Engine/Graphics/Models/ModelData.Tool.cpp +++ b/Source/Engine/Graphics/Models/ModelData.Tool.cpp @@ -799,7 +799,7 @@ void MeshData::ImproveCacheLocality() Allocator::Free(piCandidates); const auto endTime = Platform::GetTimeSeconds(); - LOG(Info, "Cache relevant optimzie for {0} vertices and {1} indices. Average output ACMR is {2}. Time: {3}s", vertexCount, indexCount, (float)iCacheMisses / indexCount / 3, Utilities::RoundTo2DecimalPlaces(endTime - startTime)); + LOG(Info, "Cache relevant optimize for {0} vertices and {1} indices. Average output ACMR is {2}. Time: {3}s", vertexCount, indexCount, (float)iCacheMisses / indexCount / 3, Utilities::RoundTo2DecimalPlaces(endTime - startTime)); } float MeshData::CalculateTrianglesArea() const diff --git a/Source/Engine/Graphics/Models/ModelData.cpp b/Source/Engine/Graphics/Models/ModelData.cpp index 56981e65d..71b511b4f 100644 --- a/Source/Engine/Graphics/Models/ModelData.cpp +++ b/Source/Engine/Graphics/Models/ModelData.cpp @@ -2,6 +2,7 @@ #include "ModelData.h" #include "Engine/Core/Log.h" +#include "Engine/Animations/CurveSerialization.h" #include "Engine/Serialization/WriteStream.h" #include "Engine/Debug/Exceptions/ArgumentNullException.h" #include "Engine/Debug/Exceptions/ArgumentOutOfRangeException.h" @@ -753,7 +754,7 @@ bool ModelData::Pack2SkinnedModelHeader(WriteStream* stream) const stream->Write(&sphere); // TODO: calculate Sphere and Box at once - make it faster using SSE - + // Blend Shapes const int32 blendShapes = mesh.BlendShapes.Count(); stream->WriteUint16(blendShapes); @@ -830,9 +831,9 @@ bool ModelData::Pack2AnimationHeader(WriteStream* stream) const auto& anim = Animation.Channels[i]; stream->WriteString(anim.NodeName, 172); - anim.Position.Serialize(*stream); - anim.Rotation.Serialize(*stream); - anim.Scale.Serialize(*stream); + Serialization::Serialize(*stream, anim.Position); + Serialization::Serialize(*stream, anim.Rotation); + Serialization::Serialize(*stream, anim.Scale); } return false; diff --git a/Source/Engine/Graphics/Models/ModelData.h b/Source/Engine/Graphics/Models/ModelData.h index 1e4b59d0a..a3bb47fcc 100644 --- a/Source/Engine/Graphics/Models/ModelData.h +++ b/Source/Engine/Graphics/Models/ModelData.h @@ -5,7 +5,7 @@ #include "Engine/Core/Common.h" #include "Engine/Core/Math/BoundingSphere.h" #include "Engine/Core/Math/BoundingBox.h" -#include "Engine/Core/Math/VectorInt.h" +#include "Engine/Core/Math/Int4.h" #include "Engine/Serialization/Stream.h" #include "Engine/Graphics/Enums.h" #include "Types.h" diff --git a/Source/Engine/Graphics/Models/SkinnedMesh.cpp b/Source/Engine/Graphics/Models/SkinnedMesh.cpp index 90aa03d86..9e26867ab 100644 --- a/Source/Engine/Graphics/Models/SkinnedMesh.cpp +++ b/Source/Engine/Graphics/Models/SkinnedMesh.cpp @@ -186,22 +186,20 @@ void SkinnedMesh::Draw(const RenderContext& renderContext, const DrawInfo& info, drawCall.Geometry.VertexBuffersOffsets[0] = 0; drawCall.Geometry.VertexBuffersOffsets[1] = 0; drawCall.Geometry.VertexBuffersOffsets[2] = 0; - drawCall.Geometry.StartIndex = 0; - drawCall.Geometry.IndicesCount = _triangles * 3; + drawCall.Draw.StartIndex = 0; + drawCall.Draw.IndicesCount = _triangles * 3; drawCall.InstanceCount = 1; - drawCall.IndirectArgsBuffer = nullptr; - drawCall.IndirectArgsOffset = 0; drawCall.Material = material; drawCall.World = *info.World; - drawCall.PrevWorld = info.DrawState->PrevWorld; drawCall.ObjectPosition = drawCall.World.GetTranslation(); - drawCall.GeometrySize = _box.GetSize(); - drawCall.Lightmap = nullptr; - drawCall.LightmapUVsArea = Rectangle::Empty; - drawCall.Skinning = info.Skinning; + drawCall.Surface.GeometrySize = _box.GetSize(); + drawCall.Surface.PrevWorld = info.DrawState->PrevWorld; + drawCall.Surface.Lightmap = nullptr; + drawCall.Surface.LightmapUVsArea = Rectangle::Empty; + drawCall.Surface.Skinning = info.Skinning; + drawCall.Surface.LODDitherFactor = lodDitherFactor; drawCall.WorldDeterminantSign = Math::FloatSelect(drawCall.World.RotDeterminant(), 1, -1); drawCall.PerInstanceRandom = info.PerInstanceRandom; - drawCall.LODDitherFactor = lodDitherFactor; renderContext.List->AddDrawCall(drawModes, StaticFlags::None, drawCall, entry.ReceiveDecals); } diff --git a/Source/Engine/Graphics/Models/SkinnedMesh.h b/Source/Engine/Graphics/Models/SkinnedMesh.h index 2c168980b..bbdf38014 100644 --- a/Source/Engine/Graphics/Models/SkinnedMesh.h +++ b/Source/Engine/Graphics/Models/SkinnedMesh.h @@ -163,7 +163,7 @@ public: /// The amount of vertices in the vertex buffer. /// The amount of triangles in the index buffer. /// The vertex buffer data. - /// The index buffer. + /// The index buffer in clockwise order. /// True if failed, otherwise false. FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0SkinnedElementType* vb, int32* ib) { @@ -176,7 +176,7 @@ public: /// The amount of vertices in the vertex buffer. /// The amount of triangles in the index buffer. /// The vertex buffer data. - /// The index buffer. + /// The index buffer, clockwise order. /// True if failed, otherwise false. FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0SkinnedElementType* vb, uint16* ib) { @@ -189,7 +189,7 @@ public: /// The amount of vertices in the vertex buffer. /// The amount of triangles in the index buffer. /// The vertex buffer data. - /// The index buffer. + /// The index buffer in clockwise order. /// True if index buffer uses 16-bit index buffer, otherwise 32-bit. /// True if failed, otherwise false. bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0SkinnedElementType* vb, void* ib, bool use16BitIndices); diff --git a/Source/Engine/Graphics/Models/SkinnedMeshDrawData.cpp b/Source/Engine/Graphics/Models/SkinnedMeshDrawData.cpp index 2094be3b2..25c29b11a 100644 --- a/Source/Engine/Graphics/Models/SkinnedMeshDrawData.cpp +++ b/Source/Engine/Graphics/Models/SkinnedMeshDrawData.cpp @@ -4,53 +4,7 @@ #include "Engine/Graphics/GPUDevice.h" #include "Engine/Animations/Config.h" #include "Engine/Core/Math/Matrix.h" - -struct SkinMatrix3x4 -{ - float M[3][4]; - - FORCE_INLINE void SetMatrix(const Matrix& mat) - { - const float* src = mat.Raw; - float* dest = &(M[0][0]); - - dest[0] = src[0]; // [0][0] - dest[1] = src[1]; // [0][1] - dest[2] = src[2]; // [0][2] - dest[3] = src[3]; // [0][3] - - dest[4] = src[4]; // [1][0] - dest[5] = src[5]; // [1][1] - dest[6] = src[6]; // [1][2] - dest[7] = src[7]; // [1][3] - - dest[8] = src[8]; // [2][0] - dest[9] = src[9]; // [2][1] - dest[10] = src[10]; // [2][2] - dest[11] = src[11]; // [2][3] - } - - FORCE_INLINE void SetMatrixTranspose(const Matrix& mat) - { - const float* src = mat.Raw; - float* dest = &(M[0][0]); - - dest[0] = src[0]; // [0][0] - dest[1] = src[4]; // [1][0] - dest[2] = src[8]; // [2][0] - dest[3] = src[12]; // [3][0] - - dest[4] = src[1]; // [0][1] - dest[5] = src[5]; // [1][1] - dest[6] = src[9]; // [2][1] - dest[7] = src[13]; // [3][1] - - dest[8] = src[2]; // [0][2] - dest[9] = src[6]; // [1][2] - dest[10] = src[10]; // [2][2] - dest[11] = src[14]; // [3][2] - } -}; +#include "Engine/Core/Math/Matrix3x4.h" SkinnedMeshDrawData::~SkinnedMeshDrawData() { @@ -99,7 +53,6 @@ void SkinnedMeshDrawData::SetData(const Matrix* bones, bool dropHistory) LOG(Fatal, "Failed to initialize the skinned mesh bones buffer"); } } - Swap(PrevBoneMatrices, BoneMatrices); } else @@ -109,15 +62,15 @@ void SkinnedMeshDrawData::SetData(const Matrix* bones, bool dropHistory) // Copy bones to the buffer const int32 count = BonesCount; - const int32 PreFetchStride = 2; + const int32 preFetchStride = 2; const Matrix* input = bones; - const auto output = (SkinMatrix3x4*)Data.Get(); - ASSERT(Data.Count() == count * sizeof(SkinMatrix3x4)); + const auto output = (Matrix3x4*)Data.Get(); + ASSERT(Data.Count() == count * sizeof(Matrix3x4)); for (int32 i = 0; i < count; i++) { - SkinMatrix3x4* bone = output + i; - Platform::Prefetch(bone + PreFetchStride); - Platform::Prefetch((byte*)(bone + PreFetchStride) + PLATFORM_CACHE_LINE_SIZE); + Matrix3x4* bone = output + i; + Platform::Prefetch(bone + preFetchStride); + Platform::Prefetch((byte*)(bone + preFetchStride) + PLATFORM_CACHE_LINE_SIZE); bone->SetMatrixTranspose(input[i]); } diff --git a/Source/Engine/Graphics/Models/Types.h b/Source/Engine/Graphics/Models/Types.h index 248988243..9a95b8815 100644 --- a/Source/Engine/Graphics/Models/Types.h +++ b/Source/Engine/Graphics/Models/Types.h @@ -9,7 +9,7 @@ #include "Engine/Core/Math/Vector4.h" #include "Engine/Core/Math/Color.h" #include "Engine/Core/Math/Color32.h" -#include "Engine/Core/Math/VectorInt.h" +#include "Engine/Core/Math/Int4.h" class Model; class SkinnedModel; diff --git a/Source/Engine/Graphics/PostProcessSettings.h b/Source/Engine/Graphics/PostProcessSettings.h index 4279cfc9b..400deb3c1 100644 --- a/Source/Engine/Graphics/PostProcessSettings.h +++ b/Source/Engine/Graphics/PostProcessSettings.h @@ -387,7 +387,7 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(ToneMappingSettings); float WhiteTemperature = 6500.0f; /// - /// Adjusts the white balance temperature tint for the scene by adjusting the cyan and magenta color ranges. Ideally, this setting should be used once you've adjusted the white balance temporature to get accurate colors. Under some light temperatures, the colors may appear to be more yellow or blue. This can be used to balance the resulting color to look more natural. The default value is `0`. + /// Adjusts the white balance temperature tint for the scene by adjusting the cyan and magenta color ranges. Ideally, this setting should be used once you've adjusted the white balance temperature to get accurate colors. Under some light temperatures, the colors may appear to be more yellow or blue. This can be used to balance the resulting color to look more natural. The default value is `0`. /// API_FIELD(Attributes="DefaultValue(0.0f), Limit(-1, 1, 0.001f), EditorOrder(1), PostProcessSetting((int)ToneMappingSettingsOverride.WhiteTint)") float WhiteTint = 0.0f; diff --git a/Source/Engine/Graphics/RenderTargetPool.cpp b/Source/Engine/Graphics/RenderTargetPool.cpp index 109ff8de9..a9db0283d 100644 --- a/Source/Engine/Graphics/RenderTargetPool.cpp +++ b/Source/Engine/Graphics/RenderTargetPool.cpp @@ -108,5 +108,5 @@ void RenderTargetPool::Release(GPUTexture* rt) } } - LOG(Error, "Trying to release temporary render target which has not been registred in service!"); + LOG(Error, "Trying to release temporary render target which has not been registered in service!"); } diff --git a/Source/Engine/Graphics/RenderTask.cpp b/Source/Engine/Graphics/RenderTask.cpp index e3e261c96..405e0fe45 100644 --- a/Source/Engine/Graphics/RenderTask.cpp +++ b/Source/Engine/Graphics/RenderTask.cpp @@ -260,6 +260,11 @@ void SceneRenderTask::OnCollectDrawCalls(RenderContext& renderContext) CollectDrawCalls(renderContext); } +void SceneRenderTask::OnPreRender(GPUContext* context, RenderContext& renderContext) +{ + PreRender(context, renderContext); +} + void SceneRenderTask::OnPostRender(GPUContext* context, RenderContext& renderContext) { PostRender(context, renderContext); @@ -278,11 +283,10 @@ Viewport SceneRenderTask::GetViewport() const GPUTextureView* SceneRenderTask::GetOutputView() const { - if (Output) + if (Output && Output->IsAllocated()) return Output->View(); if (SwapChain) return SwapChain->GetBackBufferView(); - CRASH; return nullptr; } diff --git a/Source/Engine/Graphics/RenderTask.h b/Source/Engine/Graphics/RenderTask.h index b00d80a5f..265905e2a 100644 --- a/Source/Engine/Graphics/RenderTask.h +++ b/Source/Engine/Graphics/RenderTask.h @@ -313,6 +313,18 @@ public: /// The rendering context. virtual void OnCollectDrawCalls(RenderContext& renderContext); + /// + /// The action called after scene rendering. Can be used to perform custom pre-rendering or to modify the render view. + /// + API_EVENT() Delegate PreRender; + + /// + /// Called before scene rendering. Can be used to perform custom pre-rendering or to modify the render view. + /// + /// The GPU commands context. + /// The rendering context. + virtual void OnPreRender(GPUContext* context, RenderContext& renderContext); + /// /// The action called after scene rendering. Can be used to render additional visual elements to the output. /// diff --git a/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp b/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp index 0ff65edf0..4d8b8cf1d 100644 --- a/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp +++ b/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp @@ -3,6 +3,7 @@ #include "ShaderAssetBase.h" #include "ShaderStorage.h" #include "ShaderCacheManager.h" +#include "Engine/Engine/CommandLine.h" #include "Engine/Serialization/MemoryReadStream.h" #include "Engine/ShadowsOfMordor/AtlasChartsPacker.h" @@ -225,8 +226,11 @@ bool ShaderAssetBase::LoadShaderCache(ShaderCacheResult& result) options.SourceLength = sourceLength; options.Profile = shaderProfile; options.Output = &cacheStream; - //options.GenerateDebugData = true; - //options.NoOptimize = true; + if (CommandLine::Options.ShaderDebug) + { + options.GenerateDebugData = true; + options.NoOptimize = true; + } auto& platformDefine = options.Macros.AddOne(); #if PLATFORM_WINDOWS platformDefine.Name = "PLATFORM_WINDOWS"; diff --git a/Source/Engine/Graphics/Shaders/GPUShader.h b/Source/Engine/Graphics/Shaders/GPUShader.h index 79b165396..76c8df546 100644 --- a/Source/Engine/Graphics/Shaders/GPUShader.h +++ b/Source/Engine/Graphics/Shaders/GPUShader.h @@ -97,7 +97,7 @@ public: /// /// Gets the vertex shader. /// - /// Thr shader program name. + /// The shader program name. /// The shader permutation index. /// The shader object. API_FUNCTION() FORCE_INLINE GPUShaderProgramVS* GetVS(const StringAnsiView& name, int32 permutationIndex = 0) const @@ -108,7 +108,7 @@ public: /// /// Gets the hull shader. /// - /// Thr shader program name. + /// The shader program name. /// The shader permutation index. /// The shader object. API_FUNCTION() FORCE_INLINE GPUShaderProgramHS* GetHS(const StringAnsiView& name, int32 permutationIndex = 0) const @@ -119,7 +119,7 @@ public: /// /// Gets domain shader. /// - /// Thr shader program name. + /// The shader program name. /// The shader permutation index. /// The shader object. API_FUNCTION() FORCE_INLINE GPUShaderProgramDS* GetDS(const StringAnsiView& name, int32 permutationIndex = 0) const @@ -130,7 +130,7 @@ public: /// /// Gets the geometry shader. /// - /// Thr shader program name. + /// The shader program name. /// The shader permutation index. /// The shader object. API_FUNCTION() FORCE_INLINE GPUShaderProgramGS* GetGS(const StringAnsiView& name, int32 permutationIndex = 0) const @@ -141,7 +141,7 @@ public: /// /// Gets the pixel shader. /// - /// Thr shader program name. + /// The shader program name. /// The shader permutation index. /// The shader object. API_FUNCTION() FORCE_INLINE GPUShaderProgramPS* GetPS(const StringAnsiView& name, int32 permutationIndex = 0) const @@ -152,7 +152,7 @@ public: /// /// Gets the compute shader. /// - /// Thr shader program name. + /// The shader program name. /// The shader permutation index. /// The shader object. API_FUNCTION() FORCE_INLINE GPUShaderProgramCS* GetCS(const StringAnsiView& name, int32 permutationIndex = 0) const @@ -176,7 +176,7 @@ public: /// /// Determines whether the specified shader program is in the shader. /// - /// Thr shader program name. + /// The shader program name. /// The shader permutation index. /// true if the shader is valid; otherwise, false. FORCE_INLINE bool HasShader(const StringAnsiView& name, int32 permutationIndex = 0) const diff --git a/Source/Engine/Graphics/SkinnedMesh.cs b/Source/Engine/Graphics/SkinnedMesh.cs index e8e0d8353..ea4cf283e 100644 --- a/Source/Engine/Graphics/SkinnedMesh.cs +++ b/Source/Engine/Graphics/SkinnedMesh.cs @@ -104,7 +104,7 @@ namespace FlaxEngine /// Mesh data will be cached and uploaded to the GPU with a delay. /// /// The mesh vertices positions. Cannot be null. - /// The mesh index buffer (triangles). Uses 32-bit stride buffer. Cannot be null. + /// The mesh index buffer (clockwise triangles). Uses 32-bit stride buffer. Cannot be null. /// The skinned mesh blend indices buffer. Contains indices of the skeleton bones (up to 4 bones per vertex) to use for vertex position blending. Cannot be null. /// The skinned mesh blend weights buffer (normalized). Contains weights per blend bone (up to 4 bones per vertex) of the skeleton bones to mix for vertex position blending. Cannot be null. /// The normal vectors (per vertex). @@ -140,7 +140,7 @@ namespace FlaxEngine /// Mesh data will be cached and uploaded to the GPU with a delay. /// /// The mesh vertices positions. Cannot be null. - /// The mesh index buffer (triangles). Uses 16-bit stride buffer. Cannot be null. + /// The mesh index buffer (clockwise triangles). Uses 16-bit stride buffer. Cannot be null. /// The skinned mesh blend indices buffer. Contains indices of the skeleton bones (up to 4 bones per vertex) to use for vertex position blending. Cannot be null. /// The skinned mesh blend weights buffer (normalized). Contains weights per blend bone (up to 4 bones per vertex) of the skeleton bones to mix for vertex position blending. Cannot be null. /// The normal vectors (per vertex). diff --git a/Source/Engine/Graphics/Textures/GPUTexture.cpp b/Source/Engine/Graphics/Textures/GPUTexture.cpp index e51d2e510..29268e721 100644 --- a/Source/Engine/Graphics/Textures/GPUTexture.cpp +++ b/Source/Engine/Graphics/Textures/GPUTexture.cpp @@ -458,43 +458,13 @@ protected: // [ThreadPoolTask] bool Run() override { - // Check resources auto texture = _texture.Get(); if (texture == nullptr || _staging == nullptr || _data == nullptr) { LOG(Warning, "Cannot download texture data. Missing objects."); return true; } - - const auto arraySize = texture->ArraySize(); - const auto mipLevels = texture->MipLevels(); - - // Get all mip maps for each array slice - auto& rawResultData = _data->Items; - rawResultData.Resize(arraySize, false); - for (int32 arrayIndex = 0; arrayIndex < arraySize; arrayIndex++) - { - auto& arraySlice = rawResultData[arrayIndex]; - arraySlice.Mips.Resize(mipLevels); - - for (int32 mipMapIndex = 0; mipMapIndex < mipLevels; mipMapIndex++) - { - auto& mip = arraySlice.Mips[mipMapIndex]; - const int32 mipWidth = _data->Width >> mipMapIndex; - const int32 mipHeight = _data->Height >> mipMapIndex; - uint32 mipRowPitch, mipSlicePitch; - RenderTools::ComputePitch(_data->Format, mipWidth, mipHeight, mipRowPitch, mipSlicePitch); - - // Gather data - if (_staging->GetData(arrayIndex, mipMapIndex, mip, mipRowPitch)) - { - LOG(Warning, "Staging resource of \'{0}\' get data failed.", texture->ToString()); - return true; - } - } - } - - return false; + return _staging->DownloadData(*_data); } void OnEnd() override @@ -508,6 +478,57 @@ protected: bool GPUTexture::DownloadData(TextureData& result) { + // Skip for empty ones + if (MipLevels() == 0) + { + LOG(Warning, "Cannot download GPU texture data from an empty texture."); + return true; + } + if (Depth() != 1) + { + MISSING_CODE("support volume texture data downloading."); + } + + // Use faster path for staging resources + if (IsStaging()) + { + const auto arraySize = ArraySize(); + const auto mipLevels = MipLevels(); + + // Set texture info + result.Width = Width(); + result.Height = Height(); + result.Depth = Depth(); + result.Format = Format(); + + // Get all mip maps for each array slice + auto& rawResultData = result.Items; + rawResultData.Resize(arraySize, false); + for (int32 arrayIndex = 0; arrayIndex < arraySize; arrayIndex++) + { + auto& arraySlice = rawResultData[arrayIndex]; + arraySlice.Mips.Resize(mipLevels); + + for (int32 mipMapIndex = 0; mipMapIndex < mipLevels; mipMapIndex++) + { + auto& mip = arraySlice.Mips[mipMapIndex]; + const int32 mipWidth = result.Width >> mipMapIndex; + const int32 mipHeight = result.Height >> mipMapIndex; + uint32 mipRowPitch, mipSlicePitch; + RenderTools::ComputePitch(result.Format, mipWidth, mipHeight, mipRowPitch, mipSlicePitch); + + // Gather data + if (GetData(arrayIndex, mipMapIndex, mip, mipRowPitch)) + { + LOG(Warning, "Staging resource of \'{0}\' get data failed.", ToString()); + return true; + } + } + } + + return false; + } + const auto name = ToString(); // Ensure not running on main thread - we support DownloadData from textures only on a worker threads (Thread Pool Workers or Content Loaders) @@ -538,7 +559,8 @@ bool GPUTexture::DownloadData(TextureData& result) Task* GPUTexture::DownloadDataAsync(TextureData& result) { - if (!IsAllocated()) + // Skip for empty ones + if (MipLevels() == 0) { LOG(Warning, "Cannot download texture data. It has not ben created yet."); return nullptr; @@ -548,19 +570,12 @@ Task* GPUTexture::DownloadDataAsync(TextureData& result) MISSING_CODE("support volume texture data downloading."); } - // Set texture info - result.Width = Width(); - result.Height = Height(); - result.Depth = Depth(); - result.Format = Format(); - - // Quicker path if texture is already readback - if (_desc.Usage == GPUResourceUsage::StagingReadback) + // Use faster path for staging resources + if (IsStaging()) { // Create task to copy downloaded data to TextureData container auto getDataTask = ::New(this, this, result); ASSERT(getDataTask->HasReference(this)); - return getDataTask; } diff --git a/Source/Engine/Graphics/Textures/GPUTexture.h b/Source/Engine/Graphics/Textures/GPUTexture.h index d0ae27e7f..8b5385f9e 100644 --- a/Source/Engine/Graphics/Textures/GPUTexture.h +++ b/Source/Engine/Graphics/Textures/GPUTexture.h @@ -483,7 +483,7 @@ public: /// /// Creates new staging readback texture with the same dimensions and properties as a source texture (but without a data transferred; warning: caller must delete object). /// - /// Thr staging readback texture. + /// The staging readback texture. GPUTexture* ToStagingReadback() const; /// diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp index 45076f39f..9c9af3145 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp @@ -1,16 +1,16 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. -#include "GPUShaderDX11.h" #if GRAPHICS_API_DIRECTX11 #include "GPUContextDX11.h" -#include "Engine/Core/Math/Viewport.h" -#include "Engine/Core/Math/Rectangle.h" +#include "GPUShaderDX11.h" #include "GPUShaderProgramDX11.h" #include "GPUPipelineStateDX11.h" #include "GPUTextureDX11.h" #include "GPUBufferDX11.h" #include "Engine/GraphicsDevice/DirectX/RenderToolsDX.h" +#include "Engine/Core/Math/Viewport.h" +#include "Engine/Core/Math/Rectangle.h" #include "Engine/Profiler/RenderStats.h" #define DX11_CLEAR_SR_ON_STAGE_DISABLE 0 diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp index 64151749b..e9da74a99 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp @@ -91,7 +91,7 @@ GPUDevice* GPUDeviceDX11::Create() else if (CommandLine::Options.D3D11) maxAllowedFeatureLevel = D3D_FEATURE_LEVEL_11_0; #if !USE_EDITOR && PLATFORM_WINDOWS - auto winSettings = WindowsPlatformSettings::Instance(); + auto winSettings = WindowsPlatformSettings::Get(); if (!winSettings->SupportDX11 && !winSettings->SupportDX10) { // Skip if there is no support diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUTimerQueryDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUTimerQueryDX11.cpp index 63cd9bcb4..5df4a45a5 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUTimerQueryDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUTimerQueryDX11.cpp @@ -118,7 +118,7 @@ float GPUTimerQueryDX11::GetResult() if (!SingleShotLog) { SingleShotLog = true; - LOG(Warning, "Unrealiable GPU timer query detected."); + LOG(Warning, "Unreliable GPU timer query detected."); } #endif } diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp index ce36a7347..4d1bd3e17 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp @@ -216,7 +216,7 @@ uint64 GPUContextDX12::Execute(bool waitForCompletion) ASSERT(_currentAllocator != nullptr); auto queue = _device->GetCommandQueue(); - // Flush reaming and buffered commands + // Flush remaining and buffered commands FlushState(); _currentState = nullptr; diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp index 29d417834..944833ca3 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp @@ -45,7 +45,7 @@ GPUDevice* GPUDeviceDX12::Create() selectedAdapter.Description.VendorId = GPU_VENDOR_ID_AMD; #else #if !USE_EDITOR && PLATFORM_WINDOWS - auto winSettings = WindowsPlatformSettings::Instance(); + auto winSettings = WindowsPlatformSettings::Get(); if (!winSettings->SupportDX12) { // Skip if there is no support @@ -391,7 +391,7 @@ bool GPUDeviceDX12::Init() { // Descriptor tables D3D12_DESCRIPTOR_RANGE r[2]; - // TODO: separate ranges for pixel/vertex visiblity and one shared for all? + // TODO: separate ranges for pixel/vertex visibility and one shared for all? { D3D12_DESCRIPTOR_RANGE& range = r[0]; range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV; diff --git a/Source/Engine/GraphicsDevice/DirectX/IncludeDirectXHeaders.h b/Source/Engine/GraphicsDevice/DirectX/IncludeDirectXHeaders.h index 8351deb71..ea621c22a 100644 --- a/Source/Engine/GraphicsDevice/DirectX/IncludeDirectXHeaders.h +++ b/Source/Engine/GraphicsDevice/DirectX/IncludeDirectXHeaders.h @@ -8,7 +8,7 @@ #include "Engine/Platform/Win32/IncludeWindowsHeaders.h" #include "Engine/Platform/Windows/ComPtr.h" -// Helper define to dispose the COM object with reaming references counter checking +// Helper define to dispose the COM object with remaining references counter checking #define DX_SAFE_RELEASE_CHECK(x, refs) if(x) { auto res = (x)->Release(); (x) = nullptr; CHECK(res == refs); } #endif diff --git a/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.cpp index c327fd32a..f9a39574c 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.cpp @@ -44,7 +44,7 @@ void CmdBufferVulkan::End() ASSERT(IsOutsideRenderPass()); #if GPU_ALLOW_PROFILE_EVENTS - // End reaming events + // End remaining events while (_eventsBegin--) vkCmdEndDebugUtilsLabelEXT(GetHandle()); #endif diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp index ea8fa6502..e8f770831 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp @@ -1155,7 +1155,7 @@ void GPUContextVulkan::FlushState() void GPUContextVulkan::Flush() { - // Flush reaming and buffered commands + // Flush remaining and buffered commands FlushState(); _currentState = nullptr; diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp index 9fe49b97d..890e1e23b 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp @@ -1076,7 +1076,7 @@ GPUDeviceVulkan::GPUDeviceVulkan(ShaderProfile shaderProfile, GPUAdapterVulkan* GPUDevice* GPUDeviceVulkan::Create() { #if !USE_EDITOR && (PLATFORM_WINDOWS || PLATFORM_LINUX) - auto settings = PlatformSettings::Instance(); + auto settings = PlatformSettings::Get(); if (!settings->SupportVulkan) { // Skip if there is no support @@ -1363,23 +1363,7 @@ PixelFormat GPUDeviceVulkan::GetClosestSupportedPixelFormat(PixelFormat format, if (flags & GPUTextureFlags::UnorderedAccess) wantedFeatureFlags |= VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT; - // Check actual device for format support - const auto isSupported = [&](VkFormat vkFormat) - { - VkFormatProperties props; - vkGetPhysicalDeviceFormatProperties(Adapter->Gpu, vkFormat, &props); - const VkFormatFeatureFlags featureFlags = optimalTiling ? props.optimalTilingFeatures : props.linearTilingFeatures; - if ((featureFlags & wantedFeatureFlags) != wantedFeatureFlags) - return false; - - //VkImageFormatProperties imageProps; - //vkGetPhysicalDeviceImageFormatProperties(Adapter->Gpu, vkFormat, , &imageProps); - - return true; - }; - - VkFormat vkFormat = RenderToolsVulkan::ToVulkanFormat(format); - if (!isSupported(vkFormat)) + if (!IsVkFormatSupported(RenderToolsVulkan::ToVulkanFormat(format), wantedFeatureFlags, optimalTiling)) { // Special case for depth-stencil formats if (flags & GPUTextureFlags::DepthStencil) @@ -1389,7 +1373,7 @@ PixelFormat GPUDeviceVulkan::GetClosestSupportedPixelFormat(PixelFormat format, // Spec guarantees at least one depth-only, and one depth-stencil format to be supported if (hasStencil) { - if (isSupported(VK_FORMAT_D32_SFLOAT_S8_UINT)) + if (IsVkFormatSupported(VK_FORMAT_D32_SFLOAT_S8_UINT, wantedFeatureFlags, optimalTiling)) format = PixelFormat::D32_Float; else format = PixelFormat::D24_UNorm_S8_UInt; @@ -1493,6 +1477,20 @@ bool GPUDeviceVulkan::SaveValidationCache() #endif +bool GPUDeviceVulkan::IsVkFormatSupported(VkFormat vkFormat, VkFormatFeatureFlags wantedFeatureFlags, bool optimalTiling) const +{ + VkFormatProperties props; + vkGetPhysicalDeviceFormatProperties(Adapter->Gpu, vkFormat, &props); + const VkFormatFeatureFlags featureFlags = optimalTiling ? props.optimalTilingFeatures : props.linearTilingFeatures; + if ((featureFlags & wantedFeatureFlags) != wantedFeatureFlags) + return false; + + //VkImageFormatProperties imageProps; + //vkGetPhysicalDeviceImageFormatProperties(Adapter->Gpu, vkFormat, , &imageProps); + + return true; +} + GPUContext* GPUDeviceVulkan::GetMainContext() { return reinterpret_cast(MainContext); diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.h index 9bab15dda..ba972932b 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.h @@ -720,6 +720,10 @@ public: #endif +private: + + bool IsVkFormatSupported(VkFormat vkFormat, VkFormatFeatureFlags wantedFeatureFlags, bool optimalTiling) const; + public: // [GPUDevice] diff --git a/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.h index b18ae9c1b..cf9b7e9cf 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.h @@ -232,10 +232,10 @@ public: } /// - /// Converts Flax comparision function to the Vulkan comparision operation. + /// Converts Flax comparison function to the Vulkan comparison operation. /// - /// The Flax comparision function. - /// The Vulkan comparision operation. + /// The Flax comparison function. + /// The Vulkan comparison operation. static FORCE_INLINE VkCompareOp ToVulkanCompareOp(const ComparisonFunc value) { return ComparisonFuncToVkCompareOp[(int32)value]; diff --git a/Source/Engine/Input/Enums.h b/Source/Engine/Input/Enums.h index 764a17922..e397dd3c2 100644 --- a/Source/Engine/Input/Enums.h +++ b/Source/Engine/Input/Enums.h @@ -8,7 +8,7 @@ #define MAX_GAMEPADS 8 /// -/// Hardware mouse cursor behaviour. +/// Hardware mouse cursor behavior. /// API_ENUM() enum class CursorLockMode { diff --git a/Source/Engine/Input/Input.cpp b/Source/Engine/Input/Input.cpp index 1dfa0e5fe..73d5316eb 100644 --- a/Source/Engine/Input/Input.cpp +++ b/Source/Engine/Input/Input.cpp @@ -98,6 +98,208 @@ Delegate Input::ActionTriggered; Array Input::ActionMappings; Array Input::AxisMappings; +void InputSettings::Apply() +{ + Input::ActionMappings = ActionMappings; + Input::AxisMappings = AxisMappings; +} + +void Mouse::OnMouseMoved(const Vector2& newPosition) +{ + _prevState.MousePosition = newPosition; + _state.MousePosition = newPosition; +} + +void Mouse::OnMouseDown(const Vector2& position, const MouseButton button, Window* target) +{ + Event& e = _queue.AddOne(); + e.Type = EventType::MouseDown; + e.Target = target; + e.MouseData.Button = button; + e.MouseData.Position = position; +} + +void Mouse::OnMouseUp(const Vector2& position, const MouseButton button, Window* target) +{ + Event& e = _queue.AddOne(); + e.Type = EventType::MouseUp; + e.Target = target; + e.MouseData.Button = button; + e.MouseData.Position = position; +} + +void Mouse::OnMouseDoubleClick(const Vector2& position, const MouseButton button, Window* target) +{ + Event& e = _queue.AddOne(); + e.Type = EventType::MouseDoubleClick; + e.Target = target; + e.MouseData.Button = button; + e.MouseData.Position = position; +} + +void Mouse::OnMouseMove(const Vector2& position, Window* target) +{ + Event& e = _queue.AddOne(); + e.Type = EventType::MouseMove; + e.Target = target; + e.MouseData.Position = position; +} + +void Mouse::OnMouseLeave(Window* target) +{ + Event& e = _queue.AddOne(); + e.Type = EventType::MouseLeave; + e.Target = target; +} + +void Mouse::OnMouseWheel(const Vector2& position, float delta, Window* target) +{ + Event& e = _queue.AddOne(); + e.Type = EventType::MouseWheel; + e.Target = target; + e.MouseWheelData.WheelDelta = delta; + e.MouseWheelData.Position = position; +} + +void Mouse::ResetState() +{ + InputDevice::ResetState(); + + _prevState.Clear(); + _state.Clear(); +} + +bool Mouse::Update(EventQueue& queue) +{ + // Move the current state to the previous + Platform::MemoryCopy(&_prevState, &_state, sizeof(State)); + + // Gather new events + if (UpdateState()) + return true; + + // Handle events + _state.MouseWheelDelta = 0; + for (int32 i = 0; i < _queue.Count(); i++) + { + const Event& e = _queue[i]; + switch (e.Type) + { + case EventType::MouseDown: + { + _state.MouseButtons[static_cast(e.MouseData.Button)] = true; + break; + } + case EventType::MouseUp: + { + _state.MouseButtons[static_cast(e.MouseData.Button)] = false; + break; + } + case EventType::MouseDoubleClick: + { + _state.MouseButtons[static_cast(e.MouseData.Button)] = true; + break; + } + case EventType::MouseWheel: + { + _state.MouseWheelDelta += e.MouseWheelData.WheelDelta; + break; + } + case EventType::MouseMove: + { + _state.MousePosition = e.MouseData.Position; + break; + } + case EventType::MouseLeave: + { + break; + } + } + } + + // Send events further + queue.Add(_queue); + _queue.Clear(); + return false; +} + +void Keyboard::OnCharInput(Char c, Window* target) +{ + // Skip control characters + if (c < 32) + return; + + Event& e = _queue.AddOne(); + e.Type = EventType::Char; + e.Target = target; + e.CharData.Char = c; +} + +void Keyboard::OnKeyUp(KeyboardKeys key, Window* target) +{ + Event& e = _queue.AddOne(); + e.Type = EventType::KeyUp; + e.Target = target; + e.KeyData.Key = key; +} + +void Keyboard::OnKeyDown(KeyboardKeys key, Window* target) +{ + Event& e = _queue.AddOne(); + e.Type = EventType::KeyDown; + e.Target = target; + e.KeyData.Key = key; +} + +void Keyboard::ResetState() +{ + InputDevice::ResetState(); + + _prevState.Clear(); + _state.Clear(); +} + +bool Keyboard::Update(EventQueue& queue) +{ + // Move the current state to the previous + Platform::MemoryCopy(&_prevState, &_state, sizeof(State)); + + // Gather new events + if (UpdateState()) + return true; + + // Handle events + _state.InputTextLength = 0; + for (int32 i = 0; i < _queue.Count(); i++) + { + const Event& e = _queue[i]; + switch (e.Type) + { + case EventType::Char: + { + if (_state.InputTextLength < ARRAY_COUNT(_state.InputText) - 1) + _state.InputText[_state.InputTextLength++] = e.CharData.Char; + break; + } + case EventType::KeyDown: + { + _state.Keys[static_cast(e.KeyData.Key)] = true; + break; + } + case EventType::KeyUp: + { + _state.Keys[static_cast(e.KeyData.Key)] = false; + break; + } + } + } + + // Send events further + queue.Add(_queue); + _queue.Clear(); + return false; +} + int32 Input::GetGamepadsCount() { return Gamepads.Count(); diff --git a/Source/Engine/Input/Input.h b/Source/Engine/Input/Input.h index 33852f9ec..4ef7bfffb 100644 --- a/Source/Engine/Input/Input.h +++ b/Source/Engine/Input/Input.h @@ -21,7 +21,6 @@ class InputDevice; API_CLASS(Static) class FLAXENGINE_API Input { DECLARE_SCRIPTING_TYPE_NO_SPAWN(Input); -public: /// /// Gets the mouse (null if platform does not support mouse or it is not connected). diff --git a/Source/Engine/Input/InputSettings.h b/Source/Engine/Input/InputSettings.h index 18718bfab..7771028e8 100644 --- a/Source/Engine/Input/InputSettings.h +++ b/Source/Engine/Input/InputSettings.h @@ -5,14 +5,13 @@ #include "Engine/Core/Config/Settings.h" #include "Engine/Serialization/JsonTools.h" #include "VirtualInput.h" -#include "Input.h" /// /// Input settings container. /// -/// -class InputSettings : public Settings +API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API InputSettings : public SettingsBase { +DECLARE_SCRIPTING_TYPE_MINIMAL(InputSettings); public: /// @@ -27,19 +26,14 @@ public: public: - // [Settings] - void Apply() override - { - Input::ActionMappings = ActionMappings; - Input::AxisMappings = AxisMappings; - } + /// + /// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use. + /// + static InputSettings* Get(); + + // [SettingsBase] + void Apply() override; - void RestoreDefault() override - { - ActionMappings.Resize(0); - AxisMappings.Resize(0); - } - void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override { const auto actionMappings = stream.FindMember("ActionMappings"); diff --git a/Source/Engine/Input/Keyboard.h b/Source/Engine/Input/Keyboard.h index ae958bd86..f182f9731 100644 --- a/Source/Engine/Input/Keyboard.h +++ b/Source/Engine/Input/Keyboard.h @@ -13,7 +13,7 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(Keyboard); public: /// - /// The mouse state. + /// The keyboard state. /// struct State { @@ -94,97 +94,32 @@ public: return !_state.Keys[static_cast(key)] && _prevState.Keys[static_cast(key)]; } +public: + /// /// Called when keyboard enters input character. /// /// The Unicode character entered by the user. /// The target window to receive this event, otherwise input system will pick the window automatically. - void OnCharInput(const Char c, Window* target = nullptr) - { - // Skip control characters - if (c < 32) - return; - - Event& e = _queue.AddOne(); - e.Type = EventType::Char; - e.Target = target; - e.CharData.Char = c; - } + void OnCharInput(Char c, Window* target = nullptr); /// /// Called when key goes up. /// /// The keyboard key. /// The target window to receive this event, otherwise input system will pick the window automatically. - void OnKeyUp(const KeyboardKeys key, Window* target = nullptr) - { - Event& e = _queue.AddOne(); - e.Type = EventType::KeyUp; - e.Target = target; - e.KeyData.Key = key; - } + void OnKeyUp(KeyboardKeys key, Window* target = nullptr); /// /// Called when key goes down. /// /// The keyboard key. /// The target window to receive this event, otherwise input system will pick the window automatically. - void OnKeyDown(const KeyboardKeys key, Window* target = nullptr) - { - Event& e = _queue.AddOne(); - e.Type = EventType::KeyDown; - e.Target = target; - e.KeyData.Key = key; - } + void OnKeyDown(KeyboardKeys key, Window* target = nullptr); public: // [InputDevice] - void ResetState() override - { - InputDevice::ResetState(); - - _prevState.Clear(); - _state.Clear(); - } - - bool Update(EventQueue& queue) final override - { - // Move the current state to the previous - Platform::MemoryCopy(&_prevState, &_state, sizeof(State)); - - // Gather new events - if (UpdateState()) - return true; - - // Handle events - for (int32 i = 0; i < _queue.Count(); i++) - { - const Event& e = _queue[i]; - switch (e.Type) - { - case EventType::Char: - { - if (_state.InputTextLength < ARRAY_COUNT(_state.InputText) - 1) - _state.InputText[_state.InputTextLength++] = e.CharData.Char; - break; - } - case EventType::KeyDown: - { - _state.Keys[static_cast(e.KeyData.Key)] = true; - break; - } - case EventType::KeyUp: - { - _state.Keys[static_cast(e.KeyData.Key)] = false; - break; - } - } - } - - // Send events further - queue.Add(_queue); - _queue.Clear(); - return false; - } + void ResetState() override;; + bool Update(EventQueue& queue) final override; }; diff --git a/Source/Engine/Input/Mouse.h b/Source/Engine/Input/Mouse.h index 15fa17e6d..d58350956 100644 --- a/Source/Engine/Input/Mouse.h +++ b/Source/Engine/Input/Mouse.h @@ -124,14 +124,10 @@ public: virtual void SetMousePosition(const Vector2& newPosition) = 0; /// - /// Called when mouse cursor gets moved by the application. Invalidates the previous cached mouse position to prevent mouse jitter when locking the cursor programatically. + /// Called when mouse cursor gets moved by the application. Invalidates the previous cached mouse position to prevent mouse jitter when locking the cursor programmatically. /// /// The new mouse position. - void OnMouseMoved(const Vector2& newPosition) - { - _prevState.MousePosition = newPosition; - _state.MousePosition = newPosition; - } + void OnMouseMoved(const Vector2& newPosition); /// /// Called when mouse button goes down. @@ -139,14 +135,7 @@ public: /// The mouse position. /// The button. /// The target window to receive this event, otherwise input system will pick the window automatically. - void OnMouseDown(const Vector2& position, const MouseButton button, Window* target = nullptr) - { - Event& e = _queue.AddOne(); - e.Type = EventType::MouseDown; - e.Target = target; - e.MouseData.Button = button; - e.MouseData.Position = position; - } + void OnMouseDown(const Vector2& position, const MouseButton button, Window* target = nullptr); /// /// Called when mouse button goes up. @@ -154,14 +143,7 @@ public: /// The mouse position. /// The button. /// The target window to receive this event, otherwise input system will pick the window automatically. - void OnMouseUp(const Vector2& position, const MouseButton button, Window* target = nullptr) - { - Event& e = _queue.AddOne(); - e.Type = EventType::MouseUp; - e.Target = target; - e.MouseData.Button = button; - e.MouseData.Position = position; - } + void OnMouseUp(const Vector2& position, const MouseButton button, Window* target = nullptr); /// /// Called when mouse double clicks. @@ -169,38 +151,20 @@ public: /// The mouse position. /// The button. /// The target window to receive this event, otherwise input system will pick the window automatically. - void OnMouseDoubleClick(const Vector2& position, const MouseButton button, Window* target = nullptr) - { - Event& e = _queue.AddOne(); - e.Type = EventType::MouseDoubleClick; - e.Target = target; - e.MouseData.Button = button; - e.MouseData.Position = position; - } + void OnMouseDoubleClick(const Vector2& position, const MouseButton button, Window* target = nullptr); /// /// Called when mouse moves. /// /// The mouse position. /// The target window to receive this event, otherwise input system will pick the window automatically. - void OnMouseMove(const Vector2& position, Window* target = nullptr) - { - Event& e = _queue.AddOne(); - e.Type = EventType::MouseMove; - e.Target = target; - e.MouseData.Position = position; - } + void OnMouseMove(const Vector2& position, Window* target = nullptr); /// /// Called when mouse leaves the input source area. /// /// The target window to receive this event, otherwise input system will pick the window automatically. - void OnMouseLeave(Window* target = nullptr) - { - Event& e = _queue.AddOne(); - e.Type = EventType::MouseLeave; - e.Target = target; - } + void OnMouseLeave(Window* target = nullptr); /// /// Called when mouse wheel moves. @@ -208,76 +172,11 @@ public: /// The mouse position. /// The normalized delta (range [-1;1]). /// The target window to receive this event, otherwise input system will pick the window automatically. - void OnMouseWheel(const Vector2& position, const float delta, Window* target = nullptr) - { - Event& e = _queue.AddOne(); - e.Type = EventType::MouseWheel; - e.Target = target; - e.MouseWheelData.WheelDelta = delta; - e.MouseWheelData.Position = position; - } + void OnMouseWheel(const Vector2& position, float delta, Window* target = nullptr); public: // [InputDevice] - void ResetState() override - { - InputDevice::ResetState(); - - _prevState.Clear(); - _state.Clear(); - } - - bool Update(EventQueue& queue) final override - { - // Move the current state to the previous - Platform::MemoryCopy(&_prevState, &_state, sizeof(State)); - - // Gather new events - if (UpdateState()) - return true; - - // Handle events - _state.MouseWheelDelta = 0; - for (int32 i = 0; i < _queue.Count(); i++) - { - const Event& e = _queue[i]; - switch (e.Type) - { - case EventType::MouseDown: - { - _state.MouseButtons[static_cast(e.MouseData.Button)] = true; - break; - } - case EventType::MouseUp: - { - _state.MouseButtons[static_cast(e.MouseData.Button)] = false; - break; - } - case EventType::MouseDoubleClick: - { - break; - } - case EventType::MouseWheel: - { - _state.MouseWheelDelta += e.MouseWheelData.WheelDelta; - break; - } - case EventType::MouseMove: - { - _state.MousePosition = e.MouseData.Position; - break; - } - case EventType::MouseLeave: - { - break; - } - } - } - - // Send events further - queue.Add(_queue); - _queue.Clear(); - return false; - } + void ResetState() override; + bool Update(EventQueue& queue) final override; }; diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp index 81a5b3155..324e6ada1 100644 --- a/Source/Engine/Level/Actor.cpp +++ b/Source/Engine/Level/Actor.cpp @@ -8,7 +8,6 @@ #include "Prefabs/Prefab.h" #include "Prefabs/PrefabManager.h" #include "Engine/Core/Log.h" -#include "Engine/Core/Config/LayersTagsSettings.h" #include "Engine/Scripting/Script.h" #include "Engine/Scripting/ManagedCLR/MClass.h" #include "Engine/Threading/Threading.h" @@ -43,6 +42,8 @@ Actor::Actor(const SpawnParams& params) , _staticFlags(StaticFlags::FullyStatic) , _localTransform(Transform::Identity) , _transform(Transform::Identity) + , _sphere(BoundingSphere::Empty) + , _box(BoundingBox::Zero) , HideFlags(HideFlags::None) { } @@ -386,21 +387,19 @@ Actor* Actor::GetChildByPrefabObjectId(const Guid& prefabObjectId) const bool Actor::HasTag(const StringView& tag) const { - return HasTag() && tag == LayersAndTagsSettings::Instance()->Tags[_tag]; + return HasTag() && tag == Level::Tags[_tag]; } const String& Actor::GetLayerName() const { - const auto settings = LayersAndTagsSettings::Instance(); - return settings->Layers[_layer]; + return Level::Layers[_layer]; } const String& Actor::GetTag() const { if (HasTag()) { - const auto settings = LayersAndTagsSettings::Instance(); - return settings->Tags[_tag]; + return Level::Tags[_tag]; } return String::Empty; } @@ -420,13 +419,13 @@ void Actor::SetTagIndex(int32 tagIndex) if (tagIndex == ACTOR_TAG_INVALID) { } - else if (LayersAndTagsSettings::Instance()->Tags.IsEmpty()) + else if (Level::Tags.IsEmpty()) { tagIndex = ACTOR_TAG_INVALID; } else { - tagIndex = tagIndex < 0 ? ACTOR_TAG_INVALID : Math::Min(tagIndex, LayersAndTagsSettings::Instance()->Tags.Count() - 1); + tagIndex = tagIndex < 0 ? ACTOR_TAG_INVALID : Math::Min(tagIndex, Level::Tags.Count() - 1); } if (tagIndex == _tag) return; @@ -444,7 +443,7 @@ void Actor::SetTag(const StringView& tagName) } else { - tagIndex = LayersAndTagsSettings::Instance()->Tags.Find(tagName); + tagIndex = Level::Tags.Find(tagName); if (tagIndex == -1) { LOG(Error, "Cannot change actor tag. Given value is invalid."); @@ -600,6 +599,11 @@ void Actor::SetDirection(const Vector3& value) SetOrientation(orientation); } +void Actor::ResetLocalTransform() +{ + SetLocalTransform(Transform::Identity); +} + void Actor::SetLocalTransform(const Transform& value) { CHECK(!value.IsNanOrInfinity()); @@ -946,11 +950,19 @@ void Actor::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) const auto parent = Scripting::FindObject(parentId); if (_parent != parent) { - if (_parent) - _parent->Children.RemoveKeepOrder(this); - _parent = parent; - if (_parent) - _parent->Children.Add(this); + if (IsDuringPlay()) + { + SetParent(parent, false, false); + } + else + { + if (_parent) + _parent->Children.RemoveKeepOrder(this); + _parent = parent; + if (_parent) + _parent->Children.Add(this); + OnParentChanged(); + } } else if (!parent && parentId.IsValid()) { @@ -971,7 +983,7 @@ void Actor::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) if (tag->value.IsString() && tag->value.GetStringLength()) { const String tagName = tag->value.GetText(); - _tag = LayersAndTagsSettings::Instance()->GetOrAddTag(tagName); + _tag = Level::GetOrAddTag(tagName); } } @@ -1042,6 +1054,10 @@ void Actor::OnDisable() } } +void Actor::OnParentChanged() +{ +} + void Actor::OnTransformChanged() { ASSERT_LOW_LAYER(!_localTransform.IsNanOrInfinity()); @@ -1261,6 +1277,11 @@ Script* Actor::GetScriptByPrefabObjectId(const Guid& prefabObjectId) const return result; } +bool Actor::IsPrefabRoot() const +{ + return _isPrefabRoot != 0; +} + Actor* Actor::FindActor(const StringView& name) const { Actor* result = nullptr; diff --git a/Source/Engine/Level/Actor.cs b/Source/Engine/Level/Actor.cs index 674842a65..2d9c27b9a 100644 --- a/Source/Engine/Level/Actor.cs +++ b/Source/Engine/Level/Actor.cs @@ -4,8 +4,68 @@ using System; namespace FlaxEngine { - partial class Actor : ITransformable, ISceneObject + partial class Actor { + /// + /// Returns true if object is fully static on the scene, otherwise false. + /// + [Unmanaged] + [Tooltip("Returns true if object is fully static on the scene, otherwise false.")] + public bool IsStatic => StaticFlags == FlaxEngine.StaticFlags.FullyStatic; + + /// + /// Returns true if object has static transform. + /// + [Unmanaged] + [Tooltip("Returns true if object has static transform.")] + public bool IsTransformStatic => (StaticFlags & StaticFlags.Transform) == StaticFlags.Transform; + + /// + /// Adds the actor static flags. + /// + /// The flags to add. + [Unmanaged] + [Tooltip("Adds the actor static flags.")] + public void AddStaticFlags(StaticFlags flags) + { + StaticFlags |= flags; + } + + /// + /// Removes the actor static flags. + /// + /// The flags to remove. + [Unmanaged] + [Tooltip("Removes the actor static flags.")] + public void RemoveStaticFlags(StaticFlags flags) + { + StaticFlags &= ~flags; + } + + /// + /// Sets a single static flag to the desire value. + /// + /// The flag to change. + /// The target value of the flag. + [Unmanaged] + [Tooltip("Sets a single static flag to the desire value.")] + public void SetStaticFlag(StaticFlags flag, bool value) + { + StaticFlags = StaticFlags & ~flag | (value ? flag : StaticFlags.None); + } + + /// + /// Returns true if object has given flag(s) set. + /// + /// The flag(s) to check. + /// True if has flag(s) set, otherwise false. + [Unmanaged] + [Tooltip("Returns true if object has given flag(s) set..")] + public bool HasStaticFlag(StaticFlags flag) + { + return (StaticFlags & flag) == flag; + } + /// /// The rotation as Euler angles in degrees. /// @@ -143,7 +203,7 @@ namespace FlaxEngine } /// - /// Finds the script of the given type. + /// Finds the script of the given type from this actor. /// /// Type of the script to search for. Includes any scripts derived from the type. /// The script or null if failed to find. @@ -153,7 +213,7 @@ namespace FlaxEngine } /// - /// Tries to find the script of the given type. + /// Tries to find the script of the given type from this actor. /// /// Type of the script to search for. Includes any scripts derived from the type. /// The returned script, valid only if method returns true. @@ -180,7 +240,7 @@ namespace FlaxEngine } /// - /// Searches for all scripts of a specific type. + /// Searches for all scripts of a specific type from this actor. /// /// Type of the scripts to search for. Includes any scripts derived from the type. /// All scripts matching the specified type. diff --git a/Source/Engine/Level/Actor.h b/Source/Engine/Level/Actor.h index 334021c28..04377fd4f 100644 --- a/Source/Engine/Level/Actor.h +++ b/Source/Engine/Level/Actor.h @@ -79,7 +79,7 @@ public: /// /// Gets the object layer (index). Can be used for selective rendering or ignoring raycasts. /// - API_PROPERTY(Attributes="NoAnimate, EditorDisplay(\"General\"), EditorOrder(-69)") + API_PROPERTY(Attributes="NoAnimate, EditorDisplay(\"General\"), EditorOrder(-69), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ActorLayerEditor\")") FORCE_INLINE int32 GetLayer() const { return _layer; @@ -123,7 +123,7 @@ public: /// /// Gets the name of the tag. /// - API_PROPERTY(Attributes="NoAnimate, EditorDisplay(\"General\"), EditorOrder(-68)") + API_PROPERTY(Attributes="NoAnimate, EditorDisplay(\"General\"), EditorOrder(-68), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ActorTagEditor\")") const String& GetTag() const; /// @@ -272,14 +272,14 @@ public: } /// - /// Gets the script of the given type. + /// Gets the script of the given type from this actor. /// /// Type of the script to search for. Includes any scripts derived from the type. /// The script or null. API_FUNCTION() Script* GetScript(const MClass* type) const; /// - /// Gets the script of the given type. + /// Gets the script of the given type from this actor. /// /// The script or null. template @@ -289,14 +289,14 @@ public: } /// - /// Gets the scripts of the given type. + /// Gets the scripts of the given type from this actor. /// /// Type of the script to search for. Includes any scripts derived from the type. /// The scripts. API_FUNCTION() Array GetScripts(const MClass* type) const; /// - /// Gets the scripts of the given type. + /// Gets the scripts of the given type from this actor. /// /// The scripts. template @@ -339,7 +339,7 @@ public: /// /// Returns true if object is fully static on the scene, otherwise false. /// - API_PROPERTY() FORCE_INLINE bool IsStatic() const + FORCE_INLINE bool IsStatic() const { return _staticFlags == StaticFlags::FullyStatic; } @@ -347,7 +347,7 @@ public: /// /// Returns true if object has static transform. /// - API_PROPERTY() FORCE_INLINE bool IsTransformStatic() const + FORCE_INLINE bool IsTransformStatic() const { return (_staticFlags & StaticFlags::Transform) != 0; } @@ -355,7 +355,7 @@ public: /// /// Gets the actor static fags. /// - API_PROPERTY(Attributes="NoAnimate, EditorDisplay(\"General\"), EditorOrder(-80)") + API_PROPERTY(Attributes="NoAnimate, EditorDisplay(\"General\"), EditorOrder(-80), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ActorStaticFlagsEditor\")") FORCE_INLINE StaticFlags GetStaticFlags() const { return _staticFlags; @@ -367,11 +367,19 @@ public: /// The value to set. API_PROPERTY() virtual void SetStaticFlags(StaticFlags value); + /// + /// Returns true if object has given flag(s) set. + /// + FORCE_INLINE bool HasStaticFlag(StaticFlags flag) const + { + return (_staticFlags & flag) == (int)flag; + } + /// /// Adds the actor static flags. /// /// The flags to add. - API_FUNCTION() FORCE_INLINE void AddStaticFlags(StaticFlags flags) + FORCE_INLINE void AddStaticFlags(StaticFlags flags) { SetStaticFlags(_staticFlags | flags); } @@ -380,7 +388,7 @@ public: /// Removes the actor static flags. /// /// The flags to remove. - API_FUNCTION() FORCE_INLINE void RemoveStaticFlags(StaticFlags flags) + FORCE_INLINE void RemoveStaticFlags(StaticFlags flags) { SetStaticFlags(static_cast(_staticFlags & ~flags)); } @@ -390,7 +398,7 @@ public: /// /// The flag to change. /// The target value of the flag. - API_FUNCTION() FORCE_INLINE void SetStaticFlag(StaticFlags flag, bool value) + FORCE_INLINE void SetStaticFlag(StaticFlags flag, bool value) { SetStaticFlags(static_cast(_staticFlags & ~flag) | (value ? flag : StaticFlags::None)); } @@ -437,13 +445,13 @@ public: } /// - /// Sets actor orientation in 3D space + /// Sets actor orientation in 3D space. /// /// The value to set. API_PROPERTY() void SetOrientation(const Quaternion& value); /// - /// Gets actor scale in 3D space + /// Gets actor scale in 3D space. /// API_PROPERTY(Attributes="HideInEditor, NoSerialize") FORCE_INLINE Vector3 GetScale() const @@ -458,13 +466,13 @@ public: API_PROPERTY() void SetScale(const Vector3& value); /// - /// Gets actor rotation matrix + /// Gets actor rotation matrix. /// API_PROPERTY(Attributes="HideInEditor, NoSerialize") Matrix GetRotation() const; /// - /// Sets actor rotation matrix + /// Sets actor rotation matrix. /// /// The value to set. API_PROPERTY() void SetRotation(const Matrix& value); @@ -500,10 +508,7 @@ public: /// /// Resets the actor local transform. /// - FORCE_INLINE void ResetLocalTransform() - { - SetLocalTransform(Transform::Identity); - } + void ResetLocalTransform(); /// /// Gets local transform of the actor in parent actor space. @@ -523,7 +528,7 @@ public: /// /// Gets local position of the actor in parent actor space. /// - API_PROPERTY(Attributes="EditorDisplay(\"Transform\", \"Position\"), DefaultValue(typeof(Vector3), \"0,0,0\"), EditorOrder(-30), NoSerialize") + API_PROPERTY(Attributes="EditorDisplay(\"Transform\", \"Position\"), DefaultValue(typeof(Vector3), \"0,0,0\"), EditorOrder(-30), NoSerialize, CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ActorTransformEditor+PositionScaleEditor\")") FORCE_INLINE Vector3 GetLocalPosition() const { return _localTransform.Translation; @@ -538,7 +543,7 @@ public: /// /// Gets local rotation of the actor in parent actor space. /// - API_PROPERTY(Attributes="EditorDisplay(\"Transform\", \"Rotation\"), DefaultValue(typeof(Quaternion), \"0,0,0,1\"), EditorOrder(-20), NoSerialize") + API_PROPERTY(Attributes="EditorDisplay(\"Transform\", \"Rotation\"), DefaultValue(typeof(Quaternion), \"0,0,0,1\"), EditorOrder(-20), NoSerialize, CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ActorTransformEditor+OrientationEditor\")") FORCE_INLINE Quaternion GetLocalOrientation() const { return _localTransform.Orientation; @@ -553,7 +558,7 @@ public: /// /// Gets local scale vector of the actor in parent actor space. /// - API_PROPERTY(Attributes="EditorDisplay(\"Transform\", \"Scale\"), DefaultValue(typeof(Vector3), \"1,1,1\"), Limit(float.MinValue, float.MaxValue, 0.01f), EditorOrder(-10), NoSerialize") + API_PROPERTY(Attributes="EditorDisplay(\"Transform\", \"Scale\"), DefaultValue(typeof(Vector3), \"1,1,1\"), Limit(float.MinValue, float.MaxValue, 0.01f), EditorOrder(-10), NoSerialize, CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ActorTransformEditor+PositionScaleEditor\")") FORCE_INLINE Vector3 GetLocalScale() const { return _localTransform.Scale; @@ -708,15 +713,10 @@ public: /// The script or null. Script* GetScriptByPrefabObjectId(const Guid& prefabObjectId) const; -public: - /// /// Gets a value indicating whether this actor is a prefab instance root object. /// - API_PROPERTY() FORCE_INLINE bool IsPrefabRoot() const - { - return _isPrefabRoot != 0; - } + API_PROPERTY() bool IsPrefabRoot() const; public: @@ -918,9 +918,7 @@ public: /// /// Called when actor parent gets changed. /// - virtual void OnParentChanged() - { - } + virtual void OnParentChanged(); /// /// Called when actor transform gets changed. diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index cf3e71f13..85ee6d333 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -146,8 +146,32 @@ void AnimatedModel::GetNodeTransformation(const StringView& nodeName, Matrix& no GetNodeTransformation(SkinnedModel ? SkinnedModel->FindNode(nodeName) : -1, nodeTransformation, worldSpace); } +#define CHECK_ANIM_GRAPH_PARAM_ACCESS() \ + if (!AnimationGraph) \ + { \ + LOG(Warning, "Missing animation graph for animated model '{0}'", ToString()); \ + return; \ + } \ + if (AnimationGraph->WaitForLoaded()) \ + { \ + LOG(Warning, "Failed to load animation graph for animated model '{0}'", ToString()); \ + return; \ + } +#define CHECK_ANIM_GRAPH_PARAM_ACCESS_RESULT(result) \ + if (!AnimationGraph) \ + { \ + LOG(Warning, "Missing animation graph for animated model '{0}'", ToString()); \ + return result; \ + } \ + if (AnimationGraph->WaitForLoaded()) \ + { \ + LOG(Warning, "Failed to load animation graph for animated model '{0}'", ToString()); \ + return result; \ + } + AnimGraphParameter* AnimatedModel::GetParameter(const StringView& name) { + CHECK_ANIM_GRAPH_PARAM_ACCESS_RESULT(nullptr); for (auto& param : GraphInstance.Parameters) { if (param.Name == name) @@ -159,6 +183,7 @@ AnimGraphParameter* AnimatedModel::GetParameter(const StringView& name) Variant AnimatedModel::GetParameterValue(const StringView& name) { + CHECK_ANIM_GRAPH_PARAM_ACCESS_RESULT(Variant::Null); for (auto& param : GraphInstance.Parameters) { if (param.Name == name) @@ -170,6 +195,7 @@ Variant AnimatedModel::GetParameterValue(const StringView& name) void AnimatedModel::SetParameterValue(const StringView& name, const Variant& value) { + CHECK_ANIM_GRAPH_PARAM_ACCESS(); for (auto& param : GraphInstance.Parameters) { if (param.Name == name) @@ -183,6 +209,7 @@ void AnimatedModel::SetParameterValue(const StringView& name, const Variant& val Variant AnimatedModel::GetParameterValue(const Guid& id) { + CHECK_ANIM_GRAPH_PARAM_ACCESS_RESULT(Variant::Null); for (auto& param : GraphInstance.Parameters) { if (param.Identifier == id) @@ -194,6 +221,7 @@ Variant AnimatedModel::GetParameterValue(const Guid& id) void AnimatedModel::SetParameterValue(const Guid& id, const Variant& value) { + CHECK_ANIM_GRAPH_PARAM_ACCESS(); for (auto& param : GraphInstance.Parameters) { if (param.Identifier == id) @@ -299,14 +327,6 @@ void AnimatedModel::SyncParameters() } } -void AnimatedModel::UpdateBounds() -{ - UpdateLocalBounds(); - - BoundingBox::Transform(_boxLocal, _world, _box); - BoundingSphere::FromBox(_box, _sphere); -} - void AnimatedModel::BeginPlay(SceneBeginData* data) { if (SkinnedModel && SkinnedModel->IsLoaded()) @@ -373,6 +393,14 @@ void AnimatedModel::UpdateLocalBounds() _boxLocal = box; } +void AnimatedModel::UpdateBounds() +{ + UpdateLocalBounds(); + + BoundingBox::Transform(_boxLocal, _world, _box); + BoundingSphere::FromBox(_box, _sphere); +} + void AnimatedModel::OnAnimUpdate() { UpdateBounds(); diff --git a/Source/Engine/Level/Actors/AnimatedModel.h b/Source/Engine/Level/Actors/AnimatedModel.h index 641247273..d5d9b4424 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.h +++ b/Source/Engine/Level/Actors/AnimatedModel.h @@ -290,6 +290,8 @@ private: /// void UpdateLocalBounds(); + void UpdateBounds(); + /// /// Called after animation graph update. /// @@ -317,7 +319,6 @@ public: void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; bool IntersectsEntry(int32 entryIndex, const Ray& ray, float& distance, Vector3& normal) override; bool IntersectsEntry(const Ray& ray, float& distance, Vector3& normal, int32& entryIndex) override; - void UpdateBounds() final override; protected: diff --git a/Source/Engine/Level/Actors/BoneSocket.cpp b/Source/Engine/Level/Actors/BoneSocket.cpp index 7673781ff..028b1c507 100644 --- a/Source/Engine/Level/Actors/BoneSocket.cpp +++ b/Source/Engine/Level/Actors/BoneSocket.cpp @@ -104,6 +104,9 @@ void BoneSocket::OnParentChanged() { // Base Actor::OnParentChanged(); + + if (!IsDuringPlay()) + return; _index = -1; UpdateTransformation(); diff --git a/Source/Engine/Level/Actors/BoxBrush.cpp b/Source/Engine/Level/Actors/BoxBrush.cpp index ca38c103d..f02dd5bd3 100644 --- a/Source/Engine/Level/Actors/BoxBrush.cpp +++ b/Source/Engine/Level/Actors/BoxBrush.cpp @@ -272,6 +272,9 @@ void BoxBrush::OnParentChanged() { // Base Actor::OnParentChanged(); + + if (!IsDuringPlay()) + return; // Fire event OnBrushModified(); diff --git a/Source/Engine/Level/Actors/BoxVolume.cpp b/Source/Engine/Level/Actors/BoxVolume.cpp index 6235244bc..91912fa69 100644 --- a/Source/Engine/Level/Actors/BoxVolume.cpp +++ b/Source/Engine/Level/Actors/BoxVolume.cpp @@ -132,8 +132,10 @@ void BoxVolume::OnTransformChanged() // Base Actor::OnTransformChanged(); + const auto prevBounds = _box; OrientedBoundingBox::CreateCentered(Vector3::Zero, _size, _bounds); _bounds.Transform(_transform.GetWorld()); _bounds.GetBoundingBox(_box); BoundingSphere::FromBox(_box, _sphere); + OnBoundsChanged(prevBounds); } diff --git a/Source/Engine/Level/Actors/Camera.h b/Source/Engine/Level/Actors/Camera.h index 8076e6a60..edf94ad22 100644 --- a/Source/Engine/Level/Actors/Camera.h +++ b/Source/Engine/Level/Actors/Camera.h @@ -211,7 +211,7 @@ public: /// The result camera view matrix. /// The result camera projection matrix. /// The custom output viewport. Use null to skip it. - API_FUNCTION() void GetMatrices(API_PARAM(Out) Matrix& view, API_PARAM(Out) Matrix& projection, API_PARAM(Ref) const Viewport& viewport) const; + API_FUNCTION() virtual void GetMatrices(API_PARAM(Out) Matrix& view, API_PARAM(Out) Matrix& projection, API_PARAM(Ref) const Viewport& viewport) const; #if USE_EDITOR // Intersection check for editor picking the camera diff --git a/Source/Engine/Level/Actors/Decal.cpp b/Source/Engine/Level/Actors/Decal.cpp index 92652ea7d..c7e8475b3 100644 --- a/Source/Engine/Level/Actors/Decal.cpp +++ b/Source/Engine/Level/Actors/Decal.cpp @@ -7,6 +7,7 @@ #include "Engine/Level/Scene/SceneRendering.h" #include "Engine/Graphics/RenderView.h" #include "Engine/Graphics/RenderTask.h" +#include "Engine/Graphics/RenderTools.h" Decal::Decal(const SpawnParams& params) : Actor(params) @@ -66,6 +67,13 @@ void Decal::Draw(RenderContext& renderContext) Material->IsLoaded() && Material->IsDecal()) { + const auto lodView = (renderContext.LodProxyView ? renderContext.LodProxyView : &renderContext.View); + const float screenRadiusSquared = RenderTools::ComputeBoundsScreenRadiusSquared(_sphere.Center, _sphere.Radius, *lodView) * renderContext.View.ModelLODDistanceFactorSqrt; + + // Check if decal is being culled + if (Math::Square(DrawMinScreenSize * 0.5f) > screenRadiusSquared) + return; + renderContext.List->Decals.Add(this); } } @@ -80,6 +88,7 @@ void Decal::Serialize(SerializeStream& stream, const void* otherObj) SERIALIZE(Material); SERIALIZE_MEMBER(Size, _size); SERIALIZE(SortOrder); + SERIALIZE(DrawMinScreenSize); } void Decal::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) @@ -90,6 +99,7 @@ void Decal::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) DESERIALIZE(Material); DESERIALIZE_MEMBER(Size, _size); DESERIALIZE(SortOrder); + DESERIALIZE(DrawMinScreenSize); } bool Decal::IntersectsItself(const Ray& ray, float& distance, Vector3& normal) diff --git a/Source/Engine/Level/Actors/Decal.h b/Source/Engine/Level/Actors/Decal.h index 41a5addea..78a92bfd6 100644 --- a/Source/Engine/Level/Actors/Decal.h +++ b/Source/Engine/Level/Actors/Decal.h @@ -30,9 +30,15 @@ public: /// /// The decal rendering order. The higher values are render later (on top). /// - API_FIELD(Attributes="EditorOrder(20), DefaultValue(0), EditorDisplay(\"Decal\")") + API_FIELD(Attributes="EditorOrder(20), EditorDisplay(\"Decal\")") int32 SortOrder = 0; + /// + /// The minimum screen size for the decal drawing. If the decal size on the screen is smaller than this value then decal will be culled. Set it to higher value to make culling more aggressive. + /// + API_FIELD(Attributes="EditorOrder(30), EditorDisplay(\"Decal\")") + float DrawMinScreenSize = 0.02f; + /// /// Gets the decal bounds size (in local space). /// diff --git a/Source/Engine/Level/Actors/ModelInstanceActor.h b/Source/Engine/Level/Actors/ModelInstanceActor.h index 63c6c4807..7b2a08fd9 100644 --- a/Source/Engine/Level/Actors/ModelInstanceActor.h +++ b/Source/Engine/Level/Actors/ModelInstanceActor.h @@ -19,11 +19,6 @@ public: /// ModelInstanceEntries Entries; - /// - /// Updates the bounds of the actor. - /// - virtual void UpdateBounds() = 0; - /// /// Gets the model entries collection. Each entry contains data how to render meshes using this entry (transformation, material, shadows casting, etc.). /// @@ -42,7 +37,7 @@ public: /// Sets the material to the entry slot. Can be used to override the material of the meshes using this slot. /// /// The material slot entry index. - /// The material to set.. + /// The material to set. API_FUNCTION() void SetMaterial(int32 entryIndex, MaterialBase* material); /// @@ -63,7 +58,10 @@ public: /// When the method completes and returns true, contains the distance of the intersection (if any valid). /// When the method completes, contains the intersection surface normal vector (if any valid). /// True if the actor is intersected by the ray, otherwise false. - API_FUNCTION() virtual bool IntersectsEntry(int32 entryIndex, API_PARAM(Ref) const Ray& ray, API_PARAM(Out) float& distance, API_PARAM(Out) Vector3& normal) = 0; + API_FUNCTION() virtual bool IntersectsEntry(int32 entryIndex, API_PARAM(Ref) const Ray& ray, API_PARAM(Out) float& distance, API_PARAM(Out) Vector3& normal) + { + return false; + } /// /// Determines if there is an intersection between the model actor mesh entry and a ray. @@ -76,7 +74,10 @@ public: /// When the method completes, contains the intersection surface normal vector (if any valid). /// When the method completes, contains the intersection entry index (if any valid). /// True if the actor is intersected by the ray, otherwise false. - API_FUNCTION() virtual bool IntersectsEntry(API_PARAM(Ref) const Ray& ray, API_PARAM(Out) float& distance, API_PARAM(Out) Vector3& normal, API_PARAM(Out) int32& entryIndex) = 0; + API_FUNCTION() virtual bool IntersectsEntry(API_PARAM(Ref) const Ray& ray, API_PARAM(Out) float& distance, API_PARAM(Out) Vector3& normal, API_PARAM(Out) int32& entryIndex) + { + return false; + } protected: diff --git a/Source/Engine/Level/Actors/Skybox.cpp b/Source/Engine/Level/Actors/Skybox.cpp index 48c19191f..e8ea08c89 100644 --- a/Source/Engine/Level/Actors/Skybox.cpp +++ b/Source/Engine/Level/Actors/Skybox.cpp @@ -93,7 +93,7 @@ void Skybox::ApplySky(GPUContext* context, RenderContext& renderContext, const M Platform::MemoryClear(&drawCall, sizeof(DrawCall)); drawCall.World = world; drawCall.ObjectPosition = drawCall.World.GetTranslation(); - drawCall.GeometrySize = _box.GetSize(); + drawCall.Surface.GeometrySize = _box.GetSize(); drawCall.WorldDeterminantSign = Math::FloatSelect(world.RotDeterminant(), 1, -1); drawCall.PerInstanceRandom = GetPerInstanceRandom(); MaterialBase::BindParameters bindParams(context, renderContext, drawCall); diff --git a/Source/Engine/Level/Actors/Spline.cpp b/Source/Engine/Level/Actors/Spline.cpp new file mode 100644 index 000000000..76ecb9653 --- /dev/null +++ b/Source/Engine/Level/Actors/Spline.cpp @@ -0,0 +1,512 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#include "Spline.h" +#include "Engine/Serialization/Serialization.h" +#include "Engine/Animations/CurveSerialization.h" +#include + +Spline::Spline(const SpawnParams& params) + : Actor(params) +{ +} + +bool Spline::GetIsLoop() const +{ + return _loop; +} + +void Spline::SetIsLoop(bool value) +{ + if (_loop != value) + { + _loop = value; + UpdateSpline(); + } +} + +Vector3 Spline::GetSplinePoint(float time) const +{ + Transform t; + Curve.Evaluate(t, time, _loop); + return _transform.LocalToWorld(t.Translation); +} + +Vector3 Spline::GetSplineLocalPoint(float time) const +{ + Transform t; + Curve.Evaluate(t, time, _loop); + return t.Translation; +} + +Quaternion Spline::GetSplineOrientation(float time) const +{ + Transform t; + Curve.Evaluate(t, time, _loop); + Quaternion::Multiply(_transform.Orientation, t.Orientation, t.Orientation); + t.Orientation.Normalize(); + return t.Orientation; +} + +Quaternion Spline::GetSplineLocalOrientation(float time) const +{ + Transform t; + Curve.Evaluate(t, time, _loop); + return t.Orientation; +} + +Vector3 Spline::GetSplineScale(float time) const +{ + Transform t; + Curve.Evaluate(t, time, _loop); + return _transform.Scale * t.Scale; +} + +Vector3 Spline::GetSplineLocalScale(float time) const +{ + Transform t; + Curve.Evaluate(t, time, _loop); + return t.Scale; +} + +Transform Spline::GetSplineTransform(float time) const +{ + Transform t; + Curve.Evaluate(t, time, _loop); + return _transform.LocalToWorld(t); +} + +Transform Spline::GetSplineLocalTransform(float time) const +{ + Transform t; + Curve.Evaluate(t, time, _loop); + return t; +} + +Vector3 Spline::GetSplineDirection(float time) const +{ + return _transform.LocalToWorldVector(GetSplineLocalDirection(time)); +} + +Vector3 Spline::GetSplineLocalDirection(float time) const +{ + if (Curve.GetKeyframes().Count() == 0) + return Vector3::Forward; + Transform t; + Curve.EvaluateFirstDerivative(t, time, _loop); + t.Translation.Normalize(); + return t.Translation; +} + +Vector3 Spline::GetSplinePoint(int32 index) const +{ + CHECK_RETURN(index >= 0 && index < GetSplinePointsCount(), Vector3::Zero) + return _transform.LocalToWorld(Curve[index].Value.Translation); +} + +Vector3 Spline::GetSplineLocalPoint(int32 index) const +{ + CHECK_RETURN(index >= 0 && index < GetSplinePointsCount(), Vector3::Zero) + return Curve[index].Value.Translation; +} + +Transform Spline::GetSplineTransform(int32 index) const +{ + CHECK_RETURN(index >= 0 && index < GetSplinePointsCount(), Transform::Identity) + return _transform.LocalToWorld(Curve[index].Value); +} + +Transform Spline::GetSplineLocalTransform(int32 index) const +{ + CHECK_RETURN(index >= 0 && index < GetSplinePointsCount(), Transform::Identity) + return Curve[index].Value; +} + +Transform Spline::GetSplineTangent(int32 index, bool isIn) +{ + return _transform.LocalToWorld(GetSplineLocalTangent(index, isIn)); +} + +Transform Spline::GetSplineLocalTangent(int32 index, bool isIn) +{ + CHECK_RETURN(index >= 0 && index < GetSplinePointsCount(), Transform::Identity) + const auto& k = Curve[index]; + const auto& tangent = isIn ? k.TangentIn : k.TangentOut; + return tangent + k.Value; +} + +int32 Spline::GetSplinePointsCount() const +{ + return Curve.GetKeyframes().Count(); +} + +float Spline::GetSplineDuration() const +{ + return Curve.GetLength(); +} + +float Spline::GetSplineLength() const +{ + float sum = 0.0f; + const int32 slices = 20; + const float step = 1.0f / (float)slices; + Vector3 prevPoint = Vector3::Zero; + for (int32 i = 1; i < Curve.GetKeyframes().Count(); i++) + { + const auto& a = Curve[i = 1]; + const auto& b = Curve[i]; + + const float length = Math::Abs(b.Time - a.Time); + Vector3 leftTangent, rightTangent; + AnimationUtils::GetTangent(a.Value.Translation, a.TangentOut.Translation, length, leftTangent); + AnimationUtils::GetTangent(b.Value.Translation, b.TangentIn.Translation, length, rightTangent); + + // TODO: implement sth more analytical than brute-force solution + for (int32 slice = 0; slice < slices; slice++) + { + const float t = (float)slice * step; + Vector3 pos; + AnimationUtils::Bezier(a.Value.Translation, leftTangent, rightTangent, b.Value.Translation, t, pos); + pos *= _transform.Scale; + sum += Vector3::DistanceSquared(pos, prevPoint); + prevPoint = pos; + } + } + return Math::Sqrt(sum); +} + +float Spline::GetSplineTime(int32 index) const +{ + CHECK_RETURN(index >= 0 && index < GetSplinePointsCount(), 0.0f) + return Curve[index].Time; +} + +namespace +{ + void FindTimeClosestToPoint(const Vector3& point, const Spline::Keyframe& start, const Spline::Keyframe& end, float& bestDistanceSquared, float& bestTime) + { + // TODO: implement sth more analytical than brute-force solution + const int32 slices = 100; + const float step = 1.0f / (float)slices; + const float length = Math::Abs(end.Time - start.Time); + for (int32 i = 0; i <= slices; i++) + { + const float t = (float)i * step; + Transform result; + Spline::Keyframe::Interpolate(start, end, t, length, result); + const float distanceSquared = Vector3::DistanceSquared(point, result.Translation); + if (distanceSquared < bestDistanceSquared) + { + bestDistanceSquared = distanceSquared; + bestTime = start.Time + t * length; + } + } + } +} + +float Spline::GetSplineTimeClosestToPoint(const Vector3& point) const +{ + const int32 pointsCount = Curve.GetKeyframes().Count(); + if (pointsCount == 0) + return 0.0f; + if (pointsCount == 1) + return Curve[0].Time; + const Vector3 localPoint = _transform.WorldToLocal(point); + float bestDistanceSquared = MAX_float; + float bestTime = 0.0f; + for (int32 i = 1; i < pointsCount; i++) + FindTimeClosestToPoint(localPoint, Curve[i - 1], Curve[i], bestDistanceSquared, bestTime); + return bestTime; +} + +Vector3 Spline::GetSplinePointClosestToPoint(const Vector3& point) const +{ + return GetSplinePoint(GetSplineTimeClosestToPoint(point)); +} + +void Spline::GetSplinePoints(Array& points) const +{ + for (auto& e : Curve.GetKeyframes()) + points.Add(_transform.LocalToWorld(e.Value.Translation)); +} + +void Spline::GetSplineLocalPoints(Array& points) const +{ + for (auto& e : Curve.GetKeyframes()) + points.Add(e.Value.Translation); +} + +void Spline::GetSplinePoints(Array& points) const +{ + for (auto& e : Curve.GetKeyframes()) + points.Add(_transform.LocalToWorld(e.Value)); +} + +void Spline::GetSplineLocalPoints(Array& points) const +{ + for (auto& e : Curve.GetKeyframes()) + points.Add(e.Value); +} + +void Spline::ClearSpline() +{ + if (Curve.IsEmpty()) + return; + Curve.Clear(); + UpdateSpline(); +} + +void Spline::RemoveSplinePoint(int32 index, bool updateSpline) +{ + CHECK(index >= 0 && index < GetSplinePointsCount()); + Curve.GetKeyframes().RemoveAtKeepOrder(index); + if (updateSpline) + UpdateSpline(); +} + +void Spline::SetSplinePoint(int32 index, const Vector3& point, bool updateSpline) +{ + CHECK(index >= 0 && index < GetSplinePointsCount()); + Curve[index].Value.Translation = _transform.WorldToLocal(point); + if (updateSpline) + UpdateSpline(); +} + +void Spline::SetSplineLocalPoint(int32 index, const Vector3& point, bool updateSpline) +{ + CHECK(index >= 0 && index < GetSplinePointsCount()); + Curve[index].Value.Translation = point; + if (updateSpline) + UpdateSpline(); +} + +void Spline::SetSplineTransform(int32 index, const Transform& point, bool updateSpline) +{ + CHECK(index >= 0 && index < GetSplinePointsCount()); + Curve[index].Value = _transform.WorldToLocal(point); + if (updateSpline) + UpdateSpline(); +} + +void Spline::SetSplineLocalTransform(int32 index, const Transform& point, bool updateSpline) +{ + CHECK(index >= 0 && index < GetSplinePointsCount()); + Curve[index].Value = point; + if (updateSpline) + UpdateSpline(); +} + +void Spline::SetSplineTangent(int32 index, const Transform& point, bool isIn, bool updateSpline) +{ + SetSplineLocalTangent(index, _transform.WorldToLocal(point), isIn, updateSpline); +} + +void Spline::SetSplineLocalTangent(int32 index, const Transform& point, bool isIn, bool updateSpline) +{ + CHECK(index >= 0 && index < GetSplinePointsCount()); + auto& k = Curve[index]; + auto& tangent = isIn ? k.TangentIn : k.TangentOut; + tangent = point - k.Value; + if (updateSpline) + UpdateSpline(); +} + +void Spline::SetSplinePointTime(int32 index, float time, bool updateSpline) +{ + CHECK(index >= 0 && index < GetSplinePointsCount()); + Curve[index].Time = time; + if (updateSpline) + UpdateSpline(); +} + +void Spline::AddSplinePoint(const Vector3& point, bool updateSpline) +{ + const Keyframe k(Curve.IsEmpty() ? 0.0f : Curve.GetKeyframes().Last().Time + 1.0f, Transform(_transform.WorldToLocal(point))); + Curve.GetKeyframes().Add(k); + if (updateSpline) + UpdateSpline(); +} + +void Spline::AddSplineLocalPoint(const Vector3& point, bool updateSpline) +{ + const Keyframe k(Curve.IsEmpty() ? 0.0f : Curve.GetKeyframes().Last().Time + 1.0f, Transform(point)); + Curve.GetKeyframes().Add(k); + if (updateSpline) + UpdateSpline(); +} + +void Spline::AddSplinePoint(const Transform& point, bool updateSpline) +{ + const Keyframe k(Curve.IsEmpty() ? 0.0f : Curve.GetKeyframes().Last().Time + 1.0f, _transform.WorldToLocal(point)); + Curve.GetKeyframes().Add(k); + if (updateSpline) + UpdateSpline(); +} + +void Spline::AddSplineLocalPoint(const Transform& point, bool updateSpline) +{ + const Keyframe k(Curve.IsEmpty() ? 0.0f : Curve.GetKeyframes().Last().Time + 1.0f, point); + Curve.GetKeyframes().Add(k); + if (updateSpline) + UpdateSpline(); +} + +void Spline::InsertSplinePoint(int32 index, float time, const Transform& point, bool updateSpline) +{ + const Keyframe k(time, _transform.WorldToLocal(point)); + Curve.GetKeyframes().Insert(index, k); + if (updateSpline) + UpdateSpline(); +} + +void Spline::InsertSplineLocalPoint(int32 index, float time, const Transform& point, bool updateSpline) +{ + const Keyframe k(time, point); + Curve.GetKeyframes().Insert(index, k); + if (updateSpline) + UpdateSpline(); +} + +void Spline::SetTangentsLinear() +{ + const int32 count = Curve.GetKeyframes().Count(); + if (count < 2) + return; + + if (_loop) + Curve[count - 1].Value = Curve[0].Value; + for (int32 i = 0; i < count; i++) + { + auto& k = Curve[i]; + k.TangentIn = k.TangentOut = Transform::Identity; + } + + UpdateSpline(); +} + +void Spline::SetTangentsSmooth() +{ + const int32 count = Curve.GetKeyframes().Count(); + if (count < 2) + return; + + auto& keys = Curve.GetKeyframes(); + const int32 last = count - 2; + if (_loop) + Curve[count - 1].Value = Curve[0].Value; + for (int32 i = 0; i <= last; i++) + { + auto& key = keys[i]; + const auto& prevKey = keys[i == 0 ? (_loop ? last : 0) : i - 1]; + const auto& nextKey = keys[i == last ? (_loop ? 0 : last) : i + 1]; + const float prevTime = _loop && i == 0 ? key.Time : prevKey.Time; + const float nextTime = _loop && i == last ? key.Time : nextKey.Time; + const Vector3 slope = key.Value.Translation - prevKey.Value.Translation + nextKey.Value.Translation - key.Value.Translation; + const Vector3 tangent = slope / Math::Max(nextTime - prevTime, ZeroTolerance); + key.TangentIn.Translation = -tangent; + key.TangentOut.Translation = tangent; + } + + UpdateSpline(); +} + +void Spline::UpdateSpline() +{ + // Always keep last point in the loop + const int32 count = Curve.GetKeyframes().Count(); + if (_loop && count > 1) + { + auto& first = Curve[0]; + auto& last = Curve[count - 1]; + last.Value = first.Value; + last.TangentIn = first.TangentIn; + last.TangentOut = first.TangentOut; + } + + SplineUpdated(); +} + +void Spline::GetKeyframes(MonoArray* data) +{ + Platform::MemoryCopy(mono_array_addr_with_size(data, sizeof(Keyframe), 0), Curve.GetKeyframes().Get(), sizeof(Keyframe) * Curve.GetKeyframes().Count()); +} + +void Spline::SetKeyframes(MonoArray* data) +{ + const auto count = (int32)mono_array_length(data); + Curve.GetKeyframes().Resize(count, false); + Platform::MemoryCopy(Curve.GetKeyframes().Get(), mono_array_addr_with_size(data, sizeof(Keyframe), 0), sizeof(Keyframe) * count); + UpdateSpline(); +} + +#if USE_EDITOR + +#include "Engine/Debug/DebugDraw.h" + +namespace +{ + void DrawSpline(Spline* spline, const Color& color, const Transform& transform, bool depthTest) + { + const int32 count = spline->Curve.GetKeyframes().Count(); + if (count == 0) + return; + Spline::Keyframe* prev = spline->Curve.GetKeyframes().Get(); + Vector3 prevPos = transform.LocalToWorld(prev->Value.Translation); + DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(prevPos, 5.0f), color, 0.0f, depthTest); + for (int32 i = 1; i < count; i++) + { + Spline::Keyframe* next = prev + 1; + Vector3 nextPos = transform.LocalToWorld(next->Value.Translation); + DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(nextPos, 5.0f), color, 0.0f, depthTest); + const float d = (next->Time - prev->Time) / 3.0f; + DEBUG_DRAW_BEZIER(prevPos, prevPos + prev->TangentOut.Translation * d, nextPos + next->TangentIn.Translation * d, nextPos, color, 0.0f, depthTest); + prev = next; + prevPos = nextPos; + } + } +} + +void Spline::OnDebugDraw() +{ + const Color color = GetSplineColor(); + DrawSpline(this, color.AlphaMultiplied(0.7f), _transform, true); + + // Base + Actor::OnDebugDraw(); +} + +void Spline::OnDebugDrawSelected() +{ + const Color color = GetSplineColor(); + DrawSpline(this, color.AlphaMultiplied(0.3f), _transform, false); + + // Base + Actor::OnDebugDrawSelected(); +} + +#endif + +void Spline::Serialize(SerializeStream& stream, const void* otherObj) +{ + // Base + Actor::Serialize(stream, otherObj); + + SERIALIZE_GET_OTHER_OBJ(Spline); + + SERIALIZE_MEMBER(IsLoop, _loop); + SERIALIZE(Curve); +} + +void Spline::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) +{ + // Base + Actor::Deserialize(stream, modifier); + + DESERIALIZE_MEMBER(IsLoop, _loop); + DESERIALIZE(Curve); + + // Initialize spline when loading data during gameplay + if (IsDuringPlay()) + { + UpdateSpline(); + } +} diff --git a/Source/Engine/Level/Actors/Spline.h b/Source/Engine/Level/Actors/Spline.h new file mode 100644 index 000000000..f19f9a443 --- /dev/null +++ b/Source/Engine/Level/Actors/Spline.h @@ -0,0 +1,382 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "../Actor.h" +#include "Engine/Animations/Curve.h" + +/// +/// Spline shape actor that defines spatial curve with utility functions for general purpose usage. +/// +API_CLASS() class FLAXENGINE_API Spline : public Actor +{ +DECLARE_SCENE_OBJECT(Spline); + typedef BezierCurveKeyframe Keyframe; +private: + + bool _loop = false; + +public: + + /// + /// The spline bezier curve points represented as series of transformations in 3D space (with tangents). Points are stored in local-space of the actor. + /// + /// Ensure to call UpdateSpline() after editing curve to reflect the changes. + BezierCurve Curve; + + /// + /// Whether to use spline as closed loop. In that case, ensure to place start and end at the same location. + /// + API_PROPERTY(Attributes="EditorOrder(0), EditorDisplay(\"Spline\")") + bool GetIsLoop() const; + + /// + /// Whether to use spline as closed loop. In that case, ensure to place start and end at the same location. + /// + API_PROPERTY() void SetIsLoop(bool value); + +public: + + /// + /// Evaluates the spline curve at the given time and calculates the point location in 3D (world-space). + /// + /// The time value. Can be negative or larger than curve length (curve will loop or clamp). + /// The calculated curve point location (world-space). + API_FUNCTION() Vector3 GetSplinePoint(float time) const; + + /// + /// Evaluates the spline curve at the given time and calculates the point location in 3D (local-space). + /// + /// The time value. Can be negative or larger than curve length (curve will loop or clamp). + /// The calculated curve point location (local-space). + API_FUNCTION() Vector3 GetSplineLocalPoint(float time) const; + + /// + /// Evaluates the spline curve at the given time and calculates the point rotation in 3D (world-space). + /// + /// The time value. Can be negative or larger than curve length (curve will loop or clamp). + /// The calculated curve point rotation (world-space). + API_FUNCTION() Quaternion GetSplineOrientation(float time) const; + + /// + /// Evaluates the spline curve at the given time and calculates the point rotation in 3D (local-space). + /// + /// The time value. Can be negative or larger than curve length (curve will loop or clamp). + /// The calculated curve point rotation (local-space). + API_FUNCTION() Quaternion GetSplineLocalOrientation(float time) const; + + /// + /// Evaluates the spline curve at the given time and calculates the point scale in 3D (world-space). + /// + /// The time value. Can be negative or larger than curve length (curve will loop or clamp). + /// The calculated curve point scale (world-space). + API_FUNCTION() Vector3 GetSplineScale(float time) const; + + /// + /// Evaluates the spline curve at the given time and calculates the point scale in 3D (local-space). + /// + /// The time value. Can be negative or larger than curve length (curve will loop or clamp). + /// The calculated curve point scale (local-space). + API_FUNCTION() Vector3 GetSplineLocalScale(float time) const; + + /// + /// Evaluates the spline curve at the given time and calculates the transformation in 3D (world-space). + /// + /// The time value. Can be negative or larger than curve length (curve will loop or clamp). + /// The calculated curve point transformation (world-space). + API_FUNCTION() Transform GetSplineTransform(float time) const; + + /// + /// Evaluates the spline curve at the given time and calculates the transformation in 3D (local-space). + /// + /// The time value. Can be negative or larger than curve length (curve will loop or clamp). + /// The calculated curve point transformation (local-space). + API_FUNCTION() Transform GetSplineLocalTransform(float time) const; + + /// + /// Evaluates the spline curve direction (forward vector, aka position 1st derivative) at the given time in 3D (world-space). + /// + /// The time value. Can be negative or larger than curve length (curve will loop or clamp). + /// The calculated curve direction (world-space). + API_FUNCTION() Vector3 GetSplineDirection(float time) const; + + /// + /// Evaluates the spline curve direction (forward vector, aka position 1st derivative) at the given time in 3D (local-space). + /// + /// The time value. Can be negative or larger than curve length (curve will loop or clamp). + /// The calculated curve direction (local-space). + API_FUNCTION() Vector3 GetSplineLocalDirection(float time) const; + + /// + /// Evaluates the spline curve at the given index (world-space). + /// + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// The curve point location (world-space). + API_FUNCTION() Vector3 GetSplinePoint(int32 index) const; + + /// + /// Evaluates the spline curve at the given index (local-space). + /// + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// The curve point location (local-space). + API_FUNCTION() Vector3 GetSplineLocalPoint(int32 index) const; + + /// + /// Evaluates the spline curve at the given index (world-space). + /// + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// The curve point transformation (world-space). + API_FUNCTION() Transform GetSplineTransform(int32 index) const; + + /// + /// Evaluates the spline curve at the given index (local-space). + /// + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// The curve point transformation (local-space). + API_FUNCTION() Transform GetSplineLocalTransform(int32 index) const; + + /// + /// Gets the spline curve point tangent at the given index (world-space). + /// + /// Tangents are stored relative to the curve point but this methods converts them to be in world-space. + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// True if get arrive tangent, otherwise gets leave tangent (in or out). + /// The transformation of the tangent to set (world-space). + API_FUNCTION() Transform GetSplineTangent(int32 index, bool isIn); + + /// + /// Gets the spline curve point tangent at the given index (local-space). + /// + /// Tangents are stored relative to the curve point but this methods converts them to be in local-space of the actor. + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// True if get arrive tangent, otherwise gets leave tangent (in or out). + /// The transformation of the tangent to set (world-space). + API_FUNCTION() Transform GetSplineLocalTangent(int32 index, bool isIn); + + /// + /// Gets the amount of points in the spline. + /// + API_PROPERTY() int32 GetSplinePointsCount() const; + + /// + /// Gets the total duration of the spline curve (time of the last point). + /// + API_PROPERTY() float GetSplineDuration() const; + + /// + /// Gets the total length of the spline curve (distance between all the points). + /// + API_PROPERTY() float GetSplineLength() const; + + /// + /// Gets the time of the spline keyframe. + /// + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// The spline time. + API_FUNCTION() float GetSplineTime(int32 index) const; + + /// + /// Calculates the closest point to the given location and returns the spline time at that point. + /// + /// The point in world-space to find the spline point that is closest to it. + /// The spline time. + API_FUNCTION() float GetSplineTimeClosestToPoint(const Vector3& point) const; + + /// + /// Calculates the closest point to the given location. + /// + /// The point in world-space to find the spline point that is closest to it. + /// The spline position. + API_FUNCTION() Vector3 GetSplinePointClosestToPoint(const Vector3& point) const; + + /// + /// Gets the spline curve points list (world-space). + /// + /// The result points collection. + API_FUNCTION() void GetSplinePoints(API_PARAM(Out) Array& points) const; + + /// + /// Gets the spline curve points list (local-space). + /// + /// The result points collection. + API_FUNCTION() void GetSplineLocalPoints(API_PARAM(Out) Array& points) const; + + /// + /// Gets the spline curve points list (world-space). + /// + /// The result points collection. + API_FUNCTION() void GetSplinePoints(API_PARAM(Out) Array& points) const; + + /// + /// Gets the spline curve points list (local-space). + /// + /// The result points collection. + API_FUNCTION() void GetSplineLocalPoints(API_PARAM(Out) Array& points) const; + +public: + + /// + /// Clears the spline to be empty. + /// + API_FUNCTION() void ClearSpline(); + + /// + /// Removes the spline curve point at the given index. + /// + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// True if update spline after removing the point, otherwise false. + API_FUNCTION() void RemoveSplinePoint(int32 index, bool updateSpline = true); + + /// + /// Sets the spline curve at the given index (world-space). + /// + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// The location of the point to set (world-space). + /// True if update spline after editing the point, otherwise false. + API_FUNCTION() void SetSplinePoint(int32 index, const Vector3& point, bool updateSpline = true); + + /// + /// Sets the spline curve at the given index (local-space). + /// + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// The location of the point to set (local-space). + /// True if update spline after editing the point, otherwise false. + API_FUNCTION() void SetSplineLocalPoint(int32 index, const Vector3& point, bool updateSpline = true); + + /// + /// Sets the spline curve at the given index (world-space). + /// + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// The transformation of the point to set (world-space). + /// True if update spline after editing the point, otherwise false. + API_FUNCTION() void SetSplineTransform(int32 index, const Transform& point, bool updateSpline = true); + + /// + /// Sets the spline curve at the given index (local-space). + /// + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// The transformation of the point to set (local-space). + /// True if update spline after editing the point, otherwise false. + API_FUNCTION() void SetSplineLocalTransform(int32 index, const Transform& point, bool updateSpline = true); + + /// + /// Sets the spline curve point tangent at the given index (world-space). + /// + /// Tangents are stored relative to the curve point but this methods converts them to be in world-space. + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// The transformation of the tangent to set (world-space). + /// True if set arrive tangent, otherwise sets leave tangent (in or out). + /// True if update spline after editing the point, otherwise false. + API_FUNCTION() void SetSplineTangent(int32 index, const Transform& point, bool isIn, bool updateSpline = true); + + /// + /// Sets the spline curve point tangent at the given index (local-space). + /// + /// Tangents are stored relative to the curve point but this methods converts them to be in local-space of the actor. + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// The transformation of the tangent to set (local-space). + /// True if set arrive tangent, otherwise sets leave tangent (in or out). + /// True if update spline after editing the point, otherwise false. + API_FUNCTION() void SetSplineLocalTangent(int32 index, const Transform& point, bool isIn, bool updateSpline = true); + + /// + /// Sets the spline curve point time at the given index (world-space). + /// + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// The time to set. + /// True if update spline after editing the point, otherwise false. + API_FUNCTION() void SetSplinePointTime(int32 index, float time, bool updateSpline = true); + + /// + /// Adds the point to the spline curve (at the end). + /// + /// The location of the point to add to the curve (world-space). + /// True if update spline after editing the point, otherwise false. + API_FUNCTION() void AddSplinePoint(const Vector3& point, bool updateSpline = true); + + /// + /// Adds the point to the spline curve (at the end). + /// + /// The location of the point to add to the curve (local-space). + /// True if update spline after adding the point, otherwise false. + API_FUNCTION() void AddSplineLocalPoint(const Vector3& point, bool updateSpline = true); + + /// + /// Adds the point to the spline curve (at the end). + /// + /// The transformation of the point to add to the curve (world-space). + /// True if update spline after adding the point, otherwise false. + API_FUNCTION() void AddSplinePoint(const Transform& point, bool updateSpline = true); + + /// + /// Adds the point to the spline curve (at the end). + /// + /// The transformation of the point to add to the curve (local-space). + /// True if update spline after adding the point, otherwise false. + API_FUNCTION() void AddSplineLocalPoint(const Transform& point, bool updateSpline = true); + + /// + /// Inserts the spline curve point at the given index (world-space). + /// + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// The time value. + /// The location of the point to add to the curve (world-space). + /// True if update spline after removing the point, otherwise false. + API_FUNCTION() void InsertSplinePoint(int32 index, float time, const Transform& point, bool updateSpline = true); + + /// + /// Inserts the spline curve point at the given index (local-space). + /// + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// The time value. + /// The location of the point to add to the curve (local-space). + /// True if update spline after removing the point, otherwise false. + API_FUNCTION() void InsertSplineLocalPoint(int32 index, float time, const Transform& point, bool updateSpline = true); + + /// + /// Updates the curve tangent points to make curve linear. + /// + API_FUNCTION() void SetTangentsLinear(); + + /// + /// Updates the curve tangent points to make curve smooth. + /// + API_FUNCTION() void SetTangentsSmooth(); + +public: + + /// + /// Called when spline gets updated (eg. after curve modification). + /// + API_EVENT() Action SplineUpdated; + + /// + /// Updates the spline after it was modified. Recreates the collision and/or any cached state that depends on the spline type. + /// + API_FUNCTION() virtual void UpdateSpline(); + +protected: + +#if USE_EDITOR + virtual Color GetSplineColor() + { + return Color::White; + } +#endif + +private: + + // Internal bindings + API_FUNCTION(NoProxy) void GetKeyframes(MonoArray* data); + API_FUNCTION(NoProxy) void SetKeyframes(MonoArray* data); + +public: + + // [Actor] +#if USE_EDITOR + void OnDebugDraw() override; + void OnDebugDrawSelected() override; +#endif + void Serialize(SerializeStream& stream, const void* otherObj) override; + void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; +}; diff --git a/Source/Engine/Level/Actors/SplineModel.cpp b/Source/Engine/Level/Actors/SplineModel.cpp new file mode 100644 index 000000000..84306716b --- /dev/null +++ b/Source/Engine/Level/Actors/SplineModel.cpp @@ -0,0 +1,478 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#include "SplineModel.h" +#include "Spline.h" +#include "Engine/Engine/Engine.h" +#include "Engine/Core/Math/Matrix3x4.h" +#include "Engine/Serialization/Serialization.h" +#include "Engine/Graphics/GPUDevice.h" +#include "Engine/Graphics/RenderTools.h" +#include "Engine/Profiler/ProfilerCPU.h" +#if USE_EDITOR +#include "Editor/Editor.h" +#endif + +#define SPLINE_RESOLUTION 32.0f + +SplineModel::SplineModel(const SpawnParams& params) + : ModelInstanceActor(params) +{ + Model.Changed.Bind(this); + Model.Loaded.Bind(this); +} + +SplineModel::~SplineModel() +{ + SAFE_DELETE_GPU_RESOURCE(_deformationBuffer); + if (_deformationBufferData) + Allocator::Free(_deformationBufferData); +} + +Transform SplineModel::GetPreTransform() const +{ + return _preTransform; +} + +void SplineModel::SetPreTransform(const Transform& value) +{ + if (_preTransform == value) + return; + _preTransform = value; + OnSplineUpdated(); +} + +float SplineModel::GetQuality() const +{ + return _quality; +} + +void SplineModel::SetQuality(float value) +{ + value = Math::Clamp(value, 0.0f, 100.0f); + if (Math::NearEqual(value, _quality)) + return; + _quality = value; + OnSplineUpdated(); +} + +float SplineModel::GetBoundsScale() const +{ + return _boundsScale; +} + +void SplineModel::SetBoundsScale(float value) +{ + if (Math::NearEqual(_boundsScale, value)) + return; + _boundsScale = value; + OnSplineUpdated(); +} + +int32 SplineModel::GetLODBias() const +{ + return static_cast(_lodBias); +} + +void SplineModel::SetLODBias(int32 value) +{ + _lodBias = static_cast(Math::Clamp(value, -100, 100)); +} + +int32 SplineModel::GetForcedLOD() const +{ + return static_cast(_forcedLod); +} + +void SplineModel::SetForcedLOD(int32 value) +{ + _forcedLod = static_cast(Math::Clamp(value, -1, 100)); +} + +void SplineModel::OnModelChanged() +{ + Entries.Release(); + + if (Model && !Model->IsLoaded()) + { + OnSplineUpdated(); + } +} + +void SplineModel::OnModelLoaded() +{ + Entries.SetupIfInvalid(Model); + + OnSplineUpdated(); +} + +void SplineModel::OnSplineUpdated() +{ + // Skip updates when actor is disabled or something is missing + if (!_spline || !Model || !Model->IsLoaded() || !IsActiveInHierarchy() || _spline->GetSplinePointsCount() < 2) + { + _box = BoundingBox(_transform.Translation, _transform.Translation); + BoundingSphere::FromBox(_box, _sphere); + return; + } + PROFILE_CPU(); + + // Setup model instances over the spline segments + const auto& keyframes = _spline->Curve.GetKeyframes(); + const int32 segments = keyframes.Count() - 1; + const int32 chunksPerSegment = Math::Clamp(Math::CeilToInt(SPLINE_RESOLUTION * _quality), 2, 1024); + const float chunksPerSegmentInv = 1.0f / (float)chunksPerSegment; + const Transform splineTransform = GetTransform(); + _instances.Resize(segments, false); + BoundingBox localModelBounds(Vector3::Maximum, Vector3::Minimum); + { + auto& meshes = Model->LODs[0].Meshes; + Vector3 corners[8]; + for (int32 j = 0; j < meshes.Count(); j++) + { + const auto& mesh = meshes[j]; + mesh.GetCorners(corners); + + for (int32 i = 0; i < 8; i++) + { + // Transform mesh corner using pre-transform but use double-precision to prevent issues when rotating model + Vector3 tmp = corners[i] * _preTransform.Scale; + double rotation[4] = { (double)_preTransform.Orientation.X, (double)_preTransform.Orientation.Y, (double)_preTransform.Orientation.Z, (double)_preTransform.Orientation.W }; + const double length = sqrt(rotation[0] * rotation[0] + rotation[1] * rotation[1] + rotation[2] * rotation[2] + rotation[3] * rotation[3]); + const double inv = 1.0 / length; + rotation[0] *= inv; + rotation[1] *= inv; + rotation[2] *= inv; + rotation[3] *= inv; + double pos[3] = { (double)tmp.X, (double)tmp.Y, (double)tmp.Z }; + const double x = rotation[0] + rotation[0]; + const double y = rotation[1] + rotation[1]; + const double z = rotation[2] + rotation[2]; + const double wx = rotation[3] * x; + const double wy = rotation[3] * y; + const double wz = rotation[3] * z; + const double xx = rotation[0] * x; + const double xy = rotation[0] * y; + const double xz = rotation[0] * z; + const double yy = rotation[1] * y; + const double yz = rotation[1] * z; + const double zz = rotation[2] * z; + tmp = Vector3( + (float)(pos[0] * (1.0 - yy - zz) + pos[1] * (xy - wz) + pos[2] * (xz + wy)) + _preTransform.Translation.X, + (float)(pos[0] * (xy + wz) + pos[1] * (1.0 - xx - zz) + pos[2] * (yz - wx)) + _preTransform.Translation.Y, + (float)(pos[0] * (xz - wy) + pos[1] * (yz + wx) + pos[2] * (1.0 - xx - yy)) + _preTransform.Translation.Z); + + localModelBounds.Minimum = Vector3::Min(localModelBounds.Minimum, tmp); + localModelBounds.Maximum = Vector3::Max(localModelBounds.Maximum, tmp); + } + } + } + _meshMinZ = localModelBounds.Minimum.Z; + _meshMaxZ = localModelBounds.Maximum.Z; + Transform chunkLocal, chunkWorld, leftTangent, rightTangent; + Array segmentPoints; + segmentPoints.Resize(chunksPerSegment); + for (int32 segment = 0; segment < segments; segment++) + { + auto& instance = _instances[segment]; + const auto& start = keyframes[segment]; + const auto& end = keyframes[segment + 1]; + const float length = end.Time - start.Time; + AnimationUtils::GetTangent(start.Value, start.TangentOut, length, leftTangent); + AnimationUtils::GetTangent(end.Value, end.TangentIn, length, rightTangent); + + // Find maximum scale over the segment spline and collect the segment positions for bounds + segmentPoints.Clear(); + segmentPoints.Add(end.Value.Translation); + float maxScale = end.Value.Scale.GetAbsolute().MaxValue(); + for (int32 chunk = 0; chunk < chunksPerSegment; chunk++) + { + const float alpha = (float)chunk * chunksPerSegmentInv; + AnimationUtils::Bezier(start.Value, leftTangent, rightTangent, end.Value, alpha, chunkLocal); + splineTransform.LocalToWorld(chunkLocal, chunkWorld); + segmentPoints.Add(chunkWorld.Translation); + maxScale = Math::Max(maxScale, chunkWorld.Scale.GetAbsolute().MaxValue()); + } + BoundingSphere::FromPoints(segmentPoints.Get(), segmentPoints.Count(), instance.Sphere); + instance.Sphere.Radius *= maxScale * _boundsScale; + } + + // Update deformation buffer during next drawing + _deformationDirty = true; + + // Update bounds + _sphere = _instances.First().Sphere; + for (int32 i = 1; i < _instances.Count(); i++) + BoundingSphere::Merge(_sphere, _instances[i].Sphere, _sphere); + BoundingBox::FromSphere(_sphere, _box); +} + +void SplineModel::UpdateDeformationBuffer() +{ + PROFILE_CPU(); + + // Deformation buffer contains precomputed matrices for each chunk of the spline segment (packed with transposed float3x4 matrix) + _deformationDirty = false; + if (!_deformationBuffer) + _deformationBuffer = GPUDevice::Instance->CreateBuffer(GetName()); + const auto& keyframes = _spline->Curve.GetKeyframes(); + const int32 segments = keyframes.Count() - 1; + const int32 chunksPerSegment = Math::Clamp(Math::CeilToInt(SPLINE_RESOLUTION * _quality), 2, 1024); + const int32 count = (chunksPerSegment * segments + 1) * 3; + const uint32 size = count * sizeof(Vector4); + if (_deformationBuffer->GetSize() != size) + { + if (_deformationBufferData) + { + Allocator::Free(_deformationBufferData); + _deformationBufferData = nullptr; + } + if (_deformationBuffer->Init(GPUBufferDescription::Typed(count, PixelFormat::R32G32B32A32_Float, false, IsTransformStatic() ? GPUResourceUsage::Default : GPUResourceUsage::Dynamic))) + { + LOG(Error, "Failed to initialize the spline model {0} deformation buffer.", ToString()); + return; + } + } + if (!_deformationBufferData) + _deformationBufferData = Allocator::Allocate(size); + _chunksPerSegment = (float)chunksPerSegment; + + // Update pre-calculated matrices for spline chunks + auto ptr = (Matrix3x4*)_deformationBufferData; + const float chunksPerSegmentInv = 1.0f / (float)chunksPerSegment; + Matrix m; + Transform transform, leftTangent, rightTangent; + for (int32 segment = 0; segment < segments; segment++) + { + auto& instance = _instances[segment]; + const auto& start = keyframes[segment]; + const auto& end = keyframes[segment + 1]; + const float length = end.Time - start.Time; + AnimationUtils::GetTangent(start.Value, start.TangentOut, length, leftTangent); + AnimationUtils::GetTangent(end.Value, end.TangentIn, length, rightTangent); + for (int32 chunk = 0; chunk < chunksPerSegment; chunk++) + { + const float alpha = (float)chunk * chunksPerSegmentInv; + + // Evaluate transformation at the curve + AnimationUtils::Bezier(start.Value, leftTangent, rightTangent, end.Value, alpha, transform); + + // Apply spline direction (from position 1st derivative) + Vector3 direction; + AnimationUtils::BezierFirstDerivative(start.Value.Translation, leftTangent.Translation, rightTangent.Translation, end.Value.Translation, alpha, direction); + direction.Normalize(); + Quaternion orientation; + if (direction.IsZero()) + orientation = Quaternion::Identity; + else if (Vector3::Dot(direction, Vector3::Up) >= 0.999f) + Quaternion::RotationAxis(Vector3::Left, PI_HALF, orientation); + else + Quaternion::LookRotation(direction, Vector3::Cross(Vector3::Cross(direction, Vector3::Up), direction), orientation); + transform.Orientation = orientation * transform.Orientation; + + // Write transform into deformation buffer + transform.GetWorld(m); + ptr->SetMatrixTranspose(m); + ptr++; + } + instance.RotDeterminant = m.RotDeterminant(); + } + + // Add last transformation to prevent issues when sampling spline deformation buffer with alpha=1 + { + const auto& start = keyframes[segments - 1]; + const auto& end = keyframes[segments]; + const float length = end.Time - start.Time; + const float alpha = 1.0f - ZeroTolerance; // Offset to prevent zero derivative at the end of the curve + AnimationUtils::GetTangent(start.Value, start.TangentOut, length, leftTangent); + AnimationUtils::GetTangent(end.Value, end.TangentIn, length, rightTangent); + AnimationUtils::Bezier(start.Value, leftTangent, rightTangent, end.Value, alpha, transform); + Vector3 direction; + AnimationUtils::BezierFirstDerivative(start.Value.Translation, leftTangent.Translation, rightTangent.Translation, end.Value.Translation, alpha, direction); + direction.Normalize(); + Quaternion orientation; + if (direction.IsZero()) + orientation = Quaternion::Identity; + else if (Vector3::Dot(direction, Vector3::Up) >= 0.999f) + Quaternion::RotationAxis(Vector3::Left, PI_HALF, orientation); + else + Quaternion::LookRotation(direction, Vector3::Cross(Vector3::Cross(direction, Vector3::Up), direction), orientation); + transform.Orientation = orientation * transform.Orientation; + transform.GetWorld(m); + ptr->SetMatrixTranspose(m); + } + + // Flush data with GPU + auto context = GPUDevice::Instance->GetMainContext(); + context->UpdateBuffer(_deformationBuffer, _deformationBufferData, size); + + // Static splines are rarely updated so release scratch memory + if (IsTransformStatic()) + { + Allocator::Free(_deformationBufferData); + _deformationBufferData = nullptr; + } +} + +void SplineModel::OnParentChanged() +{ + if (_spline) + { + _spline->SplineUpdated.Unbind(this); + } + + // Base + Actor::OnParentChanged(); + + _spline = Cast(_parent); + if (_spline) + { + _spline->SplineUpdated.Bind(this); + } + + OnSplineUpdated(); +} + +bool SplineModel::HasContentLoaded() const +{ + return (Model == nullptr || Model->IsLoaded()) && Entries.HasContentLoaded(); +} + +void SplineModel::Draw(RenderContext& renderContext) +{ + const DrawPass actorDrawModes = (DrawPass)(DrawModes & renderContext.View.Pass); + if (!_spline || !Model || !Model->IsLoaded() || !Model->CanBeRendered() || actorDrawModes == DrawPass::None) + return; + auto model = Model.Get(); + if (!Entries.IsValidFor(model)) + Entries.Setup(model); + + // Build mesh deformation buffer for the whole spline + if (_deformationDirty) + UpdateDeformationBuffer(); + + // Draw all segments + DrawCall drawCall; + drawCall.InstanceCount = 1; + drawCall.Deformable.SplineDeformation = _deformationBuffer; + drawCall.Deformable.ChunksPerSegment = _chunksPerSegment; + drawCall.Deformable.MeshMinZ = _meshMinZ; + drawCall.Deformable.MeshMaxZ = _meshMaxZ; + drawCall.Deformable.GeometrySize = _box.GetSize(); + drawCall.PerInstanceRandom = GetPerInstanceRandom(); + _preTransform.GetWorld(drawCall.Deformable.LocalMatrix); + const Transform splineTransform = GetTransform(); + splineTransform.GetWorld(drawCall.World); + drawCall.ObjectPosition = drawCall.World.GetTranslation() + drawCall.Deformable.LocalMatrix.GetTranslation(); + const float worldDeterminantSign = drawCall.World.RotDeterminant() * drawCall.Deformable.LocalMatrix.RotDeterminant(); + for (int32 segment = 0; segment < _instances.Count(); segment++) + { + auto& instance = _instances[segment]; + if (!renderContext.View.CullingFrustum.Intersects(instance.Sphere)) + continue; + drawCall.Deformable.Segment = (float)segment; + + // Select a proper LOD index (model may be culled) + int32 lodIndex; + if (_forcedLod != -1) + { + lodIndex = _forcedLod; + } + else + { + lodIndex = RenderTools::ComputeModelLOD(model, instance.Sphere.Center, instance.Sphere.Radius, renderContext); + if (lodIndex == -1) + continue; + } + lodIndex += _lodBias + renderContext.View.ModelLODBias; + lodIndex = model->ClampLODIndex(lodIndex); + + // Draw + const auto& lod = model->LODs[lodIndex]; + for (int32 i = 0; i < lod.Meshes.Count(); i++) + { + const auto mesh = &lod.Meshes[i]; + + // Cache data + const auto& entry = Entries[mesh->GetMaterialSlotIndex()]; + if (!entry.Visible || !mesh->IsInitialized()) + continue; + const MaterialSlot& slot = model->MaterialSlots[mesh->GetMaterialSlotIndex()]; + + // Check if skip rendering + const auto shadowsMode = static_cast(entry.ShadowsMode & slot.ShadowsMode); + const auto drawModes = static_cast(actorDrawModes & renderContext.View.GetShadowsDrawPassMask(shadowsMode)); + if (drawModes == DrawPass::None) + continue; + + // Select material + MaterialBase* material = nullptr; + if (entry.Material && entry.Material->IsLoaded()) + material = entry.Material; + else if (slot.Material && slot.Material->IsLoaded()) + material = slot.Material; + if (!material || !material->IsDeformable()) + material = GPUDevice::Instance->GetDefaultDeformableMaterial(); + if (!material || !material->IsDeformable()) + continue; + + // Submit draw call + mesh->GetDrawCallGeometry(drawCall); + drawCall.Material = material; + drawCall.WorldDeterminantSign = Math::FloatSelect(worldDeterminantSign * instance.RotDeterminant, 1, -1); + renderContext.List->AddDrawCall(drawModes, _staticFlags, drawCall, entry.ReceiveDecals); + } + } +} + +void SplineModel::DrawGeneric(RenderContext& renderContext) +{ + Draw(renderContext); +} + +bool SplineModel::IntersectsItself(const Ray& ray, float& distance, Vector3& normal) +{ + return false; +} + +void SplineModel::Serialize(SerializeStream& stream, const void* otherObj) +{ + // Base + ModelInstanceActor::Serialize(stream, otherObj); + + SERIALIZE_GET_OTHER_OBJ(SplineModel); + + SERIALIZE_MEMBER(Quality, _quality); + SERIALIZE_MEMBER(BoundsScale, _boundsScale); + SERIALIZE_MEMBER(LODBias, _lodBias); + SERIALIZE_MEMBER(ForcedLOD, _forcedLod); + SERIALIZE_MEMBER(PreTransform, _preTransform) + SERIALIZE(Model); + SERIALIZE(DrawModes); + + stream.JKEY("Buffer"); + stream.Object(&Entries, other ? &other->Entries : nullptr); +} + +void SplineModel::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) +{ + // Base + ModelInstanceActor::Deserialize(stream, modifier); + + DESERIALIZE_MEMBER(Quality, _quality); + DESERIALIZE_MEMBER(BoundsScale, _boundsScale); + DESERIALIZE_MEMBER(LODBias, _lodBias); + DESERIALIZE_MEMBER(ForcedLOD, _forcedLod); + DESERIALIZE_MEMBER(PreTransform, _preTransform); + DESERIALIZE(Model); + DESERIALIZE(DrawModes); + + Entries.DeserializeIfExists(stream, "Buffer", modifier); +} + +void SplineModel::OnTransformChanged() +{ + // Base + ModelInstanceActor::OnTransformChanged(); + + OnSplineUpdated(); +} diff --git a/Source/Engine/Level/Actors/SplineModel.h b/Source/Engine/Level/Actors/SplineModel.h new file mode 100644 index 000000000..6a0393cdc --- /dev/null +++ b/Source/Engine/Level/Actors/SplineModel.h @@ -0,0 +1,128 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "ModelInstanceActor.h" +#include "Engine/Content/Assets/Model.h" + +class Spline; + +/// +/// Renders model over the spline segments. +/// +API_CLASS() class FLAXENGINE_API SplineModel : public ModelInstanceActor +{ +DECLARE_SCENE_OBJECT(SplineModel); +private: + + struct Instance + { + BoundingSphere Sphere; + float RotDeterminant; + }; + + float _boundsScale = 1.0f, _quality = 1.0f; + char _lodBias = 0; + char _forcedLod = -1; + bool _deformationDirty = false; + Array _instances; + Transform _preTransform = Transform::Identity; + Spline* _spline = nullptr; + GPUBuffer* _deformationBuffer = nullptr; + void* _deformationBufferData = nullptr; + float _chunksPerSegment, _meshMinZ, _meshMaxZ; + +public: + + ~SplineModel(); + + /// + /// The model asset to draw. + /// + API_FIELD(Attributes="EditorOrder(20), DefaultValue(null), EditorDisplay(\"Model\")") + AssetReference Model; + + /// + /// Gets the transformation applied to the model geometry before placing it over the spline. Can be used to change the way model goes over the spline. + /// + API_PROPERTY(Attributes="EditorOrder(21), EditorDisplay(\"Model\")") + Transform GetPreTransform() const; + + /// + /// Sets the transformation applied to the model geometry before placing it over the spline. Can be used to change the way model goes over the spline. + /// + API_PROPERTY() void SetPreTransform(const Transform& value); + + /// + /// The draw passes to use for rendering this object. + /// + API_FIELD(Attributes="EditorOrder(15), DefaultValue(DrawPass.Default), EditorDisplay(\"Model\")") + DrawPass DrawModes = DrawPass::Default; + + /// + /// Gets the spline model quality scale. Higher values improve the spline representation (better tessellation) but reduce performance. + /// + API_PROPERTY(Attributes="EditorOrder(11), DefaultValue(1.0f), EditorDisplay(\"Model\"), Limit(0.1f, 100.0f, 0.1f)") + float GetQuality() const; + + /// + /// Sets the spline model quality scale. Higher values improve the spline representation (better tessellation) but reduce performance. + /// + API_PROPERTY() void SetQuality(float value); + + /// + /// Gets the model bounds scale. It is useful when using Position Offset to animate the vertices of the object outside of its bounds. + /// + API_PROPERTY(Attributes="EditorOrder(12), DefaultValue(1.0f), EditorDisplay(\"Model\"), Limit(0, 10.0f, 0.1f)") + float GetBoundsScale() const; + + /// + /// Sets the model bounds scale. It is useful when using Position Offset to animate the vertices of the object outside of its bounds. + /// + API_PROPERTY() void SetBoundsScale(float value); + + /// + /// Gets the model Level Of Detail bias value. Allows to increase or decrease rendered model quality. + /// + API_PROPERTY(Attributes="EditorOrder(40), DefaultValue(0), Limit(-100, 100, 0.1f), EditorDisplay(\"Model\", \"LOD Bias\")") + int32 GetLODBias() const; + + /// + /// Sets the model Level Of Detail bias value. Allows to increase or decrease rendered model quality. + /// + API_PROPERTY() void SetLODBias(int32 value); + + /// + /// Gets the model forced Level Of Detail index. Allows to bind the given model LOD to show. Value -1 disables this feature. + /// + API_PROPERTY(Attributes="EditorOrder(50), DefaultValue(-1), Limit(-1, 100, 0.1f), EditorDisplay(\"Model\", \"Forced LOD\")") + int32 GetForcedLOD() const; + + /// + /// Sets the model forced Level Of Detail index. Allows to bind the given model LOD to show. Value -1 disables this feature. + /// + API_PROPERTY() void SetForcedLOD(int32 value); + +private: + + void OnModelChanged(); + void OnModelLoaded(); + void OnSplineUpdated(); + void UpdateDeformationBuffer(); + +public: + + // [ModelInstanceActor] + bool HasContentLoaded() const override; + void Draw(RenderContext& renderContext) override; + void DrawGeneric(RenderContext& renderContext) override; + bool IntersectsItself(const Ray& ray, float& distance, Vector3& normal) override; + void Serialize(SerializeStream& stream, const void* otherObj) override; + void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; + void OnParentChanged() override; + +protected: + + // [ModelInstanceActor] + void OnTransformChanged() override; +}; diff --git a/Source/Engine/Level/Actors/StaticModel.cpp b/Source/Engine/Level/Actors/StaticModel.cpp index cba13a601..a884c9707 100644 --- a/Source/Engine/Level/Actors/StaticModel.cpp +++ b/Source/Engine/Level/Actors/StaticModel.cpp @@ -98,7 +98,7 @@ void StaticModel::SetVertexColor(int32 lodIndex, int32 meshIndex, int32 vertexIn { if (!Model || Model->WaitForLoaded()) { - LOG(Warning, "Cannot set vertex color if model is missing or faied to load."); + LOG(Warning, "Cannot set vertex color if model is missing or failed to load."); return; } diff --git a/Source/Engine/Level/Actors/StaticModel.h b/Source/Engine/Level/Actors/StaticModel.h index 5ca6e05e4..a255ba4ab 100644 --- a/Source/Engine/Level/Actors/StaticModel.h +++ b/Source/Engine/Level/Actors/StaticModel.h @@ -181,6 +181,7 @@ private: void OnModelChanged(); void OnModelLoaded(); + void UpdateBounds(); public: @@ -193,7 +194,6 @@ public: void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; bool IntersectsEntry(int32 entryIndex, const Ray& ray, float& distance, Vector3& normal) override; bool IntersectsEntry(const Ray& ray, float& distance, Vector3& normal, int32& entryIndex) override; - void UpdateBounds() override; protected: diff --git a/Source/Engine/Level/Level.Build.cs b/Source/Engine/Level/Level.Build.cs index 204ea9e55..8409226dd 100644 --- a/Source/Engine/Level/Level.Build.cs +++ b/Source/Engine/Level/Level.Build.cs @@ -21,7 +21,6 @@ public class Level : EngineModule options.PublicDependencies.Add("Scripting"); options.PublicDependencies.Add("Serialization"); - options.PublicDependencies.Add("Navigation"); if (options.Target.IsEditor) { diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index 18d9d4a52..ba4ce4014 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -9,6 +9,7 @@ #include "Engine/Core/Cache.h" #include "Engine/Core/Collections/CollectionPoolCache.h" #include "Engine/Core/ObjectsRemovalService.h" +#include "Engine/Core/Config/LayersTagsSettings.h" #include "Engine/Debug/Exceptions/ArgumentException.h" #include "Engine/Debug/Exceptions/ArgumentNullException.h" #include "Engine/Debug/Exceptions/InvalidOperationException.h" @@ -97,6 +98,7 @@ public: { } + bool Init() override; void Update() override; void LateUpdate() override; void FixedUpdate() override; @@ -124,6 +126,8 @@ Delegate Level::SceneUnloaded; Action Level::ScriptsReloadStart; Action Level::ScriptsReload; Action Level::ScriptsReloadEnd; +Array Level::Tags; +String Level::Layers[32]; bool LevelImpl::spawnActor(Actor* actor, Actor* parent) { @@ -158,6 +162,15 @@ bool LevelImpl::deleteActor(Actor* actor) return false; } +bool LevelService::Init() +{ + auto& settings = *LayersAndTagsSettings::Get(); + Level::Tags = settings.Tags; + for (int32 i = 0; i < ARRAY_COUNT(Level::Layers); i++) + Level::Layers[i] = settings.Layers[i]; + return false; +} + void LevelService::Update() { PROFILE_CPU(); @@ -642,6 +655,25 @@ void LevelImpl::CallSceneEvent(SceneEventType eventType, Scene* scene, Guid scen } } +int32 Level::GetOrAddTag(const StringView& tag) +{ + int32 index = Tags.Find(tag); + if (index == INVALID_INDEX) + { + index = Tags.Count(); + Tags.AddOne() = tag; + } + return index; +} + +int32 Level::GetNonEmptyLayerNamesCount() +{ + int32 result = 31; + while (result >= 0 && Layers[result].IsEmpty()) + result--; + return result + 1; +} + void Level::callActorEvent(ActorEventType eventType, Actor* a, Actor* b) { PROFILE_CPU(); @@ -961,7 +993,7 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, bool autoI } // Synchronize prefab instances (prefab may have new objects added or some removed so deserialized instances need to synchronize with it) - // TODO: resave and force sync scenes durign game cooking so this step could be skipped in game + // TODO: resave and force sync scenes during game cooking so this step could be skipped in game Scripting::ObjectsLookupIdMapping.Set(&modifier.Value->IdsMapping); SceneObjectsFactory::SynchronizePrefabInstances(*sceneObjects.Value, actorToRemovedObjectsData, modifier.Value); Scripting::ObjectsLookupIdMapping.Set(nullptr); @@ -973,7 +1005,7 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, bool autoI if (obj && obj->GetParent() == nullptr) { sceneObjects->At(i) = nullptr; - LOG(Warning, "Scene object {0} {1} has missing parent objct after scene load. Removing it.", obj->GetID(), obj->ToString()); + LOG(Warning, "Scene object {0} {1} has missing parent object after load. Removing it.", obj->GetID(), obj->ToString()); obj->DeleteObject(); } } @@ -1326,12 +1358,8 @@ Actor* Level::FindActor(const MClass* type) CHECK_RETURN(type, nullptr); Actor* result = nullptr; ScopeLock lock(ScenesLock); - for (int32 i = 0; result == nullptr && i < Scenes.Count(); i++) - { result = Scenes[i]->FindActor(type); - } - return result; } @@ -1340,25 +1368,57 @@ Script* Level::FindScript(const MClass* type) CHECK_RETURN(type, nullptr); Script* result = nullptr; ScopeLock lock(ScenesLock); - for (int32 i = 0; result == nullptr && i < Scenes.Count(); i++) - { result = Scenes[i]->FindScript(type); + return result; +} + +namespace +{ + void GetActors(const MClass* type, Actor* actor, Array& result) + { + if (actor->GetClass()->IsSubClassOf(type)) + result.Add(actor); + for (auto child : actor->Children) + GetActors(type, child, result); } + void GetScripts(const MClass* type, Actor* actor, Array& result) + { + for (auto script : actor->Scripts) + if (script->GetClass()->IsSubClassOf(type)) + result.Add(script); + for (auto child : actor->Children) + GetScripts(type, child, result); + } +} + +Array Level::GetActors(const MClass* type) +{ + Array result; + CHECK_RETURN(type, result); + ScopeLock lock(ScenesLock); + for (int32 i = 0; i < Scenes.Count(); i++) + ::GetActors(type, Scenes[i], result); + return result; +} + +Array Level::GetScripts(const MClass* type) +{ + Array result; + CHECK_RETURN(type, result); + ScopeLock lock(ScenesLock); + for (int32 i = 0; i < Scenes.Count(); i++) + ::GetScripts(type, Scenes[i], result); return result; } Scene* Level::FindScene(const Guid& id) { ScopeLock lock(ScenesLock); - for (int32 i = 0; i < Scenes.Count(); i++) - { if (Scenes[i]->GetID() == id) return Scenes[i]; - } - return nullptr; } diff --git a/Source/Engine/Level/Level.cs b/Source/Engine/Level/Level.cs index 6232d2e54..1eafe08d5 100644 --- a/Source/Engine/Level/Level.cs +++ b/Source/Engine/Level/Level.cs @@ -77,5 +77,33 @@ namespace FlaxEngine { return FindActor(id) as T; } + + /// + /// Finds all the scripts of the given type in all the loaded scenes. + /// + /// Type of the object. + /// Found scripts list. + public static T[] GetScripts() where T : Script + { + var scripts = GetScripts(typeof(T)); + var result = new T[scripts.Length]; + for (int i = 0; i < scripts.Length; i++) + result[i] = scripts[i] as T; + return result; + } + + /// + /// Finds all the actors of the given type in all the loaded scenes. + /// + /// Type of the object. + /// Found actors list. + public static T[] GetActors() where T : Actor + { + var actors = GetActors(typeof(T)); + var result = new T[actors.Length]; + for (int i = 0; i < actors.Length; i++) + result[i] = actors[i] as T; + return result; + } } } diff --git a/Source/Engine/Level/Level.h b/Source/Engine/Level/Level.h index b1ee090f4..64bef8d27 100644 --- a/Source/Engine/Level/Level.h +++ b/Source/Engine/Level/Level.h @@ -355,7 +355,7 @@ public: /// /// Actor instance if found, null otherwise. template - FORCE_INLINE T* FindActor() const + FORCE_INLINE static T* FindActor() { return (T*)FindActor(T::GetStaticClass()); } @@ -372,11 +372,25 @@ public: /// /// Script instance if found, null otherwise. template - FORCE_INLINE T* FindScript() const + FORCE_INLINE static T* FindScript() { return (T*)FindScript(T::GetStaticClass()); } + /// + /// Finds all the actors of the given type in all the loaded scenes. + /// + /// Type of the actor to search for. Includes any actors derived from the type. + /// Found actors list. + API_FUNCTION() static Array GetActors(const MClass* type); + + /// + /// Finds all the scripts of the given type in all the loaded scenes. + /// + /// Type of the script to search for. Includes any scripts derived from the type. + /// Found scripts list. + API_FUNCTION() static Array GetScripts(const MClass* type); + /// /// Tries to find scene with given ID. /// @@ -420,6 +434,31 @@ public: /// Output array with only parents static void ConstructParentActorsTreeList(const Array& input, Array& output); +public: + + /// + /// The tags names. + /// + static Array Tags; + + /// + /// The layers names. + /// + static String Layers[32]; + + /// + /// Gets or adds the tag (returns the tag index). + /// + /// The tag. + /// The tag index. + static int32 GetOrAddTag(const StringView& tag); + + /// + /// Gets the amount of non empty layer names (from the beginning, trims the last ones). + /// + /// The layers count. + static int32 GetNonEmptyLayerNamesCount(); + private: // Actor API diff --git a/Source/Engine/Level/Prefabs/Prefab.Apply.cpp b/Source/Engine/Level/Prefabs/Prefab.Apply.cpp index 84bd17962..c0029aa93 100644 --- a/Source/Engine/Level/Prefabs/Prefab.Apply.cpp +++ b/Source/Engine/Level/Prefabs/Prefab.Apply.cpp @@ -509,7 +509,7 @@ bool FindCyclicReferences(Actor* actor, const Guid& prefabRootId) bool Prefab::ApplyAll(Actor* targetActor) { - // TODO: use more cached dictionaries and other collections containers to prevent memory allocations during apply (optimize fo apply 10 times per second the same prefab on many changes in editor) + // TODO: use more cached dictionaries and other collections containers to prevent memory allocations during apply (optimize for apply 10 times per second the same prefab on many changes in editor) PROFILE_CPU(); const auto startTime = DateTime::NowUTC(); @@ -850,7 +850,11 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr } // Keep root unlinked - root->_parent = nullptr; + if (root->_parent) + { + root->_parent->Children.Remove(root); + root->_parent = nullptr; + } } // Link objects hierarchy diff --git a/Source/Engine/Level/Prefabs/PrefabManager.cpp b/Source/Engine/Level/Prefabs/PrefabManager.cpp index f1fbf4766..512d6e9b7 100644 --- a/Source/Engine/Level/Prefabs/PrefabManager.cpp +++ b/Source/Engine/Level/Prefabs/PrefabManager.cpp @@ -173,9 +173,16 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent, DictionaryAt(0); + if (!root) + { + LOG(Warning, "Failed to load prefab root object."); + return nullptr; + } // Prepare parent linkage for prefab root actor root->_parent = parent; + if (parent) + parent->Children.Add(root); // Link actors hierarchy for (int32 i = 0; i < sceneObjects->Count(); i++) @@ -221,7 +228,7 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent, DictionaryGetParent() == nullptr) { sceneObjects->At(i) = nullptr; - LOG(Warning, "Scene object {0} {1} has missing parent objct after prefab spawn. Removing it.", obj->GetID(), obj->ToString()); + LOG(Warning, "Scene object {0} {1} has missing parent object after load. Removing it.", obj->GetID(), obj->ToString()); obj->DeleteObject(); } } diff --git a/Source/Engine/Level/Scene/Lightmap.cpp b/Source/Engine/Level/Scene/Lightmap.cpp index b55b603f5..4a2eec796 100644 --- a/Source/Engine/Level/Scene/Lightmap.cpp +++ b/Source/Engine/Level/Scene/Lightmap.cpp @@ -79,7 +79,7 @@ void Lightmap::EnsureSize(int32 size) { // Unlink texture that cannot be loaded LOG(Warning, "Lightmap::EnsureSize failed to load texture"); - texture.Unlink(); + texture = nullptr; } else { @@ -88,7 +88,7 @@ void Lightmap::EnsureSize(int32 size) { // Unlink texture and import new with valid size LOG(Info, "Changing lightmap {0}:{1} size from {2} to {3}", _index, textureIndex, texture->GetTexture()->Size(), size); - texture.Unlink(); + texture = nullptr; } } } diff --git a/Source/Engine/Level/Scene/Scene.cpp b/Source/Engine/Level/Scene/Scene.cpp index 2d48b9f14..d38bd4225 100644 --- a/Source/Engine/Level/Scene/Scene.cpp +++ b/Source/Engine/Level/Scene/Scene.cpp @@ -8,7 +8,10 @@ #include "Engine/Physics/Colliders/MeshCollider.h" #include "Engine/Level/Actors/StaticModel.h" #include "Engine/Level/ActorsCache.h" -#include "Engine/Navigation/NavigationScene.h" +#include "Engine/Navigation/NavigationSettings.h" +#include "Engine/Navigation/NavMeshBoundsVolume.h" +#include "Engine/Navigation/NavMesh.h" +#include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Serialization/Serialization.h" REGISTER_JSON_ASSET(SceneAsset, "FlaxEngine.SceneAsset"); @@ -27,7 +30,6 @@ Scene::Scene(const SpawnParams& params) , Ticking(this) , LightmapsData(this) , CSGData(this) - , Navigation(::New(this)) { // Default name _name = TEXT("Scene"); @@ -42,7 +44,6 @@ Scene::Scene(const SpawnParams& params) Scene::~Scene() { - Delete(Navigation); } LightmapSettings Scene::GetLightmapSettings() const @@ -55,6 +56,31 @@ void Scene::SetLightmapSettings(const LightmapSettings& value) Info.LightmapSettings = value; } +BoundingBox Scene::GetNavigationBounds() +{ + if (NavigationVolumes.IsEmpty()) + return BoundingBox::Empty; + PROFILE_CPU_NAMED("GetNavigationBounds"); + auto box = NavigationVolumes[0]->GetBox(); + for (int32 i = 1; i < NavigationVolumes.Count(); i++) + BoundingBox::Merge(box, NavigationVolumes[i]->GetBox(), box); + return box; +} + +NavMeshBoundsVolume* Scene::FindNavigationBoundsOverlap(const BoundingBox& bounds) +{ + NavMeshBoundsVolume* result = nullptr; + for (int32 i = 0; i < NavigationVolumes.Count(); i++) + { + if (NavigationVolumes[i]->GetBox().Intersects(bounds)) + { + result = NavigationVolumes[i]; + break; + } + } + return result; +} + void Scene::ClearLightmaps() { LightmapsData.ClearLightmaps(); @@ -216,12 +242,6 @@ void Scene::Serialize(SerializeStream& stream, const void* otherObj) // Update scene info object SaveTime = DateTime::NowUTC(); -#if USE_EDITOR - // Save navmesh tiles to asset (if modified) - if (Navigation->IsDataDirty) - Navigation->SaveNavMesh(); -#endif - LightmapsData.SaveLightmaps(Info.Lightmaps); Info.Serialize(stream, other ? &other->Info : nullptr); @@ -230,8 +250,6 @@ void Scene::Serialize(SerializeStream& stream, const void* otherObj) stream.JKEY("CSG"); stream.Object(&CSGData, other ? &other->CSGData : nullptr); } - - SERIALIZE_MEMBER(NavMesh, Navigation->DataAsset); } void Scene::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) @@ -243,15 +261,45 @@ void Scene::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) LightmapsData.LoadLightmaps(Info.Lightmaps); CSGData.DeserializeIfExists(stream, "CSG", modifier); - DESERIALIZE_MEMBER(NavMesh, Navigation->DataAsset); + // [Deprecated on 13.01.2021, expires on 13.01.2023] + if (modifier->EngineBuild <= 6215 && NavigationMeshes.IsEmpty()) + { + const auto e = SERIALIZE_FIND_MEMBER(stream, "NavMesh"); + if (e != stream.MemberEnd()) + { + // Upgrade from old single hidden navmesh data into NavMesh actors on a scene + AssetReference dataAsset; + Serialization::Deserialize(e->value, dataAsset, modifier); + const auto settings = NavigationSettings::Get(); + if (dataAsset && settings->NavMeshes.HasItems()) + { + auto navMesh = New(); + navMesh->SetStaticFlags(StaticFlags::FullyStatic); + navMesh->SetName(TEXT("NavMesh.") + settings->NavMeshes[0].Name); + navMesh->DataAsset = dataAsset; + navMesh->Properties = settings->NavMeshes[0]; + if (IsDuringPlay()) + { + navMesh->SetParent(this, false); + } + else + { + navMesh->_parent = this; + navMesh->_scene = this; + Children.Add(navMesh); + navMesh->CreateManaged(); + } + } + } + } } void Scene::OnDeleteObject() { // Cleanup LightmapsData.UnloadLightmaps(); - CSGData.Model.Unlink(); - CSGData.CollisionData.Unlink(); + CSGData.Model = nullptr; + CSGData.CollisionData = nullptr; // Base Actor::OnDeleteObject(); @@ -309,22 +357,6 @@ void Scene::EndPlay() Actor::EndPlay(); } -void Scene::OnEnable() -{ - // Base - Actor::OnEnable(); - - Navigation->OnEnable(); -} - -void Scene::OnDisable() -{ - Navigation->OnDisable(); - - // Base - Actor::OnDisable(); -} - void Scene::OnTransformChanged() { // Base diff --git a/Source/Engine/Level/Scene/Scene.h b/Source/Engine/Level/Scene/Scene.h index 628726e4b..d3c1502c6 100644 --- a/Source/Engine/Level/Scene/Scene.h +++ b/Source/Engine/Level/Scene/Scene.h @@ -12,8 +12,9 @@ class MeshCollider; class Level; -class NavigationScene; class ReloadScriptsAction; +class NavMeshBoundsVolume; +class NavMesh; /// /// The scene root object that contains a hierarchy of actors. @@ -69,11 +70,6 @@ public: /// CSG::SceneCSGData CSGData; - /// - /// The navigation scene (always valid). - /// - NavigationScene* Navigation; - /// /// Gets the lightmap settings (per scene). /// @@ -85,6 +81,31 @@ public: /// API_PROPERTY() void SetLightmapSettings(const LightmapSettings& value); +public: + + /// + /// The list of registered navigation bounds volumes (in the scene). + /// + Array NavigationVolumes; + + /// + /// The list of registered navigation meshes (in the scene). + /// + Array NavigationMeshes; + + /// + /// Gets the total navigation volumes bounds. + /// + /// The navmesh bounds. + BoundingBox GetNavigationBounds(); + + /// + /// Finds the navigation volume bounds that have intersection with the given world-space bounding box. + /// + /// The bounds. + /// The intersecting volume or null if none found. + NavMeshBoundsVolume* FindNavigationBoundsOverlap(const BoundingBox& bounds); + public: /// @@ -148,12 +169,10 @@ public: protected: - // [Scene] + // [Actor] void PostLoad() override; void PostSpawn() override; void BeginPlay(SceneBeginData* data) override; - void OnEnable() override; - void OnDisable() override; void OnTransformChanged() override; }; diff --git a/Source/Engine/Level/SceneObjectsFactory.cpp b/Source/Engine/Level/SceneObjectsFactory.cpp index 30cd21abc..a5cb27712 100644 --- a/Source/Engine/Level/SceneObjectsFactory.cpp +++ b/Source/Engine/Level/SceneObjectsFactory.cpp @@ -79,7 +79,7 @@ SceneObject* SceneObjectsFactory::Spawn(ISerializable::DeserializeStream& stream if (type) { const ScriptingObjectSpawnParams params(id, type); - obj = (SceneObject*)type.GetType().Class.Spawn(params); + obj = (SceneObject*)type.GetType().Script.Spawn(params); if (obj == nullptr) { LOG(Warning, "Failed to spawn object of type {0}.", type.ToString(true)); @@ -109,7 +109,7 @@ SceneObject* SceneObjectsFactory::Spawn(ISerializable::DeserializeStream& stream if (type) { const ScriptingObjectSpawnParams params(id, type); - obj = (SceneObject*)type.GetType().Class.Spawn(params); + obj = (SceneObject*)type.GetType().Script.Spawn(params); if (obj == nullptr) { LOG(Warning, "Failed to spawn object of type {0}.", type.ToString(true)); @@ -384,7 +384,7 @@ Actor* SceneObjectsFactory::CreateActor(int32 typeId, const Guid& id) if (type) { const ScriptingObjectSpawnParams params(id, type); - const auto result = dynamic_cast(type.GetType().Class.Spawn(params)); + const auto result = dynamic_cast(type.GetType().Script.Spawn(params)); if (result == nullptr) { LOG(Warning, "Failed to spawn object of type {0}.", type.ToString(true)); diff --git a/Source/Engine/Level/SceneQuery.h b/Source/Engine/Level/SceneQuery.h index 4dedf0b8d..245c4c8a7 100644 --- a/Source/Engine/Level/SceneQuery.h +++ b/Source/Engine/Level/SceneQuery.h @@ -3,10 +3,10 @@ #pragma once // Enables locking scenes during scene query execution can provide some safety when using scene queries from other threads but may provide stalls on a main thread -#define SCENE_QUERIES_WITH_LOCK 0 +#define SCENE_QUERIES_WITH_LOCK 1 -#include "Scene/Scene.h" #include "Level.h" +#include "Scene/Scene.h" #if SCENE_QUERIES_WITH_LOCK #include "Engine/Threading/Threading.h" #endif diff --git a/Source/Engine/Level/Spline.cs b/Source/Engine/Level/Spline.cs new file mode 100644 index 000000000..29c65763a --- /dev/null +++ b/Source/Engine/Level/Spline.cs @@ -0,0 +1,63 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System; +using System.Runtime.InteropServices; + +namespace FlaxEngine +{ + partial class Spline + { + private BezierCurve.Keyframe[] _keyframes; + + /// + /// Gets or sets the spline keyframes collection. + /// + [Unmanaged] + [Tooltip("Spline keyframes collection.")] + [EditorOrder(10), EditorDisplay("Spline"), Collection(CanReorderItems = false)] + public BezierCurve.Keyframe[] SplineKeyframes + { + get + { + var count = SplinePointsCount; + if (_keyframes == null || _keyframes.Length != count) + _keyframes = new BezierCurve.Keyframe[count]; +#if !BUILD_RELEASE + if (Marshal.SizeOf(typeof(BezierCurve.Keyframe)) != Transform.SizeInBytes * 3 + sizeof(float)) + throw new Exception("Invalid size of BezierCurve keyframe " + Marshal.SizeOf(typeof(BezierCurve.Keyframe)) + " bytes."); +#endif + Internal_GetKeyframes(__unmanagedPtr, _keyframes); + return _keyframes; + } + set + { + if (value == null) + value = Utils.GetEmptyArray.Keyframe>(); + _keyframes = null; + Internal_SetKeyframes(__unmanagedPtr, value); + } + } + + /// + /// Gets the spline keyframe. + /// + /// The spline point index. + /// The keyframe. + public BezierCurve.Keyframe GetSplineKeyframe(int index) + { + return SplineKeyframes[index]; + } + + /// + /// Sets the spline keyframe. + /// + /// The spline point index. + /// The keyframe. + public void SetSplineKeyframe(int index, BezierCurve.Keyframe keyframe) + { + SetSplineLocalTransform(index, keyframe.Value, false); + SetSplineLocalTangent(index, keyframe.Value + keyframe.TangentIn, true, false); + SetSplineLocalTangent(index, keyframe.Value + keyframe.TangentOut, false); + } + } +} diff --git a/Source/Engine/Navigation/NavLink.cpp b/Source/Engine/Navigation/NavLink.cpp index 121bd499f..e0ba84821 100644 --- a/Source/Engine/Navigation/NavLink.cpp +++ b/Source/Engine/Navigation/NavLink.cpp @@ -14,7 +14,6 @@ NavLink::NavLink(const SpawnParams& params) void NavLink::UpdateBounds() { - // Cache bounds const auto start = _transform.LocalToWorld(Start); const auto end = _transform.LocalToWorld(End); BoundingBox::FromPoints(start, end, _box); diff --git a/Source/Engine/Navigation/NavMesh.cpp b/Source/Engine/Navigation/NavMesh.cpp index 406aada30..fe7756049 100644 --- a/Source/Engine/Navigation/NavMesh.cpp +++ b/Source/Engine/Navigation/NavMesh.cpp @@ -1,340 +1,169 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "NavMesh.h" -#include "Engine/Core/Log.h" -#include "NavigationScene.h" -#include "Engine/Profiler/ProfilerCPU.h" -#include "Engine/Threading/Threading.h" -#include -#include - -#define MAX_NODES 2048 -#define USE_DATA_LINK 0 -#define USE_NAV_MESH_ALLOC 1 - -NavMesh::NavMesh() -{ - _navMesh = nullptr; - _navMeshQuery = dtAllocNavMeshQuery(); - _tileSize = 0; -} - -NavMesh::~NavMesh() -{ - dtFreeNavMesh(_navMesh); - dtFreeNavMeshQuery(_navMeshQuery); -} - -int32 NavMesh::GetTilesCapacity() const -{ - return _navMesh ? _navMesh->getMaxTiles() : 0; -} - -void NavMesh::SetTileSize(float tileSize) -{ - ScopeLock lock(Locker); - - // Skip if the same or invalid - if (Math::NearEqual(_tileSize, tileSize) || tileSize < 1) - return; - - // Dispose the existing mesh (its invalid) - if (_navMesh) - { - dtFreeNavMesh(_navMesh); - _navMesh = nullptr; - _tiles.Clear(); - } - - _tileSize = tileSize; -} - -void NavMesh::EnsureCapacity(int32 tilesToAddCount) -{ - ScopeLock lock(Locker); - - const int32 newTilesCount = _tiles.Count() + tilesToAddCount; - const int32 capacity = GetTilesCapacity(); - if (newTilesCount <= capacity) - return; - - PROFILE_CPU_NAMED("NavMesh.EnsureCapacity"); - - // Navmesh tiles capacity growing rule - int32 newCapacity = 0; - if (capacity) - { - while (newCapacity < newTilesCount) - { - newCapacity = Math::RoundUpToPowerOf2(newCapacity); - } - } - else - { - newCapacity = 32; - } - - LOG(Info, "Resizing navmesh from {0} to {1} tiles capacity", capacity, newCapacity); - - // Ensure to have size assigned - ASSERT(_tileSize != 0); - - // Fre previous data (if any) - if (_navMesh) - { - dtFreeNavMesh(_navMesh); - } - - // Allocate new navmesh - _navMesh = dtAllocNavMesh(); - if (dtStatusFailed(_navMeshQuery->init(_navMesh, MAX_NODES))) - { - LOG(Fatal, "Failed to initialize nav mesh."); - } - - // Prepare parameters - dtNavMeshParams params; - params.orig[0] = 0.0f; - params.orig[1] = 0.0f; - params.orig[2] = 0.0f; - params.tileWidth = _tileSize; - params.tileHeight = _tileSize; - params.maxTiles = newCapacity; - const int32 tilesBits = (int32)Math::Log2((float)Math::RoundUpToPowerOf2(params.maxTiles)); - params.maxPolys = 1 << (22 - tilesBits); - - // Initialize nav mesh - if (dtStatusFailed(_navMesh->init(¶ms))) - { - LOG(Fatal, "Navmesh init failed."); - return; - } - - // Prepare tiles container - _tiles.EnsureCapacity(newCapacity); - - // Restore previous tiles - for (auto& tile : _tiles) - { - const int32 dataSize = tile.Data.Length(); -#if USE_NAV_MESH_ALLOC - const auto flags = DT_TILE_FREE_DATA; - const auto data = (byte*)dtAlloc(dataSize, DT_ALLOC_PERM); - Platform::MemoryCopy(data, tile.Data.Get(), dataSize); -#else - const auto flags = 0; - const auto data = tile.Data.Get(); +#include "NavMeshRuntime.h" +#include "Engine/Level/Scene/Scene.h" +#include "Engine/Serialization/Serialization.h" +#if COMPILE_WITH_ASSETS_IMPORTER +#include "Engine/ContentImporters/AssetsImportingManager.h" +#include "Engine/Serialization/MemoryWriteStream.h" +#if USE_EDITOR +#include "Editor/Editor.h" #endif - if (dtStatusFailed(_navMesh->addTile(data, dataSize, flags, 0, nullptr))) - { - LOG(Warning, "Could not add tile to navmesh."); - } - } -} - -void NavMesh::AddTiles(NavigationScene* scene) -{ - // Skip if no data - ASSERT(scene); - if (scene->Data.Tiles.IsEmpty()) - return; - auto& data = scene->Data; - - PROFILE_CPU_NAMED("NavMesh.AddTiles"); - - ScopeLock lock(Locker); - - // Validate data (must match navmesh) or init navmesh to match the tiles options - if (_navMesh) - { - if (Math::NotNearEqual(data.TileSize, _tileSize)) - { - LOG(Warning, "Cannot add navigation scene tiles to the navmesh. Navmesh tile size: {0}, input tiles size: {1}", _tileSize, data.TileSize); - return; - } - } - else - { - _tileSize = data.TileSize; - } - - // Ensure to have space for new tiles - EnsureCapacity(data.Tiles.Count()); - - // Add new tiles - for (auto& tileData : data.Tiles) - { - AddTileInternal(scene, tileData); - } -} - -void NavMesh::AddTile(NavigationScene* scene, NavMeshTileData& tileData) -{ - ASSERT(scene); - auto& data = scene->Data; - - PROFILE_CPU_NAMED("NavMesh.AddTile"); - - ScopeLock lock(Locker); - - // Validate data (must match navmesh) or init navmesh to match the tiles options - if (_navMesh) - { - if (Math::NotNearEqual(data.TileSize, _tileSize)) - { - LOG(Warning, "Cannot add navigation scene tile to the navmesh. Navmesh tile size: {0}, input tile size: {1}", _tileSize, data.TileSize); - return; - } - } - else - { - _tileSize = data.TileSize; - } - - // Ensure to have space for new tile - EnsureCapacity(1); - - // Add new tile - AddTileInternal(scene, tileData); -} - -bool IsTileFromScene(const NavMesh* navMesh, const NavMeshTile& tile, void* customData) -{ - return tile.Scene == (NavigationScene*)customData; -} - -void NavMesh::RemoveTiles(NavigationScene* scene) -{ - RemoveTiles(IsTileFromScene, scene); -} - -void NavMesh::RemoveTile(int32 x, int32 y, int32 layer) -{ - ScopeLock lock(Locker); - - // Skip if no data - if (!_navMesh) - return; - - PROFILE_CPU_NAMED("NavMesh.RemoveTile"); - - const auto tileRef = _navMesh->getTileRefAt(x, y, layer); - if (tileRef == 0) - { - return; - } - - if (dtStatusFailed(_navMesh->removeTile(tileRef, nullptr, nullptr))) - { - LOG(Warning, "Failed to remove tile from navmesh."); - } - - for (int32 i = 0; i < _tiles.Count(); i++) - { - auto& tile = _tiles[i]; - if (tile.X == x && tile.Y == y && tile.Layer == layer) - { - _tiles.RemoveAt(i); - break; - } - } -} - -void NavMesh::RemoveTiles(bool (* prediction)(const NavMesh* navMesh, const NavMeshTile& tile, void* customData), void* userData) -{ - ScopeLock lock(Locker); - - // Skip if no data - ASSERT(prediction); - if (!_navMesh) - return; - - PROFILE_CPU_NAMED("NavMesh.RemoveTiles"); - - for (int32 i = 0; i < _tiles.Count(); i++) - { - auto& tile = _tiles[i]; - if (prediction(this, tile, userData)) - { - const auto tileRef = _navMesh->getTileRefAt(tile.X, tile.Y, tile.Layer); - if (tileRef == 0) - { - LOG(Warning, "Missing navmesh tile at {0}x{1}, layer: {2}", tile.X, tile.Y, tile.Layer); - } - else - { - if (dtStatusFailed(_navMesh->removeTile(tileRef, nullptr, nullptr))) - { - LOG(Warning, "Failed to remove tile from navmesh."); - } - } - - _tiles.RemoveAt(i--); - } - } -} - -void NavMesh::Dispose() -{ - if (_navMesh) - { - dtFreeNavMesh(_navMesh); - _navMesh = nullptr; - } - _tiles.Resize(0); -} - -void NavMesh::AddTileInternal(NavigationScene* scene, NavMeshTileData& tileData) -{ - // Check if that tile has been added to navmesh - NavMeshTile* tile = nullptr; - const auto tileRef = _navMesh->getTileRefAt(tileData.PosX, tileData.PosY, tileData.Layer); - if (tileRef) - { - // Remove any existing tile at that location - _navMesh->removeTile(tileRef, nullptr, nullptr); - - // Reuse tile data container - for (int32 i = 0; i < _tiles.Count(); i++) - { - auto& e = _tiles[i]; - if (e.X == tileData.PosX && e.Y == tileData.PosY && e.Layer == tileData.Layer) - { - tile = &e; - break; - } - } - } - else - { - // Add tile - tile = &_tiles.AddOne(); - } - ASSERT(tile); - - // Copy tile properties - tile->Scene = scene; - tile->X = tileData.PosX; - tile->Y = tileData.PosY; - tile->Layer = tileData.Layer; -#if USE_DATA_LINK - tile->Data.Link(tileData.Data); -#else - tile->Data.Copy(tileData.Data); #endif - // Add tile to navmesh - const int32 dataSize = tile->Data.Length(); -#if USE_NAV_MESH_ALLOC - const auto flags = DT_TILE_FREE_DATA; - const auto data = (byte*)dtAlloc(dataSize, DT_ALLOC_PERM); - Platform::MemoryCopy(data, tile->Data.Get(), dataSize); -#else - const auto flags = 0; - const auto data = tile->Data.Get(); +NavMesh::NavMesh(const SpawnParams& params) + : Actor(params) + , IsDataDirty(false) +{ + DataAsset.Loaded.Bind(this); +} + +void NavMesh::SaveNavMesh() +{ +#if COMPILE_WITH_ASSETS_IMPORTER + + // Skip if scene is missing + const auto scene = GetScene(); + if (!scene) + return; + +#if USE_EDITOR + // Skip if game is running in editor (eg. game scripts update dynamic navmesh) + if (Editor::IsPlayMode) + return; #endif - if (dtStatusFailed(_navMesh->addTile(data, dataSize, flags, 0, nullptr))) + + // Clear flag + IsDataDirty = false; + + // Check if has no navmesh data generated (someone could just remove navmesh volumes or generate for empty scene) + if (Data.Tiles.IsEmpty()) { - LOG(Warning, "Could not add tile to navmesh."); + DataAsset = nullptr; + return; + } + + // Prepare + Guid assetId = DataAsset.GetID(); + if (!assetId.IsValid()) + assetId = Guid::New(); + const String assetPath = scene->GetDataFolderPath() / TEXT("NavMesh") + Properties.Name + ASSET_FILES_EXTENSION_WITH_DOT; + + // Generate navmesh tiles data + const int32 streamInitialCapacity = Math::RoundUpToPowerOf2((Data.Tiles.Count() + 1) * 1024); + MemoryWriteStream stream(streamInitialCapacity); + Data.Save(stream); + BytesContainer bytesContainer; + bytesContainer.Link(stream.GetHandle(), stream.GetPosition()); + + // Save asset to file + if (AssetsImportingManager::Create(AssetsImportingManager::CreateRawDataTag, assetPath, assetId, (void*)&bytesContainer)) + { + LOG(Warning, "Failed to save navmesh tiles data to file."); + return; + } + + // Link the created asset + DataAsset = assetId; + +#endif +} + +void NavMesh::ClearData() +{ + if (Data.Tiles.HasItems()) + { + IsDataDirty = true; + Data.TileSize = 0.0f; + Data.Tiles.Resize(0); } } + +NavMeshRuntime* NavMesh::GetRuntime(bool createIfMissing) const +{ + return NavMeshRuntime::Get(Properties, createIfMissing); +} + +void NavMesh::AddTiles() +{ + auto navMesh = NavMeshRuntime::Get(Properties, true); + navMesh->AddTiles(this); +} + +void NavMesh::RemoveTiles() +{ + auto navMesh = NavMeshRuntime::Get(Properties, false); + if (navMesh) + navMesh->RemoveTiles(this); +} + +void NavMesh::OnDataAssetLoaded() +{ + // Skip if already has data (prevent reloading navmesh on saving) + if (Data.Tiles.HasItems()) + return; + + const bool isEnabled = IsDuringPlay() && IsActiveInHierarchy(); + + // Remove added tiles + if (isEnabled) + { + RemoveTiles(); + } + + // Load navmesh tiles + BytesContainer data; + data.Link(DataAsset->Data); + Data.Load(data, false); + IsDataDirty = false; + + // Add loaded tiles + if (isEnabled) + { + AddTiles(); + } +} + +void NavMesh::Serialize(SerializeStream& stream, const void* otherObj) +{ + // Base + Actor::Serialize(stream, otherObj); + +#if USE_EDITOR + // Save navmesh tiles to asset (if modified) + if (IsDataDirty) + SaveNavMesh(); +#endif + + SERIALIZE_GET_OTHER_OBJ(NavMesh); + SERIALIZE(DataAsset); + SERIALIZE(Properties); +} + +void NavMesh::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) +{ + // Base + Actor::Deserialize(stream, modifier); + + DESERIALIZE(DataAsset); + DESERIALIZE(Properties); +} + +void NavMesh::OnEnable() +{ + // Base + Actor::OnEnable(); + + GetScene()->NavigationMeshes.Add(this); + AddTiles(); +} + +void NavMesh::OnDisable() +{ + RemoveTiles(); + GetScene()->NavigationMeshes.Remove(this); + + // Base + Actor::OnDisable(); +} diff --git a/Source/Engine/Navigation/NavMesh.h b/Source/Engine/Navigation/NavMesh.h index cdce2cf6b..c9096d323 100644 --- a/Source/Engine/Navigation/NavMesh.h +++ b/Source/Engine/Navigation/NavMesh.h @@ -2,122 +2,84 @@ #pragma once -#include "Engine/Core/Types/BaseTypes.h" -#include "Engine/Platform/CriticalSection.h" #include "NavMeshData.h" +#include "NavigationTypes.h" +#include "Engine/Content/AssetReference.h" +#include "Engine/Content/Assets/RawDataAsset.h" +#include "Engine/Level/Actor.h" -class NavigationScene; -class dtNavMesh; -class dtNavMeshQuery; +class NavMeshBoundsVolume; +class NavMeshRuntime; -class NavMeshTile +/// +/// The navigation mesh actor that holds a navigation data for a scene. +/// +API_CLASS() class FLAXENGINE_API NavMesh : public Actor { +DECLARE_SCENE_OBJECT(NavMesh); public: - int32 X; - int32 Y; - int32 Layer; - NavigationScene* Scene; - BytesContainer Data; -}; + /// + /// The flag used to mark that navigation data has been modified since load. Used to save runtime data to the file on scene serialization. + /// + bool IsDataDirty; -class FLAXENGINE_API NavMesh -{ -private: + /// + /// The navmesh tiles data. + /// + NavMeshData Data; - dtNavMesh* _navMesh; - dtNavMeshQuery* _navMeshQuery; - float _tileSize; - Array _tiles; + /// + /// The cached navmesh data asset. + /// + AssetReference DataAsset; -public: +#if USE_EDITOR - NavMesh(); + /// + /// If checked, the navmesh will be drawn in debug view when showing navigation data. + /// + API_FIELD(Attributes="EditorOrder(1), EditorDisplay(\"Nav Mesh\")") bool ShowDebugDraw = true; - ~NavMesh(); +#endif + + /// + /// The navigation mesh properties. + /// + API_FIELD(Attributes="EditorOrder(10), EditorDisplay(\"Nav Mesh\")") NavMeshProperties Properties; public: /// - /// The NavMesh object locker. + /// Saves the nav mesh tiles data to the asset. Supported only in builds with assets saving enabled (eg. editor) and not during gameplay (eg. design time). /// - CriticalSection Locker; + void SaveNavMesh(); /// - /// Gets the size of the tile (in world-units). Returns zero if not initialized yet. + /// Clears the data. /// - /// The tile size. - FORCE_INLINE float GetTileSize() const - { - return _tileSize; - } - - dtNavMesh* GetNavMesh() const - { - return _navMesh; - } - - dtNavMeshQuery* GetNavMeshQuery() const - { - return _navMeshQuery; - } - - int32 GetTilesCapacity() const; - -public: + void ClearData(); /// - /// Sets the size of the tile (if not assigned). Disposes the mesh if added tiles have different size. + /// Gets the navmesh runtime object that matches with properties. /// - /// The size of the tile. - void SetTileSize(float tileSize); - - /// - /// Ensures the navmesh capacity for adding new tiles. Performs resizing if needed. - /// - /// The new tiles amount. - void EnsureCapacity(int32 tilesToAddCount); - - /// - /// Adds the tiles from the given scene to the runtime navmesh. - /// - /// The navigation scene. - void AddTiles(NavigationScene* scene); - - /// - /// Adds the tile from the given scene to the runtime navmesh. - /// - /// The navigation scene. - /// The tile data. - void AddTile(NavigationScene* scene, NavMeshTileData& tileData); - - /// - /// Removes all the tiles from the navmesh that has been added from the given navigation scene. - /// - /// The scene. - void RemoveTiles(NavigationScene* scene); - - /// - /// Removes the tile from the navmesh. - /// - /// The tile X coordinate. - /// The tile Y coordinate. - /// The tile layer. - void RemoveTile(int32 x, int32 y, int32 layer); - - /// - /// Removes all the tiles that custom prediction callback marks. - /// - /// The prediction callback, returns true for tiles to remove and false for tiles to preserve. - /// The user data passed to the callback method. - void RemoveTiles(bool (*prediction)(const NavMesh* navMesh, const NavMeshTile& tile, void* customData), void* userData); - - /// - /// Releases the navmesh. - /// - void Dispose(); + NavMeshRuntime* GetRuntime(bool createIfMissing = true) const; private: - void AddTileInternal(NavigationScene* scene, NavMeshTileData& tileData); + void AddTiles(); + void RemoveTiles(); + void OnDataAssetLoaded(); + +public: + + // [Actor] + void Serialize(SerializeStream& stream, const void* otherObj) override; + void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; + +protected: + + // [Actor] + void OnEnable() override; + void OnDisable() override; }; diff --git a/Source/Engine/Navigation/NavMeshBoundsVolume.cpp b/Source/Engine/Navigation/NavMeshBoundsVolume.cpp index 34a881ede..700442b2f 100644 --- a/Source/Engine/Navigation/NavMeshBoundsVolume.cpp +++ b/Source/Engine/Navigation/NavMeshBoundsVolume.cpp @@ -2,7 +2,7 @@ #include "NavMeshBoundsVolume.h" #include "Engine/Level/Scene/Scene.h" -#include "NavigationScene.h" +#include "Engine/Serialization/Serialization.h" #if USE_EDITOR #include "Editor/Editor.h" #include "Editor/Managed/ManagedEditor.h" @@ -14,17 +14,35 @@ NavMeshBoundsVolume::NavMeshBoundsVolume(const SpawnParams& params) { } +void NavMeshBoundsVolume::Serialize(SerializeStream& stream, const void* otherObj) +{ + // Base + BoxVolume::Serialize(stream, otherObj); + + SERIALIZE_GET_OTHER_OBJ(NavMeshBoundsVolume); + + SERIALIZE_MEMBER(AgentsMask, AgentsMask.Mask); +} + +void NavMeshBoundsVolume::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) +{ + // Base + BoxVolume::Deserialize(stream, modifier); + + DESERIALIZE_MEMBER(AgentsMask, AgentsMask.Mask); +} + void NavMeshBoundsVolume::OnEnable() { - GetScene()->Navigation->Volumes.Add(this); - // Base Actor::OnEnable(); + + GetScene()->NavigationVolumes.Add(this); } void NavMeshBoundsVolume::OnDisable() { - GetScene()->Navigation->Volumes.Remove(this); + GetScene()->NavigationVolumes.Remove(this); // Base Actor::OnDisable(); diff --git a/Source/Engine/Navigation/NavMeshBoundsVolume.h b/Source/Engine/Navigation/NavMeshBoundsVolume.h index 2846f9e4f..674ba4877 100644 --- a/Source/Engine/Navigation/NavMeshBoundsVolume.h +++ b/Source/Engine/Navigation/NavMeshBoundsVolume.h @@ -3,13 +3,28 @@ #pragma once #include "Engine/Level/Actors/BoxVolume.h" +#include "NavigationTypes.h" /// -/// A special type of volume that defines the areas of the scene in which navigation meshes are generated. +/// A special type of volume that defines the area of the scene in which navigation meshes are generated. /// API_CLASS() class FLAXENGINE_API NavMeshBoundsVolume : public BoxVolume { DECLARE_SCENE_OBJECT(NavMeshBoundsVolume); +public: + + /// + /// The agent types used by this navmesh bounds volume (from navigation settings). Can be used to generate navmesh for a certain set of agents. + /// + API_FIELD(Attributes="EditorDisplay(\"Box Volume\"), EditorOrder(10)") + NavAgentMask AgentsMask; + +public: + + // [BoxVolume] + void Serialize(SerializeStream& stream, const void* otherObj) override; + void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; + protected: // [BoxVolume] diff --git a/Source/Engine/Navigation/NavMeshBuilder.cpp b/Source/Engine/Navigation/NavMeshBuilder.cpp index b8e9a7544..55319bb9e 100644 --- a/Source/Engine/Navigation/NavMeshBuilder.cpp +++ b/Source/Engine/Navigation/NavMeshBuilder.cpp @@ -3,10 +3,20 @@ #if COMPILE_WITH_NAV_MESH_BUILDER #include "NavMeshBuilder.h" +#include "NavMesh.h" +#include "NavigationSettings.h" +#include "NavMeshBoundsVolume.h" +#include "NavLink.h" +#include "NavModifierVolume.h" +#include "NavMeshRuntime.h" +#include "Engine/Core/Log.h" #include "Engine/Core/Math/BoundingBox.h" -#include "Engine/Core/Math/VectorInt.h" +#include "Engine/Core/Math/Int3.h" #include "Engine/Physics/Colliders/BoxCollider.h" +#include "Engine/Physics/Colliders/SphereCollider.h" +#include "Engine/Physics/Colliders/CapsuleCollider.h" #include "Engine/Physics/Colliders/MeshCollider.h" +#include "Engine/Physics/Colliders/SplineCollider.h" #include "Engine/Threading/ThreadPoolTask.h" #include "Engine/Terrain/TerrainPatch.h" #include "Engine/Terrain/Terrain.h" @@ -14,13 +24,6 @@ #include "Engine/Level/Scene/Scene.h" #include "Engine/Level/Level.h" #include "Engine/Level/SceneQuery.h" -#include "Engine/Core/Log.h" -#include "NavigationScene.h" -#include "NavigationSettings.h" -#include "NavMeshBoundsVolume.h" -#include "NavMesh.h" -#include "NavLink.h" -#include "Navigation.h" #include #include #include @@ -54,9 +57,17 @@ struct OffMeshLink int32 Id; }; +struct Modifier +{ + BoundingBox Bounds; + NavAreaProperties* NavArea; +}; + struct NavigationSceneRasterization { - BoundingBox TileBounds; + NavMesh* NavMesh; + BoundingBox TileBoundsNavMesh; + Matrix WorldToNavMesh; rcContext* Context; rcConfig* Config; rcHeightfield* Heightfield; @@ -64,15 +75,21 @@ struct NavigationSceneRasterization Array VertexBuffer; Array IndexBuffer; Array* OffMeshLinks; + Array* Modifiers; + const bool IsWorldToNavMeshIdentity; - NavigationSceneRasterization(const BoundingBox& tileBounds, rcContext* context, rcConfig* config, rcHeightfield* heightfield, Array* offMeshLinks) - : TileBounds(tileBounds) + NavigationSceneRasterization(::NavMesh* navMesh, const BoundingBox& tileBoundsNavMesh, const Matrix& worldToNavMesh, rcContext* context, rcConfig* config, rcHeightfield* heightfield, Array* offMeshLinks, Array* modifiers) + : TileBoundsNavMesh(tileBoundsNavMesh) + , WorldToNavMesh(worldToNavMesh) + , IsWorldToNavMeshIdentity(worldToNavMesh.IsIdentity()) { + NavMesh = navMesh; Context = context; Config = config; Heightfield = heightfield; WalkableThreshold = Math::Cos(config->walkableSlopeAngle * DegreesToRadians); OffMeshLinks = offMeshLinks; + Modifiers = modifiers; } void RasterizeTriangles() @@ -83,23 +100,118 @@ struct NavigationSceneRasterization return; // Rasterize triangles - for (int32 i0 = 0; i0 < ib.Count();) + if (IsWorldToNavMeshIdentity) { - auto v0 = vb[ib[i0++]]; - auto v1 = vb[ib[i0++]]; - auto v2 = vb[ib[i0++]]; + // Faster path + for (int32 i0 = 0; i0 < ib.Count();) + { + auto v0 = vb[ib[i0++]]; + auto v1 = vb[ib[i0++]]; + auto v2 = vb[ib[i0++]]; - auto n = Vector3::Cross(v0 - v1, v0 - v2); - n.Normalize(); - const char area = n.Y > WalkableThreshold ? RC_WALKABLE_AREA : 0; - rcRasterizeTriangle(Context, &v0.X, &v1.X, &v2.X, area, *Heightfield); + auto n = Vector3::Cross(v0 - v1, v0 - v2); + n.Normalize(); + const char area = n.Y > WalkableThreshold ? RC_WALKABLE_AREA : 0; + rcRasterizeTriangle(Context, &v0.X, &v1.X, &v2.X, area, *Heightfield); + } + } + else + { + // Transform vertices from world space into the navmesh space + const Matrix worldToNavMesh = WorldToNavMesh; + for (int32 i0 = 0; i0 < ib.Count();) + { + auto v0 = Vector3::Transform(vb[ib[i0++]], worldToNavMesh); + auto v1 = Vector3::Transform(vb[ib[i0++]], worldToNavMesh); + auto v2 = Vector3::Transform(vb[ib[i0++]], worldToNavMesh); + + auto n = Vector3::Cross(v0 - v1, v0 - v2); + n.Normalize(); + const char area = n.Y > WalkableThreshold ? RC_WALKABLE_AREA : RC_NULL_AREA; + rcRasterizeTriangle(Context, &v0.X, &v1.X, &v2.X, area, *Heightfield); + } + } + } + + static void TriangulateBox(Array& vb, Array& ib, const OrientedBoundingBox& box) + { + vb.Resize(8); + box.GetCorners(vb.Get()); + ib.Add(BoxTrianglesIndicesCache, 36); + } + + static void TriangulateBox(Array& vb, Array& ib, const BoundingBox& box) + { + vb.Resize(8); + box.GetCorners(vb.Get()); + ib.Add(BoxTrianglesIndicesCache, 36); + } + + static void TriangulateSphere(Array& vb, Array& ib, const BoundingSphere& sphere) + { + const int32 sphereResolution = 12; + const int32 verticalSegments = sphereResolution; + const int32 horizontalSegments = sphereResolution * 2; + + // Generate vertices for unit sphere + Vector3 vertices[(verticalSegments + 1) * (horizontalSegments + 1)]; + int32 vertexCount = 0; + for (int32 j = 0; j <= horizontalSegments; j++) + vertices[vertexCount++] = Vector3(0, -1, 0); + for (int32 i = 1; i < verticalSegments; i++) + { + const float latitude = (float)i * PI / verticalSegments - PI / 2.0f; + const float dy = Math::Sin(latitude); + const float dxz = Math::Cos(latitude); + auto& firstHorizontalVertex = vertices[vertexCount++]; + firstHorizontalVertex = Vector3(0, dy, dxz); + for (int32 j = 1; j < horizontalSegments; j++) + { + const float longitude = (float)j * 2.0f * PI / horizontalSegments; + const float dx = Math::Sin(longitude) * dxz; + const float dz = Math::Cos(longitude) * dxz; + vertices[vertexCount++] = Vector3(dx, dy, dz); + } + vertices[vertexCount++] = firstHorizontalVertex; + } + for (int32 j = 0; j <= horizontalSegments; j++) + vertices[vertexCount++] = Vector3(0, 1, 0); + + // Transform vertices into world space vertex buffer + vb.Resize(vertexCount); + for (int32 i = 0; i < vertexCount; i++) + vb[i] = sphere.Center + vertices[i] * sphere.Radius; + + // Generate index buffer + const int32 stride = horizontalSegments + 1; + int32 indexCount = 0; + ib.Resize(verticalSegments * (horizontalSegments + 1) * 6); + for (int32 i = 0; i < verticalSegments; i++) + { + const int32 nextI = i + 1; + for (int32 j = 0; j <= horizontalSegments; j++) + { + const int32 nextJ = (j + 1) % stride; + + ib[indexCount++] = i * stride + j; + ib[indexCount++] = nextI * stride + j; + ib[indexCount++] = i * stride + nextJ; + + ib[indexCount++] = i * stride + nextJ; + ib[indexCount++] = nextI * stride + j; + ib[indexCount++] = nextI * stride + nextJ; + } } } static bool Walk(Actor* actor, NavigationSceneRasterization& e) { // Early out if object is not intersecting with the tile bounds or is not using navigation - if (!actor->GetIsActive() || !(actor->GetStaticFlags() & StaticFlags::Navigation) || !actor->GetBox().Intersects(e.TileBounds)) + if (!actor->GetIsActive() || !(actor->GetStaticFlags() & StaticFlags::Navigation)) + return true; + BoundingBox actorBoxNavMesh; + BoundingBox::Transform(actor->GetBox(), e.WorldToNavMesh, actorBoxNavMesh); + if (!actorBoxNavMesh.Intersects(e.TileBoundsNavMesh)) return true; // Prepare buffers (for triangles) @@ -113,12 +225,26 @@ struct NavigationSceneRasterization { PROFILE_CPU_NAMED("BoxCollider"); - OrientedBoundingBox box = boxCollider->GetOrientedBox(); + const OrientedBoundingBox box = boxCollider->GetOrientedBox(); + TriangulateBox(vb, ib, box); - vb.Resize(8); - box.GetCorners(vb.Get()); + e.RasterizeTriangles(); + } + else if (const auto* sphereCollider = dynamic_cast(actor)) + { + PROFILE_CPU_NAMED("SphereCollider"); - ib.Add(BoxTrianglesIndicesCache, 36); + const BoundingSphere sphere = sphereCollider->GetSphere(); + TriangulateSphere(vb, ib, sphere); + + e.RasterizeTriangles(); + } + else if (const auto* capsuleCollider = dynamic_cast(actor)) + { + PROFILE_CPU_NAMED("CapsuleCollider"); + + const BoundingBox box = capsuleCollider->GetBox(); + TriangulateBox(vb, ib, box); e.RasterizeTriangles(); } @@ -127,13 +253,25 @@ struct NavigationSceneRasterization PROFILE_CPU_NAMED("MeshCollider"); auto collisionData = meshCollider->CollisionData.Get(); - if (!collisionData || collisionData->WaitForLoaded(1000.0f)) + if (!collisionData || collisionData->WaitForLoaded()) return true; collisionData->ExtractGeometry(vb, ib); e.RasterizeTriangles(); } + else if (const auto* splineCollider = dynamic_cast(actor)) + { + PROFILE_CPU_NAMED("SplineCollider"); + + auto collisionData = splineCollider->CollisionData.Get(); + if (!collisionData || collisionData->WaitForLoaded()) + return true; + + splineCollider->ExtractGeometry(vb, ib); + + e.RasterizeTriangles(); + } else if (const auto* terrain = dynamic_cast(actor)) { PROFILE_CPU_NAMED("Terrain"); @@ -141,7 +279,9 @@ struct NavigationSceneRasterization for (int32 patchIndex = 0; patchIndex < terrain->GetPatchesCount(); patchIndex++) { const auto patch = terrain->GetPatch(patchIndex); - if (!patch->GetBounds().Intersects(e.TileBounds)) + BoundingBox patchBoundsNavMesh; + BoundingBox::Transform(patch->GetBounds(), e.WorldToNavMesh, patchBoundsNavMesh); + if (!patchBoundsNavMesh.Intersects(e.TileBoundsNavMesh)) continue; patch->ExtractCollisionGeometry(vb, ib); @@ -155,60 +295,78 @@ struct NavigationSceneRasterization OffMeshLink link; link.Start = navLink->GetTransform().LocalToWorld(navLink->Start); + Vector3::Transform(link.Start, e.WorldToNavMesh, link.Start); link.End = navLink->GetTransform().LocalToWorld(navLink->End); + Vector3::Transform(link.End, e.WorldToNavMesh, link.End); link.Radius = navLink->Radius; link.BiDir = navLink->BiDirectional; link.Id = GetHash(navLink->GetID()); e.OffMeshLinks->Add(link); } + else if (const auto* navModifierVolume = dynamic_cast(actor)) + { + if (navModifierVolume->AgentsMask.IsNavMeshSupported(e.NavMesh->Properties)) + { + PROFILE_CPU_NAMED("NavModifierVolume"); - // TODO: nav mesh for capsule collider - // TODO: nav mesh for sphere collider + Modifier modifier; + OrientedBoundingBox bounds = navModifierVolume->GetOrientedBox(); + bounds.Transform(e.WorldToNavMesh); + bounds.GetBoundingBox(modifier.Bounds); + modifier.NavArea = navModifierVolume->GetNavArea(); + + e.Modifiers->Add(modifier); + } + } return true; } }; -void RasterizeGeometry(const BoundingBox& tileBounds, rcContext* context, rcConfig* config, rcHeightfield* heightfield, Array* offMeshLinks) +void RasterizeGeometry(NavMesh* navMesh, const BoundingBox& tileBoundsNavMesh, const Matrix& worldToNavMesh, rcContext* context, rcConfig* config, rcHeightfield* heightfield, Array* offMeshLinks, Array* modifiers) { PROFILE_CPU_NAMED("RasterizeGeometry"); - NavigationSceneRasterization rasterization(tileBounds, context, config, heightfield, offMeshLinks); + NavigationSceneRasterization rasterization(navMesh, tileBoundsNavMesh, worldToNavMesh, context, config, heightfield, offMeshLinks, modifiers); Function treeWalkFunction(NavigationSceneRasterization::Walk); SceneQuery::TreeExecute(treeWalkFunction, rasterization); } // Builds navmesh tile bounds and check if there are any valid navmesh volumes at that tile location // Returns true if tile is intersecting with any navmesh bounds volume actor - which means tile is in use -bool GetNavMeshTileBounds(NavigationScene* scene, int32 x, int32 y, float tileSize, BoundingBox& tileBounds) +bool GetNavMeshTileBounds(Scene* scene, NavMesh* navMesh, int32 x, int32 y, float tileSize, BoundingBox& tileBoundsNavMesh, const Matrix& worldToNavMesh) { // Build initial tile bounds (with infinite extent) - tileBounds.Minimum.X = x * tileSize; - tileBounds.Minimum.Y = -NAV_MESH_TILE_MAX_EXTENT; - tileBounds.Minimum.Z = y * tileSize; - tileBounds.Maximum.X = tileBounds.Minimum.X + tileSize; - tileBounds.Maximum.Y = NAV_MESH_TILE_MAX_EXTENT; - tileBounds.Maximum.Z = tileBounds.Minimum.Z + tileSize; + tileBoundsNavMesh.Minimum.X = (float)x * tileSize; + tileBoundsNavMesh.Minimum.Y = -NAV_MESH_TILE_MAX_EXTENT; + tileBoundsNavMesh.Minimum.Z = (float)y * tileSize; + tileBoundsNavMesh.Maximum.X = tileBoundsNavMesh.Minimum.X + tileSize; + tileBoundsNavMesh.Maximum.Y = NAV_MESH_TILE_MAX_EXTENT; + tileBoundsNavMesh.Maximum.Z = tileBoundsNavMesh.Minimum.Z + tileSize; // Check if any navmesh volume intersects with the tile bool foundAnyVolume = false; Vector2 rangeY; - for (int32 i = 0; i < scene->Volumes.Count(); i++) + for (int32 i = 0; i < scene->NavigationVolumes.Count(); i++) { - const auto volume = scene->Volumes[i]; + const auto volume = scene->NavigationVolumes[i]; + if (!volume->AgentsMask.IsNavMeshSupported(navMesh->Properties)) + continue; const auto& volumeBounds = volume->GetBox(); - if (volumeBounds.Intersects(tileBounds)) + BoundingBox volumeBoundsNavMesh; + BoundingBox::Transform(volumeBounds, worldToNavMesh, volumeBoundsNavMesh); + if (volumeBoundsNavMesh.Intersects(tileBoundsNavMesh)) { if (foundAnyVolume) { - rangeY.X = Math::Min(rangeY.X, volumeBounds.Minimum.Y); - rangeY.Y = Math::Max(rangeY.Y, volumeBounds.Maximum.Y); + rangeY.X = Math::Min(rangeY.X, volumeBoundsNavMesh.Minimum.Y); + rangeY.Y = Math::Max(rangeY.Y, volumeBoundsNavMesh.Maximum.Y); } else { - rangeY.X = volumeBounds.Minimum.Y; - rangeY.Y = volumeBounds.Maximum.Y; + rangeY.X = volumeBoundsNavMesh.Minimum.Y; + rangeY.Y = volumeBoundsNavMesh.Maximum.Y; } foundAnyVolume = true; } @@ -217,45 +375,45 @@ bool GetNavMeshTileBounds(NavigationScene* scene, int32 x, int32 y, float tileSi if (foundAnyVolume) { // Build proper tile bounds - tileBounds.Minimum.Y = rangeY.X; - tileBounds.Maximum.Y = rangeY.Y; + tileBoundsNavMesh.Minimum.Y = rangeY.X; + tileBoundsNavMesh.Maximum.Y = rangeY.Y; } return foundAnyVolume; } -void RemoveTile(NavMesh* navMesh, NavigationScene* scene, int32 x, int32 y, int32 layer) +void RemoveTile(NavMesh* navMesh, NavMeshRuntime* runtime, int32 x, int32 y, int32 layer) { - ScopeLock lock(navMesh->Locker); + ScopeLock lock(runtime->Locker); // Find tile data and remove it - for (int32 i = 0; i < scene->Data.Tiles.Count(); i++) + for (int32 i = 0; i < navMesh->Data.Tiles.Count(); i++) { - auto& tile = scene->Data.Tiles[i]; + auto& tile = navMesh->Data.Tiles[i]; if (tile.PosX == x && tile.PosY == y && tile.Layer == layer) { - scene->Data.Tiles.RemoveAt(i); - scene->IsDataDirty = true; + navMesh->Data.Tiles.RemoveAt(i); + navMesh->IsDataDirty = true; break; } } // Remove tile from navmesh - navMesh->RemoveTile(x, y, layer); + runtime->RemoveTile(x, y, layer); } -bool GenerateTile(NavigationScene* scene, int32 x, int32 y, BoundingBox& tileBounds, float tileSize, rcConfig& config) +bool GenerateTile(NavMesh* navMesh, NavMeshRuntime* runtime, int32 x, int32 y, BoundingBox& tileBoundsNavMesh, const Matrix& worldToNavMesh, float tileSize, rcConfig& config) { rcContext context; int32 layer = 0; // Expand tile bounds by a certain margin - const float tileBorderSize = (1 + config.borderSize) * config.cs; - tileBounds.Minimum -= tileBorderSize; - tileBounds.Maximum += tileBorderSize; + const float tileBorderSize = (1.0f + (float)config.borderSize) * config.cs; + tileBoundsNavMesh.Minimum -= tileBorderSize; + tileBoundsNavMesh.Maximum += tileBorderSize; - rcVcopy(config.bmin, &tileBounds.Minimum.X); - rcVcopy(config.bmax, &tileBounds.Maximum.X); + rcVcopy(config.bmin, &tileBoundsNavMesh.Minimum.X); + rcVcopy(config.bmax, &tileBoundsNavMesh.Maximum.X); rcHeightfield* heightfield = rcAllocHeightfield(); if (!heightfield) @@ -270,7 +428,8 @@ bool GenerateTile(NavigationScene* scene, int32 x, int32 y, BoundingBox& tileBou } Array offMeshLinks; - RasterizeGeometry(tileBounds, &context, &config, heightfield, &offMeshLinks); + Array modifiers; + RasterizeGeometry(navMesh, tileBoundsNavMesh, worldToNavMesh, &context, &config, heightfield, &offMeshLinks, &modifiers); rcFilterLowHangingWalkableObstacles(&context, config.walkableClimb, *heightfield); rcFilterLedgeSpans(&context, config.walkableHeight, config.walkableClimb, *heightfield); @@ -296,6 +455,13 @@ bool GenerateTile(NavigationScene* scene, int32 x, int32 y, BoundingBox& tileBou return true; } + // Mark areas + for (auto& modifier : modifiers) + { + const unsigned char areaId = modifier.NavArea ? modifier.NavArea->Id : RC_NULL_AREA; + rcMarkBoxArea(&context, &modifier.Bounds.Minimum.X, &modifier.Bounds.Maximum.X, areaId, *compactHeightfield); + } + if (!rcBuildDistanceField(&context, *compactHeightfield)) { LOG(Warning, "Could not generate navmesh: Could not build distance field."); @@ -347,15 +513,15 @@ bool GenerateTile(NavigationScene* scene, int32 x, int32 y, BoundingBox& tileBou rcFreeCompactHeightfield(compactHeightfield); rcFreeContourSet(contourSet); - for (int i = 0; i < polyMesh->npolys; ++i) + for (int i = 0; i < polyMesh->npolys; i++) { - polyMesh->flags[i] = polyMesh->areas[i] == RC_WALKABLE_AREA ? 1 : 0; + polyMesh->flags[i] = polyMesh->areas[i] != RC_NULL_AREA ? 1 : 0; } if (polyMesh->nverts == 0) { // Empty tile - RemoveTile(Navigation::GetNavMesh(), scene, x, y, layer); + RemoveTile(navMesh, runtime, x, y, layer); return false; } @@ -373,9 +539,9 @@ bool GenerateTile(NavigationScene* scene, int32 x, int32 y, BoundingBox& tileBou params.detailVertsCount = detailMesh->nverts; params.detailTris = detailMesh->tris; params.detailTriCount = detailMesh->ntris; - params.walkableHeight = config.walkableHeight * config.ch; - params.walkableRadius = config.walkableRadius * config.cs; - params.walkableClimb = config.walkableClimb * config.ch; + params.walkableHeight = (float)config.walkableHeight * config.ch; + params.walkableRadius = (float)config.walkableRadius * config.cs; + params.walkableClimb = (float)config.walkableClimb * config.ch; params.tileX = x; params.tileY = y; params.tileLayer = layer; @@ -414,7 +580,7 @@ bool GenerateTile(NavigationScene* scene, int32 x, int32 y, BoundingBox& tileBou offMeshArea[i] = RC_WALKABLE_AREA; offMeshFlags[i] = 1; - // TODO: support navigation areas, navigation area type for off mesh links + // TODO: support navigation area type for off mesh links } params.offMeshConCount = linksCount; @@ -438,20 +604,33 @@ bool GenerateTile(NavigationScene* scene, int32 x, int32 y, BoundingBox& tileBou { PROFILE_CPU_NAMED("Navigation.CreateTile"); - ScopeLock lock(Navigation::GetNavMesh()->Locker); + ScopeLock lock(runtime->Locker); - // Add tile data - scene->IsDataDirty = true; - auto& tile = scene->Data.Tiles.AddOne(); - tile.PosX = x; - tile.PosY = y; - tile.Layer = layer; + navMesh->IsDataDirty = true; + NavMeshTileData* tile = nullptr; + for (int32 i = 0; i < navMesh->Data.Tiles.Count(); i++) + { + auto& e = navMesh->Data.Tiles[i]; + if (e.PosX == x && e.PosY == y && e.Layer == layer) + { + tile = &e; + break; + } + } + if (!tile) + { + // Add new tile + tile = &navMesh->Data.Tiles.AddOne(); + tile->PosX = x; + tile->PosY = y; + tile->Layer = layer; + } - // Copy data - tile.Data.Copy(navData, navDataSize); + // Copy data to the tile + tile->Data.Copy(navData, navDataSize); // Add tile to navmesh - Navigation::GetNavMesh()->AddTile(scene, tile); + runtime->AddTile(navMesh, *tile); } dtFree(navData); @@ -461,20 +640,21 @@ bool GenerateTile(NavigationScene* scene, int32 x, int32 y, BoundingBox& tileBou float GetTileSize() { - auto& settings = *NavigationSettings::Instance(); + auto& settings = *NavigationSettings::Get(); return settings.CellSize * settings.TileSize; } -void InitConfig(rcConfig& config) +void InitConfig(rcConfig& config, NavMesh* navMesh) { - auto& settings = *NavigationSettings::Instance(); + auto& settings = *NavigationSettings::Get(); + auto& navMeshProperties = navMesh->Properties; config.cs = settings.CellSize; config.ch = settings.CellHeight; - config.walkableSlopeAngle = settings.WalkableMaxSlopeAngle; - config.walkableHeight = (int)(settings.WalkableHeight / config.ch + 0.99f); - config.walkableClimb = (int)(settings.WalkableMaxClimb / config.ch); - config.walkableRadius = (int)(settings.WalkableRadius / config.cs + 0.99f); + config.walkableSlopeAngle = navMeshProperties.Agent.MaxSlopeAngle; + config.walkableHeight = (int)(navMeshProperties.Agent.Height / config.ch + 0.99f); + config.walkableClimb = (int)(navMeshProperties.Agent.StepHeight / config.ch); + config.walkableRadius = (int)(navMeshProperties.Agent.Radius / config.cs + 0.99f); config.maxEdgeLen = (int)(settings.MaxEdgeLen / config.cs); config.maxSimplificationError = settings.MaxEdgeError; config.minRegionArea = rcSqr(settings.MinRegionArea); @@ -506,8 +686,11 @@ class NavMeshTileBuildTask : public ThreadPoolTask { public: - NavigationScene* Scene; - BoundingBox TileBounds; + Scene* Scene; + ScriptingObjectReference NavMesh; + NavMeshRuntime* Runtime; + BoundingBox TileBoundsNavMesh; + Matrix WorldToNavMesh; int32 X; int32 Y; float TileSize; @@ -520,7 +703,12 @@ public: { PROFILE_CPU_NAMED("BuildNavMeshTile"); - if (GenerateTile(Scene, X, Y, TileBounds, TileSize, Config)) + const auto navMesh = NavMesh.Get(); + if (!navMesh) + { + return false; + } + if (GenerateTile(NavMesh, Runtime, X, Y, TileBoundsNavMesh, WorldToNavMesh, TileSize, Config)) { LOG(Warning, "Failed to generate navmesh tile at {0}x{1}.", X, Y); } @@ -557,7 +745,7 @@ void OnSceneUnloading(Scene* scene, const Guid& sceneId) for (int32 i = 0; i < NavBuildTasks.Count(); i++) { auto task = NavBuildTasks[i]; - if (task->Scene == scene->Navigation) + if (task->Scene == scene) { NavBuildTasksLocker.Unlock(); @@ -600,15 +788,16 @@ float NavMeshBuilder::GetNavMeshBuildingProgress() return result; } -void BuildTileAsync(NavigationScene* scene, int32 x, int32 y, rcConfig& config, const BoundingBox& tileBounds, float tileSize) +void BuildTileAsync(NavMesh* navMesh, int32 x, int32 y, rcConfig& config, const BoundingBox& tileBoundsNavMesh, const Matrix& worldToNavMesh, float tileSize) { + NavMeshRuntime* runtime = navMesh->GetRuntime(); NavBuildTasksLocker.Lock(); // Skip if this tile is already during cooking for (int32 i = 0; i < NavBuildTasks.Count(); i++) { const auto task = NavBuildTasks[i]; - if (task->X == x && task->Y == y) + if (task->X == x && task->Y == y && task->Runtime == runtime) { NavBuildTasksLocker.Unlock(); return; @@ -617,10 +806,13 @@ void BuildTileAsync(NavigationScene* scene, int32 x, int32 y, rcConfig& config, // Create task auto task = New(); - task->Scene = scene; + task->Scene = navMesh->GetScene(); + task->NavMesh = navMesh; + task->Runtime = runtime; task->X = x; task->Y = y; - task->TileBounds = tileBounds; + task->TileBoundsNavMesh = tileBoundsNavMesh; + task->WorldToNavMesh = worldToNavMesh; task->TileSize = tileSize; task->Config = config; NavBuildTasks.Add(task); @@ -632,79 +824,23 @@ void BuildTileAsync(NavigationScene* scene, int32 x, int32 y, rcConfig& config, task->Start(); } -void BuildWholeScene(NavigationScene* scene) +void BuildDirtyBounds(Scene* scene, NavMesh* navMesh, const BoundingBox& dirtyBounds, bool rebuild) { const float tileSize = GetTileSize(); - const auto navMesh = Navigation::GetNavMesh(); - - // Compute total navigation area bounds - const BoundingBox worldBounds = scene->GetNavigationBounds(); - - // Align total bounds to tile size - BoundingBox worldBoundsAligned; - worldBoundsAligned.Minimum = Vector3::Floor(worldBounds.Minimum / tileSize) * tileSize; - worldBoundsAligned.Maximum = Vector3::Ceil(worldBounds.Maximum / tileSize) * tileSize; - - // Calculate tiles range for the given navigation world bounds (aligned to tiles size) - const Int3 tilesMin = Int3(worldBoundsAligned.Minimum / tileSize); - const Int3 tilesMax = Int3(worldBoundsAligned.Maximum / tileSize); - const int32 tilesX = tilesMax.X - tilesMin.X; - const int32 tilesY = tilesMax.Z - tilesMin.Z; - - { - PROFILE_CPU_NAMED("Prepare"); - - // Prepare navmesh - navMesh->RemoveTiles(scene); - navMesh->SetTileSize(tileSize); - navMesh->EnsureCapacity(tilesX * tilesY); - - // Prepare scene data - scene->Data.TileSize = tileSize; - scene->Data.Tiles.Clear(); - scene->Data.Tiles.EnsureCapacity(tilesX * tilesX); - scene->IsDataDirty = true; - } - - // Initialize nav mesh configuration - rcConfig config; - InitConfig(config); - - // Generate all tiles that intersect with the navigation volume bounds - { - PROFILE_CPU_NAMED("StartBuildingTiles"); - - for (int32 y = tilesMin.Z; y < tilesMax.Z; y ++) - { - for (int32 x = tilesMin.X; x < tilesMax.X; x ++) - { - BoundingBox tileBounds; - if (GetNavMeshTileBounds(scene, x, y, tileSize, tileBounds)) - { - BuildTileAsync(scene, x, y, config, tileBounds, tileSize); - } - else - { - RemoveTile(navMesh, scene, x, y, 0); - } - } - } - } -} - -void BuildDirtyBounds(NavigationScene* scene, const BoundingBox& dirtyBounds) -{ - const float tileSize = GetTileSize(); - const auto navMesh = Navigation::GetNavMesh(); + NavMeshRuntime* runtime = navMesh->GetRuntime(); + Matrix worldToNavMesh; + Matrix::RotationQuaternion(runtime->Properties.Rotation, worldToNavMesh); // Align dirty bounds to tile size + BoundingBox dirtyBoundsNavMesh; + BoundingBox::Transform(dirtyBounds, worldToNavMesh, dirtyBoundsNavMesh); BoundingBox dirtyBoundsAligned; - dirtyBoundsAligned.Minimum = Vector3::Floor(dirtyBounds.Minimum / tileSize) * tileSize; - dirtyBoundsAligned.Maximum = Vector3::Ceil(dirtyBounds.Maximum / tileSize) * tileSize; + dirtyBoundsAligned.Minimum = Vector3::Floor(dirtyBoundsNavMesh.Minimum / tileSize) * tileSize; + dirtyBoundsAligned.Maximum = Vector3::Ceil(dirtyBoundsNavMesh.Maximum / tileSize) * tileSize; // Calculate tiles range for the given navigation dirty bounds (aligned to tiles size) - const Int3 tilesMin = Int3(dirtyBoundsAligned.Minimum / tileSize); - const Int3 tilesMax = Int3(dirtyBoundsAligned.Maximum / tileSize); + const Int3 tilesMin(dirtyBoundsAligned.Minimum / tileSize); + const Int3 tilesMax(dirtyBoundsAligned.Maximum / tileSize); const int32 tilesX = tilesMax.X - tilesMin.X; const int32 tilesY = tilesMax.Z - tilesMin.Z; @@ -712,51 +848,161 @@ void BuildDirtyBounds(NavigationScene* scene, const BoundingBox& dirtyBounds) PROFILE_CPU_NAMED("Prepare"); // Prepare scene data and navmesh - if (Math::NotNearEqual(scene->Data.TileSize, tileSize)) + rebuild |= Math::NotNearEqual(navMesh->Data.TileSize, tileSize); + if (rebuild) { - navMesh->RemoveTiles(scene); - navMesh->SetTileSize(tileSize); - navMesh->EnsureCapacity(tilesX * tilesY); + // Remove all tiles from navmesh runtime + runtime->RemoveTiles(navMesh); + runtime->SetTileSize(tileSize); + runtime->EnsureCapacity(tilesX * tilesY); - scene->Data.TileSize = tileSize; - scene->Data.Tiles.Clear(); - scene->Data.Tiles.EnsureCapacity(tilesX * tilesX); - scene->IsDataDirty = true; + // Remove all tiles from navmesh data + navMesh->Data.TileSize = tileSize; + navMesh->Data.Tiles.Clear(); + navMesh->Data.Tiles.EnsureCapacity(tilesX * tilesX); + navMesh->IsDataDirty = true; } else { - // Prepare navmesh - navMesh->SetTileSize(tileSize); - navMesh->EnsureCapacity(tilesX * tilesY); + // Ensure to have enough memory for tiles + runtime->SetTileSize(tileSize); + runtime->EnsureCapacity(tilesX * tilesY); } } // Initialize nav mesh configuration rcConfig config; - InitConfig(config); + InitConfig(config, navMesh); // Generate all tiles that intersect with the navigation volume bounds { PROFILE_CPU_NAMED("StartBuildingTiles"); - for (int32 y = tilesMin.Z; y < tilesMax.Z; y ++) + for (int32 y = tilesMin.Z; y < tilesMax.Z; y++) { - for (int32 x = tilesMin.X; x < tilesMax.X; x ++) + for (int32 x = tilesMin.X; x < tilesMax.X; x++) { - BoundingBox tileBounds; - if (GetNavMeshTileBounds(scene, x, y, tileSize, tileBounds)) + BoundingBox tileBoundsNavMesh; + if (GetNavMeshTileBounds(scene, navMesh, x, y, tileSize, tileBoundsNavMesh, worldToNavMesh)) { - BuildTileAsync(scene, x, y, config, tileBounds, tileSize); + BuildTileAsync(navMesh, x, y, config, tileBoundsNavMesh, worldToNavMesh, tileSize); } else { - RemoveTile(navMesh, scene, x, y, 0); + RemoveTile(navMesh, runtime, x, y, 0); } } } } } +void BuildDirtyBounds(Scene* scene, const BoundingBox& dirtyBounds, bool rebuild) +{ + auto settings = NavigationSettings::Get(); + + // Validate nav areas ids to be unique and in valid range + for (int32 i = 0; i < settings->NavAreas.Count(); i++) + { + auto& a = settings->NavAreas[i]; + if (a.Id > RC_WALKABLE_AREA) + { + LOG(Error, "Nav Area {0} uses invalid Id. Valid values are in range 0-63 only.", a.Name); + return; + } + + for (int32 j = i + 1; j < settings->NavAreas.Count(); j++) + { + auto& b = settings->NavAreas[j]; + if (a.Id == b.Id) + { + LOG(Error, "Nav Area {0} uses the same Id={1} as Nav Area {2}. Each area hast to have unique Id.", a.Name, a.Id, b.Name); + return; + } + } + } + + // Sync navmeshes + for (auto& navMeshProperties : settings->NavMeshes) + { + NavMesh* navMesh = nullptr; + for (auto e : scene->NavigationMeshes) + { + if (e->Properties.Name == navMeshProperties.Name) + { + navMesh = e; + break; + } + } + if (navMesh) + { + // Sync settings + auto runtime = navMesh->GetRuntime(false); + navMesh->Properties = navMeshProperties; + if (runtime) + runtime->Properties = navMeshProperties; + } + else if (settings->AutoAddMissingNavMeshes) + { + // Spawn missing navmesh + navMesh = New(); + navMesh->SetStaticFlags(StaticFlags::FullyStatic); + navMesh->SetName(TEXT("NavMesh.") + navMeshProperties.Name); + navMesh->Properties = navMeshProperties; + navMesh->SetParent(scene, false); + } + } + + // Build all navmeshes on the scene + for (NavMesh* navMesh : scene->NavigationMeshes) + { + BuildDirtyBounds(scene, navMesh, dirtyBounds, rebuild); + } + + // Remove unused navmeshes + if (settings->AutoRemoveMissingNavMeshes) + { + for (NavMesh* navMesh : scene->NavigationMeshes) + { + // Skip used navmeshes + if (navMesh->Data.Tiles.HasItems()) + continue; + + // Skip navmeshes during async building + int32 usageCount = 0; + NavBuildTasksLocker.Lock(); + for (int32 i = 0; i < NavBuildTasks.Count(); i++) + { + if (NavBuildTasks[i]->NavMesh == navMesh) + usageCount++; + } + NavBuildTasksLocker.Unlock(); + if (usageCount != 0) + continue; + + navMesh->DeleteObject(); + } + } +} + +void BuildWholeScene(Scene* scene) +{ + // Compute total navigation area bounds + const BoundingBox worldBounds = scene->GetNavigationBounds(); + + BuildDirtyBounds(scene, worldBounds, true); +} + +void ClearNavigation(Scene* scene) +{ + const bool autoRemoveMissingNavMeshes = NavigationSettings::Get()->AutoRemoveMissingNavMeshes; + for (NavMesh* navMesh : scene->NavigationMeshes) + { + navMesh->ClearData(); + if (autoRemoveMissingNavMeshes) + navMesh->DeleteObject(); + } +} + void NavMeshBuilder::Update() { ScopeLock lock(NavBuildQueueLocker); @@ -769,15 +1015,12 @@ void NavMeshBuilder::Update() if (now - req.Time >= 0) { NavBuildQueue.RemoveAt(i--); - auto scene = req.Scene->Navigation; + const auto scene = req.Scene.Get(); // Early out if scene has no bounds volumes to define nav mesh area - if (scene->Volumes.IsEmpty()) + if (scene->NavigationVolumes.IsEmpty()) { - // Cleanup if no navigation to use - scene->Data.TileSize = 0; - scene->Data.Tiles.Resize(0); - scene->IsDataDirty = true; + ClearNavigation(scene); continue; } @@ -788,7 +1031,7 @@ void NavMeshBuilder::Update() } else { - BuildDirtyBounds(scene, req.DirtyBounds); + BuildDirtyBounds(scene, req.DirtyBounds, false); } } } @@ -797,9 +1040,9 @@ void NavMeshBuilder::Update() void NavMeshBuilder::Build(Scene* scene, float timeoutMs) { // Early out if scene is not using navigation - if (scene->Navigation->Volumes.IsEmpty()) + if (scene->NavigationVolumes.IsEmpty()) { - scene->Navigation->ClearData(); + ClearNavigation(scene); return; } @@ -828,9 +1071,9 @@ void NavMeshBuilder::Build(Scene* scene, float timeoutMs) void NavMeshBuilder::Build(Scene* scene, const BoundingBox& dirtyBounds, float timeoutMs) { // Early out if scene is not using navigation - if (scene->Navigation->Volumes.IsEmpty()) + if (scene->NavigationVolumes.IsEmpty()) { - scene->Navigation->ClearData(); + ClearNavigation(scene); return; } diff --git a/Source/Engine/Navigation/NavMeshBuilder.h b/Source/Engine/Navigation/NavMeshBuilder.h index 4e59027ce..27dab4273 100644 --- a/Source/Engine/Navigation/NavMeshBuilder.h +++ b/Source/Engine/Navigation/NavMeshBuilder.h @@ -4,9 +4,14 @@ #if COMPILE_WITH_NAV_MESH_BUILDER +#include "Engine/Core/Compiler.h" + class Scene; -class NavMeshBuilder +/// +/// The navigation mesh building utility. +/// +class FLAXENGINE_API NavMeshBuilder { public: diff --git a/Source/Engine/Navigation/NavMeshRuntime.cpp b/Source/Engine/Navigation/NavMeshRuntime.cpp new file mode 100644 index 000000000..3d112a39e --- /dev/null +++ b/Source/Engine/Navigation/NavMeshRuntime.cpp @@ -0,0 +1,694 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#include "NavMeshRuntime.h" +#include "NavigationSettings.h" +#include "NavMesh.h" +#include "Engine/Core/Log.h" +#include "Engine/Core/Math/Matrix.h" +#include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Threading/Threading.h" +#include +#include +#include + +#define MAX_NODES 2048 +#define USE_DATA_LINK 0 +#define USE_NAV_MESH_ALLOC 1 +// TODO: try not using USE_NAV_MESH_ALLOC + +#define DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL 50.0f +#define DEFAULT_NAV_QUERY_EXTENT_VERTICAL 250.0f + +namespace +{ + FORCE_INLINE void InitFilter(dtQueryFilter& filter) + { + Platform::MemoryCopy(filter.m_areaCost, NavMeshRuntime::NavAreasCosts, sizeof(NavMeshRuntime::NavAreasCosts)); + } +} + +NavMeshRuntime::NavMeshRuntime(const NavMeshProperties& properties) + : Properties(properties) +{ + _navMesh = nullptr; + _navMeshQuery = dtAllocNavMeshQuery(); + _tileSize = 0; +} + +NavMeshRuntime::~NavMeshRuntime() +{ + dtFreeNavMesh(_navMesh); + dtFreeNavMeshQuery(_navMeshQuery); +} + +int32 NavMeshRuntime::GetTilesCapacity() const +{ + return _navMesh ? _navMesh->getMaxTiles() : 0; +} + +bool NavMeshRuntime::FindDistanceToWall(const Vector3& startPosition, NavMeshHit& hitInfo, float maxDistance) const +{ + ScopeLock lock(Locker); + + const auto query = GetNavMeshQuery(); + if (!query) + { + return false; + } + + dtQueryFilter filter; + InitFilter(filter); + Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL); + + Vector3 startPositionNavMesh; + Vector3::Transform(startPosition, Properties.Rotation, startPositionNavMesh); + + dtPolyRef startPoly = 0; + query->findNearestPoly(&startPositionNavMesh.X, &extent.X, &filter, &startPoly, nullptr); + if (!startPoly) + { + return false; + } + + if (!dtStatusSucceed(query->findDistanceToWall(startPoly, &startPosition.X, maxDistance, &filter, &hitInfo.Distance, &hitInfo.Position.X, &hitInfo.Normal.X))) + { + return false; + } + + Quaternion invRotation; + Quaternion::Invert(Properties.Rotation, invRotation); + Vector3::Transform(hitInfo.Position, invRotation, hitInfo.Position); + Vector3::Transform(hitInfo.Normal, invRotation, hitInfo.Normal); + + return true; +} + +bool NavMeshRuntime::FindPath(const Vector3& startPosition, const Vector3& endPosition, Array& resultPath) const +{ + resultPath.Clear(); + ScopeLock lock(Locker); + + const auto query = GetNavMeshQuery(); + if (!query) + { + return false; + } + + dtQueryFilter filter; + InitFilter(filter); + Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL); + + Vector3 startPositionNavMesh, endPositionNavMesh; + Vector3::Transform(startPosition, Properties.Rotation, startPositionNavMesh); + Vector3::Transform(endPosition, Properties.Rotation, endPositionNavMesh); + + dtPolyRef startPoly = 0; + query->findNearestPoly(&startPositionNavMesh.X, &extent.X, &filter, &startPoly, nullptr); + if (!startPoly) + { + return false; + } + dtPolyRef endPoly = 0; + query->findNearestPoly(&endPositionNavMesh.X, &extent.X, &filter, &endPoly, nullptr); + if (!endPoly) + { + return false; + } + + dtPolyRef path[NAV_MESH_PATH_MAX_SIZE]; + int32 pathSize; + const auto findPathStatus = query->findPath(startPoly, endPoly, &startPositionNavMesh.X, &endPositionNavMesh.X, &filter, path, &pathSize, NAV_MESH_PATH_MAX_SIZE); + if (dtStatusFailed(findPathStatus)) + { + return false; + } + + Quaternion invRotation; + Quaternion::Invert(Properties.Rotation, invRotation); + + // Check for special case, where path has not been found, and starting polygon was the one closest to the target + if (pathSize == 1 && dtStatusDetail(findPathStatus, DT_PARTIAL_RESULT)) + { + // In this case we find a point on starting polygon, that's closest to destination and store it as path end + resultPath.Resize(2); + resultPath[0] = startPosition; + resultPath[1] = startPositionNavMesh; + query->closestPointOnPolyBoundary(startPoly, &endPositionNavMesh.X, &resultPath[1].X); + Vector3::Transform(resultPath[1], invRotation, resultPath[1]); + } + else + { + int straightPathCount = 0; + resultPath.EnsureCapacity(NAV_MESH_PATH_MAX_SIZE); + const auto findStraightPathStatus = query->findStraightPath(&startPositionNavMesh.X, &endPositionNavMesh.X, path, pathSize, (float*)resultPath.Get(), nullptr, nullptr, &straightPathCount, resultPath.Capacity(), DT_STRAIGHTPATH_AREA_CROSSINGS); + if (dtStatusFailed(findStraightPathStatus)) + { + return false; + } + resultPath.Resize(straightPathCount); + for (auto& pos : resultPath) + Vector3::Transform(pos, invRotation, pos); + } + + return true; +} + +bool NavMeshRuntime::TestPath(const Vector3& startPosition, const Vector3& endPosition) const +{ + ScopeLock lock(Locker); + + const auto query = GetNavMeshQuery(); + if (!query) + { + return false; + } + + dtQueryFilter filter; + InitFilter(filter); + Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL); + + Vector3 startPositionNavMesh, endPositionNavMesh; + Vector3::Transform(startPosition, Properties.Rotation, startPositionNavMesh); + Vector3::Transform(endPosition, Properties.Rotation, endPositionNavMesh); + + dtPolyRef startPoly = 0; + query->findNearestPoly(&startPositionNavMesh.X, &extent.X, &filter, &startPoly, nullptr); + if (!startPoly) + { + return false; + } + dtPolyRef endPoly = 0; + query->findNearestPoly(&endPositionNavMesh.X, &extent.X, &filter, &endPoly, nullptr); + if (!endPoly) + { + return false; + } + + dtPolyRef path[NAV_MESH_PATH_MAX_SIZE]; + int32 pathSize; + const auto findPathStatus = query->findPath(startPoly, endPoly, &startPositionNavMesh.X, &endPositionNavMesh.X, &filter, path, &pathSize, NAV_MESH_PATH_MAX_SIZE); + if (dtStatusFailed(findPathStatus)) + { + return false; + } + + if (dtStatusDetail(findPathStatus, DT_PARTIAL_RESULT)) + { + return false; + } + + return true; +} + +bool NavMeshRuntime::ProjectPoint(const Vector3& point, Vector3& result) const +{ + ScopeLock lock(Locker); + + const auto query = GetNavMeshQuery(); + if (!query) + { + return false; + } + + dtQueryFilter filter; + InitFilter(filter); + Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL); + + Vector3 pointNavMesh; + Vector3::Transform(point, Properties.Rotation, pointNavMesh); + + dtPolyRef startPoly = 0; + query->findNearestPoly(&pointNavMesh.X, &extent.X, &filter, &startPoly, &result.X); + if (!startPoly) + { + return false; + } + + Quaternion invRotation; + Quaternion::Invert(Properties.Rotation, invRotation); + Vector3::Transform(result, invRotation, result); + + return true; +} + +bool NavMeshRuntime::RayCast(const Vector3& startPosition, const Vector3& endPosition, NavMeshHit& hitInfo) const +{ + ScopeLock lock(Locker); + + const auto query = GetNavMeshQuery(); + if (!query) + { + return false; + } + + dtQueryFilter filter; + InitFilter(filter); + Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL); + + Vector3 startPositionNavMesh, endPositionNavMesh; + Vector3::Transform(startPosition, Properties.Rotation, startPositionNavMesh); + Vector3::Transform(endPosition, Properties.Rotation, endPositionNavMesh); + + dtPolyRef startPoly = 0; + query->findNearestPoly(&startPositionNavMesh.X, &extent.X, &filter, &startPoly, nullptr); + if (!startPoly) + { + return false; + } + + dtRaycastHit hit; + hit.path = nullptr; + hit.maxPath = 0; + const bool result = dtStatusSucceed(query->raycast(startPoly, &startPositionNavMesh.X, &endPositionNavMesh.X, &filter, 0, &hit)); + if (hit.t >= MAX_float) + { + hitInfo.Position = endPosition; + hitInfo.Distance = 0; + } + else + { + hitInfo.Position = startPosition + (endPosition - startPosition) * hit.t; + hitInfo.Distance = hit.t; + } + hitInfo.Normal = *(Vector3*)&hit.hitNormal; + + return result; +} + +void NavMeshRuntime::SetTileSize(float tileSize) +{ + ScopeLock lock(Locker); + + // Skip if the same or invalid + if (Math::NearEqual(_tileSize, tileSize) || tileSize < 1) + return; + + // Dispose the existing mesh (its invalid) + if (_navMesh) + { + dtFreeNavMesh(_navMesh); + _navMesh = nullptr; + _tiles.Clear(); + } + + _tileSize = tileSize; +} + +void NavMeshRuntime::EnsureCapacity(int32 tilesToAddCount) +{ + ScopeLock lock(Locker); + + const int32 newTilesCount = _tiles.Count() + tilesToAddCount; + const int32 capacity = GetTilesCapacity(); + if (newTilesCount <= capacity) + return; + + PROFILE_CPU_NAMED("NavMeshRuntime.EnsureCapacity"); + + // Navmesh tiles capacity growing rule + int32 newCapacity = 0; + if (capacity) + { + while (newCapacity < newTilesCount) + { + newCapacity = Math::RoundUpToPowerOf2(newCapacity); + } + } + else + { + newCapacity = 32; + } + + LOG(Info, "Resizing navmesh {2} from {0} to {1} tiles capacity", capacity, newCapacity, Properties.Name); + + // Ensure to have size assigned + ASSERT(_tileSize != 0); + + // Fre previous data (if any) + if (_navMesh) + { + dtFreeNavMesh(_navMesh); + } + + // Allocate new navmesh + _navMesh = dtAllocNavMesh(); + if (dtStatusFailed(_navMeshQuery->init(_navMesh, MAX_NODES))) + { + LOG(Error, "Failed to initialize navmesh {0}.", Properties.Name); + } + + // Prepare parameters + dtNavMeshParams params; + params.orig[0] = 0.0f; + params.orig[1] = 0.0f; + params.orig[2] = 0.0f; + params.tileWidth = _tileSize; + params.tileHeight = _tileSize; + params.maxTiles = newCapacity; + const int32 tilesBits = (int32)Math::Log2((float)Math::RoundUpToPowerOf2(params.maxTiles)); + params.maxPolys = 1 << (22 - tilesBits); + + // Initialize nav mesh + if (dtStatusFailed(_navMesh->init(¶ms))) + { + LOG(Error, "Navmesh {0} init failed.", Properties.Name); + return; + } + + // Prepare tiles container + _tiles.EnsureCapacity(newCapacity); + + // Restore previous tiles + for (auto& tile : _tiles) + { + const int32 dataSize = tile.Data.Length(); +#if USE_NAV_MESH_ALLOC + const auto flags = DT_TILE_FREE_DATA; + const auto data = (byte*)dtAlloc(dataSize, DT_ALLOC_PERM); + Platform::MemoryCopy(data, tile.Data.Get(), dataSize); +#else + const auto flags = 0; + const auto data = tile.Data.Get(); +#endif + if (dtStatusFailed(_navMesh->addTile(data, dataSize, flags, 0, nullptr))) + { + LOG(Warning, "Could not add tile to navmesh {0}.", Properties.Name); + } + } +} + +void NavMeshRuntime::AddTiles(NavMesh* navMesh) +{ + // Skip if no data + ASSERT(navMesh); + if (navMesh->Data.Tiles.IsEmpty()) + return; + auto& data = navMesh->Data; + + PROFILE_CPU_NAMED("NavMeshRuntime.AddTiles"); + + ScopeLock lock(Locker); + + // Validate data (must match navmesh) or init navmesh to match the tiles options + if (_navMesh) + { + if (Math::NotNearEqual(data.TileSize, _tileSize)) + { + LOG(Warning, "Cannot add navigation scene tiles to the navmesh {2}. Navmesh tile size: {0}, input tiles size: {1}", _tileSize, data.TileSize, Properties.Name); + return; + } + } + else + { + _tileSize = data.TileSize; + } + + // Ensure to have space for new tiles + EnsureCapacity(data.Tiles.Count()); + + // Add new tiles + for (auto& tileData : data.Tiles) + { + AddTileInternal(navMesh, tileData); + } +} + +void NavMeshRuntime::AddTile(NavMesh* navMesh, NavMeshTileData& tileData) +{ + ASSERT(navMesh); + auto& data = navMesh->Data; + + PROFILE_CPU_NAMED("NavMeshRuntime.AddTile"); + + ScopeLock lock(Locker); + + // Validate data (must match navmesh) or init navmesh to match the tiles options + if (_navMesh) + { + if (Math::NotNearEqual(data.TileSize, _tileSize)) + { + LOG(Warning, "Cannot add navigation scene tile to the navmesh {2}. Navmesh tile size: {0}, input tile size: {1}", _tileSize, data.TileSize, Properties.Name); + return; + } + } + else + { + _tileSize = data.TileSize; + } + + // Ensure to have space for new tile + EnsureCapacity(1); + + // Add new tile + AddTileInternal(navMesh, tileData); +} + +bool IsTileFromScene(const NavMeshRuntime* navMesh, const NavMeshTile& tile, void* customData) +{ + return tile.NavMesh == (NavMesh*)customData; +} + +void NavMeshRuntime::RemoveTiles(NavMesh* navMesh) +{ + RemoveTiles(IsTileFromScene, navMesh); +} + +void NavMeshRuntime::RemoveTile(int32 x, int32 y, int32 layer) +{ + ScopeLock lock(Locker); + + // Skip if no data + if (!_navMesh) + return; + + PROFILE_CPU_NAMED("NavMeshRuntime.RemoveTile"); + + const auto tileRef = _navMesh->getTileRefAt(x, y, layer); + if (tileRef == 0) + { + return; + } + + if (dtStatusFailed(_navMesh->removeTile(tileRef, nullptr, nullptr))) + { + LOG(Warning, "Failed to remove tile from navmesh {0}.", Properties.Name); + } + + for (int32 i = 0; i < _tiles.Count(); i++) + { + auto& tile = _tiles[i]; + if (tile.X == x && tile.Y == y && tile.Layer == layer) + { + _tiles.RemoveAt(i); + break; + } + } +} + +void NavMeshRuntime::RemoveTiles(bool (* prediction)(const NavMeshRuntime* navMesh, const NavMeshTile& tile, void* customData), void* userData) +{ + ScopeLock lock(Locker); + + // Skip if no data + ASSERT(prediction); + if (!_navMesh) + return; + + PROFILE_CPU_NAMED("NavMeshRuntime.RemoveTiles"); + + for (int32 i = 0; i < _tiles.Count(); i++) + { + auto& tile = _tiles[i]; + if (prediction(this, tile, userData)) + { + const auto tileRef = _navMesh->getTileRefAt(tile.X, tile.Y, tile.Layer); + if (tileRef == 0) + { + LOG(Warning, "Missing navmesh {3} tile at {0}x{1}, layer: {2}", tile.X, tile.Y, tile.Layer, Properties.Name); + } + else + { + if (dtStatusFailed(_navMesh->removeTile(tileRef, nullptr, nullptr))) + { + LOG(Warning, "Failed to remove tile from navmesh {0}.", Properties.Name); + } + } + + _tiles.RemoveAt(i--); + } + } +} + +#if COMPILE_WITH_DEBUG_DRAW + +#include "Engine/Debug/DebugDraw.h" + +void DrawPoly(NavMeshRuntime* navMesh, const Matrix& navMeshToWorld, const dtMeshTile& tile, const dtPoly& poly) +{ + const unsigned int ip = (unsigned int)(&poly - tile.polys); + const dtPolyDetail& pd = tile.detailMeshes[ip]; + const Color& areaColor = NavMeshRuntime::NavAreasColors[poly.getArea()]; + const Color color = Color::Lerp(navMesh->Properties.Color, areaColor, areaColor.A); + const float drawOffsetY = 10.0f + ((float)GetHash(color) / (float)MAX_uint32) * 10.0f; // Apply some offset to prevent Z-fighting for different navmeshes + const Color fillColor = color * 0.5f; + const Color edgesColor = Color::FromHSV(color.ToHSV() + Vector3(20.0f, 0, -0.1f), color.A); + + for (int i = 0; i < pd.triCount; i++) + { + Vector3 v[3]; + const unsigned char* t = &tile.detailTris[(pd.triBase + i) * 4]; + + for (int k = 0; k < 3; k++) + { + if (t[k] < poly.vertCount) + { + v[k] = *(Vector3*)&tile.verts[poly.verts[t[k]] * 3]; + } + else + { + v[k] = *(Vector3*)&tile.detailVerts[(pd.vertBase + t[k] - poly.vertCount) * 3]; + } + } + + v[0].Y += drawOffsetY; + v[1].Y += drawOffsetY; + v[2].Y += drawOffsetY; + + Vector3::Transform(v[0], navMeshToWorld, v[0]); + Vector3::Transform(v[1], navMeshToWorld, v[1]); + Vector3::Transform(v[2], navMeshToWorld, v[2]); + + DEBUG_DRAW_TRIANGLE(v[0], v[1], v[2], fillColor, 0, true); + } + + for (int k = 0; k < pd.triCount; k++) + { + const unsigned char* t = &tile.detailTris[(pd.triBase + k) * 4]; + Vector3 v[3]; + + for (int m = 0; m < 3; m++) + { + if (t[m] < poly.vertCount) + v[m] = *(Vector3*)&tile.verts[poly.verts[t[m]] * 3]; + else + v[m] = *(Vector3*)&tile.detailVerts[(pd.vertBase + (t[m] - poly.vertCount)) * 3]; + } + + v[0].Y += drawOffsetY; + v[1].Y += drawOffsetY; + v[2].Y += drawOffsetY; + + Vector3::Transform(v[0], navMeshToWorld, v[0]); + Vector3::Transform(v[1], navMeshToWorld, v[1]); + Vector3::Transform(v[2], navMeshToWorld, v[2]); + + for (int m = 0, n = 2; m < 3; n = m++) + { + // Skip inner detail edges + if (((t[3] >> (n * 2)) & 0x3) == 0) + continue; + + DEBUG_DRAW_LINE(v[n], v[m], edgesColor, 0, true); + } + } +} + +void NavMeshRuntime::DebugDraw() +{ + ScopeLock lock(Locker); + + const dtNavMesh* dtNavMesh = GetNavMesh(); + const int tilesCount = dtNavMesh ? dtNavMesh->getMaxTiles() : 0; + if (tilesCount == 0) + return; + Matrix worldToNavMesh, navMeshToWorld; + Matrix::RotationQuaternion(Properties.Rotation, worldToNavMesh); + Matrix::Invert(worldToNavMesh, navMeshToWorld); + + for (int tileIndex = 0; tileIndex < tilesCount; tileIndex++) + { + const dtMeshTile* tile = dtNavMesh->getTile(tileIndex); + if (!tile->header) + continue; + + //DebugDraw::DrawWireBox(*(BoundingBox*)&tile->header->bmin[0], Color::CadetBlue); + + for (int i = 0; i < tile->header->polyCount; i++) + { + const dtPoly* poly = &tile->polys[i]; + if (poly->getType() != DT_POLYTYPE_GROUND) + continue; + + DrawPoly(this, navMeshToWorld, *tile, *poly); + } + } +} + +#endif + +void NavMeshRuntime::Dispose() +{ + if (_navMesh) + { + dtFreeNavMesh(_navMesh); + _navMesh = nullptr; + } + _tiles.Resize(0); +} + +void NavMeshRuntime::AddTileInternal(NavMesh* navMesh, NavMeshTileData& tileData) +{ + // Check if that tile has been added to navmesh + NavMeshTile* tile = nullptr; + const auto tileRef = _navMesh->getTileRefAt(tileData.PosX, tileData.PosY, tileData.Layer); + if (tileRef) + { + // Remove any existing tile at that location + if (dtStatusFailed(_navMesh->removeTile(tileRef, nullptr, nullptr))) + { + LOG(Warning, "Failed to remove tile from navmesh {0}.", Properties.Name); + } + + // Reuse tile data container + for (int32 i = 0; i < _tiles.Count(); i++) + { + auto& e = _tiles[i]; + if (e.X == tileData.PosX && e.Y == tileData.PosY && e.Layer == tileData.Layer) + { + tile = &e; + break; + } + } + } + if (!tile) + { + // Add tile + tile = &_tiles.AddOne(); + } + + // Copy tile properties + tile->NavMesh = navMesh; + tile->X = tileData.PosX; + tile->Y = tileData.PosY; + tile->Layer = tileData.Layer; +#if USE_DATA_LINK + tile->Data.Link(tileData.Data); +#else + tile->Data.Copy(tileData.Data); +#endif + + // Add tile to navmesh + const int32 dataSize = tile->Data.Length(); +#if USE_NAV_MESH_ALLOC + const auto flags = DT_TILE_FREE_DATA; + const auto data = (byte*)dtAlloc(dataSize, DT_ALLOC_PERM); + Platform::MemoryCopy(data, tile->Data.Get(), dataSize); +#else + const auto flags = 0; + const auto data = tile->Data.Get(); +#endif + if (dtStatusFailed(_navMesh->addTile(data, dataSize, flags, 0, nullptr))) + { + LOG(Warning, "Could not add tile to navmesh {0}.", Properties.Name); + } +} diff --git a/Source/Engine/Navigation/NavMeshRuntime.h b/Source/Engine/Navigation/NavMeshRuntime.h new file mode 100644 index 000000000..fd8dcf794 --- /dev/null +++ b/Source/Engine/Navigation/NavMeshRuntime.h @@ -0,0 +1,199 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Core/Types/BaseTypes.h" +#include "Engine/Platform/CriticalSection.h" +#include "NavMeshData.h" +#include "NavigationTypes.h" + +class dtNavMesh; +class dtNavMeshQuery; +class NavMesh; + +/// +/// The navigation mesh tile data. +/// +class FLAXENGINE_API NavMeshTile +{ +public: + + int32 X; + int32 Y; + int32 Layer; + NavMesh* NavMesh; + BytesContainer Data; +}; + +/// +/// The navigation mesh runtime object that builds the navmesh from all loaded scenes. +/// +class FLAXENGINE_API NavMeshRuntime +{ +public: + + // Gets the navigation mesh runtime for a given navmesh name. Return null if missing. + static NavMeshRuntime* Get(const StringView& navMeshName); + + // Gets the navigation mesh runtime for a given agent properties trying to pick the best matching navmesh. + static NavMeshRuntime* Get(const NavAgentProperties& agentProperties); + + // Gets the navigation mesh runtime for a given navmesh properties. + static NavMeshRuntime* Get(const NavMeshProperties& navMeshProperties, bool createIfMissing = false); + + // The lookup table that maps areaId of the navmesh to the current properties (applied by the NavigationSettings). Cached to improve runtime performance. + static float NavAreasCosts[64]; +#if COMPILE_WITH_DEBUG_DRAW + static Color NavAreasColors[64]; +#endif + +private: + + dtNavMesh* _navMesh; + dtNavMeshQuery* _navMeshQuery; + float _tileSize; + Array _tiles; + +public: + + NavMeshRuntime(const NavMeshProperties& properties); + ~NavMeshRuntime(); + +public: + + /// + /// The object locker. + /// + CriticalSection Locker; + + /// + /// The navigation mesh properties. + /// + NavMeshProperties Properties; + + /// + /// Gets the size of the tile (in world-units). Returns zero if not initialized yet. + /// + FORCE_INLINE float GetTileSize() const + { + return _tileSize; + } + + dtNavMesh* GetNavMesh() const + { + return _navMesh; + } + + dtNavMeshQuery* GetNavMeshQuery() const + { + return _navMeshQuery; + } + + int32 GetTilesCapacity() const; + +public: + + /// + /// Finds the distance from the specified start position to the nearest polygon wall. + /// + /// The start position. + /// The result hit information. Valid only when query succeed. + /// The maximum distance to search for wall (search radius). + /// True if ray hits an matching object, otherwise false. + bool FindDistanceToWall(const Vector3& startPosition, NavMeshHit& hitInfo, float maxDistance = MAX_float) const; + + /// + /// Finds the path between the two positions presented as a list of waypoints stored in the corners array. + /// + /// The start position. + /// The end position. + /// The result path. + /// True if found valid path between given two points (it may be partial), otherwise false if failed. + bool FindPath(const Vector3& startPosition, const Vector3& endPosition, Array& resultPath) const; + + /// + /// Tests the path between the two positions (non-partial). + /// + /// The start position. + /// The end position. + /// True if found valid path between given two points, otherwise false if failed. + bool TestPath(const Vector3& startPosition, const Vector3& endPosition) const; + + /// + /// Projects the point to nav mesh surface (finds the nearest polygon). + /// + /// The source point. + /// The result position on the navmesh (valid only if method returns true). + /// True if found valid location on the navmesh, otherwise false. + bool ProjectPoint(const Vector3& point, Vector3& result) const; + + /// + /// Casts a 'walkability' ray along the surface of the navigation mesh from the start position toward the end position. + /// + /// The start position. + /// The end position. + /// The result hit information. Valid only when query succeed. + /// True if ray hits an matching object, otherwise false. + bool RayCast(const Vector3& startPosition, const Vector3& endPosition, NavMeshHit& hitInfo) const; + +public: + + /// + /// Sets the size of the tile (if not assigned). Disposes the mesh if added tiles have different size. + /// + /// The size of the tile. + void SetTileSize(float tileSize); + + /// + /// Ensures the navmesh capacity for adding new tiles. Performs resizing if needed. + /// + /// The new tiles amount. + void EnsureCapacity(int32 tilesToAddCount); + + /// + /// Adds the tiles from the given scene to the runtime navmesh. + /// + /// The navigation mesh. + void AddTiles(NavMesh* navMesh); + + /// + /// Adds the tile from the given scene to the runtime navmesh. + /// + /// The navigation mesh. + /// The tile data. + void AddTile(NavMesh* navMesh, NavMeshTileData& tileData); + + /// + /// Removes all the tiles from the navmesh that has been added from the given navigation scene. + /// + /// The navigation mesh. + void RemoveTiles(NavMesh* navMesh); + + /// + /// Removes the tile from the navmesh. + /// + /// The tile X coordinate. + /// The tile Y coordinate. + /// The tile layer. + void RemoveTile(int32 x, int32 y, int32 layer); + + /// + /// Removes all the tiles that custom prediction callback marks. + /// + /// The prediction callback, returns true for tiles to remove and false for tiles to preserve. + /// The user data passed to the callback method. + void RemoveTiles(bool (*prediction)(const NavMeshRuntime* navMesh, const NavMeshTile& tile, void* customData), void* userData); + +#if COMPILE_WITH_DEBUG_DRAW + void DebugDraw(); +#endif + + /// + /// Releases the navmesh. + /// + void Dispose(); + +private: + + void AddTileInternal(NavMesh* navMesh, NavMeshTileData& tileData); +}; diff --git a/Source/Engine/Navigation/NavModifierVolume.cpp b/Source/Engine/Navigation/NavModifierVolume.cpp new file mode 100644 index 000000000..e72393a03 --- /dev/null +++ b/Source/Engine/Navigation/NavModifierVolume.cpp @@ -0,0 +1,84 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#include "NavModifierVolume.h" +#include "NavigationSettings.h" +#include "NavMeshBuilder.h" +#include "Engine/Level/Scene/Scene.h" +#include "Engine/Serialization/Serialization.h" +#if USE_EDITOR +#include "Editor/Editor.h" +#include "Editor/Managed/ManagedEditor.h" +#endif + +NavModifierVolume::NavModifierVolume(const SpawnParams& params) + : BoxVolume(params) +{ + _size = 100.0f; +} + +NavAreaProperties* NavModifierVolume::GetNavArea() const +{ + auto settings = NavigationSettings::Get(); + for (auto& navArea : settings->NavAreas) + { + if (navArea.Name == AreaName) + return &navArea; + } + return nullptr; +} + +void NavModifierVolume::Serialize(SerializeStream& stream, const void* otherObj) +{ + // Base + BoxVolume::Serialize(stream, otherObj); + + SERIALIZE_GET_OTHER_OBJ(NavModifierVolume); + + SERIALIZE_MEMBER(AgentsMask, AgentsMask.Mask); + SERIALIZE(AreaName); +} + +void NavModifierVolume::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) +{ + // Base + BoxVolume::Deserialize(stream, modifier); + + DESERIALIZE_MEMBER(AgentsMask, AgentsMask.Mask); + DESERIALIZE(AreaName); +} + +void NavModifierVolume::OnBoundsChanged(const BoundingBox& prevBounds) +{ +#if COMPILE_WITH_NAV_MESH_BUILDER + // Auto-rebuild modified navmesh area + if ( + IsDuringPlay() && IsActiveInHierarchy() && HasStaticFlag(StaticFlags::Navigation) && + ( + // Build at runtime for dynamic modifiers + !HasStaticFlag(StaticFlags::Transform) +#if USE_EDITOR + // Build in editor when using auto-rebuild option + || (!Editor::IsPlayMode && Editor::Managed->CanAutoBuildNavMesh()) +#endif + )) + { + BoundingBox dirtyBounds; + BoundingBox::Merge(prevBounds, _box, dirtyBounds); +#if USE_EDITOR + const float timeoutMs = ManagedEditor::ManagedEditorOptions.AutoRebuildNavMeshTimeoutMs; +#else + const float timeoutMs = 0.0f; +#endif + NavMeshBuilder::Build(GetScene(), dirtyBounds, timeoutMs); + } +#endif +} + +#if USE_EDITOR + +Color NavModifierVolume::GetWiresColor() +{ + return Color::Red; +} + +#endif diff --git a/Source/Engine/Navigation/NavModifierVolume.h b/Source/Engine/Navigation/NavModifierVolume.h new file mode 100644 index 000000000..9b6a3e764 --- /dev/null +++ b/Source/Engine/Navigation/NavModifierVolume.h @@ -0,0 +1,48 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Level/Actors/BoxVolume.h" +#include "NavigationTypes.h" + +/// +/// A special type of volume that defines the area of the scene in which navigation is restricted (eg. higher traversal cost or dynamic obstacle block). +/// +API_CLASS() class FLAXENGINE_API NavModifierVolume : public BoxVolume +{ +DECLARE_SCENE_OBJECT(NavModifierVolume); +public: + + /// + /// The agent types used by this navmesh modifier volume (from navigation settings). Can be used to adjust navmesh for a certain set of agents. + /// + API_FIELD(Attributes="EditorDisplay(\"Navigation\"), EditorOrder(10)") + NavAgentMask AgentsMask; + + /// + /// The name of the nav area to apply within the modifiers volume. Nav area properties are picked from the Navigation Settings asset. + /// + API_FIELD(Attributes="EditorDisplay(\"Navigation\"), EditorOrder(20)") + String AreaName; + +public: + + /// + /// Gets the properties of the nav area used by this volume. Null if missing or invalid area name. + /// + NavAreaProperties* GetNavArea() const; + +public: + + // [BoxVolume] + void Serialize(SerializeStream& stream, const void* otherObj) override; + void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; + +protected: + + // [BoxVolume] + void OnBoundsChanged(const BoundingBox& prevBounds) override; +#if USE_EDITOR + Color GetWiresColor() override; +#endif +}; diff --git a/Source/Engine/Navigation/Navigation.Build.cs b/Source/Engine/Navigation/Navigation.Build.cs index 85662915f..647995cdf 100644 --- a/Source/Engine/Navigation/Navigation.Build.cs +++ b/Source/Engine/Navigation/Navigation.Build.cs @@ -15,6 +15,7 @@ public class Navigation : EngineModule options.PublicDefinitions.Add("COMPILE_WITH_NAV_MESH_BUILDER"); + options.PrivateDependencies.Add("Level"); options.PrivateDependencies.Add("recastnavigation"); if (options.Target.IsEditor) diff --git a/Source/Engine/Navigation/Navigation.cpp b/Source/Engine/Navigation/Navigation.cpp index ac23bedea..9ff3498d7 100644 --- a/Source/Engine/Navigation/Navigation.cpp +++ b/Source/Engine/Navigation/Navigation.cpp @@ -1,19 +1,153 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "Navigation.h" -#include "Engine/Threading/Threading.h" -#include "Engine/Level/Scene/Scene.h" -#include "Engine/Engine/EngineService.h" +#include "NavigationSettings.h" +#include "NavMeshRuntime.h" #include "NavMeshBuilder.h" +#include "Engine/Core/Config/GameSettings.h" +#include "Engine/Content/Content.h" +#include "Engine/Content/JsonAsset.h" +#include "Engine/Threading/Threading.h" +#if USE_EDITOR +#include "Engine/Level/Level.h" +#include "Engine/Level/Scene/Scene.h" +#endif #include "NavMesh.h" -#include + +#include "Engine/Engine/EngineService.h" +#include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Serialization/Serialization.h" #include -#include +#include -#define DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL 50.0f -#define DEFAULT_NAV_QUERY_EXTENT_VERTICAL 250.0f +namespace +{ + Array> NavMeshes; +} -NavMesh* _navMesh = nullptr; +NavMeshRuntime* NavMeshRuntime::Get(const StringView& navMeshName) +{ + NavMeshRuntime* result = nullptr; + for (auto navMesh : NavMeshes) + { + if (navMesh->Properties.Name == navMeshName) + { + result = navMesh; + break; + } + } + return result; +} + +NavMeshRuntime* NavMeshRuntime::Get(const NavAgentProperties& agentProperties) +{ + NavMeshRuntime* result = nullptr; + // TODO: maybe build lookup table for agentProperties -> navMesh to improve perf on frequent calls? + float bestAgentRadiusDiff = -MAX_float; + float bestAgentHeightDiff = -MAX_float; + bool bestIsValid = false; + for (auto navMesh : NavMeshes) + { + const auto& navMeshProperties = navMesh->Properties; + const float agentRadiusDiff = navMeshProperties.Agent.Radius - agentProperties.Radius; + const float agentHeightDiff = navMeshProperties.Agent.Height - agentProperties.Height; + const bool isValid = agentRadiusDiff >= 0.0f && agentHeightDiff >= 0.0f; + + // NavMesh must be valid for an agent and be first valid or have better properties than the best matching result so far + if (isValid + && + ( + !bestIsValid + || + (agentRadiusDiff + agentHeightDiff < bestAgentRadiusDiff + bestAgentHeightDiff) + ) + ) + { + result = navMesh; + bestIsValid = true; + bestAgentRadiusDiff = agentRadiusDiff; + bestAgentHeightDiff = agentHeightDiff; + } + } + return result; +} + +NavMeshRuntime* NavMeshRuntime::Get(const NavMeshProperties& navMeshProperties, bool createIfMissing) +{ + NavMeshRuntime* result = nullptr; + for (auto navMesh : NavMeshes) + { + if (navMesh->Properties == navMeshProperties) + { + result = navMesh; + break; + } + } + if (!result && createIfMissing) + { + // Create a new navmesh + result = New(navMeshProperties); + NavMeshes.Add(result); + } + return result; +} + +static_assert(ARRAY_COUNT(NavMeshRuntime::NavAreasCosts) == DT_MAX_AREAS, "Invalid nav areas amount limit."); +float NavMeshRuntime::NavAreasCosts[64]; +#if COMPILE_WITH_DEBUG_DRAW +Color NavMeshRuntime::NavAreasColors[64]; +#endif + +bool NavAgentProperties::operator==(const NavAgentProperties& other) const +{ + return Math::NearEqual(Radius, other.Radius) && Math::NearEqual(Height, other.Height) && Math::NearEqual(StepHeight, other.StepHeight) && Math::NearEqual(MaxSlopeAngle, other.MaxSlopeAngle); +} + +bool NavAgentMask::IsAgentSupported(int32 agentIndex) const +{ + return (Mask & (1 << agentIndex)) != 0; +} + +bool NavAgentMask::IsAgentSupported(const NavAgentProperties& agentProperties) const +{ + auto settings = NavigationSettings::Get(); + for (int32 agentIndex = 0; agentIndex < settings->NavMeshes.Count(); agentIndex++) + { + if (settings->NavMeshes[agentIndex].Agent == agentProperties) + { + return (Mask & (1 << agentIndex)) != 0; + } + } + return false; +} + +bool NavAgentMask::IsNavMeshSupported(const NavMeshProperties& navMeshProperties) const +{ + auto settings = NavigationSettings::Get(); + for (int32 agentIndex = 0; agentIndex < settings->NavMeshes.Count(); agentIndex++) + { + if (settings->NavMeshes[agentIndex] == navMeshProperties) + { + return (Mask & (1 << agentIndex)) != 0; + } + } + return false; +} + +bool NavAgentMask::operator==(const NavAgentMask& other) const +{ + return Mask == other.Mask; +} + +bool NavAreaProperties::operator==(const NavAreaProperties& other) const +{ + return Name == other.Name && Id == other.Id && Math::NearEqual(Cost, other.Cost); +} + +bool NavMeshProperties::operator==(const NavMeshProperties& other) const +{ + return Name == other.Name && Quaternion::NearEqual(Rotation, other.Rotation, 0.001f) && Agent == other.Agent; +} class NavigationService : public EngineService { @@ -36,11 +170,6 @@ public: NavigationService NavigationServiceInstance; -NavMesh* Navigation::GetNavMesh() -{ - return _navMesh; -} - void* dtAllocDefault(size_t size, dtAllocHint) { return Allocator::Allocate(size); @@ -51,15 +180,89 @@ void* rcAllocDefault(size_t size, rcAllocHint) return Allocator::Allocate(size); } +NavigationSettings::NavigationSettings() +{ + // Init navmeshes + NavMeshes.Resize(1); + auto& navMesh = NavMeshes[0]; + navMesh.Name = TEXT("Default"); + + // Init nav areas + NavAreas.Resize(2); + auto& areaNull = NavAreas[0]; + areaNull.Name = TEXT("Null"); + areaNull.Color = Color::Transparent; + areaNull.Id = 0; + areaNull.Cost = MAX_float; + auto& areaWalkable = NavAreas[1]; + areaWalkable.Name = TEXT("Walkable"); + areaWalkable.Color = Color::Transparent; + areaWalkable.Id = 63; + areaWalkable.Cost = 1; +} + +IMPLEMENT_SETTINGS_GETTER(NavigationSettings, Navigation); + +void NavigationSettings::Apply() +{ + // Cache areas properties + for (auto& area : NavAreas) + { + if (area.Id < DT_MAX_AREAS) + { + NavMeshRuntime::NavAreasCosts[area.Id] = area.Cost; +#if USE_EDITOR + NavMeshRuntime::NavAreasColors[area.Id] = area.Color; +#endif + } + } +} + +void NavigationSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) +{ + DESERIALIZE(AutoAddMissingNavMeshes); + DESERIALIZE(AutoRemoveMissingNavMeshes); + DESERIALIZE(CellHeight); + DESERIALIZE(CellSize); + DESERIALIZE(TileSize); + DESERIALIZE(MinRegionArea); + DESERIALIZE(MergeRegionArea); + DESERIALIZE(MaxEdgeLen); + DESERIALIZE(MaxEdgeError); + DESERIALIZE(DetailSamplingDist); + DESERIALIZE(MaxDetailSamplingError); + if (modifier->EngineBuild >= 6215) + { + DESERIALIZE(NavMeshes); + } + else + { + // [Deprecated on 12.01.2021, expires on 12.01.2022] + float WalkableRadius = 34.0f; + float WalkableHeight = 144.0f; + float WalkableMaxClimb = 35.0f; + float WalkableMaxSlopeAngle = 60.0f; + DESERIALIZE(WalkableRadius); + DESERIALIZE(WalkableHeight); + DESERIALIZE(WalkableMaxClimb); + DESERIALIZE(WalkableMaxSlopeAngle); + NavMeshes.Resize(1); + auto& navMesh = NavMeshes[0]; + navMesh.Name = TEXT("Default"); + navMesh.Agent.Radius = WalkableRadius; + navMesh.Agent.Height = WalkableHeight; + navMesh.Agent.StepHeight = WalkableMaxClimb; + navMesh.Agent.MaxSlopeAngle = WalkableMaxSlopeAngle; + } + DESERIALIZE(NavAreas); +} + bool NavigationService::Init() { // Link memory allocation calls to use engine default allocator dtAllocSetCustom(dtAllocDefault, Allocator::Free); rcAllocSetCustom(rcAllocDefault, Allocator::Free); - // Create global nav mesh - _navMesh = New(); - return false; } @@ -74,156 +277,49 @@ void NavigationService::Update() void NavigationService::Dispose() { - if (_navMesh) + // Release nav meshes + for (auto navMesh : NavMeshes) { - _navMesh->Dispose(); - Delete(_navMesh); - _navMesh = nullptr; + navMesh->Dispose(); + Delete(navMesh); } + NavMeshes.Clear(); + NavMeshes.ClearDelete(); } bool Navigation::FindDistanceToWall(const Vector3& startPosition, NavMeshHit& hitInfo, float maxDistance) { - ScopeLock lock(_navMesh->Locker); - - const auto query = _navMesh->GetNavMeshQuery(); - if (!query) - { + if (NavMeshes.IsEmpty()) return false; - } - - dtQueryFilter filter; - Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL); - - dtPolyRef startPoly = 0; - query->findNearestPoly(&startPosition.X, &extent.X, &filter, &startPoly, nullptr); - if (!startPoly) - { - return false; - } - - return dtStatusSucceed(_navMesh->GetNavMeshQuery()->findDistanceToWall(startPoly, &startPosition.X, maxDistance, &filter, &hitInfo.Distance, &hitInfo.Position.X, &hitInfo.Normal.X)); + return NavMeshes.First()->FindDistanceToWall(startPosition, hitInfo, maxDistance); } bool Navigation::FindPath(const Vector3& startPosition, const Vector3& endPosition, Array& resultPath) { - resultPath.Clear(); - ScopeLock lock(_navMesh->Locker); - - const auto query = _navMesh->GetNavMeshQuery(); - if (!query) - { + if (NavMeshes.IsEmpty()) return false; - } + return NavMeshes.First()->FindPath(startPosition, endPosition, resultPath); +} - dtQueryFilter filter; - Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL); - - dtPolyRef startPoly = 0; - query->findNearestPoly(&startPosition.X, &extent.X, &filter, &startPoly, nullptr); - if (!startPoly) - { +bool Navigation::TestPath(const Vector3& startPosition, const Vector3& endPosition) +{ + if (NavMeshes.IsEmpty()) return false; - } - dtPolyRef endPoly = 0; - query->findNearestPoly(&endPosition.X, &extent.X, &filter, &endPoly, nullptr); - if (!endPoly) - { - return false; - } - - dtPolyRef path[NAV_MESH_PATH_MAX_SIZE]; - int32 pathSize; - const auto findPathStatus = _navMesh->GetNavMeshQuery()->findPath(startPoly, endPoly, &startPosition.X, &endPosition.X, &filter, path, &pathSize, NAV_MESH_PATH_MAX_SIZE); - if (dtStatusFailed(findPathStatus)) - { - return false; - } - - // Check for special case, where path has not been found, and starting polygon was the one closest to the target - if (pathSize == 1 && dtStatusDetail(findPathStatus, DT_PARTIAL_RESULT)) - { - // In this case we find a point on starting polygon, that's closest to destination and store it as path end - resultPath.Resize(2); - resultPath[0] = startPosition; - resultPath[1] = startPosition; - _navMesh->GetNavMeshQuery()->closestPointOnPolyBoundary(startPoly, &endPosition.X, &resultPath[1].X); - } - else - { - int straightPathCount = 0; - resultPath.EnsureCapacity(NAV_MESH_PATH_MAX_SIZE); - const auto findStraightPathStatus = _navMesh->GetNavMeshQuery()->findStraightPath(&startPosition.X, &endPosition.X, path, pathSize, (float*)resultPath.Get(), nullptr, nullptr, &straightPathCount, resultPath.Capacity(), DT_STRAIGHTPATH_AREA_CROSSINGS); - if (dtStatusFailed(findStraightPathStatus)) - { - return false; - } - resultPath.Resize(straightPathCount); - } - - return true; + return NavMeshes.First()->TestPath(startPosition, endPosition); } bool Navigation::ProjectPoint(const Vector3& point, Vector3& result) { - ScopeLock lock(_navMesh->Locker); - - const auto query = _navMesh->GetNavMeshQuery(); - if (!query) - { + if (NavMeshes.IsEmpty()) return false; - } - - dtQueryFilter filter; - Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL); - - dtPolyRef startPoly = 0; - query->findNearestPoly(&point.X, &extent.X, &filter, &startPoly, &result.X); - if (!startPoly) - { - return false; - } - - return true; + return NavMeshes.First()->ProjectPoint(point, result); } bool Navigation::RayCast(const Vector3& startPosition, const Vector3& endPosition, NavMeshHit& hitInfo) { - ScopeLock lock(_navMesh->Locker); - - const auto query = _navMesh->GetNavMeshQuery(); - if (!query) - { + if (NavMeshes.IsEmpty()) return false; - } - - dtQueryFilter filter; - Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL); - - dtPolyRef startPoly = 0; - query->findNearestPoly(&startPosition.X, &extent.X, &filter, &startPoly, nullptr); - if (!startPoly) - { - return false; - } - - dtRaycastHit hit; - hit.path = nullptr; - hit.maxPath = 0; - const bool result = dtStatusSucceed(_navMesh->GetNavMeshQuery()->raycast(startPoly, &startPosition.X, &endPosition.X, &filter, 0, &hit)); - if (hit.t >= MAX_float) - { - hitInfo.Position = endPosition; - hitInfo.Distance = 0; - } - else - { - hitInfo.Position = startPosition + (endPosition - startPosition) * hit.t; - hitInfo.Distance = hit.t; - } - hitInfo.Normal = *(Vector3*)&hit.hitNormal; - - return result; + return NavMeshes.First()->RayCast(startPosition, endPosition, hitInfo); } #if COMPILE_WITH_NAV_MESH_BUILDER @@ -250,96 +346,34 @@ void Navigation::BuildNavMesh(Scene* scene, const BoundingBox& dirtyBounds, floa #endif -#if USE_EDITOR - -#include "Engine/Debug/DebugDraw.h" - -void DrawPoly(const dtMeshTile& tile, const dtPoly& poly) -{ - const unsigned int ip = (unsigned int)(&poly - tile.polys); - const dtPolyDetail& pd = tile.detailMeshes[ip]; - const float DrawOffsetY = 20.0f; - - for (int i = 0; i < pd.triCount; i++) - { - Vector3 v[3]; - const unsigned char* t = &tile.detailTris[(pd.triBase + i) * 4]; - - for (int k = 0; k < 3; k++) - { - if (t[k] < poly.vertCount) - { - v[k] = *(Vector3*)&tile.verts[poly.verts[t[k]] * 3]; - } - else - { - v[k] = *(Vector3*)&tile.detailVerts[(pd.vertBase + t[k] - poly.vertCount) * 3]; - } - } - - v[0].Y += DrawOffsetY; - v[1].Y += DrawOffsetY; - v[2].Y += DrawOffsetY; - - DEBUG_DRAW_TRIANGLE(v[0], v[1], v[2], Color::Green * 0.5f, 0, true); - } - - for (int k = 0; k < pd.triCount; k++) - { - const unsigned char* t = &tile.detailTris[(pd.triBase + k) * 4]; - Vector3 v[3]; - - for (int m = 0; m < 3; m++) - { - if (t[m] < poly.vertCount) - v[m] = *(Vector3*)&tile.verts[poly.verts[t[m]] * 3]; - else - v[m] = *(Vector3*)&tile.detailVerts[(pd.vertBase + (t[m] - poly.vertCount)) * 3]; - } - - v[0].Y += DrawOffsetY; - v[1].Y += DrawOffsetY; - v[2].Y += DrawOffsetY; - - for (int m = 0, n = 2; m < 3; n = m++) - { - // Skip inner detail edges - if (((t[3] >> (n * 2)) & 0x3) == 0) - continue; - - DEBUG_DRAW_LINE(v[n], v[m], Color::YellowGreen, 0, true); - } - } -} +#if COMPILE_WITH_DEBUG_DRAW void Navigation::DrawNavMesh() { - if (!_navMesh) - return; - - ScopeLock lock(_navMesh->Locker); - - const dtNavMesh* dtNavMesh = _navMesh->GetNavMesh(); - const int tilesCount = dtNavMesh ? dtNavMesh->getMaxTiles() : 0; - if (tilesCount == 0) - return; - - for (int tileIndex = 0; tileIndex < tilesCount; tileIndex++) + for (auto navMesh : NavMeshes) { - const dtMeshTile* tile = dtNavMesh->getTile(tileIndex); - if (!tile->header) - continue; - - //DebugDraw::DrawWireBox(*(BoundingBox*)&tile->header->bmin[0], Color::CadetBlue); - - for (int i = 0; i < tile->header->polyCount; i++) +#if USE_EDITOR + // Skip drawing if any of the navmeshes on scene has disabled ShowDebugDraw option + bool skip = false; + for (auto scene : Level::Scenes) { - const dtPoly* poly = &tile->polys[i]; - if (poly->getType() != DT_POLYTYPE_GROUND) - continue; - - DrawPoly(*tile, *poly); + for (auto e : scene->NavigationMeshes) + { + if (e->Properties == navMesh->Properties) + { + if (!e->ShowDebugDraw) + { + skip = true; + } + break; + } + } } + if (skip) + continue; +#endif + + navMesh->DebugDraw(); } } diff --git a/Source/Engine/Navigation/Navigation.h b/Source/Engine/Navigation/Navigation.h index 8c896099b..dcd6b9fbb 100644 --- a/Source/Engine/Navigation/Navigation.h +++ b/Source/Engine/Navigation/Navigation.h @@ -2,36 +2,9 @@ #pragma once -#include "Engine/Scripting/ScriptingType.h" -#include "Engine/Core/Math/Vector3.h" +#include "NavigationTypes.h" class Scene; -class NavMesh; - -#define NAV_MESH_PATH_MAX_SIZE 200 - -/// -/// The result information for navigation mesh queries. -/// -API_STRUCT() struct NavMeshHit -{ -DECLARE_SCRIPTING_TYPE_MINIMAL(NavMeshHit); - - /// - /// The hit point position. - /// - API_FIELD() Vector3 Position; - - /// - /// The distance to hit point (from the query origin). - /// - API_FIELD() float Distance; - - /// - /// The hit point normal vector. - /// - API_FIELD() Vector3 Normal; -}; /// /// The navigation service used for path finding and agents navigation system. @@ -39,14 +12,6 @@ DECLARE_SCRIPTING_TYPE_MINIMAL(NavMeshHit); API_CLASS(Static) class FLAXENGINE_API Navigation { DECLARE_SCRIPTING_TYPE_NO_SPAWN(Navigation); -public: - - /// - /// Gets the navigation mesh (read-only). Use the other API to request data to use safe access to the navigation system. - /// - /// The navigation mesh. - static NavMesh* GetNavMesh(); - public: /// @@ -67,6 +32,14 @@ public: /// True if found valid path between given two points (it may be partial), otherwise false if failed. API_FUNCTION() static bool FindPath(const Vector3& startPosition, const Vector3& endPosition, API_PARAM(Out) Array& resultPath); + /// + /// Tests the path between the two positions (non-partial). + /// + /// The start position. + /// The end position. + /// True if found valid path between given two points, otherwise false if failed. + API_FUNCTION() static bool TestPath(const Vector3& startPosition, const Vector3& endPosition); + /// /// Projects the point to nav mesh surface (finds the nearest polygon). /// @@ -121,7 +94,7 @@ public: #endif -#if USE_EDITOR +#if COMPILE_WITH_DEBUG_DRAW /// /// Draws the navigation for all the scenes (uses DebugDraw interface). diff --git a/Source/Engine/Navigation/NavigationScene.cpp b/Source/Engine/Navigation/NavigationScene.cpp deleted file mode 100644 index c92c14ac1..000000000 --- a/Source/Engine/Navigation/NavigationScene.cpp +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -#include "NavigationScene.h" -#include "NavMesh.h" -#include "Navigation.h" -#include "NavMeshBoundsVolume.h" -#include "Engine/Profiler/ProfilerCPU.h" -#include "Engine/Level/Scene/Scene.h" -#include "Engine/Content/Assets/RawDataAsset.h" -#include "Engine/Core/Log.h" -#if USE_EDITOR -#include "Editor/Editor.h" -#endif -#if COMPILE_WITH_ASSETS_IMPORTER -#include "Engine/ContentImporters/AssetsImportingManager.h" -#include "Engine/Serialization/MemoryWriteStream.h" -#endif - -NavigationScene::NavigationScene(::Scene* scene) - : Scene(scene) - , IsDataDirty(false) -{ - DataAsset.Loaded.Bind(this); -} - -BoundingBox NavigationScene::GetNavigationBounds() -{ - if (Volumes.IsEmpty()) - return BoundingBox::Empty; - - PROFILE_CPU_NAMED("GetNavigationBounds"); - - auto box = Volumes[0]->GetBox(); - for (int32 i = 1; i < Volumes.Count(); i++) - BoundingBox::Merge(box, Volumes[i]->GetBox(), box); - return box; -} - -NavMeshBoundsVolume* NavigationScene::FindNavigationBoundsOverlap(const BoundingBox& bounds) -{ - NavMeshBoundsVolume* result = nullptr; - - for (int32 i = 0; i < Volumes.Count(); i++) - { - if (Volumes[i]->GetBox().Intersects(bounds)) - { - result = Volumes[i]; - break; - } - } - - return result; -} - -void NavigationScene::SaveNavMesh() -{ -#if COMPILE_WITH_ASSETS_IMPORTER - -#if USE_EDITOR - // Skip if game is running in editor (eg. game scripts update dynamic navmesh) - if (Editor::IsPlayMode) - return; -#endif - - // Clear flag - IsDataDirty = false; - - // Check if has no navmesh data generated (someone could just remove navmesh volumes or generate for empty scene) - if (Data.Tiles.IsEmpty()) - { - // Keep asset reference valid - DataAsset.Unlink(); - return; - } - - // Prepare - Guid assetId = DataAsset.GetID(); - if (!assetId.IsValid()) - assetId = Guid::New(); - const String assetPath = Scene->GetDataFolderPath() / TEXT("NavMesh") + ASSET_FILES_EXTENSION_WITH_DOT; - - // Generate navmesh tiles data - const int32 streamInitialCapacity = Math::RoundUpToPowerOf2((Data.Tiles.Count() + 1) * 1024); - MemoryWriteStream stream(streamInitialCapacity); - Data.Save(stream); - BytesContainer bytesContainer; - bytesContainer.Link(stream.GetHandle(), stream.GetPosition()); - - // Save asset to file - if (AssetsImportingManager::Create(AssetsImportingManager::CreateRawDataTag, assetPath, assetId, (void*)&bytesContainer)) - { - LOG(Warning, "Failed to save navmesh tiles data to file."); - return; - } - - // Link the created asset - DataAsset = assetId; - -#endif -} - -void NavigationScene::OnEnable() -{ - auto navMesh = Navigation::GetNavMesh(); - if (navMesh) - navMesh->AddTiles(this); -} - -void NavigationScene::OnDisable() -{ - auto navMesh = Navigation::GetNavMesh(); - if (navMesh) - navMesh->RemoveTiles(this); -} - -void NavigationScene::OnDataAssetLoaded() -{ - // Skip if already has data (prevent reloading navmesh on saving) - if (Data.Tiles.HasItems()) - return; - - const bool isEnabled = Scene->IsDuringPlay() && Scene->IsActiveInHierarchy(); - - // Remove added tiles - if (isEnabled) - { - OnDisable(); - } - - // Load navmesh tiles - BytesContainer data; - data.Link(DataAsset->Data); - Data.Load(data, false); - IsDataDirty = false; - - // Add loaded tiles - if (isEnabled) - { - OnEnable(); - } -} diff --git a/Source/Engine/Navigation/NavigationScene.h b/Source/Engine/Navigation/NavigationScene.h deleted file mode 100644 index ecdca0744..000000000 --- a/Source/Engine/Navigation/NavigationScene.h +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -#pragma once - -#include "NavMeshData.h" -#include "Engine/Content/AssetReference.h" -#include "Engine/Content/Assets/RawDataAsset.h" - -class Scene; -class NavMeshBoundsVolume; - -/// -/// Scene object navigation data. -/// -class NavigationScene -{ - friend Scene; - -public: - - /// - /// Initializes a new instance of the class. - /// - /// The scene. - NavigationScene(Scene* scene); - -public: - - /// - /// The parent scene. - /// - Scene* Scene; - - /// - /// The flag used to mark that navigation data has been modified since load. Used to save runtime data to the file on scene serialization. - /// - bool IsDataDirty; - - /// - /// The navmesh tiles data. - /// - NavMeshData Data; - - /// - /// The cached navmesh data asset. - /// - AssetReference DataAsset; - -public: - - /// - /// The list of registered navigation bounds volumes (in the scene). - /// - Array Volumes; - - /// - /// Gets the total navigation volumes bounds. - /// - /// The navmesh bounds. - BoundingBox GetNavigationBounds(); - - /// - /// Finds the navigation volume bounds that have intersection with the given world-space bounding box. - /// - /// The bounds. - /// The intersecting volume or null if none found. - NavMeshBoundsVolume* FindNavigationBoundsOverlap(const BoundingBox& bounds); - -public: - - /// - /// Saves the nav mesh tiles data to the asset. Supported only in builds with assets saving enabled (eg. editor) and not during gameplay (eg. design time). - /// - void SaveNavMesh(); - - /// - /// Clears the data. - /// - void ClearData() - { - if (Data.Tiles.HasItems()) - { - IsDataDirty = true; - Data.TileSize = 0.0f; - Data.Tiles.Resize(0); - } - } - -protected: - - void OnEnable(); - void OnDisable(); - void OnDataAssetLoaded(); -}; diff --git a/Source/Engine/Navigation/NavigationSettings.cs b/Source/Engine/Navigation/NavigationSettings.cs new file mode 100644 index 000000000..8a97e4adc --- /dev/null +++ b/Source/Engine/Navigation/NavigationSettings.cs @@ -0,0 +1,116 @@ +// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved. + +using System; +using FlaxEngine; + +namespace FlaxEditor.Content.Settings +{ + partial class NavigationSettings + { + /// + /// Initializes a new instance of the class. + /// + public NavigationSettings() + { + // Init navmeshes + NavMeshes = new NavMeshProperties[1]; + ref var navMesh = ref NavMeshes[0]; + navMesh.Name = "Default"; + navMesh.Color = Color.Green; + navMesh.Rotation = Quaternion.Identity; + navMesh.Agent.Radius = 34.0f; + navMesh.Agent.Height = 144.0f; + navMesh.Agent.StepHeight = 35.0f; + navMesh.Agent.MaxSlopeAngle = 60.0f; + + // Init nav areas + NavAreas = new NavAreaProperties[2]; + ref var areaNull = ref NavAreas[0]; + areaNull.Name = "Null"; + areaNull.Color = Color.Transparent; + areaNull.Id = 0; + areaNull.Cost = float.MaxValue; + ref var areaWalkable = ref NavAreas[1]; + areaWalkable.Name = "Walkable"; + areaWalkable.Color = Color.Transparent; + areaWalkable.Id = 63; + areaWalkable.Cost = 1; + } + + // [Deprecated on 12.01.2021, expires on 12.01.2022] + private void UpgradeToNavMeshes() + { + if (NavMeshes != null && NavMeshes.Length == 1) + return; + NavMeshes = new NavMeshProperties[1]; + ref var navMesh = ref NavMeshes[0]; + navMesh.Name = "Default"; + navMesh.Color = Color.Green; + navMesh.Rotation = Quaternion.Identity; + navMesh.Agent.Radius = 34.0f; + navMesh.Agent.Height = 144.0f; + navMesh.Agent.StepHeight = 35.0f; + navMesh.Agent.MaxSlopeAngle = 60.0f; + } + + // [Deprecated on 12.01.2021, expires on 12.01.2022] + [Serialize, Obsolete] + private float WalkableRadius + { + get => throw new Exception(); + set + { + UpgradeToNavMeshes(); + NavMeshes[0].Agent.Radius = value; + } + } + + // [Deprecated on 12.01.2021, expires on 12.01.2022] + [Serialize, Obsolete] + private float WalkableHeight + { + get => throw new Exception(); + set + { + UpgradeToNavMeshes(); + NavMeshes[0].Agent.Height = value; + } + } + + // [Deprecated on 12.01.2021, expires on 12.01.2022] + [Serialize, Obsolete] + private float WalkableMaxClimb + { + get => throw new Exception(); + set + { + UpgradeToNavMeshes(); + NavMeshes[0].Agent.StepHeight = value; + } + } + + // [Deprecated on 12.01.2021, expires on 12.01.2022] + [Serialize, Obsolete] + private float WalkableMaxSlopeAngle + { + get => throw new Exception(); + set + { + UpgradeToNavMeshes(); + NavMeshes[0].Agent.MaxSlopeAngle = value; + } + } + } +} + +namespace FlaxEngine +{ + partial struct NavAgentProperties + { + /// + public override string ToString() + { + return $"Radius: {Radius}, Height: {Height}, StepHeight: {StepHeight}, MaxSlopeAngle: {MaxSlopeAngle}"; + } + } +} diff --git a/Source/Engine/Navigation/NavigationSettings.h b/Source/Engine/Navigation/NavigationSettings.h index 1e053d819..0b35d5153 100644 --- a/Source/Engine/Navigation/NavigationSettings.h +++ b/Source/Engine/Navigation/NavigationSettings.h @@ -2,118 +2,110 @@ #pragma once +#include "NavigationTypes.h" #include "Engine/Core/Config/Settings.h" -#include "Engine/Serialization/Serialization.h" +#include "Engine/Core/Collections/Array.h" /// /// The navigation system settings container. /// -/// -class NavigationSettings : public Settings +API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings", NoConstructor) class FLAXENGINE_API NavigationSettings : public SettingsBase { +DECLARE_SCRIPTING_TYPE_MINIMAL(NavigationSettings); public: /// - /// The height of a grid cell in the navigation mesh building steps using heightfields. - /// A lower number means higher precision on the vertical axis but longer build times. + /// If checked, enables automatic navmesh actors spawning on a scenes that are using it during navigation building. /// + API_FIELD(Attributes="EditorOrder(100), EditorDisplay(\"Navigation\")") + bool AutoAddMissingNavMeshes = true; + + /// + /// If checked, enables automatic navmesh actors removing from a scenes that are not using it during navigation building. + /// + API_FIELD(Attributes="EditorOrder(110), EditorDisplay(\"Navigation\")") + bool AutoRemoveMissingNavMeshes = true; + +public: + + /// + /// The height of a grid cell in the navigation mesh building steps using heightfields. A lower number means higher precision on the vertical axis but longer build times. + /// + API_FIELD(Attributes="Limit(1, 400), EditorOrder(210), EditorDisplay(\"Nav Mesh Options\")") float CellHeight = 10.0f; /// - /// The width/height of a grid cell in the navigation mesh building steps using heightfields. - /// A lower number means higher precision on the horizontal axes but longer build times. + /// The width/height of a grid cell in the navigation mesh building steps using heightfields. A lower number means higher precision on the horizontal axes but longer build times. /// + API_FIELD(Attributes="Limit(1, 400), EditorOrder(220), EditorDisplay(\"Nav Mesh Options\")") float CellSize = 30.0f; /// /// Tile size used for Navigation mesh tiles, the final size of a tile is CellSize*TileSize. /// + API_FIELD(Attributes="Limit(8, 4096), EditorOrder(230), EditorDisplay(\"Nav Mesh Options\")") int32 TileSize = 64; /// /// The minimum number of cells allowed to form isolated island areas. /// + API_FIELD(Attributes="Limit(0, 100), EditorOrder(240), EditorDisplay(\"Nav Mesh Options\")") int32 MinRegionArea = 0; /// /// Any regions with a span count smaller than this value will, if possible, be merged with larger regions. /// + API_FIELD(Attributes="Limit(0, 100), EditorOrder(250), EditorDisplay(\"Nav Mesh Options\")") int32 MergeRegionArea = 20; /// /// The maximum allowed length for contour edges along the border of the mesh. /// + API_FIELD(Attributes="Limit(100), EditorOrder(260), EditorDisplay(\"Nav Mesh Options\", \"Max Edge Length\")") float MaxEdgeLen = 1200.0f; /// /// The maximum distance a simplified contour's border edges should deviate from the original raw contour. /// + API_FIELD(Attributes="Limit(0.1f, 4), EditorOrder(270), EditorDisplay(\"Nav Mesh Options\")") float MaxEdgeError = 1.3f; /// /// The sampling distance to use when generating the detail mesh. /// + API_FIELD(Attributes="Limit(1), EditorOrder(280), EditorDisplay(\"Nav Mesh Options\", \"Detail Sampling Distance\")") float DetailSamplingDist = 600.0f; /// /// The maximum distance the detail mesh surface should deviate from heightfield data. /// + API_FIELD(Attributes="Limit(0, 3), EditorOrder(290), EditorDisplay(\"Nav Mesh Options\")") float MaxDetailSamplingError = 1.0f; - /// - /// The radius of the smallest objects to traverse this nav mesh. Objects can't pass through gaps of less than twice the radius. - /// - float WalkableRadius = 34.0f; - - /// - /// The height of the smallest objects to traverse this nav mesh. Objects can't enter areas with ceilings lower than this value. - /// - float WalkableHeight = 144.0f; - - /// - /// The maximum ledge height that is considered to still be traversable. - /// - float WalkableMaxClimb = 35.0f; - - /// - /// The maximum slope that is considered walkable (in degrees). Objects can't go up or down slopes higher than this value. - /// - float WalkableMaxSlopeAngle = 60.0f; - public: - // [Settings] - void RestoreDefault() final override - { - CellHeight = 10.0f; - CellSize = 30.0f; - TileSize = 64; - MinRegionArea = 0; - MergeRegionArea = 20; - MaxEdgeLen = 1200.0f; - MaxEdgeError = 1.3f; - DetailSamplingDist = 600.0f; - MaxDetailSamplingError = 1.0f; - WalkableRadius = 34.0f; - WalkableHeight = 144.0f; - WalkableMaxClimb = 35.0f; - WalkableMaxSlopeAngle = 60.0f; - } + /// + /// The configuration for navmeshes. + /// + API_FIELD(Attributes="EditorOrder(1000), EditorDisplay(\"Agents\", EditorDisplayAttribute.InlineStyle)") + Array NavMeshes; - void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override - { - DESERIALIZE(CellHeight); - DESERIALIZE(CellSize); - DESERIALIZE(TileSize); - DESERIALIZE(MinRegionArea); - DESERIALIZE(MergeRegionArea); - DESERIALIZE(MaxEdgeLen); - DESERIALIZE(MaxEdgeError); - DESERIALIZE(DetailSamplingDist); - DESERIALIZE(MaxDetailSamplingError); - DESERIALIZE(WalkableRadius); - DESERIALIZE(WalkableHeight); - DESERIALIZE(WalkableMaxClimb); - DESERIALIZE(WalkableMaxSlopeAngle); - } + /// + /// The configuration for nav areas. + /// + API_FIELD(Attributes="EditorOrder(2000), EditorDisplay(\"Areas\", EditorDisplayAttribute.InlineStyle)") + Array NavAreas; + +public: + + NavigationSettings(); + + /// + /// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use. + /// + static NavigationSettings* Get(); + + // [SettingsBase] + void Apply() override; + void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override; }; diff --git a/Source/Engine/Navigation/NavigationTypes.h b/Source/Engine/Navigation/NavigationTypes.h new file mode 100644 index 000000000..11ebaeb1a --- /dev/null +++ b/Source/Engine/Navigation/NavigationTypes.h @@ -0,0 +1,179 @@ +// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Scripting/ScriptingType.h" +#include "Engine/Core/Types/String.h" +#include "Engine/Core/Math/Color.h" +#include "Engine/Core/Math/Vector3.h" +#include "Engine/Core/Math/Quaternion.h" +#include "Engine/Core/ISerializable.h" + +#define NAV_MESH_PATH_MAX_SIZE 200 + +/// +/// The navigation system agent properties container for navmesh building and querying. +/// +API_STRUCT() struct FLAXENGINE_API NavAgentProperties : ISerializable +{ +API_AUTO_SERIALIZATION(); +DECLARE_SCRIPTING_TYPE_MINIMAL(NavAgentProperties); + + /// + /// The radius of the agent used for navigation. Agents can't pass through gaps of less than twice the radius. + /// + API_FIELD(Attributes="EditorOrder(0)") + float Radius = 34.0f; + + /// + /// The height of the agent used for navigation. Agents can't enter areas with ceilings lower than this value. + /// + API_FIELD(Attributes="EditorOrder(10)") + float Height = 144.0f; + + /// + /// The step height used for navigation. Defines the maximum ledge height that is considered to still be traversable by the agent. + /// + API_FIELD(Attributes="EditorOrder(20)") + float StepHeight = 35.0f; + + /// + /// The maximum slope (in degrees) that is considered walkable for navigation. Agents can't go up or down slopes higher than this value. + /// + API_FIELD(Attributes="EditorOrder(30)") + float MaxSlopeAngle = 60.0f; + + bool operator==(const NavAgentProperties& other) const; + + bool operator!=(const NavAgentProperties& other) const + { + return !operator==(other); + } +}; + +/// +/// The navigation mesh properties container for navmesh building. +/// +API_STRUCT() struct FLAXENGINE_API NavMeshProperties : ISerializable +{ +API_AUTO_SERIALIZATION(); +DECLARE_SCRIPTING_TYPE_MINIMAL(NavMeshProperties); + + /// + /// The navmesh type name. Identifies different types of the navmeshes, used to sync navmesh properties with settings asset. + /// + API_FIELD(Attributes="EditorOrder(0)") + String Name; + + /// + /// The navmesh type color (for debugging). + /// + API_FIELD(Attributes="EditorOrder(10)") + Color Color = Color::Green; + + /// + /// The navmesh rotation applied to navigation surface. Used during building to the rotate scene geometry and to revert back result during path finding queries. Can be used to generate navmesh on walls. + /// + API_FIELD(Attributes="EditorOrder(20)") + Quaternion Rotation = Quaternion::Identity; + + /// + /// The properties of the agent used to generate walkable navigation surface. + /// + API_FIELD(Attributes="EditorOrder(30)") + NavAgentProperties Agent; + + bool operator==(const NavMeshProperties& other) const; + + bool operator!=(const NavMeshProperties& other) const + { + return !operator==(other); + } +}; + +/// +/// The navigation system agents selection mask (from navigation system settings). Uses 1 bit per agent type (up to 32 agents). +/// +API_STRUCT() struct FLAXENGINE_API NavAgentMask +{ +DECLARE_SCRIPTING_TYPE_MINIMAL(NavAgentMask); + + /// + /// The agents selection mask. + /// + API_FIELD() uint32 Mask = MAX_uint32; + + bool IsAgentSupported(int32 agentIndex) const; + bool IsAgentSupported(const NavAgentProperties& agentProperties) const; + bool IsNavMeshSupported(const NavMeshProperties& navMeshProperties) const; + + bool operator==(const NavAgentMask& other) const; + + bool operator!=(const NavAgentMask& other) const + { + return !operator==(other); + } +}; + +/// +/// The result information for navigation mesh queries. +/// +API_STRUCT() struct FLAXENGINE_API NavMeshHit +{ +DECLARE_SCRIPTING_TYPE_MINIMAL(NavMeshHit); + + /// + /// The hit point position. + /// + API_FIELD() Vector3 Position; + + /// + /// The distance to hit point (from the query origin). + /// + API_FIELD() float Distance; + + /// + /// The hit point normal vector. + /// + API_FIELD() Vector3 Normal; +}; + +/// +/// The navigation area properties container for navmesh building and navigation runtime. +/// +API_STRUCT() struct FLAXENGINE_API NavAreaProperties : ISerializable +{ +API_AUTO_SERIALIZATION(); +DECLARE_SCRIPTING_TYPE_MINIMAL(NavAreaProperties); + + /// + /// The area type name. Identifies different types of the areas. + /// + API_FIELD(Attributes="EditorOrder(0)") + String Name; + + /// + /// The area type color (for debugging). Alpha channel is used to blend with navmesh color (alpha 0 to use navmesh color only). + /// + API_FIELD(Attributes="EditorOrder(10)") + Color Color = Color::Transparent; + + /// + /// The area id. It must be unique for the project. Valid range 0-63. Value 0 is reserved for Null areas (empty, non-navigable areas). + /// + API_FIELD(Attributes="EditorOrder(20), Limit(0, 63)") + byte Id; + + /// + /// The cost scale for the area traversal for agents. The higher the cost, the less likely agent wil choose the path that goes over it. For instance, areas that are harder to move like sand should have higher cost for proper path finding. + /// + API_FIELD(Attributes="EditorOrder(30), Limit(0, float.MaxValue, 0.1f)") + float Cost = 1; + + bool operator==(const NavAreaProperties& other) const; + + bool operator!=(const NavAreaProperties& other) const + { + return !operator==(other); + } +}; diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp index bebe80479..7aa68e32e 100644 --- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp +++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp @@ -69,7 +69,7 @@ namespace scale *= 1.72531f; } - return noise / weight; + return noise / Math::Max(weight, ZeroTolerance); } VariantType::Types GetVariantType(ParticleAttribute::ValueTypes type) @@ -486,7 +486,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* float particleDrag = drag; \ if (useSpriteSize) \ particleDrag *= ((Vector2*)spriteSizePtr)->MulValues(); \ - *((Vector3*)velocityPtr) *= Math::Max(0.0f, 1.0f - (particleDrag * _deltaTime) / *(float*)massPtr); \ + *((Vector3*)velocityPtr) *= Math::Max(0.0f, 1.0f - (particleDrag * _deltaTime) / Math::Max(*(float*)massPtr, ZeroTolerance)); \ velocityPtr += stride; \ massPtr += stride; \ spriteSizePtr += stride @@ -545,7 +545,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* Vector3 vectorFieldUVW = Vector3::Transform(*((Vector3*)positionPtr), invFieldTransformMatrix); \ Vector3 force = Noise3D(vectorFieldUVW + 0.5f, octavesCount, roughness); \ force = Vector3::Transform(force, fieldTransformMatrix) * intensity; \ - *((Vector3*)velocityPtr) += force * (_deltaTime / *(float*)massPtr); \ + *((Vector3*)velocityPtr) += force * (_deltaTime / Math::Max(*(float*)massPtr, ZeroTolerance)); \ positionPtr += stride; \ velocityPtr += stride; \ massPtr += stride @@ -1009,7 +1009,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* #define INPUTS_FETCH() \ const Vector3 center = (Vector3)GetValue(centerBox, 2); \ - const float radius = (float)GetValue(radiusBox, 3); \ + const float radius = Math::Max((float)GetValue(radiusBox, 3), ZeroTolerance); \ const float thickness = (float)GetValue(thicknessBox, 4); \ const float arc = (float)GetValue(arcBox, 5) * DegreesToRadians #define LOGIC() \ @@ -1017,20 +1017,20 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* float sinTheta, cosTheta; \ Math::SinCos(u.X * TWO_PI, sinTheta, cosTheta); \ float r = Math::Saturate(thickness / radius); \ - Vector2 s1_1 = r * Vector2(cosTheta, sinTheta) + Vector2(1, 0); \ - Vector2 s1_2 = r * Vector2(-cosTheta, sinTheta) + Vector2(1, 0); \ - float w = s1_1.X / (s1_1.X + s1_2.X); \ + Vector2 s11 = r * Vector2(cosTheta, sinTheta) + Vector2(1, 0); \ + Vector2 s12 = r * Vector2(-cosTheta, sinTheta) + Vector2(1, 0); \ + float w = s11.X / (s11.X + s12.X); \ Vector3 t; \ float phi; \ if (u.Y < w) \ { \ phi = arc * u.Y / w; \ - t = Vector3(s1_1.X, 0, s1_1.Y); \ + t = Vector3(s11.X, 0, s11.Y); \ } \ else \ { \ phi = arc * (u.Y - w) / (1.0f - w); \ - t = Vector3(s1_2.X, 0, s1_2.Y); \ + t = Vector3(s12.X, 0, s12.Y); \ } \ float s, c; \ Math::SinCos(phi, c, s); \ @@ -1262,7 +1262,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* if (sign * sqrLength <= sign * totalRadius * totalRadius) \ { \ float dist = Math::Sqrt(sqrLength); \ - Vector3 n = sign * dir / dist; \ + Vector3 n = sign * dir / Math::Max(dist, ZeroTolerance); \ *(Vector3*)positionPtr = position - n * (dist - totalRadius) * sign; \ COLLISION_LOGIC() @@ -1374,7 +1374,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* collision = Math::Abs(dir.Y) > halfHeight || sqrLength > cylinderRadiusT * cylinderRadiusT; \ if (collision) \ { \ - float dist = Math::Sqrt(sqrLength); \ + float dist = Math::Max(Math::Sqrt(sqrLength), ZeroTolerance); \ float distToCap = sign * (halfHeight - Math::Abs(dir.Y)); \ float distToSide = sign * (cylinderRadiusT - dist); \ Vector3 n = Vector3(dir.X / dist, Math::Sign(dir.Y), dir.Z / dist) * sign; \ diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp index 08db1c0d3..d56bf6d5c 100644 --- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp +++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp @@ -284,7 +284,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupParticles(Box* box, Node* node { const float age = GET_PARTICLE_ATTRIBUTE(0, float); const float lifetime = GET_PARTICLE_ATTRIBUTE(1, float); - value = age / lifetime; + value = age / Math::Max(lifetime, ZeroTolerance); break; } // Effect Position diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.h b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.h index a93c50ec2..42aca7dbb 100644 --- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.h +++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.h @@ -100,7 +100,7 @@ public: public: /// - /// Determinates whenever this emitter uses lights rendering. + /// Determines whenever this emitter uses lights rendering. /// /// True if emitter uses lights rendering, otherwise false. FORCE_INLINE bool UsesLightRendering() const diff --git a/Source/Engine/Particles/Graph/GPU/GPUParticles.cpp b/Source/Engine/Particles/Graph/GPU/GPUParticles.cpp index befd5b25d..cd16a9c52 100644 --- a/Source/Engine/Particles/Graph/GPU/GPUParticles.cpp +++ b/Source/Engine/Particles/Graph/GPU/GPUParticles.cpp @@ -155,7 +155,7 @@ void GPUParticles::Execute(GPUContext* context, ParticleEmitter* emitter, Partic // Setup parameters MaterialParameter::BindMeta bindMeta; bindMeta.Context = context; - bindMeta.Buffer0 = hasCB ? _cbData.Get() + sizeof(GPUParticlesData) : nullptr; + bindMeta.Constants = hasCB ? _cbData.Get() + sizeof(GPUParticlesData) : nullptr; bindMeta.Input = nullptr; if (viewTask) { diff --git a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Parameters.cpp b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Parameters.cpp deleted file mode 100644 index bcb672eb3..000000000 --- a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Parameters.cpp +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -#if COMPILE_WITH_PARTICLE_GPU_GRAPH - -#include "ParticleEmitterGraph.GPU.h" - -void ParticleEmitterGPUGenerator::ProcessGroupParameters(Box* box, Node* node, Value& value) -{ - switch (node->TypeID) - { - // Get - case 1: - case 2: - { - // Get parameter - const auto param = findParam((Guid)node->Values[0]); - if (param) - { - switch (param->Type) - { - case MaterialParameterType::Bool: - value = Value(VariantType::Bool, param->ShaderName); - break; - case MaterialParameterType::Integer: - case MaterialParameterType::SceneTexture: - value = Value(VariantType::Int, param->ShaderName); - break; - case MaterialParameterType::Float: - value = Value(VariantType::Float, param->ShaderName); - break; - case MaterialParameterType::Vector2: - case MaterialParameterType::Vector3: - case MaterialParameterType::Vector4: - case MaterialParameterType::Color: - { - // Set result values based on box ID - const Value sample(box->Type.Type, param->ShaderName); - switch (box->ID) - { - case 0: - value = sample; - break; - case 1: - value.Value = sample.Value + _subs[0]; - break; - case 2: - value.Value = sample.Value + _subs[1]; - break; - case 3: - value.Value = sample.Value + _subs[2]; - break; - case 4: - value.Value = sample.Value + _subs[3]; - break; - default: CRASH; - break; - } - value.Type = box->Type.Type; - break; - } - - case MaterialParameterType::Matrix: - { - value = Value(box->Type.Type, String::Format(TEXT("{0}[{1}]"), param->ShaderName, box->ID)); - break; - } - case MaterialParameterType::ChannelMask: - { - const auto input = tryGetValue(node->GetBox(0), Value::Zero); - value = writeLocal(VariantType::Float, String::Format(TEXT("dot({0}, {1})"), input.Value, param->ShaderName), node); - break; - } - case MaterialParameterType::CubeTexture: - case MaterialParameterType::Texture: - case MaterialParameterType::GPUTextureArray: - case MaterialParameterType::GPUTextureCube: - case MaterialParameterType::GPUTextureVolume: - case MaterialParameterType::GPUTexture: - value = Value(VariantType::Object, param->ShaderName); - break; - default: - CRASH; - break; - } - } - else - { - OnError(node, box, String::Format(TEXT("Missing graph parameter {0}."), node->Values[0])); - value = Value::Zero; - } - break; - } - default: - break; - } -} - -#endif diff --git a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.ParticleModules.cpp b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.ParticleModules.cpp index 23dac1622..57d4ededc 100644 --- a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.ParticleModules.cpp +++ b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.ParticleModules.cpp @@ -177,7 +177,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) " {{\n" " // Linear Drag\n" " float drag = {2} * {3}.x * {3}.y;\n" - " {0} *= max(0.0f, 1.0f - (drag * DeltaTime) / {1});\n" + " {0} *= max(0.0f, 1.0f - (drag * DeltaTime) / max({1}, PARTICLE_THRESHOLD));\n" " }}\n" ), velocity.Value, mass.Value, drag.Value, spriteSize.Value); } @@ -188,7 +188,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) " {{\n" " // Linear Drag\n" " float drag = {2};\n" - " {0} *= max(0.0f, 1.0f - (drag * DeltaTime) / {1});\n" + " {0} *= max(0.0f, 1.0f - (drag * DeltaTime) / max({1}, PARTICLE_THRESHOLD));\n" " }}\n" ), velocity.Value, mass.Value, drag.Value); } @@ -219,7 +219,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) " float3 vectorFieldUVW = mul(invFieldTransformMatrix, float4({0}, 1.0f)).xyz;\n" " float3 force = Noise3D(vectorFieldUVW + 0.5f, {8}, {6});\n" " force = mul(fieldTransformMatrix, float4(force, 0.0f)).xyz * {7};\n" - " {1} += force * (DeltaTime / {2});\n" + " {1} += force * (DeltaTime / max({2}, PARTICLE_THRESHOLD));\n" " }}\n" ), position.Value, velocity.Value, mass.Value, fieldPosition.Value, fieldRotation.Value, fieldScale.Value, roughness.Value, intensity.Value, octavesCount.Value); break; @@ -486,21 +486,21 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) " float3 u = RAND3;\n" " float sinTheta, cosTheta;\n" " sincos(u.x * PI * 2.0f, sinTheta, cosTheta);\n" - " float r = saturate((float){4} / {3});\n" - " float2 s1_1 = r * float2( cosTheta, sinTheta) + float2(1, 0);\n" - " float2 s1_2 = r * float2(-cosTheta, sinTheta) + float2(1, 0);\n" - " float w = s1_1.x / (s1_1.x + s1_2.x);\n" + " float r = saturate((float){4} / max({3}, PARTICLE_THRESHOLD));\n" + " float2 s11 = r * float2( cosTheta, sinTheta) + float2(1, 0);\n" + " float2 s12 = r * float2(-cosTheta, sinTheta) + float2(1, 0);\n" + " float w = s11.x / (s11.x + s12.x);\n" " float3 t;\n" " float phi;\n" " if (u.y < w)\n" " {{\n" " phi = radians({5}) * u.y / w;\n" - " t = float3(s1_1.x, 0, s1_1.y);\n" + " t = float3(s11.x, 0, s11.y);\n" " }}\n" " else\n" " {{\n" " phi = radians({5}) * (u.y - w) / (1.0f - w);\n" - " t = float3(s1_2.x, 0, s1_2.y);\n" + " t = float3(s12.x, 0, s12.y);\n" " }}\n" " float s, c;\n" " sincos(phi, c, s);\n" @@ -693,7 +693,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) " if ({4} * sqrLength <= {4} * totalRadius * totalRadius)\n" " {{\n" " float dist = sqrt(sqrLength);\n" - " float3 n = {4} * dir / dist;\n" + " float3 n = {4} * dir / max(dist, PARTICLE_THRESHOLD);\n" " {0} -= n * (dist - totalRadius) * {4};\n" COLLISION_LOGIC() " }}\n" @@ -787,7 +787,7 @@ void ParticleEmitterGPUGenerator::ProcessModule(Node* node) " collision = abs(dir.y) > halfHeight || sqrLength > cylinderRadiusT * cylinderRadiusT;\n" " if (collision)\n" " {{\n" - " float dist = sqrt(sqrLength);\n" + " float dist = max(sqrt(sqrLength), PARTICLE_THRESHOLD);\n" " float distToCap = {4} * (halfHeight - abs(dir.y));\n" " float distToSide = {4} * (cylinderRadiusT - dist);\n" " float3 n = float3(dir.x / dist, sign(dir.y), dir.z / dist) * {4};\n" diff --git a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Particles.cpp b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Particles.cpp index 4839d0415..08e7b24ca 100644 --- a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Particles.cpp +++ b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Particles.cpp @@ -113,6 +113,131 @@ ParticleEmitterGPUGenerator::Value ParticleEmitterGPUGenerator::AccessParticleAt return value.Variable; } +void ParticleEmitterGPUGenerator::ProcessGroupParameters(Box* box, Node* node, Value& value) +{ + switch (node->TypeID) + { + // Get + case 1: + case 2: + { + // Get parameter + const auto param = findParam((Guid)node->Values[0]); + if (param) + { + switch (param->Type) + { + case MaterialParameterType::Bool: + value = Value(VariantType::Bool, param->ShaderName); + break; + case MaterialParameterType::Integer: + case MaterialParameterType::SceneTexture: + value = Value(VariantType::Int, param->ShaderName); + break; + case MaterialParameterType::Float: + value = Value(VariantType::Float, param->ShaderName); + break; + case MaterialParameterType::Vector2: + case MaterialParameterType::Vector3: + case MaterialParameterType::Vector4: + case MaterialParameterType::Color: + { + // Set result values based on box ID + const Value sample(box->Type.Type, param->ShaderName); + switch (box->ID) + { + case 0: + value = sample; + break; + case 1: + value.Value = sample.Value + _subs[0]; + break; + case 2: + value.Value = sample.Value + _subs[1]; + break; + case 3: + value.Value = sample.Value + _subs[2]; + break; + case 4: + value.Value = sample.Value + _subs[3]; + break; + default: CRASH; + break; + } + value.Type = box->Type.Type; + break; + } + + case MaterialParameterType::Matrix: + { + value = Value(box->Type.Type, String::Format(TEXT("{0}[{1}]"), param->ShaderName, box->ID)); + break; + } + case MaterialParameterType::ChannelMask: + { + const auto input = tryGetValue(node->GetBox(0), Value::Zero); + value = writeLocal(VariantType::Float, String::Format(TEXT("dot({0}, {1})"), input.Value, param->ShaderName), node); + break; + } + case MaterialParameterType::CubeTexture: + case MaterialParameterType::Texture: + case MaterialParameterType::GPUTextureArray: + case MaterialParameterType::GPUTextureCube: + case MaterialParameterType::GPUTextureVolume: + case MaterialParameterType::GPUTexture: + value = Value(VariantType::Object, param->ShaderName); + break; + default: + CRASH; + break; + } + } + else + { + OnError(node, box, String::Format(TEXT("Missing graph parameter {0}."), node->Values[0])); + value = Value::Zero; + } + break; + } + default: + break; + } +} + +void ParticleEmitterGPUGenerator::ProcessGroupTools(Box* box, Node* node, Value& value) +{ + switch (node->TypeID) + { + // Linearize Depth + case 7: + { + // Get input + const Value depth = tryGetValue(node->GetBox(0), Value::Zero).AsFloat(); + + // Linearize raw device depth + linearizeSceneDepth(node, depth, value); + break; + } + // Time + case 8: + value = box->ID == 0 ? Value(VariantType::Float, TEXT("Time")) : Value(VariantType::Float, TEXT("DeltaTime")); + break; + // Transform Position To Screen UV + case 9: + { + const Value position = tryGetValue(node->GetBox(0), Value::Zero).AsVector3(); + const Value projPos = writeLocal(VariantType::Vector4, String::Format(TEXT("mul(float4({0}, 1.0f), ViewProjectionMatrix)"), position.Value), node); + _writer.Write(TEXT("\t{0}.xy /= {0}.w;\n"), projPos.Value); + _writer.Write(TEXT("\t{0}.xy = {0}.xy * 0.5f + 0.5f;\n"), projPos.Value); + value = Value(VariantType::Vector2, projPos.Value + TEXT(".xy")); + break; + } + default: + ShaderGenerator::ProcessGroupTools(box, node, value); + break; + } +} + void ParticleEmitterGPUGenerator::ProcessGroupParticles(Box* box, Node* node, Value& value) { switch (node->TypeID) diff --git a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Textures.cpp b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Textures.cpp index 61273b3a1..e1d864b4e 100644 --- a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Textures.cpp +++ b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Textures.cpp @@ -256,10 +256,8 @@ void ParticleEmitterGPUGenerator::ProcessGroupTextures(Box* box, Node* node, Val } // Scene Depth case 8: - { sampleSceneDepth(node, value, box); break; - } // Texture case 11: { diff --git a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Tools.cpp b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Tools.cpp deleted file mode 100644 index 299e1a422..000000000 --- a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Tools.cpp +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -#if COMPILE_WITH_PARTICLE_GPU_GRAPH - -#include "ParticleEmitterGraph.GPU.h" - -void ParticleEmitterGPUGenerator::ProcessGroupTools(Box* box, Node* node, Value& value) -{ - switch (node->TypeID) - { - // Linearize Depth - case 7: - { - // Get input - const Value depth = tryGetValue(node->GetBox(0), Value::Zero).AsFloat(); - - // Linearize raw device depth - linearizeSceneDepth(node, depth, value); - break; - } - // Time - case 8: - { - value = box->ID == 0 ? Value(VariantType::Float, TEXT("Time")) : Value(VariantType::Float, TEXT("DeltaTime")); - break; - } - // Transform Position To Screen UV - case 9: - { - const Value position = tryGetValue(node->GetBox(0), Value::Zero).AsVector3(); - const Value projPos = writeLocal(VariantType::Vector4, String::Format(TEXT("mul(float4({0}, 1.0f), ViewProjectionMatrix)"), position.Value), node); - _writer.Write(TEXT("\t{0}.xy /= {0}.w;\n"), projPos.Value); - _writer.Write(TEXT("\t{0}.xy = {0}.xy * 0.5f + 0.5f;\n"), projPos.Value); - value = Value(VariantType::Vector2, projPos.Value + TEXT(".xy")); - break; - } - default: - ShaderGenerator::ProcessGroupTools(box, node, value); - break; - } -} - -#endif diff --git a/Source/Engine/Particles/Graph/ParticleEmitterGraph.h b/Source/Engine/Particles/Graph/ParticleEmitterGraph.h index 0f92d5f22..76af84011 100644 --- a/Source/Engine/Particles/Graph/ParticleEmitterGraph.h +++ b/Source/Engine/Particles/Graph/ParticleEmitterGraph.h @@ -103,7 +103,7 @@ protected: public: /// - /// The Particle Emitter Graph data version number. Used to sync the Particle Emitter Graph data with the instances state. Handles graph reloads to enure data is valid. + /// The Particle Emitter Graph data version number. Used to sync the Particle Emitter Graph data with the instances state. Handles graph reloads to ensure data is valid. /// uint32 Version = 0; diff --git a/Source/Engine/Particles/ParticleManager.cpp b/Source/Engine/Particles/ParticleManager.cpp index 0e5640545..f2e70fee3 100644 --- a/Source/Engine/Particles/ParticleManager.cpp +++ b/Source/Engine/Particles/ParticleManager.cpp @@ -83,8 +83,8 @@ public: drawCall.Geometry.VertexBuffersOffsets[0] = 0; drawCall.Geometry.VertexBuffersOffsets[1] = 0; drawCall.Geometry.VertexBuffersOffsets[2] = 0; - drawCall.Geometry.StartIndex = 0; - drawCall.Geometry.IndicesCount = IndexCount; + drawCall.Draw.StartIndex = 0; + drawCall.Draw.IndicesCount = IndexCount; } }; @@ -173,10 +173,6 @@ void DrawEmitterCPU(RenderContext& renderContext, ParticleBuffer* buffer, DrawCa const auto context = GPUDevice::Instance->GetMainContext(); auto emitter = buffer->Emitter; - drawCall.InstanceCount = 1; - drawCall.IndirectArgsBuffer = nullptr; - drawCall.IndirectArgsOffset = 0; - // Check if need to perform any particles sorting if (emitter->Graph.SortModules.HasItems() && renderContext.View.Pass != DrawPass::Depth) { @@ -423,7 +419,7 @@ void DrawEmitterCPU(RenderContext& renderContext, ParticleBuffer* buffer, DrawCa { const int32 moduleIndex = renderModulesIndices[index]; auto module = emitter->Graph.RenderModules[moduleIndex]; - drawCall.Module = module; + drawCall.Particle.Module = module; switch (module->TypeID) { @@ -486,7 +482,7 @@ void DrawEmitterCPU(RenderContext& renderContext, ParticleBuffer* buffer, DrawCa int32 count = buffer->CPU.Count; // Setup ribbon data - auto& ribbon = drawCall.Ribbon; + auto& ribbon = drawCall.Particle.Ribbon; ribbon.UVTilingDistance = uvTilingDistance; ribbon.SegmentCount = ribbonModulesSegmentCount[ribbonModuleIndex]; ribbon.UVScaleX = uvScale.X; @@ -516,8 +512,8 @@ void DrawEmitterCPU(RenderContext& renderContext, ParticleBuffer* buffer, DrawCa drawCall.Geometry.VertexBuffersOffsets[0] = 0; drawCall.Geometry.VertexBuffersOffsets[1] = 0; drawCall.Geometry.VertexBuffersOffsets[2] = 0; - drawCall.Geometry.StartIndex = ribbonModulesDrawIndicesStart[ribbonModuleIndex]; - drawCall.Geometry.IndicesCount = ribbonModulesDrawIndicesCount[ribbonModuleIndex]; + drawCall.Draw.StartIndex = ribbonModulesDrawIndicesStart[ribbonModuleIndex]; + drawCall.Draw.IndicesCount = ribbonModulesDrawIndicesCount[ribbonModuleIndex]; drawCall.InstanceCount = 1; renderContext.List->AddDrawCall((DrawPass)(drawModes & moduleDrawModes), staticFlags, drawCall, false); @@ -557,7 +553,7 @@ void OnShaderReloading(Asset* obj) void CleanupGPUParticlesSorting() { - GPUParticlesSorting.Unlink(); + GPUParticlesSorting = nullptr; } void DrawEmitterGPU(RenderContext& renderContext, ParticleBuffer* buffer, DrawCall& drawCall, DrawPass drawModes, StaticFlags staticFlags, ParticleEmitterInstance& emitterData, const RenderModulesIndices& renderModulesIndices) @@ -788,7 +784,7 @@ void DrawEmitterGPU(RenderContext& renderContext, ParticleBuffer* buffer, DrawCa { int32 moduleIndex = renderModulesIndices[index]; auto module = emitter->Graph.RenderModules[moduleIndex]; - drawCall.Module = module; + drawCall.Particle.Module = module; switch (module->TypeID) { @@ -802,8 +798,8 @@ void DrawEmitterGPU(RenderContext& renderContext, ParticleBuffer* buffer, DrawCa // Submit draw call SpriteRenderer.SetupDrawCall(drawCall); drawCall.InstanceCount = 0; - drawCall.IndirectArgsBuffer = buffer->GPU.IndirectDrawArgsBuffer; - drawCall.IndirectArgsOffset = indirectDrawCallIndex * sizeof(GPUDrawIndexedIndirectArgs); + drawCall.Draw.IndirectArgsBuffer = buffer->GPU.IndirectDrawArgsBuffer; + drawCall.Draw.IndirectArgsOffset = indirectDrawCallIndex * sizeof(GPUDrawIndexedIndirectArgs); renderContext.List->AddDrawCall((DrawPass)(drawModes & moduleDrawModes), staticFlags, drawCall, false); indirectDrawCallIndex++; @@ -830,8 +826,8 @@ void DrawEmitterGPU(RenderContext& renderContext, ParticleBuffer* buffer, DrawCa // Execute draw call mesh.GetDrawCallGeometry(drawCall); drawCall.InstanceCount = 0; - drawCall.IndirectArgsBuffer = buffer->GPU.IndirectDrawArgsBuffer; - drawCall.IndirectArgsOffset = indirectDrawCallIndex * sizeof(GPUDrawIndexedIndirectArgs); + drawCall.Draw.IndirectArgsBuffer = buffer->GPU.IndirectDrawArgsBuffer; + drawCall.Draw.IndirectArgsOffset = indirectDrawCallIndex * sizeof(GPUDrawIndexedIndirectArgs); renderContext.List->AddDrawCall((DrawPass)(drawModes & moduleDrawModes), staticFlags, drawCall, false); indirectDrawCallIndex++; } @@ -875,9 +871,7 @@ void ParticleManager::DrawParticles(RenderContext& renderContext, ParticleEffect // Setup a draw call common data DrawCall drawCall; - drawCall.LightmapUVsArea = Rectangle::Empty; drawCall.PerInstanceRandom = effect->GetPerInstanceRandom(); - drawCall.LODDitherFactor = 1.0f; drawCall.ObjectPosition = world.GetTranslation(); // Draw all emitters @@ -890,9 +884,8 @@ void ParticleManager::DrawParticles(RenderContext& renderContext, ParticleEffect auto emitter = buffer->Emitter; drawCall.World = emitter->SimulationSpace == ParticlesSimulationSpace::World ? Matrix::Identity : world; - drawCall.PrevWorld = drawCall.World; drawCall.WorldDeterminantSign = Math::FloatSelect(drawCall.World.RotDeterminant(), 1, -1); - drawCall.Particles = buffer; + drawCall.Particle.Particles = buffer; // Check if need to render any module RenderModulesIndices renderModulesIndices; @@ -1172,7 +1165,7 @@ void ParticleManagerService::Update() // Update bounds after first system update updateBounds = true; } - // TODO: if using fixed timestep quantize the dt and accumulate reaming part for the next update? + // TODO: if using fixed timestep quantize the dt and accumulate remaining part for the next update? if (dt <= 1.0f / 240.0f) continue; dt *= effect->SimulationSpeed; diff --git a/Source/Engine/Particles/ParticlesSimulation.cpp b/Source/Engine/Particles/ParticlesSimulation.cpp index bbba833ff..0b6e3691d 100644 --- a/Source/Engine/Particles/ParticlesSimulation.cpp +++ b/Source/Engine/Particles/ParticlesSimulation.cpp @@ -115,7 +115,7 @@ int32 ParticleSystemInstance::GetParticlesCount() const const auto desc = GPUBufferDescription::Buffer(Emitters.Count() * sizeof(uint32), GPUBufferFlags::None, PixelFormat::Unknown, nullptr, sizeof(uint32), GPUResourceUsage::StagingReadback); if (GPUParticlesCountReadback->Init(desc)) { - LOG(Error, "Failed to create GPU particles coun readback buffer."); + LOG(Error, "Failed to create GPU particles count readback buffer."); } } diff --git a/Source/Engine/Physics/Actors/IPhysicsActor.h b/Source/Engine/Physics/Actors/IPhysicsActor.h index 8aebe3aeb..28f7fddf4 100644 --- a/Source/Engine/Physics/Actors/IPhysicsActor.h +++ b/Source/Engine/Physics/Actors/IPhysicsActor.h @@ -2,7 +2,11 @@ #pragma once -#include +namespace physx +{ + class PxRigidActor; + class PxTransform; +} /// /// A base interface for all physical actors types/owners that can responds on transformation changed event. @@ -20,7 +24,7 @@ public: /// Gets the rigid actor (PhysX object) may be null. /// /// PhysX rigid actor or null if not using - virtual PxRigidActor* GetRigidActor() = 0; + virtual physx::PxRigidActor* GetRigidActor() = 0; /// /// Called when actor's active transformation gets changed after the physics simulation step. diff --git a/Source/Engine/Physics/Actors/SplineRopeBody.cpp b/Source/Engine/Physics/Actors/SplineRopeBody.cpp new file mode 100644 index 000000000..a610e76ce --- /dev/null +++ b/Source/Engine/Physics/Actors/SplineRopeBody.cpp @@ -0,0 +1,183 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#include "SplineRopeBody.h" +#include "Engine/Level/Actors/Spline.h" +#include "Engine/Level/Scene/Scene.h" +#include "Engine/Physics/Physics.h" +#include "Engine/Engine/Time.h" +#include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Serialization/Serialization.h" + +SplineRopeBody::SplineRopeBody(const SpawnParams& params) + : Actor(params) +{ +} + +void SplineRopeBody::Tick() +{ + if (!_spline || _spline->GetSplinePointsCount() < 2) + return; + PROFILE_CPU(); + + // Cache data + const Vector3 gravity = Physics::GetGravity() * GravityScale; + auto& keyframes = _spline->Curve.GetKeyframes(); + const Transform splineTransform = _spline->GetTransform(); + const int32 keyframesCount = keyframes.Count(); + const float substepTime = SubstepTime; + const float substepTimeSqr = substepTime * substepTime; + bool splineDirty = false; + + // Synchronize spline keyframes with simulated masses + if (_masses.Count() > keyframesCount) + _masses.Resize(keyframesCount); + else + { + _masses.EnsureCapacity(keyframesCount); + while (_masses.Count() < keyframesCount) + { + const int32 i = _masses.Count(); + auto& mass = _masses.AddOne(); + mass.PrevPosition = splineTransform.LocalToWorld(keyframes[i].Value.Translation); + if (i != 0) + mass.SegmentLength = Vector3::Distance(mass.PrevPosition, _masses[i - 1].PrevPosition); + else + mass.SegmentLength = 0.0f; + } + } + { + // Rope start + auto& mass = _masses.First(); + mass.Position = mass.PrevPosition = GetPosition(); + mass.Unconstrained = false; + if (splineTransform.LocalToWorld(keyframes.First().Value.Translation) != mass.Position) + splineDirty = true; + } + for (int32 i = 1; i < keyframesCount; i++) + { + auto& mass = _masses[i]; + mass.Unconstrained = true; + mass.Position = splineTransform.LocalToWorld(keyframes[i].Value.Translation); + } + if (AttachEnd) + { + // Rope end + auto& mass = _masses.Last(); + mass.Position = mass.PrevPosition = AttachEnd->GetPosition(); + mass.Unconstrained = false; + if (splineTransform.LocalToWorld(keyframes.Last().Value.Translation) != mass.Position) + splineDirty = true; + } + + // Perform simulation in substeps to have better stability + _time += Time::Update.DeltaTime.GetTotalSeconds(); + while (_time > substepTime) + { + // Verlet integration + // [Reference: https://en.wikipedia.org/wiki/Verlet_integration] + const Vector3 force = gravity + AdditionalForce; + for (int32 i = 0; i < keyframesCount; i++) + { + auto& mass = _masses[i]; + if (mass.Unconstrained) + { + const Vector3 velocity = mass.Position - mass.PrevPosition; + mass.PrevPosition = mass.Position; + mass.Position = mass.Position + velocity + (substepTimeSqr * force); + keyframes[i].Value.Translation = splineTransform.WorldToLocal(mass.Position); + } + } + + // Distance constraint + for (int32 i = 1; i < keyframesCount; i++) + { + auto& massA = _masses[i - 1]; + auto& massB = _masses[i]; + Vector3 offset = massB.Position - massA.Position; + const float distance = offset.Length(); + const float scale = (distance - massB.SegmentLength) / Math::Max(distance, ZeroTolerance); + if (massA.Unconstrained && massB.Unconstrained) + { + offset *= scale * 0.5f; + massA.Position += offset; + massB.Position -= offset; + } + else if (massA.Unconstrained) + { + massA.Position += scale * offset; + } + else if (massB.Unconstrained) + { + massB.Position -= scale * offset; + } + } + + // Stiffness constraint + if (EnableStiffness) + { + for (int32 i = 2; i < keyframesCount; i++) + { + auto& massA = _masses[i - 2]; + auto& massB = _masses[i]; + Vector3 offset = massB.Position - massA.Position; + const float distance = offset.Length(); + const float scale = (distance - massB.SegmentLength * 2.0f) / Math::Max(distance, ZeroTolerance); + if (massA.Unconstrained && massB.Unconstrained) + { + offset *= scale * 0.5f; + massA.Position += offset; + massB.Position -= offset; + } + else if (massA.Unconstrained) + { + massA.Position += scale * offset; + } + else if (massB.Unconstrained) + { + massB.Position -= scale * offset; + } + } + } + + _time -= substepTime; + splineDirty = true; + } + + // Update spline and relevant components (eg. spline model) + if (splineDirty) + { + for (int32 i = 0; i < keyframesCount; i++) + keyframes[i].Value.Translation = splineTransform.WorldToLocal(_masses[i].Position); + + _spline->UpdateSpline(); + } +} + +void SplineRopeBody::OnEnable() +{ + GetScene()->Ticking.FixedUpdate.AddTick(this); + + Actor::OnEnable(); +} + +void SplineRopeBody::OnDisable() +{ + Actor::OnDisable(); + + GetScene()->Ticking.FixedUpdate.RemoveTick(this); +} + +void SplineRopeBody::OnParentChanged() +{ + Actor::OnParentChanged(); + + _spline = Cast(_parent); +} + +void SplineRopeBody::OnTransformChanged() +{ + Actor::OnTransformChanged(); + + _box = BoundingBox(_transform.Translation, _transform.Translation); + _sphere = BoundingSphere(_transform.Translation, 0.0f); +} diff --git a/Source/Engine/Physics/Actors/SplineRopeBody.h b/Source/Engine/Physics/Actors/SplineRopeBody.h new file mode 100644 index 000000000..32e175ba7 --- /dev/null +++ b/Source/Engine/Physics/Actors/SplineRopeBody.h @@ -0,0 +1,75 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Level/Actor.h" +#include "Engine/Scripting/ScriptingObjectReference.h" + +class Spline; + +/// +/// Physical simulation actor for ropes, chains and cables represented by a spline. +/// +/// +API_CLASS() class FLAXENGINE_API SplineRopeBody : public Actor +{ +API_AUTO_SERIALIZATION(); +DECLARE_SCENE_OBJECT(SplineRopeBody); +private: + + struct Mass + { + Vector3 Position; + float SegmentLength; + Vector3 PrevPosition; + bool Unconstrained; + }; + + Spline* _spline = nullptr; + float _time = 0.0f; + Array _masses; + +public: + + /// + /// The target actor too attach the rope end to. If unset the rope end will run freely. + /// + API_FIELD(Attributes="EditorOrder(0), DefaultValue(null), EditorDisplay(\"Rope\")") + ScriptingObjectReference AttachEnd; + + /// + /// The world gravity scale applied to the rope. Can be used to adjust gravity force or disable it. + /// + API_FIELD(Attributes="EditorOrder(10), EditorDisplay(\"Rope\")") + float GravityScale = 1.0f; + + /// + /// The additional, external force applied to rope (world-space). This can be eg. wind force. + /// + API_FIELD(Attributes="EditorOrder(20), EditorDisplay(\"Rope\")") + Vector3 AdditionalForce = Vector3::Zero; + + /// + /// If checked, the physics solver will use stiffness constraint for rope. It will be less likely to bend over and will preserve more it's shape. + /// + API_FIELD(Attributes="EditorOrder(30), EditorDisplay(\"Rope\")") + bool EnableStiffness = false; + + /// + /// The rope simulation update substep (in seconds). Defines the frequency of physics update. + /// + API_FIELD(Attributes="EditorOrder(40), Limit(0, 0.1f, 0.0001f), EditorDisplay(\"Rope\")") + float SubstepTime = 0.02f; + +private: + + void Tick(); + +public: + + // [Actor] + void OnEnable() override; + void OnDisable() override; + void OnTransformChanged() override; + void OnParentChanged() override; +}; diff --git a/Source/Engine/Physics/Colliders/BoxCollider.cpp b/Source/Engine/Physics/Colliders/BoxCollider.cpp index da78e6a2f..487059148 100644 --- a/Source/Engine/Physics/Colliders/BoxCollider.cpp +++ b/Source/Engine/Physics/Colliders/BoxCollider.cpp @@ -4,9 +4,6 @@ #include "Engine/Serialization/Serialization.h" #include "Engine/Physics/Utilities.h" #include -#if USE_EDITOR -#include "Engine/Level/Scene/SceneRendering.h" -#endif BoxCollider::BoxCollider(const SpawnParams& params) : Collider(params) @@ -123,26 +120,6 @@ void BoxCollider::Deserialize(DeserializeStream& stream, ISerializeModifier* mod DESERIALIZE_MEMBER(Size, _size); } -#if USE_EDITOR - -void BoxCollider::OnEnable() -{ - GetSceneRendering()->AddPhysicsDebug(this); - - // Base - Collider::OnEnable(); -} - -void BoxCollider::OnDisable() -{ - GetSceneRendering()->RemovePhysicsDebug(this); - - // Base - Collider::OnDisable(); -} - -#endif - void BoxCollider::UpdateBounds() { // Cache bounds @@ -152,32 +129,11 @@ void BoxCollider::UpdateBounds() BoundingSphere::FromBox(_box, _sphere); } -void BoxCollider::CreateShape() +void BoxCollider::GetGeometry(PxGeometryHolder& geometry) { - // Setup shape geometry - _cachedScale = GetScale(); Vector3 size = _size * _cachedScale; size.Absolute(); const float minSize = 0.001f; - const PxBoxGeometry geometry(Math::Max(size.X * 0.5f, minSize), Math::Max(size.Y * 0.5f, minSize), Math::Max(size.Z * 0.5f, minSize)); - - // Setup shape - CreateShapeBase(geometry); -} - -void BoxCollider::UpdateGeometry() -{ - // Check if has no shape created - if (_shape == nullptr) - return; - - // Setup shape geometry - _cachedScale = GetScale(); - Vector3 size = _size * _cachedScale; - size.Absolute(); - const float minSize = 0.001f; - const PxBoxGeometry geometry(Math::Max(size.X * 0.5f, minSize), Math::Max(size.Y * 0.5f, minSize), Math::Max(size.Z * 0.5f, minSize)); - - // Setup shape - _shape->setGeometry(geometry); + const PxBoxGeometry box(Math::Max(size.X * 0.5f, minSize), Math::Max(size.Y * 0.5f, minSize), Math::Max(size.Z * 0.5f, minSize)); + geometry.storeAny(box); } diff --git a/Source/Engine/Physics/Colliders/BoxCollider.h b/Source/Engine/Physics/Colliders/BoxCollider.h index ac381e878..89850b101 100644 --- a/Source/Engine/Physics/Colliders/BoxCollider.h +++ b/Source/Engine/Physics/Colliders/BoxCollider.h @@ -47,12 +47,6 @@ public: return _bounds; } -private: - -#if USE_EDITOR - void DrawPhysicsDebug(RenderView& view); -#endif - public: // [Collider] @@ -67,11 +61,9 @@ public: protected: // [Collider] -#if USE_EDITOR - void OnEnable() override; - void OnDisable() override; -#endif void UpdateBounds() override; - void CreateShape() override; - void UpdateGeometry() override; + void GetGeometry(PxGeometryHolder& geometry) override; +#if USE_EDITOR + void DrawPhysicsDebug(RenderView& view) override; +#endif }; diff --git a/Source/Engine/Physics/Colliders/CapsuleCollider.cpp b/Source/Engine/Physics/Colliders/CapsuleCollider.cpp index e3e717839..d3bc4dc30 100644 --- a/Source/Engine/Physics/Colliders/CapsuleCollider.cpp +++ b/Source/Engine/Physics/Colliders/CapsuleCollider.cpp @@ -4,9 +4,6 @@ #include "Engine/Serialization/Serialization.h" #include "Engine/Physics/Utilities.h" #include -#if USE_EDITOR -#include "Engine/Level/Scene/SceneRendering.h" -#endif CapsuleCollider::CapsuleCollider(const SpawnParams& params) : Collider(params) @@ -93,26 +90,6 @@ void CapsuleCollider::Deserialize(DeserializeStream& stream, ISerializeModifier* DESERIALIZE_MEMBER(Height, _height); } -#if USE_EDITOR - -void CapsuleCollider::OnEnable() -{ - GetSceneRendering()->AddPhysicsDebug(this); - - // Base - Collider::OnEnable(); -} - -void CapsuleCollider::OnDisable() -{ - GetSceneRendering()->RemovePhysicsDebug(this); - - // Base - Collider::OnDisable(); -} - -#endif - void CapsuleCollider::UpdateBounds() { // Cache bounds @@ -123,34 +100,12 @@ void CapsuleCollider::UpdateBounds() BoundingSphere::FromBox(_box, _sphere); } -void CapsuleCollider::CreateShape() +void CapsuleCollider::GetGeometry(PxGeometryHolder& geometry) { - // Setup shape geometry - _cachedScale = GetScale(); const float scaling = _cachedScale.GetAbsolute().MaxValue(); const float minSize = 0.001f; const float radius = Math::Max(Math::Abs(_radius) * scaling, minSize); const float height = Math::Max(Math::Abs(_height) * scaling, minSize); - const PxCapsuleGeometry geometry(radius, height * 0.5f); - - // Setup shape - CreateShapeBase(geometry); -} - -void CapsuleCollider::UpdateGeometry() -{ - // Check if has no shape created - if (_shape == nullptr) - return; - - // Setup shape geometry - _cachedScale = GetScale(); - const float scaling = _cachedScale.GetAbsolute().MaxValue(); - const float minSize = 0.001f; - const float radius = Math::Max(Math::Abs(_radius) * scaling, minSize); - const float height = Math::Max(Math::Abs(_height) * scaling, minSize); - const PxCapsuleGeometry geometry(radius, height * 0.5f); - - // Setup shape - _shape->setGeometry(geometry); + const PxCapsuleGeometry capsule(radius, height * 0.5f); + geometry.storeAny(capsule); } diff --git a/Source/Engine/Physics/Colliders/CapsuleCollider.h b/Source/Engine/Physics/Colliders/CapsuleCollider.h index 5efe18c13..4a66ca877 100644 --- a/Source/Engine/Physics/Colliders/CapsuleCollider.h +++ b/Source/Engine/Physics/Colliders/CapsuleCollider.h @@ -63,12 +63,6 @@ public: /// API_PROPERTY() void SetHeight(float value); -private: - -#if USE_EDITOR - void DrawPhysicsDebug(RenderView& view); -#endif - public: // [Collider] @@ -82,11 +76,9 @@ public: protected: // [Collider] -#if USE_EDITOR - void OnEnable() override; - void OnDisable() override; -#endif void UpdateBounds() override; - void CreateShape() override; - void UpdateGeometry() override; + void GetGeometry(PxGeometryHolder& geometry) override; +#if USE_EDITOR + void DrawPhysicsDebug(RenderView& view) override; +#endif }; diff --git a/Source/Engine/Physics/Colliders/CharacterController.cpp b/Source/Engine/Physics/Colliders/CharacterController.cpp index 6c75879ad..7657c7594 100644 --- a/Source/Engine/Physics/Colliders/CharacterController.cpp +++ b/Source/Engine/Physics/Colliders/CharacterController.cpp @@ -7,9 +7,6 @@ #include "Engine/Serialization/Serialization.h" #include "Engine/Engine/Time.h" #include "Engine/Physics/PhysicalMaterial.h" -#if USE_EDITOR -#include "Engine/Level/Scene/SceneRendering.h" -#endif #include #include #include @@ -29,6 +26,7 @@ CharacterController::CharacterController(const SpawnParams& params) , _nonWalkableMode(CharacterController::NonWalkableModes::PreventClimbing) , _lastFlags(CollisionFlags::None) { + static_assert(sizeof(_filterData) == sizeof(PxFilterData), "Invalid filter data size."); } void CharacterController::SetRadius(const float value) @@ -55,7 +53,7 @@ void CharacterController::SetHeight(const float value) void CharacterController::SetSlopeLimit(float value) { - value = Math::Clamp(value, 0.0f, 90.0f); + value = Math::Clamp(value, 0.0f, 89.0f); if (Math::NearEqual(value, _slopeLimit)) return; @@ -112,7 +110,7 @@ CharacterController::CollisionFlags CharacterController::Move(const Vector3& dis { const float deltaTime = Time::GetCurrentSafe()->DeltaTime.GetTotalSeconds(); PxControllerFilters filters; - filters.mFilterData = &_filterData; + filters.mFilterData = (PxFilterData*)&_filterData; filters.mFilterCallback = Physics::GetCharacterQueryFilterCallback(); filters.mFilterFlags = PxQueryFlag::eDYNAMIC | PxQueryFlag::eSTATIC | PxQueryFlag::ePREFILTER; @@ -177,7 +175,7 @@ void CharacterController::CreateActor() const float scaling = _cachedScale.GetAbsolute().MaxValue(); const float minSize = 0.001f; desc.height = Math::Max(Math::Abs(_height) * scaling, minSize); - desc.radius = Math::Max(Math::Abs(_radius) * scaling, minSize); + desc.radius = Math::Max(Math::Abs(_radius) * scaling - desc.contactOffset, minSize); // Create controller _controller = (PxCapsuleController*)Physics::GetControllerManager()->createController(desc); @@ -202,7 +200,7 @@ void CharacterController::UpdateSize() const { const float scaling = _cachedScale.GetAbsolute().MaxValue(); const float minSize = 0.001f; - const float radius = Math::Max(Math::Abs(_radius) * scaling, minSize); + const float radius = Math::Max(Math::Abs(_radius) * scaling - Math::Max(_contactOffset, ZeroTolerance), minSize); const float height = Math::Max(Math::Abs(_height) * scaling, minSize); _controller->setRadius(radius); @@ -275,13 +273,18 @@ void CharacterController::UpdateGeometry() UpdateSize(); } +void CharacterController::GetGeometry(PxGeometryHolder& geometry) +{ + // Unused +} + void CharacterController::UpdateLayerBits() { // Base Collider::UpdateLayerBits(); // Cache filter data - _filterData = _shape->getSimulationFilterData(); + *(PxFilterData*)&_filterData = _shape->getSimulationFilterData(); } void CharacterController::BeginPlay(SceneBeginData* data) @@ -308,26 +311,6 @@ void CharacterController::EndPlay() _shape = nullptr; } -#if USE_EDITOR - -void CharacterController::OnEnable() -{ - GetSceneRendering()->AddPhysicsDebug(this); - - // Base - Collider::OnEnable(); -} - -void CharacterController::OnDisable() -{ - GetSceneRendering()->RemovePhysicsDebug(this); - - // Base - Collider::OnDisable(); -} - -#endif - void CharacterController::OnActiveInTreeChanged() { // Skip collider base @@ -360,6 +343,11 @@ void CharacterController::OnTransformChanged() UpdateScale(); UpdateBounds(); } + else if (!_controller) + { + _box = BoundingBox(_transform.Translation, _transform.Translation); + BoundingSphere::FromBox(_box, _sphere); + } } void CharacterController::Serialize(SerializeStream& stream, const void* otherObj) diff --git a/Source/Engine/Physics/Colliders/CharacterController.h b/Source/Engine/Physics/Colliders/CharacterController.h index e79cea5e3..f850b9124 100644 --- a/Source/Engine/Physics/Colliders/CharacterController.h +++ b/Source/Engine/Physics/Colliders/CharacterController.h @@ -4,7 +4,6 @@ #include "Collider.h" #include "Engine/Physics/Actors/IPhysicsActor.h" -#include /// /// Physical objects that allows to easily do player movement constrained by collisions without having to deal with a rigidbody. @@ -68,7 +67,7 @@ private: bool _isUpdatingTransform; NonWalkableModes _nonWalkableMode; CollisionFlags _lastFlags; - PxFilterData _filterData; + uint32 _filterData[4]; public: @@ -143,7 +142,7 @@ public: API_PROPERTY() void SetStepOffset(float value); /// - /// Gets the minimum move distance of the character controller. The minimum travelled distance to consider. If travelled distance is smaller, the character doesn't move. This is used to stop the recursive motion algorithm when remaining distance to travel is small. + /// Gets the minimum move distance of the character controller. The minimum traveled distance to consider. If traveled distance is smaller, the character doesn't move. This is used to stop the recursive motion algorithm when remaining distance to travel is small. /// API_PROPERTY(Attributes="EditorOrder(230), DefaultValue(0.0f), Limit(0, 1000), EditorDisplay(\"Character Controller\")") FORCE_INLINE float GetMinMoveDistance() const @@ -152,7 +151,7 @@ public: } /// - /// Sets the minimum move distance of the character controller.The minimum travelled distance to consider. If travelled distance is smaller, the character doesn't move. This is used to stop the recursive motion algorithm when remaining distance to travel is small. + /// Sets the minimum move distance of the character controller.The minimum traveled distance to consider. If traveled distance is smaller, the character doesn't move. This is used to stop the recursive motion algorithm when remaining distance to travel is small. /// API_PROPERTY() void SetMinMoveDistance(float value); @@ -213,12 +212,6 @@ protected: /// void UpdateSize() const; -private: - -#if USE_EDITOR - void DrawPhysicsDebug(RenderView& view); -#endif - public: // [Collider] @@ -241,12 +234,12 @@ protected: // [PhysicsActor] void UpdateGeometry() override; + void GetGeometry(PxGeometryHolder& geometry) override; void UpdateLayerBits() override; void BeginPlay(SceneBeginData* data) override; void EndPlay() override; #if USE_EDITOR - void OnEnable() override; - void OnDisable() override; + void DrawPhysicsDebug(RenderView& view) override; #endif void OnActiveInTreeChanged() override; void OnParentChanged() override; diff --git a/Source/Engine/Physics/Colliders/Collider.cpp b/Source/Engine/Physics/Colliders/Collider.cpp index e73963c7c..a1a84a523 100644 --- a/Source/Engine/Physics/Colliders/Collider.cpp +++ b/Source/Engine/Physics/Colliders/Collider.cpp @@ -2,6 +2,9 @@ #include "Collider.h" #include "Engine/Core/Log.h" +#if USE_EDITOR +#include "Engine/Level/Scene/SceneRendering.h" +#endif #include "Engine/Serialization/Serialization.h" #include "Engine/Physics/Utilities.h" #include "Engine/Physics/PhysicsSettings.h" @@ -182,6 +185,16 @@ bool Collider::IsAttached() const return _shape && _shape->getActor() != nullptr; } +bool Collider::CanAttach(RigidBody* rigidBody) const +{ + return true; +} + +bool Collider::CanBeTrigger() const +{ + return true; +} + RigidBody* Collider::GetAttachedRigidBody() const { if (_shape && _staticActor == nullptr) @@ -193,6 +206,26 @@ RigidBody* Collider::GetAttachedRigidBody() const return nullptr; } +#if USE_EDITOR + +void Collider::OnEnable() +{ + GetSceneRendering()->AddPhysicsDebug(this); + + // Base + Actor::OnEnable(); +} + +void Collider::OnDisable() +{ + // Base + Actor::OnDisable(); + + GetSceneRendering()->RemovePhysicsDebug(this); +} + +#endif + void Collider::Attach(RigidBody* rigidBody) { ASSERT(CanAttach(rigidBody)); @@ -232,17 +265,20 @@ void Collider::UpdateLayerBits() filterData.word0 = GetLayerMask(); // Own layer mask - filterData.word1 = PhysicsSettings::Instance()->LayerMasks[GetLayer()]; + filterData.word1 = Physics::LayerMasks[GetLayer()]; _shape->setSimulationFilterData(filterData); _shape->setQueryFilterData(filterData); } -void Collider::CreateShapeBase(const PxGeometry& geometry) +void Collider::CreateShape() { - ASSERT(_shape == nullptr); + // Setup shape geometry + _cachedScale = GetScale(); + PxGeometryHolder geometry; + GetGeometry(geometry); - // Prepare + // Create shape const bool isTrigger = _isTrigger && CanBeTrigger(); const PxShapeFlags shapeFlags = GetShapeFlags(isTrigger, IsActiveInHierarchy()); PxMaterial* material = Physics::GetDefaultMaterial(); @@ -250,17 +286,62 @@ void Collider::CreateShapeBase(const PxGeometry& geometry) { material = ((PhysicalMaterial*)Material->Instance)->GetPhysXMaterial(); } - - // Create shape - _shape = CPhysX->createShape(geometry, *material, true, shapeFlags); + ASSERT(_shape == nullptr); + _shape = CPhysX->createShape(geometry.any(), *material, true, shapeFlags); ASSERT(_shape); _shape->userData = this; - - // Setup properties _shape->setContactOffset(Math::Max(_shape->getRestOffset() + ZeroTolerance, _contactOffset)); UpdateLayerBits(); } +void Collider::UpdateGeometry() +{ + if (_shape == nullptr) + return; + + // Setup shape geometry + _cachedScale = GetScale(); + PxGeometryHolder geometry; + GetGeometry(geometry); + + // Recreate shape if geometry has different type + if (_shape->getGeometryType() != geometry.getType()) + { + // Detach from the actor + auto actor = _shape->getActor(); + if (actor) + actor->detachShape(*_shape); + + // Release shape + Physics::RemoveCollider(this); + _shape->release(); + _shape = nullptr; + + // Recreate shape + CreateShape(); + + // Reattach again (only if can, see CanAttach function) + if (actor) + { + const auto rigidBody = dynamic_cast(GetParent()); + if (_staticActor != nullptr || (rigidBody && CanAttach(rigidBody))) + { + actor->attachShape(*_shape); + } + else + { + // Be static triangle mesh + CreateStaticActor(); + } + } + + return; + } + + // Update shape + _shape->setGeometry(geometry.any()); +} + void Collider::CreateStaticActor() { ASSERT(_staticActor == nullptr); @@ -285,6 +366,14 @@ void Collider::RemoveStaticActor() _staticActor = nullptr; } +#if USE_EDITOR + +void Collider::DrawPhysicsDebug(RenderView& view) +{ +} + +#endif + void Collider::OnMaterialChanged() { // Update the shape material diff --git a/Source/Engine/Physics/Colliders/Collider.h b/Source/Engine/Physics/Colliders/Collider.h index 8c6eeb59a..3e7b5ecfd 100644 --- a/Source/Engine/Physics/Colliders/Collider.h +++ b/Source/Engine/Physics/Colliders/Collider.h @@ -160,22 +160,16 @@ public: /// /// The rigid body. /// true if this collider can be attached the specified rigid body; otherwise, false. - virtual bool CanAttach(RigidBody* rigidBody) const - { - return true; - } + virtual bool CanAttach(RigidBody* rigidBody) const; /// /// Determines whether this collider can be a trigger shape. /// /// true if this collider can be trigger; otherwise, false. - virtual bool CanBeTrigger() const - { - return true; - } + virtual bool CanBeTrigger() const; /// - /// Attaches collider to the the specified rigid body. + /// Attaches collider to the specified rigid body. /// /// The rigid body. void Attach(RigidBody* rigidBody); @@ -198,20 +192,20 @@ protected: virtual void UpdateBounds() = 0; /// - /// Creates the collider shape. + /// Gets the collider shape geometry. /// - virtual void CreateShape() = 0; + /// The output geometry. + virtual void GetGeometry(PxGeometryHolder& geometry) = 0; /// - /// Creates the collider shape from the given geometry. + /// Creates the collider shape. /// - /// The geometry. - void CreateShapeBase(const PxGeometry& geometry); + virtual void CreateShape(); /// /// Updates the shape geometry. /// - virtual void UpdateGeometry() = 0; + virtual void UpdateGeometry(); /// /// Creates the static actor. @@ -223,6 +217,10 @@ protected: /// void RemoveStaticActor(); +#if USE_EDITOR + virtual void DrawPhysicsDebug(RenderView& view); +#endif + private: void OnMaterialChanged(); @@ -237,6 +235,10 @@ public: protected: // [PhysicsColliderActor] +#if USE_EDITOR + void OnEnable() override; + void OnDisable() override; +#endif void BeginPlay(SceneBeginData* data) override; void EndPlay() override; void OnActiveInTreeChanged() override; diff --git a/Source/Engine/Physics/Colliders/MeshCollider.cpp b/Source/Engine/Physics/Colliders/MeshCollider.cpp index 1b72d83eb..7ba76f963 100644 --- a/Source/Engine/Physics/Colliders/MeshCollider.cpp +++ b/Source/Engine/Physics/Colliders/MeshCollider.cpp @@ -5,11 +5,6 @@ #include "Engine/Serialization/Serialization.h" #include "Engine/Physics/Utilities.h" #include "Engine/Physics/Physics.h" -#include -#include -#if USE_EDITOR -#include "Engine/Level/Scene/SceneRendering.h" -#endif MeshCollider::MeshCollider(const SpawnParams& params) : Collider(params) @@ -117,26 +112,6 @@ void MeshCollider::Deserialize(DeserializeStream& stream, ISerializeModifier* mo DESERIALIZE(CollisionData); } -#if USE_EDITOR - -void MeshCollider::OnEnable() -{ - GetSceneRendering()->AddPhysicsDebug(this); - - // Base - Collider::OnEnable(); -} - -void MeshCollider::OnDisable() -{ - GetSceneRendering()->RemovePhysicsDebug(this); - - // Base - Collider::OnDisable(); -} - -#endif - void MeshCollider::UpdateBounds() { // Cache bounds @@ -149,11 +124,10 @@ void MeshCollider::UpdateBounds() BoundingSphere::FromBox(_box, _sphere); } -void MeshCollider::CreateShape() +void MeshCollider::GetGeometry(PxGeometryHolder& geometry) { // Prepare scale - Vector3 scale = GetScale(); - _cachedScale = scale; + Vector3 scale = _cachedScale; scale.Absolute(); const float minSize = 0.001f; scale = Vector3::Max(scale, minSize); @@ -165,100 +139,23 @@ void MeshCollider::CreateShape() if (type == CollisionDataType::ConvexMesh) { // Convex mesh - PxConvexMeshGeometry geometry; - geometry.scale.scale = C2P(scale); - geometry.convexMesh = CollisionData->GetConvex(); - CreateShapeBase(geometry); + PxConvexMeshGeometry convexMesh; + convexMesh.scale.scale = C2P(scale); + convexMesh.convexMesh = CollisionData->GetConvex(); + geometry.storeAny(convexMesh); } else if (type == CollisionDataType::TriangleMesh) { // Triangle mesh - PxTriangleMeshGeometry geometry; - geometry.scale.scale = C2P(scale); - geometry.triangleMesh = CollisionData->GetTriangle(); - CreateShapeBase(geometry); + PxTriangleMeshGeometry triangleMesh; + triangleMesh.scale.scale = C2P(scale); + triangleMesh.triangleMesh = CollisionData->GetTriangle(); + geometry.storeAny(triangleMesh); } else { // Dummy geometry - const PxSphereGeometry geometry(0.01f); - CreateShapeBase(geometry); - } -} - -void MeshCollider::UpdateGeometry() -{ - // Check if has no shape created - if (_shape == nullptr) - return; - - // Recreate shape if geometry has different type - CollisionDataType type = CollisionDataType::None; - if (CollisionData && CollisionData->IsLoaded()) - type = CollisionData->GetOptions().Type; - if ((type == CollisionDataType::ConvexMesh && _shape->getGeometryType() != PxGeometryType::eCONVEXMESH) - || (type == CollisionDataType::TriangleMesh && _shape->getGeometryType() != PxGeometryType::eTRIANGLEMESH) - || (type == CollisionDataType::None && _shape->getGeometryType() != PxGeometryType::eSPHERE) - ) - { - // Detach from the actor - auto actor = _shape->getActor(); - if (actor) - actor->detachShape(*_shape); - - // Release shape - Physics::RemoveCollider(this); - _shape->release(); - _shape = nullptr; - - // Recreate shape - CreateShape(); - - // Reattach again (only if can, see CanAttach function) - if (actor) - { - if (_staticActor != nullptr || type != CollisionDataType::TriangleMesh) - { - actor->attachShape(*_shape); - } - else - { - // Be static triangle mesh - CreateStaticActor(); - } - } - - return; - } - - // Prepare scale - Vector3 scale = GetScale(); - _cachedScale = scale; - scale.Absolute(); - const float minSize = 0.001f; - scale = Vector3::Max(scale, minSize); - - // Setup shape (based on type) - if (type == CollisionDataType::ConvexMesh) - { - // Convex mesh - PxConvexMeshGeometry geometry; - geometry.scale.scale = C2P(scale); - geometry.convexMesh = CollisionData->GetConvex(); - _shape->setGeometry(geometry); - } - else if (type == CollisionDataType::TriangleMesh) - { - // Triangle mesh - PxTriangleMeshGeometry geometry; - geometry.scale.scale = C2P(scale); - geometry.triangleMesh = CollisionData->GetTriangle(); - _shape->setGeometry(geometry); - } - else - { - // Dummy geometry - const PxSphereGeometry geometry(0.01f); - _shape->setGeometry(geometry); + const PxSphereGeometry sphere(minSize); + geometry.storeAny(sphere); } } diff --git a/Source/Engine/Physics/Colliders/MeshCollider.h b/Source/Engine/Physics/Colliders/MeshCollider.h index 22dbd755f..efcf60cee 100644 --- a/Source/Engine/Physics/Colliders/MeshCollider.h +++ b/Source/Engine/Physics/Colliders/MeshCollider.h @@ -25,9 +25,6 @@ private: void OnCollisionDataChanged(); void OnCollisionDataLoaded(); -#if USE_EDITOR - void DrawPhysicsDebug(RenderView& view); -#endif public: @@ -45,10 +42,8 @@ protected: // [Collider] #if USE_EDITOR - void OnEnable() override; - void OnDisable() override; + void DrawPhysicsDebug(RenderView& view) override; #endif void UpdateBounds() override; - void CreateShape() override; - void UpdateGeometry() override; + void GetGeometry(PxGeometryHolder& geometry) override; }; diff --git a/Source/Engine/Physics/Colliders/SphereCollider.cpp b/Source/Engine/Physics/Colliders/SphereCollider.cpp index ef308cf8a..b602b8f84 100644 --- a/Source/Engine/Physics/Colliders/SphereCollider.cpp +++ b/Source/Engine/Physics/Colliders/SphereCollider.cpp @@ -4,9 +4,6 @@ #include "Engine/Serialization/Serialization.h" #include "Engine/Physics/Utilities.h" #include -#if USE_EDITOR -#include "Engine/Level/Scene/SceneRendering.h" -#endif SphereCollider::SphereCollider(const SpawnParams& params) : Collider(params) @@ -67,26 +64,6 @@ void SphereCollider::Deserialize(DeserializeStream& stream, ISerializeModifier* DESERIALIZE_MEMBER(Radius, _radius); } -#if USE_EDITOR - -void SphereCollider::OnEnable() -{ - GetSceneRendering()->AddPhysicsDebug(this); - - // Base - Collider::OnEnable(); -} - -void SphereCollider::OnDisable() -{ - GetSceneRendering()->RemovePhysicsDebug(this); - - // Base - Collider::OnDisable(); -} - -#endif - void SphereCollider::UpdateBounds() { // Cache bounds @@ -95,32 +72,11 @@ void SphereCollider::UpdateBounds() _sphere.GetBoundingBox(_box); } -void SphereCollider::CreateShape() +void SphereCollider::GetGeometry(PxGeometryHolder& geometry) { - // Setup shape geometry - _cachedScale = GetScale(); const float scaling = _cachedScale.GetAbsolute().MaxValue(); const float radius = Math::Abs(_radius) * scaling; const float minSize = 0.001f; - const PxSphereGeometry geometry(Math::Max(radius, minSize)); - - // Setup shape - CreateShapeBase(geometry); -} - -void SphereCollider::UpdateGeometry() -{ - // Check if has no shape created - if (_shape == nullptr) - return; - - // Setup shape geometry - _cachedScale = GetScale(); - const float scaling = _cachedScale.GetAbsolute().MaxValue(); - const float radius = Math::Abs(_radius) * scaling; - const float minSize = 0.001f; - const PxSphereGeometry geometry(Math::Max(radius, minSize)); - - // Setup shape - _shape->setGeometry(geometry); + const PxSphereGeometry sphere(Math::Max(radius, minSize)); + geometry.storeAny(sphere); } diff --git a/Source/Engine/Physics/Colliders/SphereCollider.h b/Source/Engine/Physics/Colliders/SphereCollider.h index 0835152f0..ed2d4cd09 100644 --- a/Source/Engine/Physics/Colliders/SphereCollider.h +++ b/Source/Engine/Physics/Colliders/SphereCollider.h @@ -37,12 +37,6 @@ public: /// API_PROPERTY() void SetRadius(float value); -private: - -#if USE_EDITOR - void DrawPhysicsDebug(RenderView& view); -#endif - public: // [Collider] @@ -57,10 +51,8 @@ protected: // [Collider] #if USE_EDITOR - void OnEnable() override; - void OnDisable() override; + void DrawPhysicsDebug(RenderView& view) override; #endif void UpdateBounds() override; - void CreateShape() override; - void UpdateGeometry() override; + void GetGeometry(PxGeometryHolder& geometry) override; }; diff --git a/Source/Engine/Physics/Colliders/SplineCollider.cpp b/Source/Engine/Physics/Colliders/SplineCollider.cpp new file mode 100644 index 000000000..e44d986cc --- /dev/null +++ b/Source/Engine/Physics/Colliders/SplineCollider.cpp @@ -0,0 +1,340 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#include "SplineCollider.h" +#include "Engine/Core/Log.h" +#include "Engine/Core/Math/Matrix.h" +#include "Engine/Level/Actors/Spline.h" +#include "Engine/Serialization/Serialization.h" +#include "Engine/Physics/Utilities.h" +#include "Engine/Physics/Physics.h" +#include "Engine/Profiler/ProfilerCPU.h" +#if COMPILE_WITH_PHYSICS_COOKING +#include "Engine/Physics/CollisionCooking.h" +#include +#include +#endif +#include + +SplineCollider::SplineCollider(const SpawnParams& params) + : Collider(params) +{ + CollisionData.Changed.Bind(this); + CollisionData.Loaded.Bind(this); +} + +Transform SplineCollider::GetPreTransform() const +{ + return _preTransform; +} + +void SplineCollider::SetPreTransform(const Transform& value) +{ + if (_preTransform == value) + return; + _preTransform = value; + UpdateGeometry(); +} + +void SplineCollider::ExtractGeometry(Array& vertexBuffer, Array& indexBuffer) const +{ + vertexBuffer.Add(_vertexBuffer); + indexBuffer.Add(_indexBuffer); +} + +void SplineCollider::OnCollisionDataChanged() +{ + // This should not be called during physics simulation, if it happened use write lock on physx scene + ASSERT(!Physics::IsDuringSimulation()); + + if (CollisionData) + { + // Ensure that collision asset is loaded (otherwise objects might fall though collider that is not yet loaded on play begin) + CollisionData->WaitForLoaded(); + } + + UpdateGeometry(); +} + +void SplineCollider::OnCollisionDataLoaded() +{ + UpdateGeometry(); +} + +void SplineCollider::OnSplineUpdated() +{ + if (!_spline || !IsActiveInHierarchy() || _spline->GetSplinePointsCount() < 2 || !CollisionData || !CollisionData->IsLoaded()) + { + _box = BoundingBox(_transform.Translation, _transform.Translation); + BoundingSphere::FromBox(_box, _sphere); + return; + } + + UpdateGeometry(); +} + +bool SplineCollider::CanAttach(RigidBody* rigidBody) const +{ + return false; +} + +bool SplineCollider::CanBeTrigger() const +{ + return false; +} + +#if USE_EDITOR + +#include "Engine/Debug/DebugDraw.h" + +void SplineCollider::DrawPhysicsDebug(RenderView& view) +{ + DEBUG_DRAW_WIRE_TRIANGLES_EX(_vertexBuffer, _indexBuffer, Color::GreenYellow * 0.8f, 0, true); +} + +void SplineCollider::OnDebugDrawSelected() +{ + DEBUG_DRAW_WIRE_TRIANGLES_EX(_vertexBuffer, _indexBuffer, Color::GreenYellow, 0, false); + + // Base + Collider::OnDebugDrawSelected(); +} + +#endif + +bool SplineCollider::IntersectsItself(const Ray& ray, float& distance, Vector3& normal) +{ + // Use detailed hit + if (_shape) + { + RayCastHit hitInfo; + if (!RayCast(ray.Position, ray.Direction, hitInfo)) + return false; + distance = hitInfo.Distance; + normal = hitInfo.Normal; + return true; + } + + // Fallback to AABB + return _box.Intersects(ray, distance, normal); +} + +void SplineCollider::Serialize(SerializeStream& stream, const void* otherObj) +{ + // Base + Collider::Serialize(stream, otherObj); + + SERIALIZE_GET_OTHER_OBJ(SplineCollider); + + SERIALIZE(CollisionData); + SERIALIZE_MEMBER(PreTransform, _preTransform) +} + +void SplineCollider::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) +{ + // Base + Collider::Deserialize(stream, modifier); + + DESERIALIZE(CollisionData); + DESERIALIZE_MEMBER(PreTransform, _preTransform); +} + +void SplineCollider::OnParentChanged() +{ + if (_spline) + { + _spline->SplineUpdated.Unbind(this); + } + + // Base + Collider::OnParentChanged(); + + _spline = Cast(_parent); + if (_spline) + { + _spline->SplineUpdated.Bind(this); + } + + OnSplineUpdated(); +} + +void SplineCollider::EndPlay() +{ + // Base + Collider::EndPlay(); + + // Cleanup + if (_triangleMesh) + { + Physics::RemoveObject(_triangleMesh); + _triangleMesh = nullptr; + } +} + +void SplineCollider::UpdateBounds() +{ + // Unused as bounds are updated during collision building +} + +void SplineCollider::GetGeometry(PxGeometryHolder& geometry) +{ + // Reset bounds + _box = BoundingBox(_transform.Translation, _transform.Translation); + BoundingSphere::FromBox(_box, _sphere); + + // Skip if sth is missing + if (!_spline || !IsActiveInHierarchy() || _spline->GetSplinePointsCount() < 2 || !CollisionData || !CollisionData->IsLoaded()) + { + geometry.storeAny(PxSphereGeometry(0.001f)); + return; + } + PROFILE_CPU(); + + // Extract collision geometry + // TODO: cache memory allocation for dynamic colliders + Array collisionVertices; + Array collisionIndices; + CollisionData->ExtractGeometry(collisionVertices, collisionIndices); + if (collisionIndices.IsEmpty()) + { + geometry.storeAny(PxSphereGeometry(0.001f)); + return; + } + + // Apply local mesh transformation + if (!_preTransform.IsIdentity()) + { + for (int32 i = 0; i < collisionVertices.Count(); i++) + collisionVertices[i] = _preTransform.LocalToWorld(collisionVertices[i]); + } + + // Find collision geometry local bounds + BoundingBox localModelBounds; + localModelBounds.Minimum = localModelBounds.Maximum = collisionVertices[0]; + for (int32 i = 1; i < collisionVertices.Count(); i++) + { + Vector3 v = collisionVertices[i]; + localModelBounds.Minimum = Vector3::Min(localModelBounds.Minimum, v); + localModelBounds.Maximum = Vector3::Max(localModelBounds.Maximum, v); + } + auto localModelBoundsSize = localModelBounds.GetSize(); + + // Deform geometry over the spline + const auto& keyframes = _spline->Curve.GetKeyframes(); + const int32 segments = keyframes.Count() - 1; + _vertexBuffer.Resize(collisionVertices.Count() * segments); + _indexBuffer.Resize(collisionIndices.Count() * segments); + const Transform splineTransform = _spline->GetTransform(); + const Transform colliderTransform = GetTransform(); + Transform curveTransform, leftTangent, rightTangent; + for (int32 segment = 0; segment < segments; segment++) + { + // Setup for the spline segment + auto offsetVertices = segment * collisionVertices.Count(); + auto offsetIndices = segment * collisionIndices.Count(); + const auto& start = keyframes[segment]; + const auto& end = keyframes[segment + 1]; + const float length = end.Time - start.Time; + AnimationUtils::GetTangent(start.Value, start.TangentOut, length, leftTangent); + AnimationUtils::GetTangent(end.Value, end.TangentIn, length, rightTangent); + + // Vertex buffer is deformed along the spline + auto srcVertices = collisionVertices.Get(); + auto dstVertices = _vertexBuffer.Get() + offsetVertices; + for (int32 i = 0; i < collisionVertices.Count(); i++) + { + Vector3 v = srcVertices[i]; + const float alpha = Math::Saturate((v.Z - localModelBounds.Minimum.Z) / localModelBoundsSize.Z); + v.Z = alpha; + + // Evaluate transformation at the curve + AnimationUtils::Bezier(start.Value, leftTangent, rightTangent, end.Value, alpha, curveTransform); + + // Apply spline direction (from position 1st derivative) + Vector3 direction; + AnimationUtils::BezierFirstDerivative(start.Value.Translation, leftTangent.Translation, rightTangent.Translation, end.Value.Translation, alpha, direction); + direction.Normalize(); + Quaternion orientation; + if (direction.IsZero()) + orientation = Quaternion::Identity; + else if (Vector3::Dot(direction, Vector3::Up) >= 0.999f) + Quaternion::RotationAxis(Vector3::Left, PI_HALF, orientation); + else + Quaternion::LookRotation(direction, Vector3::Cross(Vector3::Cross(direction, Vector3::Up), direction), orientation); + curveTransform.Orientation = orientation * curveTransform.Orientation; + + // Transform vertex + v = curveTransform.LocalToWorld(v); + v = splineTransform.LocalToWorld(v); + v = colliderTransform.WorldToLocal(v); + + dstVertices[i] = v; + } + + // Index buffer is the same for every segment except it's shifted + auto srcIndices = collisionIndices.Get(); + auto dstIndices = _indexBuffer.Get() + offsetIndices; + for (int32 i = 0; i < collisionIndices.Count(); i++) + dstIndices[i] = srcIndices[i] + offsetVertices; + } + + // Prepare scale + Vector3 scale = _cachedScale; + scale.Absolute(); + const float minSize = 0.001f; + scale = Vector3::Max(scale, minSize); + + // TODO: add support for cooking collision for static splines in editor and reusing it in game + +#if COMPILE_WITH_PHYSICS_COOKING + // Cook triangle mesh collision + CollisionCooking::CookingInput cookingInput; + cookingInput.VertexCount = _vertexBuffer.Count(); + cookingInput.VertexData = _vertexBuffer.Get(); + cookingInput.IndexCount = _indexBuffer.Count(); + cookingInput.IndexData = _indexBuffer.Get(); + cookingInput.Is16bitIndexData = false; + BytesContainer collisionData; + if (!CollisionCooking::CookTriangleMesh(cookingInput, collisionData)) + { + // Create triangle mesh + if (_triangleMesh) + { + Physics::RemoveObject(_triangleMesh); + _triangleMesh = nullptr; + } + PxDefaultMemoryInputData input(collisionData.Get(), collisionData.Length()); + // TODO: try using getVerticesForModification for dynamic triangle mesh vertices updating when changing curve in the editor + _triangleMesh = Physics::GetPhysics()->createTriangleMesh(input); + if (!_triangleMesh) + { + LOG(Error, "Failed to create triangle mesh from collision data of {0}.", ToString()); + geometry.storeAny(PxSphereGeometry(0.001f)); + return; + } + + // Transform vertices back to world space for debug shapes drawing and navmesh building + for (int32 i = 0; i < _vertexBuffer.Count(); i++) + _vertexBuffer[i] = colliderTransform.LocalToWorld(_vertexBuffer[i]); + + // Update bounds + _box = P2C(_triangleMesh->getLocalBounds()); + Matrix splineWorld; + colliderTransform.GetWorld(splineWorld); + BoundingBox::Transform(_box, splineWorld, _box); + BoundingSphere::FromBox(_box, _sphere); + + // Setup geometry + PxTriangleMeshGeometry triangleMesh; + triangleMesh.scale.scale = C2P(scale); + triangleMesh.triangleMesh = _triangleMesh; + geometry.storeAny(triangleMesh); + + // TODO: find a way of releasing _vertexBuffer and _indexBuffer for static colliders (note: ExtractGeometry usage for navmesh generation at runtime) + + return; + } +#endif + + LOG(Error, "Cannot build collision data for {0} due to runtime collision cooking diabled.", ToString()); + geometry.storeAny(PxSphereGeometry(0.001f)); +} diff --git a/Source/Engine/Physics/Colliders/SplineCollider.h b/Source/Engine/Physics/Colliders/SplineCollider.h new file mode 100644 index 000000000..c1cdab7b7 --- /dev/null +++ b/Source/Engine/Physics/Colliders/SplineCollider.h @@ -0,0 +1,80 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Collider.h" +#include "Engine/Content/AssetReference.h" +#include "Engine/Physics/CollisionData.h" + +class Spline; + +/// +/// A collider represented by an arbitrary mesh that goes over the spline. +/// +/// +/// +API_CLASS() class FLAXENGINE_API SplineCollider : public Collider +{ +DECLARE_SCENE_OBJECT(SplineCollider); +private: + Spline* _spline = nullptr; + PxTriangleMesh* _triangleMesh = nullptr; + Array _vertexBuffer; + Array _indexBuffer; + Transform _preTransform = Transform::Identity; + +public: + + /// + /// Linked collision data asset that contains convex mesh or triangle mesh used to represent a spline collider shape. + /// + API_FIELD(Attributes="EditorOrder(100), DefaultValue(null), EditorDisplay(\"Collider\")") + AssetReference CollisionData; + + /// + /// Gets the transformation applied to the collision data model geometry before placing it over the spline. Can be used to change the way model goes over the spline. + /// + API_PROPERTY(Attributes="EditorOrder(101), EditorDisplay(\"Collider\")") + Transform GetPreTransform() const; + + /// + /// Sets the transformation applied to the collision data model geometry before placing it over the spline. Can be used to change the way model goes over the spline. + /// + API_PROPERTY() void SetPreTransform(const Transform& value); + + /// + /// Extracts the collision data geometry into list of triangles. + /// + /// The output vertex buffer. + /// The output index buffer. + void ExtractGeometry(Array& vertexBuffer, Array& indexBuffer) const; + +private: + + void OnCollisionDataChanged(); + void OnCollisionDataLoaded(); + void OnSplineUpdated(); + +public: + + // [Collider] + bool CanAttach(RigidBody* rigidBody) const override; + bool CanBeTrigger() const override; +#if USE_EDITOR + void OnDebugDrawSelected() override; +#endif + bool IntersectsItself(const Ray& ray, float& distance, Vector3& normal) override; + void Serialize(SerializeStream& stream, const void* otherObj) override; + void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; + void OnParentChanged() override; + void EndPlay() override; + +protected: + + // [Collider] +#if USE_EDITOR + void DrawPhysicsDebug(RenderView& view) override; +#endif + void UpdateBounds() override; + void GetGeometry(PxGeometryHolder& geometry) override; +}; diff --git a/Source/Engine/Physics/CollisionCooking.cpp b/Source/Engine/Physics/CollisionCooking.cpp index ea0f2f442..9076d979e 100644 --- a/Source/Engine/Physics/CollisionCooking.cpp +++ b/Source/Engine/Physics/CollisionCooking.cpp @@ -10,10 +10,12 @@ #include #include +#define CONVEX_VERTEX_MIN 8 +#define CONVEX_VERTEX_MAX 255 #define ENSURE_CAN_COOK \ if (Physics::GetCooking() == nullptr) \ { \ - LOG(Warning, "Physics collisions cooking is disabled at runtime. Enable Physics Settigns option SupportCookingAtRuntime to use terrain generation at runtime."); \ + LOG(Warning, "Physics collisions cooking is disabled at runtime. Enable Physics Settings option SupportCookingAtRuntime to use collision generation at runtime."); \ return true; \ } @@ -27,7 +29,10 @@ bool CollisionCooking::CookConvexMesh(CookingInput& input, BytesContainer& outpu desc.points.stride = sizeof(Vector3); desc.points.data = input.VertexData; desc.flags = PxConvexFlag::eCOMPUTE_CONVEX; - desc.vertexLimit = input.ConvexVertexLimit; + if (input.ConvexVertexLimit == 0) + desc.vertexLimit = CONVEX_VERTEX_MAX; + else + desc.vertexLimit = (PxU16)Math::Clamp(input.ConvexVertexLimit, CONVEX_VERTEX_MIN, CONVEX_VERTEX_MAX); if (input.ConvexFlags & ConvexMeshGenerationFlags::SkipValidation) desc.flags |= PxConvexFlag::Enum::eDISABLE_MESH_VALIDATION; if (input.ConvexFlags & ConvexMeshGenerationFlags::UsePlaneShifting) @@ -83,9 +88,9 @@ bool CollisionCooking::CookTriangleMesh(CookingInput& input, BytesContainer& out bool CollisionCooking::CookCollision(const Argument& arg, CollisionData::SerializedOptions& outputOptions, BytesContainer& outputData) { - int32 convexVertexLimit = Math::Clamp(arg.ConvexVertexLimit, 8, 255); + int32 convexVertexLimit = Math::Clamp(arg.ConvexVertexLimit, CONVEX_VERTEX_MIN, CONVEX_VERTEX_MAX); if (arg.ConvexVertexLimit == 0) - convexVertexLimit = 255; + convexVertexLimit = CONVEX_VERTEX_MAX; DataContainer finalVertexData; DataContainer finalIndexData; @@ -106,19 +111,19 @@ bool CollisionCooking::CookCollision(const Argument& arg, CollisionData::Seriali auto lod = &arg.OverrideModelData->LODs[lodIndex]; const int32 meshesCount = lod->Meshes.Count(); - // Count vertex/index buffer sizes - int32 vCount = 0; - int32 iCount = 0; - for (int32 i = 0; i < meshesCount; i++) - { - const auto mesh = lod->Meshes[i]; - vCount += mesh->Positions.Count(); + // Count vertex/index buffer sizes + int32 vCount = 0; + int32 iCount = 0; + for (int32 i = 0; i < meshesCount; i++) + { + const auto mesh = lod->Meshes[i]; + vCount += mesh->Positions.Count(); - if (needIndexBuffer) - { - iCount += mesh->Indices.Count() * 3; - } + if (needIndexBuffer) + { + iCount += mesh->Indices.Count() * 3; } + } if (meshesCount == 1) { diff --git a/Source/Engine/Physics/CollisionCooking.h b/Source/Engine/Physics/CollisionCooking.h index b368af547..658f88428 100644 --- a/Source/Engine/Physics/CollisionCooking.h +++ b/Source/Engine/Physics/CollisionCooking.h @@ -24,13 +24,13 @@ public: struct CookingInput { - int32 VertexCount; - Vector3* VertexData; - int32 IndexCount; - void* IndexData; - bool Is16bitIndexData; - ConvexMeshGenerationFlags ConvexFlags; - int32 ConvexVertexLimit; + int32 VertexCount = 0; + Vector3* VertexData = nullptr; + int32 IndexCount = 0; + void* IndexData = nullptr; + bool Is16bitIndexData = false; + ConvexMeshGenerationFlags ConvexFlags = ConvexMeshGenerationFlags::None; + int32 ConvexVertexLimit = 255; }; /// @@ -38,21 +38,12 @@ public: /// struct Argument { - CollisionDataType Type; - ModelData* OverrideModelData; + CollisionDataType Type = CollisionDataType::None; + ModelData* OverrideModelData = nullptr; AssetReference Model; - int32 ModelLodIndex; - ConvexMeshGenerationFlags ConvexFlags; - int32 ConvexVertexLimit; - - Argument() - { - Type = CollisionDataType::None; - OverrideModelData = nullptr; - ModelLodIndex = 0; - ConvexFlags = ConvexMeshGenerationFlags::None; - ConvexVertexLimit = 255; - } + int32 ModelLodIndex = 0; + ConvexMeshGenerationFlags ConvexFlags = ConvexMeshGenerationFlags::None; + int32 ConvexVertexLimit = 255; }; /// diff --git a/Source/Engine/Physics/Joints/Joint.cpp b/Source/Engine/Physics/Joints/Joint.cpp index cda249d71..56c5c1b32 100644 --- a/Source/Engine/Physics/Joints/Joint.cpp +++ b/Source/Engine/Physics/Joints/Joint.cpp @@ -295,6 +295,9 @@ void Joint::OnParentChanged() // Base Actor::OnParentChanged(); + if (!IsDuringPlay()) + return; + // Check reparenting Joint case const auto parent = dynamic_cast(GetParent()); if (parent == nullptr) diff --git a/Source/Engine/Physics/PhysicalMaterial.cpp b/Source/Engine/Physics/PhysicalMaterial.cpp deleted file mode 100644 index bd1ecd7e0..000000000 --- a/Source/Engine/Physics/PhysicalMaterial.cpp +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -#include "PhysicalMaterial.h" -#include "PhysicsSettings.h" -#include "Engine/Serialization/JsonTools.h" -#include "Physics.h" -#include -#include - -PhysicalMaterial::PhysicalMaterial() - : _material(nullptr) -{ -} - -PhysicalMaterial::~PhysicalMaterial() -{ - if (_material) - { - Physics::RemoveMaterial(_material); - } -} - -PxMaterial* PhysicalMaterial::GetPhysXMaterial() -{ - if (_material == nullptr && CPhysX) - { - _material = CPhysX->createMaterial(Friction, Friction, Restitution); - _material->userData = this; - - const PhysicsCombineMode useFrictionCombineMode = (OverrideFrictionCombineMode ? FrictionCombineMode : PhysicsSettings::Instance()->FrictionCombineMode); - _material->setFrictionCombineMode(static_cast(useFrictionCombineMode)); - - const PhysicsCombineMode useRestitutionCombineMode = (OverrideRestitutionCombineMode ? RestitutionCombineMode : PhysicsSettings::Instance()->RestitutionCombineMode); - _material->setRestitutionCombineMode(static_cast(useRestitutionCombineMode)); - } - - return _material; -} - -void PhysicalMaterial::UpdatePhysXMaterial() -{ - if (_material != nullptr) - { - _material->setStaticFriction(Friction); - _material->setDynamicFriction(Friction); - const PhysicsCombineMode useFrictionCombineMode = (OverrideFrictionCombineMode ? FrictionCombineMode : PhysicsSettings::Instance()->FrictionCombineMode); - _material->setFrictionCombineMode(static_cast(useFrictionCombineMode)); - - _material->setRestitution(Restitution); - const PhysicsCombineMode useRestitutionCombineMode = (OverrideRestitutionCombineMode ? RestitutionCombineMode : PhysicsSettings::Instance()->RestitutionCombineMode); - _material->setRestitutionCombineMode(static_cast(useRestitutionCombineMode)); - } -} - -void PhysicalMaterial::Serialize(SerializeStream& stream, const void* otherObj) -{ - MISSING_CODE("PhysicalMaterial::Serialize is not implemented"); -} - -void PhysicalMaterial::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) -{ - Friction = JsonTools::GetFloat(stream, "Friction", PhysicalMaterial_Friction); - FrictionCombineMode = JsonTools::GetEnum(stream, "FrictionCombineMode", PhysicalMaterial_FrictionCombineMode); - OverrideFrictionCombineMode = JsonTools::GetBool(stream, "OverrideFrictionCombineMode", PhysicalMaterial_OverrideFrictionCombineMode); - Restitution = JsonTools::GetFloat(stream, "Restitution", PhysicalMaterial_Restitution); - RestitutionCombineMode = JsonTools::GetEnum(stream, "RestitutionCombineMode", PhysicalMaterial_RestitutionCombineMode); - OverrideRestitutionCombineMode = JsonTools::GetBool(stream, "OverrideRestitutionCombineMode", PhysicalMaterial_OverrideRestitutionCombineMode); -} diff --git a/Source/Engine/Physics/PhysicalMaterial.h b/Source/Engine/Physics/PhysicalMaterial.h index 3b06a2a43..66a321b86 100644 --- a/Source/Engine/Physics/PhysicalMaterial.h +++ b/Source/Engine/Physics/PhysicalMaterial.h @@ -5,20 +5,13 @@ #include "Types.h" #include "Engine/Serialization/ISerializable.h" -// Default values for the physical material - -#define PhysicalMaterial_Friction 0.7f -#define PhysicalMaterial_FrictionCombineMode PhysicsCombineMode::Average -#define PhysicalMaterial_OverrideFrictionCombineMode false -#define PhysicalMaterial_Restitution 0.3f -#define PhysicalMaterial_RestitutionCombineMode PhysicsCombineMode::Average -#define PhysicalMaterial_OverrideRestitutionCombineMode false - /// /// Physical materials are used to define the response of a physical object when interacting dynamically with the world. /// -class FLAXENGINE_API PhysicalMaterial : public ISerializable +API_CLASS() class FLAXENGINE_API PhysicalMaterial final : public ISerializable { +API_AUTO_SERIALIZATION(); +DECLARE_SCRIPTING_TYPE_MINIMAL(PhysicalMaterial); private: PxMaterial* _material; @@ -40,32 +33,38 @@ public: /// /// The friction value of surface, controls how easily things can slide on this surface. /// - float Friction = PhysicalMaterial_Friction; + API_FIELD(Attributes="EditorOrder(0), Limit(0), EditorDisplay(\"Physical Material\")") + float Friction = 0.7f; /// /// The friction combine mode, controls how friction is computed for multiple materials. /// - PhysicsCombineMode FrictionCombineMode = PhysicalMaterial_FrictionCombineMode; + API_FIELD(Attributes="EditorOrder(1), EditorDisplay(\"Physical Material\")") + PhysicsCombineMode FrictionCombineMode = PhysicsCombineMode::Average; /// /// If set we will use the FrictionCombineMode of this material, instead of the FrictionCombineMode found in the Physics settings. /// - bool OverrideFrictionCombineMode = PhysicalMaterial_OverrideFrictionCombineMode; + API_FIELD(Attributes="HideInEditor") + bool OverrideFrictionCombineMode = false; /// /// The restitution or 'bounciness' of this surface, between 0 (no bounce) and 1 (outgoing velocity is same as incoming). /// - float Restitution = PhysicalMaterial_Restitution; + API_FIELD(Attributes="EditorOrder(3), Range(0, 1), EditorDisplay(\"Physical Material\")") + float Restitution = 0.3f; /// /// The restitution combine mode, controls how restitution is computed for multiple materials. /// - PhysicsCombineMode RestitutionCombineMode = PhysicalMaterial_RestitutionCombineMode; + API_FIELD(Attributes="EditorOrder(4), EditorDisplay(\"Physical Material\")") + PhysicsCombineMode RestitutionCombineMode = PhysicsCombineMode::Average; /// /// If set we will use the RestitutionCombineMode of this material, instead of the RestitutionCombineMode found in the Physics settings. /// - bool OverrideRestitutionCombineMode = PhysicalMaterial_OverrideRestitutionCombineMode; + API_FIELD(Attributes="HideInEditor") + bool OverrideRestitutionCombineMode = false; public: @@ -79,10 +78,4 @@ public: /// Updates the PhysX material (after any property change). /// void UpdatePhysXMaterial(); - -public: - - // [ISerializable] - void Serialize(SerializeStream& stream, const void* otherObj) override; - void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; }; diff --git a/Source/Engine/Physics/Physics.cpp b/Source/Engine/Physics/Physics.cpp index 2daa47010..98237948f 100644 --- a/Source/Engine/Physics/Physics.cpp +++ b/Source/Engine/Physics/Physics.cpp @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "Physics.h" +#include "PhysicalMaterial.h" #include "Engine/Core/Log.h" #include "Engine/Threading/Threading.h" #include "Engine/Platform/CPUInfo.h" @@ -13,6 +14,8 @@ #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Core/Memory/Memory.h" #include "Engine/Engine/EngineService.h" +#include "Engine/Serialization/Serialization.h" +#include "Engine/Engine/Time.h" #include #include #if WITH_PVD @@ -110,34 +113,43 @@ struct ActionData }; PxPhysics* CPhysX = nullptr; -PhysXAllocator PhysXAllocatorCallback; -PhysXError PhysXErrorCallback; -PxSimulationFilterShader PhysXDefaultFilterShader = PxDefaultSimulationFilterShader; #if WITH_PVD PxPvd* CPVD = nullptr; #endif -PxTolerancesScale ToleranceScale; -SimulationEventCallback EventsCallback; -void* ScratchMemory = nullptr; -FixedStepper* Stepper = nullptr; -CriticalSection FlushLocker; -Array NewActors; -Array Actions; -Array DeadActors; -Array DeadMaterials; -Array _deadObjects; -Array DeadColliders; -Array DeadJoints; -bool _isDuringSimulation = false; -PxFoundation* _foundation = nullptr; + +namespace +{ + PhysXAllocator PhysXAllocatorCallback; + PhysXError PhysXErrorCallback; + PxSimulationFilterShader PhysXDefaultFilterShader = PxDefaultSimulationFilterShader; + PxTolerancesScale ToleranceScale; + SimulationEventCallback EventsCallback; + void* ScratchMemory = nullptr; + FixedStepper* Stepper = nullptr; + CriticalSection FlushLocker; + Array NewActors; + Array Actions; + Array DeadActors; + Array DeadMaterials; + Array _deadObjects; + Array DeadColliders; + Array DeadJoints; + bool _queriesHitTriggers = true; + bool _isDuringSimulation = false; + PhysicsCombineMode _frictionCombineMode = PhysicsCombineMode::Average; + PhysicsCombineMode _restitutionCombineMode = PhysicsCombineMode::Average; + PxFoundation* _foundation = nullptr; #if COMPILE_WITH_PHYSICS_COOKING -PxCooking* Cooking = nullptr; + PxCooking* Cooking = nullptr; #endif -PxScene* PhysicsScene = nullptr; -PxMaterial* DefaultMaterial = nullptr; -PxControllerManager* ControllerManager = nullptr; -PxCpuDispatcher* CpuDispatcher = nullptr; + PxScene* PhysicsScene = nullptr; + PxMaterial* DefaultMaterial = nullptr; + PxControllerManager* ControllerManager = nullptr; + PxCpuDispatcher* CpuDispatcher = nullptr; +} + bool Physics::AutoSimulation = true; +uint32 Physics::LayerMasks[32]; class PhysicsService : public EngineService { @@ -146,6 +158,8 @@ public: PhysicsService() : EngineService(TEXT("Physics"), 0) { + for (int32 i = 0; i < 32; i++) + Physics::LayerMasks[i] = MAX_uint32; } bool Init() override; @@ -155,12 +169,146 @@ public: PhysicsService PhysicsServiceInstance; +PxShapeFlags GetShapeFlags(bool isTrigger, bool isEnabled) +{ +#if WITH_PVD + PxShapeFlags flags = PxShapeFlag::eVISUALIZATION; +#else + PxShapeFlags flags = static_cast(0); +#endif + + if (isEnabled) + { + if (isTrigger) + { + flags |= PxShapeFlag::eTRIGGER_SHAPE; + if (_queriesHitTriggers) + flags |= PxShapeFlag::eSCENE_QUERY_SHAPE; + } + else + { + flags = PxShapeFlag::eSIMULATION_SHAPE | PxShapeFlag::eSCENE_QUERY_SHAPE; + } + } + + return flags; +} + +void PhysicsSettings::Apply() +{ + Time::_physicsMaxDeltaTime = MaxDeltaTime; + _queriesHitTriggers = QueriesHitTriggers; + _frictionCombineMode = FrictionCombineMode; + _restitutionCombineMode = RestitutionCombineMode; + Platform::MemoryCopy(Physics::LayerMasks, LayerMasks, sizeof(LayerMasks)); + Physics::SetGravity(DefaultGravity); + Physics::SetBounceThresholdVelocity(BounceThresholdVelocity); + Physics::SetEnableCCD(!DisableCCD); + + // TODO: setting eADAPTIVE_FORCE requires PxScene setup (physx docs: This flag is not mutable, and must be set in PxSceneDesc at scene creation.) + // TODO: update all shapes filter data + // TODO: update all shapes flags + + /* + { + get all actors and then: + + const PxU32 numShapes = actor->getNbShapes(); + PxShape** shapes = (PxShape**)SAMPLE_ALLOC(sizeof(PxShape*)*numShapes); + actor->getShapes(shapes, numShapes); + for (PxU32 i = 0; i < numShapes; i++) + { + .. + } + SAMPLE_FREE(shapes); + }*/ +} + +PhysicsSettings::PhysicsSettings() +{ + for (int32 i = 0; i < 32; i++) + LayerMasks[i] = MAX_uint32; +} + +void PhysicsSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) +{ + DESERIALIZE(DefaultGravity); + DESERIALIZE(TriangleMeshTriangleMinAreaThreshold); + DESERIALIZE(BounceThresholdVelocity); + DESERIALIZE(FrictionCombineMode); + DESERIALIZE(RestitutionCombineMode); + DESERIALIZE(DisableCCD); + DESERIALIZE(EnableAdaptiveForce); + DESERIALIZE(MaxDeltaTime); + DESERIALIZE(EnableSubstepping); + DESERIALIZE(SubstepDeltaTime); + DESERIALIZE(MaxSubsteps); + DESERIALIZE(QueriesHitTriggers); + DESERIALIZE(SupportCookingAtRuntime); + + const auto layers = stream.FindMember("LayerMasks"); + if (layers != stream.MemberEnd()) + { + auto& layersArray = layers->value; + ASSERT(layersArray.IsArray()); + for (uint32 i = 0; i < layersArray.Size() && i < 32; i++) + { + LayerMasks[i] = layersArray[i].GetUint(); + } + } +} + +PhysicalMaterial::PhysicalMaterial() + : _material(nullptr) +{ +} + +PhysicalMaterial::~PhysicalMaterial() +{ + if (_material) + { + Physics::RemoveMaterial(_material); + } +} + +PxMaterial* PhysicalMaterial::GetPhysXMaterial() +{ + if (_material == nullptr && CPhysX) + { + _material = CPhysX->createMaterial(Friction, Friction, Restitution); + _material->userData = this; + + const PhysicsCombineMode useFrictionCombineMode = OverrideFrictionCombineMode ? FrictionCombineMode : _frictionCombineMode; + _material->setFrictionCombineMode(static_cast(useFrictionCombineMode)); + + const PhysicsCombineMode useRestitutionCombineMode = OverrideRestitutionCombineMode ? RestitutionCombineMode : _restitutionCombineMode; + _material->setRestitutionCombineMode(static_cast(useRestitutionCombineMode)); + } + + return _material; +} + +void PhysicalMaterial::UpdatePhysXMaterial() +{ + if (_material != nullptr) + { + _material->setStaticFriction(Friction); + _material->setDynamicFriction(Friction); + const PhysicsCombineMode useFrictionCombineMode = OverrideFrictionCombineMode ? FrictionCombineMode : _frictionCombineMode; + _material->setFrictionCombineMode(static_cast(useFrictionCombineMode)); + + _material->setRestitution(Restitution); + const PhysicsCombineMode useRestitutionCombineMode = OverrideRestitutionCombineMode ? RestitutionCombineMode : _restitutionCombineMode; + _material->setRestitutionCombineMode(static_cast(useRestitutionCombineMode)); + } +} + bool PhysicsService::Init() { #define CHECK_INIT(value, msg) if(!value) { LOG(Error, msg); return true; } auto cpuInfo = Platform::GetCPUInfo(); - auto settings = PhysicsSettings::Instance(); + auto& settings = *PhysicsSettings::Get(); // Send info LOG(Info, "Setup NVIDIA PhysX {0}.{1}.{2}", PX_PHYSICS_VERSION_MAJOR, PX_PHYSICS_VERSION_MINOR, PX_PHYSICS_VERSION_BUGFIX); @@ -220,7 +368,7 @@ bool PhysicsService::Init() #if COMPILE_WITH_PHYSICS_COOKING #if !USE_EDITOR - if (settings->SupportCookingAtRuntime) + if (settings.SupportCookingAtRuntime) #endif { // Init cooking @@ -235,16 +383,16 @@ bool PhysicsService::Init() // Create scene description PxSceneDesc sceneDesc(CPhysX->getTolerancesScale()); - sceneDesc.gravity = C2P(settings->DefaultGravity); + sceneDesc.gravity = C2P(settings.DefaultGravity); sceneDesc.flags |= PxSceneFlag::eENABLE_ACTIVE_ACTORS; //sceneDesc.flags |= PxSceneFlag::eEXCLUDE_KINEMATICS_FROM_ACTIVE_ACTORS; // TODO: set it? - if (!settings->DisableCCD) + if (!settings.DisableCCD) sceneDesc.flags |= PxSceneFlag::eENABLE_CCD; - if (settings->EnableAdaptiveForce) + if (settings.EnableAdaptiveForce) sceneDesc.flags |= PxSceneFlag::eADAPTIVE_FORCE; sceneDesc.simulationEventCallback = &EventsCallback; sceneDesc.filterShader = PhysiXFilterShader; - sceneDesc.bounceThresholdVelocity = settings->BounceThresholdVelocity; + sceneDesc.bounceThresholdVelocity = settings.BounceThresholdVelocity; if (sceneDesc.cpuDispatcher == nullptr) { CpuDispatcher = PxDefaultCpuDispatcherCreate(Math::Clamp(cpuInfo.ProcessorCoreCount - 1, 1, 4)); @@ -366,7 +514,7 @@ void Physics::SetGravity(const Vector3& value) bool Physics::GetEnableCCD() { - return PhysicsScene ? (PhysicsScene->getFlags() & PxSceneFlag::eENABLE_CCD) == PxSceneFlag::eENABLE_CCD : !PhysicsSettings_DisableCCD; + return PhysicsScene ? (PhysicsScene->getFlags() & PxSceneFlag::eENABLE_CCD) == PxSceneFlag::eENABLE_CCD : !PhysicsSettings::Get()->DisableCCD; } void Physics::SetEnableCCD(const bool value) @@ -377,7 +525,7 @@ void Physics::SetEnableCCD(const bool value) float Physics::GetBounceThresholdVelocity() { - return PhysicsScene ? PhysicsScene->getBounceThresholdVelocity() : PhysicsSettings_BounceThresholdVelocity; + return PhysicsScene ? PhysicsScene->getBounceThresholdVelocity() : PhysicsSettings::Get()->BounceThresholdVelocity; } void Physics::SetBounceThresholdVelocity(const float value) @@ -390,13 +538,13 @@ void Physics::Simulate(float dt) { ASSERT(IsInMainThread() && !_isDuringSimulation); ASSERT(CPhysX); - const auto settings = PhysicsSettings::Instance(); + const auto& settings = *PhysicsSettings::Get(); // Flush the old/new objects and the other requests before the simulation FlushRequests(); // Clamp delta - dt = Math::Clamp(dt, 0.0f, settings->MaxDeltaTime); + dt = Math::Clamp(dt, 0.0f, settings.MaxDeltaTime); // Prepare util objects if (ScratchMemory == nullptr) @@ -407,10 +555,10 @@ void Physics::Simulate(float dt) { Stepper = New(); } - if (settings->EnableSubstepping) + if (settings.EnableSubstepping) { // Use substeps - Stepper->Setup(settings->SubstepDeltaTime, settings->MaxSubsteps); + Stepper->Setup(settings.SubstepDeltaTime, settings.MaxSubsteps); } else { diff --git a/Source/Engine/Physics/Physics.h b/Source/Engine/Physics/Physics.h index bdc1326d3..05b64cb0f 100644 --- a/Source/Engine/Physics/Physics.h +++ b/Source/Engine/Physics/Physics.h @@ -155,6 +155,11 @@ public: /// API_PROPERTY() static void SetBounceThresholdVelocity(float value); + /// + /// The collision layers masks. Used to define layer-based collision detection. + /// + static uint32 LayerMasks[32]; + public: /// diff --git a/Source/Engine/Physics/PhysicsSettings.cpp b/Source/Engine/Physics/PhysicsSettings.cpp deleted file mode 100644 index be8bff331..000000000 --- a/Source/Engine/Physics/PhysicsSettings.cpp +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -#include "PhysicsSettings.h" -#include "Engine/Serialization/Serialization.h" -#include "Physics.h" - -void PhysicsSettings::Apply() -{ - // Set simulation parameters - Physics::SetGravity(DefaultGravity); - Physics::SetBounceThresholdVelocity(BounceThresholdVelocity); - Physics::SetEnableCCD(!DisableCCD); - - // TODO: setting eADAPTIVE_FORCE requires PxScene setup (physx docs: This flag is not mutable, and must be set in PxSceneDesc at scene creation.) - - // Update all shapes - - // TODO: update all shapes filter data - // TODO: update all shapes flags - - /* - { - get all actors and then: - - const PxU32 numShapes = actor->getNbShapes(); - PxShape** shapes = (PxShape**)SAMPLE_ALLOC(sizeof(PxShape*)*numShapes); - actor->getShapes(shapes, numShapes); - for (PxU32 i = 0; i < numShapes; i++) - { - .. - } - SAMPLE_FREE(shapes); - }*/ -} - -void PhysicsSettings::RestoreDefault() -{ - DefaultGravity = PhysicsSettings_DefaultGravity; - TriangleMeshTriangleMinAreaThreshold = PhysicsSettings_TriangleMeshTriangleMinAreaThreshold; - BounceThresholdVelocity = PhysicsSettings_BounceThresholdVelocity; - FrictionCombineMode = PhysicsSettings_FrictionCombineMode; - RestitutionCombineMode = PhysicsSettings_RestitutionCombineMode; - DisableCCD = PhysicsSettings_DisableCCD; - EnableAdaptiveForce = PhysicsSettings_EnableAdaptiveForce; - MaxDeltaTime = PhysicsSettings_MaxDeltaTime; - EnableSubstepping = PhysicsSettings_EnableSubstepping; - SubstepDeltaTime = PhysicsSettings_SubstepDeltaTime; - MaxSubsteps = PhysicsSettings_MaxSubsteps; - QueriesHitTriggers = PhysicsSettings_QueriesHitTriggers; - SupportCookingAtRuntime = PhysicsSettings_SupportCookingAtRuntime; - - for (int32 i = 0; i < 32; i++) - LayerMasks[i] = MAX_uint32; -} - -void PhysicsSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) -{ - DESERIALIZE(DefaultGravity); - DESERIALIZE(TriangleMeshTriangleMinAreaThreshold); - DESERIALIZE(BounceThresholdVelocity); - DESERIALIZE(FrictionCombineMode); - DESERIALIZE(RestitutionCombineMode); - DESERIALIZE(DisableCCD); - DESERIALIZE(EnableAdaptiveForce); - DESERIALIZE(MaxDeltaTime); - DESERIALIZE(EnableSubstepping); - DESERIALIZE(SubstepDeltaTime); - DESERIALIZE(MaxSubsteps); - DESERIALIZE(QueriesHitTriggers); - DESERIALIZE(SupportCookingAtRuntime); - - const auto layers = stream.FindMember("LayerMasks"); - if (layers != stream.MemberEnd()) - { - auto& layersArray = layers->value; - ASSERT(layersArray.IsArray()); - for (uint32 i = 0; i < layersArray.Size() && i < 32; i++) - { - LayerMasks[i] = layersArray[i].GetUint(); - } - } -} diff --git a/Source/Engine/Physics/PhysicsSettings.h b/Source/Engine/Physics/PhysicsSettings.h index 84c00deb9..214c86558 100644 --- a/Source/Engine/Physics/PhysicsSettings.h +++ b/Source/Engine/Physics/PhysicsSettings.h @@ -6,105 +6,91 @@ #include "Engine/Core/Math/Vector3.h" #include "Types.h" -// Default values for the physics settings - -#define PhysicsSettings_DefaultGravity Vector3(0, -981.0f, 0) -#define PhysicsSettings_TriangleMeshTriangleMinAreaThreshold (5.0f) -#define PhysicsSettings_BounceThresholdVelocity (200.0f) -#define PhysicsSettings_FrictionCombineMode PhysicsCombineMode::Average -#define PhysicsSettings_RestitutionCombineMode PhysicsCombineMode::Average -#define PhysicsSettings_DisableCCD false -#define PhysicsSettings_EnableAdaptiveForce false -#define PhysicsSettings_MaxDeltaTime (1.0f / 10.0f) -#define PhysicsSettings_EnableSubstepping false -#define PhysicsSettings_SubstepDeltaTime (1.0f / 120.0f) -#define PhysicsSettings_MaxSubsteps 5 -#define PhysicsSettings_QueriesHitTriggers true -#define PhysicsSettings_SupportCookingAtRuntime false - /// /// Physics simulation settings container. /// -/// -class PhysicsSettings : public Settings +API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings", NoConstructor) class FLAXENGINE_API PhysicsSettings : public SettingsBase { -public: - - /// - /// Initializes a new instance of the class. - /// - PhysicsSettings() - { - for (int32 i = 0; i < 32; i++) - LayerMasks[i] = MAX_uint32; - } - +DECLARE_SCRIPTING_TYPE_MINIMAL(PhysicsSettings); public: /// /// The default gravity force value (in cm^2/s). /// - Vector3 DefaultGravity = PhysicsSettings_DefaultGravity; - - /// - /// Triangles from triangle meshes (CSG) with an area less than or equal to this value will be removed from physics collision data. Set to less than or equal 0 to disable. - /// - float TriangleMeshTriangleMinAreaThreshold = PhysicsSettings_TriangleMeshTriangleMinAreaThreshold; - - /// - /// Minimum relative velocity required for an object to bounce. A typical value for simulation stability is about 0.2 * gravity - /// - float BounceThresholdVelocity = PhysicsSettings_BounceThresholdVelocity; - - /// - /// Default friction combine mode, controls how friction is computed for multiple materials. - /// - PhysicsCombineMode FrictionCombineMode = PhysicsSettings_FrictionCombineMode; - - /// - /// Default restitution combine mode, controls how restitution is computed for multiple materials. - /// - PhysicsCombineMode RestitutionCombineMode = PhysicsSettings_RestitutionCombineMode; - - /// - /// If true CCD will be ignored. This is an optimization when CCD is never used which removes the need for physx to check it internally. - /// - bool DisableCCD = PhysicsSettings_DisableCCD; - - /// - /// Enables adaptive forces to accelerate convergence of the solver. Can improve physics simulation performance but lead to artifacts. - /// - bool EnableAdaptiveForce = PhysicsSettings_EnableAdaptiveForce; - - /// - /// The maximum allowed delta time (in seconds) for the physics simulation step. - /// - float MaxDeltaTime = PhysicsSettings_MaxDeltaTime; - - /// - /// Whether to substep the physics simulation. - /// - bool EnableSubstepping = PhysicsSettings_EnableSubstepping; - - /// - /// Delta time (in seconds) for an individual simulation substep. - /// - float SubstepDeltaTime = PhysicsSettings_SubstepDeltaTime; - - /// - /// The maximum number of substeps for physics simulation. - /// - int32 MaxSubsteps = PhysicsSettings_MaxSubsteps; + API_FIELD(Attributes="EditorOrder(0), DefaultValue(typeof(Vector3), \"0,-981.0,0\"), EditorDisplay(\"Simulation\")") + Vector3 DefaultGravity = Vector3(0, -981.0f, 0); /// /// If enabled, any Raycast or other scene query that intersects with a Collider marked as a Trigger will returns with a hit. Individual raycasts can override this behavior. /// - bool QueriesHitTriggers = PhysicsSettings_QueriesHitTriggers; + API_FIELD(Attributes="EditorOrder(10), DefaultValue(true), EditorDisplay(\"Framerate\")") + bool QueriesHitTriggers = true; + + /// + /// Triangles from triangle meshes (CSG) with an area less than or equal to this value will be removed from physics collision data. Set to less than or equal 0 to disable. + /// + API_FIELD(Attributes="EditorOrder(20), DefaultValue(5.0f), EditorDisplay(\"Simulation\")") + float TriangleMeshTriangleMinAreaThreshold = 5.0f; + + /// + /// Minimum relative velocity required for an object to bounce. A typical value for simulation stability is about 0.2 * gravity + /// + API_FIELD(Attributes="EditorOrder(30), DefaultValue(200.0f), EditorDisplay(\"Simulation\")") + float BounceThresholdVelocity = 200.0f; + + /// + /// Default friction combine mode, controls how friction is computed for multiple materials. + /// + API_FIELD(Attributes="EditorOrder(40), DefaultValue(PhysicsCombineMode.Average), EditorDisplay(\"Simulation\")") + PhysicsCombineMode FrictionCombineMode = PhysicsCombineMode::Average; + + /// + /// Default restitution combine mode, controls how restitution is computed for multiple materials. + /// + API_FIELD(Attributes="EditorOrder(40), DefaultValue(PhysicsCombineMode.Average), EditorDisplay(\"Simulation\")") + PhysicsCombineMode RestitutionCombineMode = PhysicsCombineMode::Average; + + /// + /// If true CCD will be ignored. This is an optimization when CCD is never used which removes the need for physx to check it internally. + /// + API_FIELD(Attributes="EditorOrder(70), DefaultValue(false), EditorDisplay(\"Simulation\")") + bool DisableCCD = false; + + /// + /// Enables adaptive forces to accelerate convergence of the solver. Can improve physics simulation performance but lead to artifacts. + /// + API_FIELD(Attributes="EditorOrder(80), DefaultValue(false), EditorDisplay(\"Simulation\")") + bool EnableAdaptiveForce = false; + + /// + /// The maximum allowed delta time (in seconds) for the physics simulation step. + /// + API_FIELD(Attributes="EditorOrder(1000), DefaultValue(1.0f / 10.0f), EditorDisplay(\"Framerate\")") + float MaxDeltaTime = 1.0f / 10.0f; + + /// + /// Whether to substep the physics simulation. + /// + API_FIELD(Attributes="EditorOrder(1005), DefaultValue(false), EditorDisplay(\"Framerate\")") + bool EnableSubstepping = false; + + /// + /// Delta time (in seconds) for an individual simulation substep. + /// + API_FIELD(Attributes="EditorOrder(1010), DefaultValue(1.0f / 120.0f), EditorDisplay(\"Framerate\")") + float SubstepDeltaTime = 1.0f / 120.0f; + + /// + /// The maximum number of substeps for physics simulation. + /// + API_FIELD(Attributes="EditorOrder(1020), DefaultValue(5), EditorDisplay(\"Framerate\")") + int32 MaxSubsteps = 5; /// /// Enables support for cooking physical collision shapes geometry at runtime. Use it to enable generating runtime terrain collision or convex mesh colliders. /// - bool SupportCookingAtRuntime = PhysicsSettings_SupportCookingAtRuntime; + API_FIELD(Attributes="EditorOrder(1100), DefaultValue(false), EditorDisplay(\"Other\")") + bool SupportCookingAtRuntime = false; public: @@ -115,8 +101,17 @@ public: public: - // [Settings] + /// + /// Initializes a new instance of the class. + /// + PhysicsSettings(); + + /// + /// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use. + /// + static PhysicsSettings* Get(); + + // [SettingsBase] void Apply() override; - void RestoreDefault() override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override; }; diff --git a/Source/Engine/Physics/Types.h b/Source/Engine/Physics/Types.h index 80a4e30b9..6465dd900 100644 --- a/Source/Engine/Physics/Types.h +++ b/Source/Engine/Physics/Types.h @@ -28,6 +28,7 @@ namespace physx class PxFoundation; class PxShape; class PxGeometry; + class PxGeometryHolder; class PxProfileZoneManager; class PxMaterial; class PxPvd; diff --git a/Source/Engine/Physics/Utilities.h b/Source/Engine/Physics/Utilities.h index 11914277f..34ee7f66d 100644 --- a/Source/Engine/Physics/Utilities.h +++ b/Source/Engine/Physics/Utilities.h @@ -14,9 +14,6 @@ #include #include #include -#include "PhysicsSettings.h" - -//////////////////////////// Conversion between PhysX and Flax types inline PxVec2& C2P(const Vector2& v) { @@ -43,8 +40,6 @@ inline PxBounds3& C2P(const BoundingBox& v) return *(PxBounds3*)&v; } -//////////////////////////// - inline Vector2& P2C(const PxVec2& v) { return *(Vector2*)&v; @@ -79,29 +74,4 @@ inline Vector3 P2C(const PxExtendedVec3& v) #endif } -//////////////////////////// - -inline PxShapeFlags GetShapeFlags(bool isTrigger, bool isEnabled) -{ -#if WITH_PVD - PxShapeFlags flags = PxShapeFlag::eVISUALIZATION; -#else - PxShapeFlags flags = static_cast(0); -#endif - - if (isEnabled) - { - if (isTrigger) - { - flags |= PxShapeFlag::eTRIGGER_SHAPE; - if (PhysicsSettings::Instance()->QueriesHitTriggers) - flags |= PxShapeFlag::eSCENE_QUERY_SHAPE; - } - else - { - flags = PxShapeFlag::eSIMULATION_SHAPE | PxShapeFlag::eSCENE_QUERY_SHAPE; - } - } - - return flags; -} +extern PxShapeFlags GetShapeFlags(bool isTrigger, bool isEnabled); diff --git a/Source/Engine/Platform/Android/AndroidPlatformSettings.h b/Source/Engine/Platform/Android/AndroidPlatformSettings.h index eabe6f6b1..0526849a9 100644 --- a/Source/Engine/Platform/Android/AndroidPlatformSettings.h +++ b/Source/Engine/Platform/Android/AndroidPlatformSettings.h @@ -9,41 +9,37 @@ /// /// Android platform settings. /// -/// -class AndroidPlatformSettings : public Settings +API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API AndroidPlatformSettings : public SettingsBase { +DECLARE_SCRIPTING_TYPE_MINIMAL(AndroidPlatformSettings); public: /// /// The application package name (eg. com.company.product). Custom tokens: ${PROJECT_NAME}, ${COMPANY_NAME}. /// - String PackageName; + API_FIELD(Attributes="EditorOrder(0), EditorDisplay(\"General\")") + String PackageName = TEXT("com.${COMPANY_NAME}.${PROJECT_NAME}"); /// /// The application permissions list (eg. android.media.action.IMAGE_CAPTURE). Added to the generated manifest file. /// + API_FIELD(Attributes="EditorOrder(100), EditorDisplay(\"General\")") Array Permissions; /// /// Custom icon texture (asset id) to use for the application (overrides the default one). /// + API_FIELD(Attributes="EditorOrder(1030), AssetReference(typeof(Texture)), EditorDisplay(\"Other\")") Guid OverrideIcon; public: - AndroidPlatformSettings() - { - RestoreDefault(); - } - - // [Settings] - void RestoreDefault() final override - { - PackageName = TEXT("com.${COMPANY_NAME}.${PROJECT_NAME}"); - Permissions.Clear(); - OverrideIcon = Guid::Empty; - } + /// + /// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use. + /// + static AndroidPlatformSettings* Get(); + // [SettingsBase] void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override { DESERIALIZE(PackageName); diff --git a/Source/Engine/Platform/Base/NetworkBase.cpp b/Source/Engine/Platform/Base/NetworkBase.cpp new file mode 100644 index 000000000..6ba5208f8 --- /dev/null +++ b/Source/Engine/Platform/Base/NetworkBase.cpp @@ -0,0 +1,103 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#include "NetworkBase.h" + +bool NetworkBase::CreateSocket(NetworkSocket& socket, NetworkProtocol proto, NetworkIPVersion ipv) +{ + return true; +} + +bool NetworkBase::DestroySocket(NetworkSocket& socket) +{ + return true; +} + +bool NetworkBase::SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, bool value) +{ + return true; +} + +bool NetworkBase::SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32 value) +{ + return true; +} + +bool NetworkBase::GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, bool* value) +{ + return true; +} + +bool NetworkBase::GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32* value) +{ + return true; +} + +bool NetworkBase::ConnectSocket(NetworkSocket& socket, NetworkEndPoint& endPoint) +{ + return true; +} + +bool NetworkBase::BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint) +{ + return true; +} + +bool NetworkBase::Listen(NetworkSocket& socket, uint16 queueSize) +{ + return true; +} + +bool NetworkBase::Accept(NetworkSocket& serverSock, NetworkSocket& newSock, NetworkEndPoint& newEndPoint) +{ + return true; +} + +bool NetworkBase::IsReadable(NetworkSocket& socket) +{ + return true; +} + +bool NetworkBase::IsWriteable(NetworkSocket& socket) +{ + return true; +} + +int32 NetworkBase::Poll(NetworkSocketGroup& group) +{ + return -1; +} + +bool NetworkBase::GetSocketState(NetworkSocketGroup& group, uint32 index, NetworkSocketState& state) +{ + return true; +} + +int32 NetworkBase::AddSocketToGroup(NetworkSocketGroup& group, NetworkSocket& socket) +{ + return -1; +} + +void NetworkBase::ClearGroup(NetworkSocketGroup& group) +{ + group.Count = 0; +} + +int32 NetworkBase::WriteSocket(NetworkSocket socket, byte* data, uint32 length, NetworkEndPoint* endPoint) +{ + return -1; +} + +int32 NetworkBase::ReadSocket(NetworkSocket socket, byte* buffer, uint32 bufferSize, NetworkEndPoint* endPoint) +{ + return -1; +} + +bool NetworkBase::CreateEndPoint(String* address, String* port, NetworkIPVersion ipv, NetworkEndPoint& endPoint, bool bindable) +{ + return true; +} + +NetworkEndPoint NetworkBase::RemapEndPointToIPv6(NetworkEndPoint& endPoint) +{ + return NetworkEndPoint(); +} diff --git a/Source/Engine/Platform/Base/NetworkBase.h b/Source/Engine/Platform/Base/NetworkBase.h new file mode 100644 index 000000000..e5ad03f98 --- /dev/null +++ b/Source/Engine/Platform/Base/NetworkBase.h @@ -0,0 +1,267 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Core/Types/BaseTypes.h" +#include "Engine/Core/Types/String.h" +API_INJECT_CPP_CODE("#include \"Engine/Platform/Network.h\""); + +#define SOCKGROUP_MAXCOUNT 64 +#define SOCKGROUP_ITEMSIZE 16 + +enum class FLAXENGINE_API NetworkProtocol +{ + /// Not specified. + Undefined, + /// User Datagram Protocol. + Udp, + /// Transmission Control Protocol. + Tcp +}; + +enum class FLAXENGINE_API NetworkIPVersion +{ + /// Not specified. + Undefined, + /// Internet Protocol version 4. + IPv4, + /// Internet Protocol version 6. + IPv6 +}; + +struct FLAXENGINE_API NetworkSocket +{ + NetworkProtocol Protocol = NetworkProtocol::Undefined; + NetworkIPVersion IPVersion = NetworkIPVersion::Undefined; + byte Data[8] = {}; +}; + +struct FLAXENGINE_API NetworkEndPoint +{ + NetworkIPVersion IPVersion = NetworkIPVersion::Undefined; + String Address; + String Port; + byte Data[28] = {}; +}; + +enum class FLAXENGINE_API NetworkSocketOption +{ + /// Enables debugging info recording. + Debug, + /// Allows local address reusing. + ReuseAddr, + /// Keeps connections alive. + KeepAlive, + /// Indicates that outgoing data should be sent on whatever interface the socket is bound to and not a routed on some other interface. + DontRoute, + /// Allows for sending broadcast data. + Broadcast, + /// Uses the local loopback address when sending data from this socket. + UseLoopback, + /// Lingers on close if data present. + Linger, + /// Allows out-of-bound data to be returned in-line with regular data. + OOBInline, + /// Socket send data buffer size. + SendBuffer, + /// Socket receive data buffer size. + RecvBuffer, + /// The timeout in milliseconds for blocking send calls. + SendTimeout, + /// The timeout in milliseconds for blocking receive calls. + RecvTimeout, + /// The last socket error code. + Error, + /// Enables the Nagle algorithm for TCP sockets. + NoDelay, + /// Enables IPv6/Ipv4 dual-stacking, UDP/TCP. + IPv6Only, + /// Retrieve the current path MTU, the socket must be connected UDP/TCP. + Mtu +}; + +struct FLAXENGINE_API NetworkSocketState +{ + bool Error = false; + bool Invalid = false; + bool Disconnected = false; + bool Readable = false; + bool Writeable = false; +}; + +struct FLAXENGINE_API NetworkSocketGroup +{ + uint32 Count = 0; + byte Data[SOCKGROUP_MAXCOUNT * SOCKGROUP_ITEMSIZE] = {}; +}; + +class FLAXENGINE_API NetworkBase +{ +public: + /// + /// Creates a new native socket. + /// + /// The socket struct to fill in. + /// The protocol. + /// The ip version. + /// Returns true on error, otherwise false. + static bool CreateSocket(NetworkSocket& socket, NetworkProtocol proto, NetworkIPVersion ipv); + + /// + /// Closes native socket. + /// + /// The socket. + /// Returns true on error, otherwise false. + static bool DestroySocket(NetworkSocket& socket); + + /// + /// Sets the specified socket option. + /// + /// The socket. + /// The option. + /// The value. + /// Returns true on error, otherwise false. + static bool SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, bool value); + + /// + /// Sets the specified socket option. + /// + /// The socket. + /// The option. + /// The value. + /// Returns true on error, otherwise false. + static bool SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32 value); + + /// + /// Gets the specified socket option. + /// + /// The socket. + /// The option. + /// The returned value. + /// Returns true on error, otherwise false. + static bool GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, bool* value); + + /// + /// Gets the specified socket option. + /// + /// The socket. + /// The option. + /// The returned value. + /// Returns true on error, otherwise false. + static bool GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32* value); + + /// + /// Connects a socket to the specified end point. + /// + /// The socket. + /// The end point. + /// Returns true on error, otherwise false. + static bool ConnectSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); + + /// + /// Binds a socket to the specified end point. + /// + /// The socket. + /// The end point. + /// Returns true on error, otherwise false. + static bool BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); + + /// + /// Listens for incoming connection. + /// + /// The socket. + /// Pending connection queue size. + /// Returns true on error, otherwise false. + static bool Listen(NetworkSocket& socket, uint16 queueSize); + + /// + /// Accepts a pending connection. + /// + /// The socket. + /// The newly connected socket. + /// The end point of the new socket. + /// Returns true on error, otherwise false. + static bool Accept(NetworkSocket& serverSock, NetworkSocket& newSock, NetworkEndPoint& newEndPoint); + + /// + /// Checks for socket readability. + /// + /// The socket. + /// Returns true when data is available. Otherwise false. + static bool IsReadable(NetworkSocket& socket); + + /// + /// Checks for socket writeability. + /// + /// The socket. + /// Returns true when data can be written. Otherwise false. + static bool IsWriteable(NetworkSocket& socket); + + /// + /// Updates sockets states. + /// + /// The sockets group. + /// Returns -1 on error, The number of elements where states are nonzero, otherwise 0. + static int32 Poll(NetworkSocketGroup& group); + + /// + /// Retrieves socket state. + /// + /// The group. + /// The socket index in group. + /// The returned state. + /// Returns true on error, otherwise false. + static bool GetSocketState(NetworkSocketGroup& group, uint32 index, NetworkSocketState& state); + + /// + /// Adds a socket to a group. + /// + /// The group. + /// The socket. + /// Returns the socket index in group or -1 on error. + static int32 AddSocketToGroup(NetworkSocketGroup& group, NetworkSocket& socket); + + /// + /// Clears the socket group. + /// + /// The group. + static void ClearGroup(NetworkSocketGroup& group); + + /// + /// Writes data to the socket. + /// + /// The socket. + /// The data to write. + /// The length of data. + /// If protocol is UDP , the destination end point. Otherwise nullptr. + /// Returns -1 on error, otherwise bytes written. + static int32 WriteSocket(NetworkSocket socket, byte* data, uint32 length, NetworkEndPoint* endPoint = nullptr); + + /// + /// Reads data on the socket. + /// + /// The socket. + /// The buffer. + /// Size of the buffer. + /// If UDP, the end point from where data is coming. Otherwise nullptr. + /// Returns -1 on error, otherwise bytes read. + static int32 ReadSocket(NetworkSocket socket, byte* buffer, uint32 bufferSize, NetworkEndPoint* endPoint = nullptr); + + /// + /// Creates an end point. + /// + /// The address (hostname, IPv4 or IPv6). + /// The port. + /// The ip version. + /// The created end point. + /// True if the end point will be connected or binded. + /// Returns true on error, otherwise false. + static bool CreateEndPoint(String* address, String* port, NetworkIPVersion ipv, NetworkEndPoint& endPoint, bool bindable = false); + + /// + /// Remaps an ipv4 end point to an ipv6 one. + /// + /// The ipv4 end point. + /// The ipv6 end point. + static NetworkEndPoint RemapEndPointToIPv6(NetworkEndPoint& endPoint); +}; diff --git a/Source/Engine/Platform/Base/PlatformBase.cpp b/Source/Engine/Platform/Base/PlatformBase.cpp index 445146ae4..ba2d6d5f6 100644 --- a/Source/Engine/Platform/Base/PlatformBase.cpp +++ b/Source/Engine/Platform/Base/PlatformBase.cpp @@ -19,6 +19,7 @@ #include "Engine/Engine/CommandLine.h" #include "Engine/Engine/Engine.h" #include "Engine/Utilities/StringConverter.h" +#include "Engine/Platform/BatteryInfo.h" #include // Check types sizes @@ -381,6 +382,11 @@ void PlatformBase::SetHighDpiAwarenessEnabled(bool enable) { } +BatteryInfo PlatformBase::GetBatteryInfo() +{ + return BatteryInfo(); +} + int32 PlatformBase::GetDpi() { return 96; diff --git a/Source/Engine/Platform/Base/PlatformBase.h b/Source/Engine/Platform/Base/PlatformBase.h index f7162b587..d2d34c88f 100644 --- a/Source/Engine/Platform/Base/PlatformBase.h +++ b/Source/Engine/Platform/Base/PlatformBase.h @@ -11,6 +11,7 @@ struct CPUInfo; struct MemoryStats; struct ProcessMemoryStats; struct CreateWindowSettings; +struct BatteryInfo; // ReSharper disable CppFunctionIsNotImplemented @@ -548,6 +549,11 @@ public: /// static void SetHighDpiAwarenessEnabled(bool enable); + /// + /// Gets the battery information. + /// + API_PROPERTY() static BatteryInfo GetBatteryInfo(); + /// /// Gets the screen DPI setting. /// diff --git a/Source/Engine/Platform/Base/WindowBase.h b/Source/Engine/Platform/Base/WindowBase.h index 3da48bb7a..a8215bc8b 100644 --- a/Source/Engine/Platform/Base/WindowBase.h +++ b/Source/Engine/Platform/Base/WindowBase.h @@ -284,6 +284,7 @@ protected: Vector2 _trackingMouseOffset; bool _isUsingMouseOffset; + Rectangle _mouseOffsetScreenSize; bool _isTrackingMouse; explicit WindowBase(const CreateWindowSettings& settings); diff --git a/Source/Engine/Platform/Base/WindowsManager.cpp b/Source/Engine/Platform/Base/WindowsManager.cpp index b49cbd058..75082c4f9 100644 --- a/Source/Engine/Platform/Base/WindowsManager.cpp +++ b/Source/Engine/Platform/Base/WindowsManager.cpp @@ -77,7 +77,7 @@ void WindowsManagerService::Update() void WindowsManagerService::Dispose() { - // Close reaming windows + // Close remaining windows WindowsManager::WindowsLocker.Lock(); auto windows = WindowsManager::Windows; for (auto& win : windows) diff --git a/Source/Engine/Platform/BatteryInfo.h b/Source/Engine/Platform/BatteryInfo.h new file mode 100644 index 000000000..2ed6b5cac --- /dev/null +++ b/Source/Engine/Platform/BatteryInfo.h @@ -0,0 +1,49 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Core/Types/BaseTypes.h" + +/// +/// Contains information about power supply (battery). +/// +API_STRUCT() struct BatteryInfo +{ +DECLARE_SCRIPTING_TYPE_MINIMAL(BatteryInfo); + + /// + /// Power supply status. + /// + API_ENUM() enum class States + { + /// + /// Unknown status. + /// + Unknown, + + /// + /// Power supply is connected and battery is charging. + /// + BatteryCharging, + + /// + /// Device is running on a battery. + /// + BatteryDischarging, + + /// + /// Device is connected to the stable power supply (AC). + /// + Connected, + }; + + /// + /// Power supply state. + /// + API_FIELD() BatteryInfo::States State = BatteryInfo::States::Unknown; + + /// + /// Battery percentage left (normalized to 0-1 range). + /// + API_FIELD() float BatteryLifePercent = 1.0f; +}; diff --git a/Source/Engine/Platform/Linux/LinuxPlatform.cpp b/Source/Engine/Platform/Linux/LinuxPlatform.cpp index aefae96ad..13b61eb38 100644 --- a/Source/Engine/Platform/Linux/LinuxPlatform.cpp +++ b/Source/Engine/Platform/Linux/LinuxPlatform.cpp @@ -348,7 +348,7 @@ static int X11_MessageBoxCreateWindow(MessageBoxData* data) { windowdata = data->Parent; windowdataWin = (X11::Window)windowdata->GetNativePtr(); - // TODO: place popup on the the screen that parent window is + // TODO: place popup on the screen that parent window is data->screen = X11_DefaultScreen(display); } else diff --git a/Source/Engine/Platform/Linux/LinuxPlatformSettings.h b/Source/Engine/Platform/Linux/LinuxPlatformSettings.h index bb5092354..7da0ec406 100644 --- a/Source/Engine/Platform/Linux/LinuxPlatformSettings.h +++ b/Source/Engine/Platform/Linux/LinuxPlatformSettings.h @@ -9,66 +9,67 @@ /// /// Linux platform settings. /// -/// -class LinuxPlatformSettings : public Settings +API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API LinuxPlatformSettings : public SettingsBase { +DECLARE_SCRIPTING_TYPE_MINIMAL(LinuxPlatformSettings); public: /// /// The default game window mode. /// + API_FIELD(Attributes="EditorOrder(10), DefaultValue(GameWindowMode.Windowed), EditorDisplay(\"Window\")") GameWindowMode WindowMode = GameWindowMode::Windowed; /// /// The default game window width (in pixels). /// + API_FIELD(Attributes="EditorOrder(20), DefaultValue(1280), EditorDisplay(\"Window\")") int32 ScreenWidth = 1280; /// /// The default game window height (in pixels). /// + API_FIELD(Attributes="EditorOrder(30), DefaultValue(720), EditorDisplay(\"Window\")") int32 ScreenHeight = 720; - /// - /// Enables game running when application window loses focus. - /// - bool RunInBackground = false; - /// /// Enables resizing the game window by the user. /// + API_FIELD(Attributes="EditorOrder(40), DefaultValue(false), EditorDisplay(\"Window\")") bool ResizableWindow = false; + /// + /// Enables game running when application window loses focus. + /// + API_FIELD(Attributes="EditorOrder(1010), DefaultValue(false), EditorDisplay(\"Other\", \"Run In Background\")") + bool RunInBackground = false; + /// /// Limits maximum amount of concurrent game instances running to one, otherwise user may launch application more than once. /// + API_FIELD(Attributes="EditorOrder(1020), DefaultValue(false), EditorDisplay(\"Other\")") bool ForceSingleInstance = false; /// /// Custom icon texture (asset id) to use for the application (overrides the default one). /// - Guid OverrideIcon = Guid::Empty; + API_FIELD(Attributes="EditorOrder(1030), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.AssetRefEditor\"), AssetReference(typeof(Texture)), EditorDisplay(\"Other\")") + Guid OverrideIcon; /// /// Enables support for Vulkan. Disabling it reduces compiled shaders count. /// + API_FIELD(Attributes="EditorOrder(2000), DefaultValue(true), EditorDisplay(\"Graphics\")") bool SupportVulkan = true; public: - // [Settings] - void RestoreDefault() final override - { - WindowMode = GameWindowMode::Windowed; - ScreenWidth = 1280; - ScreenHeight = 720; - RunInBackground = false; - ResizableWindow = false; - ForceSingleInstance = false; - OverrideIcon = Guid::Empty; - SupportVulkan = true; - } + /// + /// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use. + /// + static LinuxPlatformSettings* Get(); + // [SettingsBase] void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override { DESERIALIZE(WindowMode); diff --git a/Source/Engine/Platform/Linux/LinuxWindow.cpp b/Source/Engine/Platform/Linux/LinuxWindow.cpp index b2d5a26c4..ab52ad0d3 100644 --- a/Source/Engine/Platform/Linux/LinuxWindow.cpp +++ b/Source/Engine/Platform/Linux/LinuxWindow.cpp @@ -10,7 +10,7 @@ #include "Engine/Core/Log.h" #include "Engine/Core/Math/Math.h" #include "Engine/Core/Math/Color32.h" -#include "Engine/Core/Math/VectorInt.h" +#include "Engine/Core/Math/Int2.h" #include "Engine/Core/Collections/Array.h" #include "Engine/Core/Collections/Dictionary.h" #include "Engine/Utilities/StringConverter.h" diff --git a/Source/Engine/Platform/Network.h b/Source/Engine/Platform/Network.h new file mode 100644 index 000000000..b8c198770 --- /dev/null +++ b/Source/Engine/Platform/Network.h @@ -0,0 +1,21 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#if PLATFORM_WINDOWS +#include "Win32/Win32Network.h" +#elif PLATFORM_UWP +#include "Win32/Win32Network.h" +#elif PLATFORM_LINUX +#include "Base/NetworkBase.h" +#elif PLATFORM_PS4 +#include "Base/NetworkBase.h" +#elif PLATFORM_XBOX_SCARLETT +#include "Win32/Win32Network.h" +#elif PLATFORM_ANDROID +#include "Base/NetworkBase.h" +#else +#error Missing Network implementation! +#endif + +#include "Types.h" diff --git a/Source/Engine/Platform/Platform.Build.cs b/Source/Engine/Platform/Platform.Build.cs index b7a2baa97..72a706685 100644 --- a/Source/Engine/Platform/Platform.Build.cs +++ b/Source/Engine/Platform/Platform.Build.cs @@ -62,5 +62,15 @@ public class Platform : EngineModule break; default: throw new InvalidPlatformException(options.Platform.Target); } + if (options.Target.IsEditor) + { + // Include platform settings headers + options.SourceFiles.Add(Path.Combine(FolderPath, "Windows", "WindowsPlatformSettings.h")); + options.SourceFiles.Add(Path.Combine(FolderPath, "UWP", "UWPPlatformSettings.h")); + options.SourceFiles.Add(Path.Combine(FolderPath, "Linux", "LinuxPlatformSettings.h")); + options.SourceFiles.Add(Path.Combine(FolderPath, "Android", "AndroidPlatformSettings.h")); + AddSourceFileIfExists(options, Path.Combine(Globals.EngineRoot, "Source", "Platforms", "XboxScarlett", "Engine", "Platform", "XboxScarlettPlatformSettings.h")); + AddSourceFileIfExists(options, Path.Combine(Globals.EngineRoot, "Source", "Platforms", "PS4", "Engine", "Platform", "PS4PlatformSettings.h")); + } } } diff --git a/Source/Engine/Platform/Types.h b/Source/Engine/Platform/Types.h index c8e955c1b..ad67e0b01 100644 --- a/Source/Engine/Platform/Types.h +++ b/Source/Engine/Platform/Types.h @@ -22,6 +22,8 @@ class Win32Thread; typedef Win32Thread Thread; class WindowsWindow; typedef WindowsWindow Window; +class Win32Network; +typedef Win32Network Network; #elif PLATFORM_UWP @@ -43,6 +45,8 @@ class Win32Thread; typedef Win32Thread Thread; class UWPWindow; typedef UWPWindow Window; +class NetworkBase; +typedef NetworkBase Network; #elif PLATFORM_LINUX @@ -64,6 +68,8 @@ class LinuxThread; typedef LinuxThread Thread; class LinuxWindow; typedef LinuxWindow Window; +class NetworkBase; +typedef NetworkBase Network; #elif PLATFORM_PS4 @@ -85,6 +91,8 @@ class PS4Thread; typedef PS4Thread Thread; class PS4Window; typedef PS4Window Window; +class NetworkBase; +typedef NetworkBase Network; #elif PLATFORM_XBOX_SCARLETT @@ -106,6 +114,8 @@ class Win32Thread; typedef Win32Thread Thread; class XboxScarlettWindow; typedef XboxScarlettWindow Window; +class NetworkBase; +typedef NetworkBase Network; #elif PLATFORM_ANDROID @@ -127,6 +137,8 @@ class AndroidThread; typedef AndroidThread Thread; class AndroidWindow; typedef AndroidWindow Window; +class NetworkBase; +typedef NetworkBase Network; #else diff --git a/Source/Engine/Platform/UWP/UWPPlatform.cpp b/Source/Engine/Platform/UWP/UWPPlatform.cpp index 3bd654f10..ed01948aa 100644 --- a/Source/Engine/Platform/UWP/UWPPlatform.cpp +++ b/Source/Engine/Platform/UWP/UWPPlatform.cpp @@ -111,6 +111,7 @@ void UWPPlatform::BeforeExit() void UWPPlatform::Exit() { + Win32Platform::Exit(); } int32 UWPPlatform::GetDpi() diff --git a/Source/Engine/Platform/UWP/UWPPlatformSettings.h b/Source/Engine/Platform/UWP/UWPPlatformSettings.h index c668bfb72..e600cead7 100644 --- a/Source/Engine/Platform/UWP/UWPPlatformSettings.h +++ b/Source/Engine/Platform/UWP/UWPPlatformSettings.h @@ -9,15 +9,15 @@ /// /// Universal Windows Platform settings. /// -/// -class UWPPlatformSettings : public Settings +API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API UWPPlatformSettings : public SettingsBase { +DECLARE_SCRIPTING_TYPE_MINIMAL(UWPPlatformSettings); public: /// /// The preferred launch windowing mode. /// - enum class WindowMode + API_ENUM() enum class WindowMode { /// /// The full screen mode @@ -33,7 +33,7 @@ public: /// /// The display orientation modes. Can be combined as flags. /// - enum class DisplayOrientations + API_ENUM(Attributes="Flags") enum class DisplayOrientations { /// /// The none. @@ -71,40 +71,41 @@ public: /// /// The preferred launch windowing mode. Always fullscreen on Xbox. /// + API_FIELD(Attributes="EditorOrder(10), DefaultValue(WindowMode.FullScreen), EditorDisplay(\"Window\")") WindowMode PreferredLaunchWindowingMode = WindowMode::FullScreen; /// /// The display orientation modes. Can be combined as flags. /// + API_FIELD(Attributes="EditorOrder(20), DefaultValue(DisplayOrientations.All), EditorDisplay(\"Window\")") DisplayOrientations AutoRotationPreferences = DisplayOrientations::All; /// /// The location of the package certificate (relative to the project). /// + API_FIELD(Attributes="EditorOrder(1010), DefaultValue(\"\"), EditorDisplay(\"Other\")") String CertificateLocation; /// /// Enables support for DirectX 11. Disabling it reduces compiled shaders count. /// + API_FIELD(Attributes="EditorOrder(2000), DefaultValue(true), EditorDisplay(\"Graphics\", \"Support DirectX 11\")") bool SupportDX11 = true; /// /// Enables support for DirectX 10 and DirectX 10.1. Disabling it reduces compiled shaders count. /// + API_FIELD(Attributes="EditorOrder(2010), DefaultValue(false), EditorDisplay(\"Graphics\", \"Support DirectX 10\")") bool SupportDX10 = false; public: - // [Settings] - void RestoreDefault() final override - { - PreferredLaunchWindowingMode = WindowMode::FullScreen; - AutoRotationPreferences = DisplayOrientations::All; - CertificateLocation.Clear(); - SupportDX11 = true; - SupportDX10 = false; - } + /// + /// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use. + /// + static UWPPlatformSettings* Get(); + // [SettingsBase] void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override { DESERIALIZE(PreferredLaunchWindowingMode); diff --git a/Source/Engine/Platform/Win32/Win32Network.cpp b/Source/Engine/Platform/Win32/Win32Network.cpp new file mode 100644 index 000000000..38b99a25e --- /dev/null +++ b/Source/Engine/Platform/Win32/Win32Network.cpp @@ -0,0 +1,489 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#include "Win32Network.h" +#include "Engine/Core/Log.h" +#include "Engine/Core/Collections/Array.h" +#include +#include +#include + +#define SOCKOPT(OPTENUM, OPTLEVEL, OPTNAME) case OPTENUM: *level = OPTLEVEL; *name = OPTNAME; break; + +static_assert(sizeof NetworkSocket::Data >= sizeof SOCKET, "NetworkSocket::Data is not big enough to contains SOCKET !"); +static_assert(sizeof NetworkEndPoint::Data >= sizeof sockaddr_in6, "NetworkEndPoint::Data is not big enough to contains sockaddr_in6 !"); + +// @formatter:off +static const IN6_ADDR v4MappedPrefix = { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00 } }; +// @formatter:on + +/* + * Known issues : + * Even if dualstacking is enabled it's not possible to bind an Ipv4mappedIPv6 endpoint. windows limitation + */ + +static String GetErrorMessage(int error) +{ + wchar_t* s = nullptr; + FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, error, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + reinterpret_cast(&s), 0, nullptr); + String str(s); + LocalFree(s); + return str; +} + +static String GetLastErrorMessage() +{ + return GetErrorMessage(WSAGetLastError()); +} + +static int GetAddrSize(const sockaddr& addr) +{ + return addr.sa_family == AF_INET6 ? sizeof sockaddr_in6 : sizeof sockaddr_in; +} + +static int GetAddrSizeFromEP(NetworkEndPoint& endPoint) +{ + return endPoint.IPVersion == NetworkIPVersion::IPv6 ? sizeof sockaddr_in6 : sizeof sockaddr_in; +} + +static NetworkIPVersion GetIPVersionFromAddr(const sockaddr& addr) +{ + return addr.sa_family == AF_INET6 ? NetworkIPVersion::IPv6 : NetworkIPVersion::IPv4;; +} + +static bool CreateEndPointFromAddr(sockaddr* addr, NetworkEndPoint& endPoint) +{ + uint32 size = GetAddrSize(*addr); + uint16 port; + void* paddr; + if (addr->sa_family == AF_INET6) + { + paddr = &((sockaddr_in6*)addr)->sin6_addr; + port = ntohs(((sockaddr_in6*)addr)->sin6_port); + } + else if (addr->sa_family == AF_INET) + { + paddr = &((sockaddr_in*)addr)->sin_addr; + port = ntohs(((sockaddr_in*)addr)->sin_port); + } + else + { + LOG(Error, "Unable to create endpoint, sockaddr must be INET or INET6! Family : {0}", addr->sa_family); + return true; + } + + char ip[INET6_ADDRSTRLEN]; + if (inet_ntop(addr->sa_family, paddr, ip, INET6_ADDRSTRLEN) == nullptr) + { + LOG(Error, "Unable to extract address from sockaddr! Error : {0}", GetLastErrorMessage()); + return true; + } + endPoint.Address = String(ip); + char strPort[6]; + _itoa(port, strPort, 10); + endPoint.Port = String(strPort); + endPoint.IPVersion = GetIPVersionFromAddr(*addr); + memcpy(endPoint.Data, addr, size); + return false; +} + +static void PrintAddrFromInfo(addrinfoW& info) +{ + addrinfoW* curr; + for (curr = &info; curr != nullptr; curr = curr->ai_next) + { + void* addr; + if (curr->ai_family == AF_INET) + { + sockaddr_in* ipv4 = (struct sockaddr_in*)curr->ai_addr; + addr = &(ipv4->sin_addr); + } + else + { + sockaddr_in6* ipv6 = (struct sockaddr_in6*)curr->ai_addr; + addr = &(ipv6->sin6_addr); + } + + char str[INET6_ADDRSTRLEN]; + inet_ntop(curr->ai_family, addr, str, INET6_ADDRSTRLEN); + LOG(Info, "ADDR INFO family : {0} socktype : {1}, proto : {2} address : {3}", curr->ai_family, curr->ai_socktype, curr->ai_protocol, StringAnsi(str).ToString()); + } +} + +static void TranslateSockOptToNative(NetworkSocketOption option, int32* level, int32* name) +{ + switch (option) + { + SOCKOPT(NetworkSocketOption::Debug, SOL_SOCKET, SO_DEBUG) + SOCKOPT(NetworkSocketOption::ReuseAddr, SOL_SOCKET, SO_REUSEADDR) + SOCKOPT(NetworkSocketOption::KeepAlive, SOL_SOCKET, SO_KEEPALIVE) + SOCKOPT(NetworkSocketOption::DontRoute, SOL_SOCKET, SO_DONTROUTE) + SOCKOPT(NetworkSocketOption::Broadcast, SOL_SOCKET, SO_BROADCAST) + SOCKOPT(NetworkSocketOption::UseLoopback, SOL_SOCKET, SO_USELOOPBACK) + SOCKOPT(NetworkSocketOption::Linger, SOL_SOCKET, SO_LINGER) + SOCKOPT(NetworkSocketOption::OOBInline, SOL_SOCKET, SO_OOBINLINE) + SOCKOPT(NetworkSocketOption::SendBuffer, SOL_SOCKET, SO_SNDBUF) + SOCKOPT(NetworkSocketOption::RecvBuffer, SOL_SOCKET, SO_RCVBUF) + SOCKOPT(NetworkSocketOption::SendTimeout, SOL_SOCKET, SO_SNDTIMEO) + SOCKOPT(NetworkSocketOption::RecvTimeout, SOL_SOCKET, SO_RCVTIMEO) + SOCKOPT(NetworkSocketOption::Error, SOL_SOCKET, SO_ERROR) + SOCKOPT(NetworkSocketOption::NoDelay, IPPROTO_TCP, TCP_NODELAY) + SOCKOPT(NetworkSocketOption::IPv6Only, IPPROTO_IPV6, IPV6_V6ONLY) + SOCKOPT(NetworkSocketOption::Mtu, IPPROTO_IP , IP_MTU) + } +} + +bool Win32Network::CreateSocket(NetworkSocket& socket, NetworkProtocol proto, NetworkIPVersion ipv) +{ + socket.Protocol = proto; + socket.IPVersion = ipv; + const uint8 family = socket.IPVersion == NetworkIPVersion::IPv6 ? AF_INET6 : AF_INET; + const uint8 stype = socket.Protocol == NetworkProtocol::Tcp ? SOCK_STREAM : SOCK_DGRAM; + const uint8 prot = socket.Protocol == NetworkProtocol::Tcp ? IPPROTO_TCP : IPPROTO_UDP; + SOCKET sock; + + if ((sock = ::socket(family, stype, prot)) == INVALID_SOCKET) + { + LOG(Error, "Can't create native socket! Error : {0}", GetLastErrorMessage()); + return true; + } + memcpy(socket.Data, &sock, sizeof sock); + unsigned long value = 1; + if (ioctlsocket(sock, FIONBIO, &value) == SOCKET_ERROR) + { + LOG(Error, "Can't set socket to NON-BLOCKING type! Error : {0}", GetLastErrorMessage()); + return true; // Support using blocking socket , need to test it + } + return false; +} + +bool Win32Network::DestroySocket(NetworkSocket& socket) +{ + const SOCKET sock = *(SOCKET*)socket.Data; + if (sock != INVALID_SOCKET) + { + closesocket(sock); + return false; + } + LOG(Warning, "Unable to delete socket INVALID_SOCKET! Socket : {0}", sock); + return true; +} + +bool Win32Network::SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, bool value) +{ + const int32 v = value; + return SetSocketOption(socket, option, v); +} + +bool Win32Network::SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32 value) +{ + int32 optlvl = 0; + int32 optnme = 0; + + TranslateSockOptToNative(option, &optlvl, &optnme); + + if (setsockopt(*(SOCKET*)socket.Data, optlvl, optnme, (char*)&value, sizeof value) == SOCKET_ERROR) + { + LOG(Warning, "Unable to set socket option ! Socket : {0} Error : {1}", *(SOCKET*)socket.Data, GetLastErrorMessage()); + return true; + } + return false; +} + +bool Win32Network::GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, bool* value) +{ + int32 v; + const bool status = GetSocketOption(socket, option, &v); + *value = v == 1 ? true : false; + return status; +} + +bool Win32Network::GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32* value) +{ + int32 optlvl = 0; + int32 optnme = 0; + + TranslateSockOptToNative(option, &optlvl, &optnme); + + int32 size; + if (getsockopt(*(SOCKET*)socket.Data, optlvl, optnme, (char*)value, &size) == SOCKET_ERROR) + { + LOG(Warning, "Unable to get socket option ! Socket : {0} Error : {1}", *(SOCKET*)socket.Data, GetLastErrorMessage()); + return true; + } + return false; +} + +bool Win32Network::ConnectSocket(NetworkSocket& socket, NetworkEndPoint& endPoint) +{ + const uint16 size = GetAddrSizeFromEP(endPoint); + if (connect(*(SOCKET*)socket.Data, (const sockaddr*)endPoint.Data, size) == SOCKET_ERROR) + { + int error = WSAGetLastError(); + if (error == WSAEWOULDBLOCK) + return false; + LOG(Error, "Unable to connect socket to address! Socket : {0} Address : {1} Port : {2} Error : {3}", *(SOCKET*)socket.Data, endPoint.Address, endPoint.Port, GetErrorMessage(error)); + return true; + } + return false; +} + +bool Win32Network::BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint) +{ + if (socket.IPVersion != endPoint.IPVersion) + { + LOG(Error, "Can't bind socket to end point, Socket.IPVersion != EndPoint.IPVersion! Socket : {0}", *(SOCKET*)socket.Data); + return true; + } + + const uint16 size = endPoint.IPVersion == NetworkIPVersion::IPv6 ? sizeof sockaddr_in6 : sizeof sockaddr_in; + if (bind(*(SOCKET*)socket.Data, (const sockaddr*)endPoint.Data, size) == SOCKET_ERROR) + { + LOG(Error, "Unable to bind socket! Socket : {0} Address : {1} Port : {2} Error : {3}", *(SOCKET*)socket.Data, endPoint.Address, endPoint.Port, GetLastErrorMessage()); + return true; + } + return false; +} + +bool Win32Network::Listen(NetworkSocket& socket, uint16 queueSize) +{ + if (listen(*(SOCKET*)socket.Data, (int32)queueSize) == SOCKET_ERROR) + { + LOG(Error, "Unable to listen ! Socket : {0} Error : {1}", GetLastErrorMessage()); + return true; + } + return false; +} + +bool Win32Network::Accept(NetworkSocket& serverSock, NetworkSocket& newSock, NetworkEndPoint& newEndPoint) +{ + if (serverSock.Protocol != NetworkProtocol::Tcp) + { + LOG(Warning, "Can't accept connection on UDP socket! Socket : {0}", *(SOCKET*)serverSock.Data); + return true; + } + SOCKET sock; + sockaddr_in6 addr; + int32 size = sizeof sockaddr_in6; + if ((sock = accept(*(SOCKET*)serverSock.Data, (sockaddr*)&addr, &size)) == INVALID_SOCKET) + { + int32 error = WSAGetLastError(); + if (error == WSAEWOULDBLOCK) + return false; + LOG(Warning, "Unable to accept incoming connection! Socket : {0} Error : {1}", *(SOCKET*)serverSock.Data, GetErrorMessage(error)); + return true; + } + memcpy(newSock.Data, &sock, sizeof sock); + memcpy(newEndPoint.Data, &addr, size); + newSock.Protocol = serverSock.Protocol; + newSock.IPVersion = serverSock.IPVersion; + if (CreateEndPointFromAddr((sockaddr*)&addr, newEndPoint)) + return true; + return false; +} + +bool Win32Network::IsReadable(NetworkSocket& socket) +{ + pollfd entry; + entry.fd = *(SOCKET*)socket.Data; + entry.events = POLLRDNORM; + if (WSAPoll(&entry, 1, 0) == SOCKET_ERROR) + { + int32 error = WSAGetLastError(); + if (error == WSAEWOULDBLOCK) + return false; + LOG(Error, "Unable to poll socket! Socket : {0} Error : {1}", *(SOCKET*)socket.Data, GetErrorMessage(error)); + return false; + } + if (entry.revents & POLLRDNORM) + return true; + return false; +} + +bool Win32Network::IsWriteable(NetworkSocket& socket) +{ + pollfd entry; + entry.fd = *(SOCKET*)socket.Data; + entry.events = POLLWRNORM; + if (WSAPoll(&entry, 1, 0) == SOCKET_ERROR) + { + int32 error = WSAGetLastError(); + if (error == WSAEWOULDBLOCK) + return false; + LOG(Error, "Unable to poll socket! Socket : {0} Error : {1}", *(SOCKET*)socket.Data, GetErrorMessage(error)); + return false; + } + if (entry.revents & POLLWRNORM) + return true; + return false; +} + +int32 Win32Network::Poll(NetworkSocketGroup& group) +{ + int32 pollret = WSAPoll((pollfd*)group.Data, group.Count, 0); + if (pollret == SOCKET_ERROR) + LOG(Error, "Unable to poll socket group! Error : {0}", GetLastErrorMessage()); + return pollret; +} + +bool Win32Network::GetSocketState(NetworkSocketGroup& group, uint32 index, NetworkSocketState& state) +{ + if (index >= SOCKGROUP_MAXCOUNT) + return true; + pollfd* pollptr = (pollfd*)&group.Data[index * SOCKGROUP_ITEMSIZE]; + memset(&state, 0, sizeof state); + if (pollptr->revents & POLLERR) + state.Error = true; + if (pollptr->revents & POLLHUP) + state.Disconnected = true; + if (pollptr->revents & POLLNVAL) + state.Invalid = true; + if (pollptr->revents & POLLRDNORM) + state.Readable = true; + if (pollptr->revents & POLLWRNORM) + state.Writeable = true; + return false; +} + +int32 Win32Network::AddSocketToGroup(NetworkSocketGroup& group, NetworkSocket& socket) +{ + if (group.Count >= SOCKGROUP_MAXCOUNT) + return -1; + pollfd pollinfo; + pollinfo.fd = *(SOCKET*)socket.Data; + pollinfo.events = POLLRDNORM | POLLWRNORM; + *(pollfd*)&group.Data[group.Count * SOCKGROUP_ITEMSIZE] = pollinfo; + group.Count++; + return group.Count - 1; +} + +void Win32Network::ClearGroup(NetworkSocketGroup& group) +{ + group.Count = 0; +} + +int32 Win32Network::WriteSocket(NetworkSocket socket, byte* data, uint32 length, NetworkEndPoint* endPoint) +{ + if (endPoint != nullptr && socket.IPVersion != endPoint->IPVersion) + { + LOG(Error, "Unable to send data, Socket.IPVersion != EndPoint.IPVersion! Socket : {0}", *(SOCKET*)socket.Data); + return -1; + } + uint32 size; + if (endPoint == nullptr && socket.Protocol == NetworkProtocol::Tcp) + { + if ((size = send(*(SOCKET*)socket.Data, (const char*)data, length, 0)) == SOCKET_ERROR) + { + LOG(Error, "Unable to send data! Socket : {0} Data Length : {1} Error : {2}", *(SOCKET*)socket.Data, length, GetLastErrorMessage()); + return -1; + } + } + else if (endPoint != nullptr && socket.Protocol == NetworkProtocol::Udp) + { + if ((size = sendto(*(SOCKET*)socket.Data, (const char*)data, length, 0, (const sockaddr*)endPoint->Data, GetAddrSizeFromEP(*endPoint))) == SOCKET_ERROR) + { + LOG(Error, "Unable to send data! Socket : {0} Address : {1} Port : {2} Data Length : {3} Error : {4}", *(SOCKET*)socket.Data, endPoint->Address, endPoint->Port, length, GetLastErrorMessage()); + return -1; + } + } + else + { + // TODO: better explanation + LOG(Error, "Unable to send data! Socket : {0} Data Length : {1}", *(SOCKET*)socket.Data, length); + return -1; + } + return size; +} + +int32 Win32Network::ReadSocket(NetworkSocket socket, byte* buffer, uint32 bufferSize, NetworkEndPoint* endPoint) +{ + uint32 size; + if (endPoint == nullptr) // TCP + { + if ((size = recv(*(SOCKET*)socket.Data, (char*)buffer, bufferSize, 0)) == SOCKET_ERROR) + { + const int32 error = WSAGetLastError(); + if (error == WSAEWOULDBLOCK) + return 0; + LOG(Error, "Unable to read data! Socket : {0} Buffer Size : {1} Error : {2}", *(SOCKET*)socket.Data, bufferSize, GetErrorMessage(error)); + return -1; + } + } + else // UDP + { + int32 addrsize = sizeof sockaddr_in6; + sockaddr_in6 addr; + if ((size = recvfrom(*(SOCKET*)socket.Data, (char*)buffer, bufferSize, 0, (sockaddr*)&addr, &addrsize)) == SOCKET_ERROR) + { + LOG(Error, "Unable to read data! Socket : {0} Buffer Size : {1} Error : {2}", *(SOCKET*)socket.Data, bufferSize, GetLastErrorMessage()); + return -1; + } + if (CreateEndPointFromAddr((sockaddr*)&addr, *endPoint)) + return true; + } + return size; +} + +bool Win32Network::CreateEndPoint(String* address, String* port, NetworkIPVersion ipv, NetworkEndPoint& endPoint, bool bindable) +{ + int status; + addrinfoW hints; + addrinfoW* info; + memset(&hints, 0, sizeof hints); + hints.ai_family = ipv == NetworkIPVersion::IPv6 ? AF_INET6 : ipv == NetworkIPVersion::IPv4 ? AF_INET : AF_UNSPEC; + hints.ai_flags |= AI_ADDRCONFIG; + hints.ai_flags |= AI_V4MAPPED; + if (bindable) + hints.ai_flags = AI_PASSIVE; + + // consider using NUMERICHOST/NUMERICSERV if address is a valid Ipv4 or IPv6 so we can skip some look up ( potentially slow when resolving host names ) + if ((status = GetAddrInfoW(address == nullptr ? nullptr : address->Get(), port->Get(), &hints, &info)) != 0) + { + LOG(Error, "Unable to query info for address : {0} Error : {1}", address ? address->Get() : String("ANY"), gai_strerror(status)); + return true; + } + + if (info == nullptr) + { + LOG(Error, "Unable to resolve address! Address : {0}", address ? address->Get() : String("ANY")); + return true; + } + + if (CreateEndPointFromAddr(info->ai_addr, endPoint)) + { + FreeAddrInfoW(info); + return true; + } + FreeAddrInfoW(info); + + return false; +} + +NetworkEndPoint Win32Network::RemapEndPointToIPv6(NetworkEndPoint endPoint) +{ + if (endPoint.IPVersion == NetworkIPVersion::IPv6) + { + LOG(Warning, "Unable to remap EndPoint, already in IPv6 format!"); + return endPoint; + } + + NetworkEndPoint pv6; + sockaddr_in* addr4 = (sockaddr_in*)endPoint.Data; + sockaddr_in6* addr6 = (sockaddr_in6*)pv6.Data; + const SCOPE_ID scope = SCOPEID_UNSPECIFIED_INIT; + + // Can be replaced by windows built-in macro IN6ADDR_SETV4MAPPED() + memset(addr6, 0, sizeof sockaddr_in6); + addr6->sin6_family = AF_INET6; + addr6->sin6_scope_struct = scope; + addr6->sin6_addr = v4MappedPrefix; + addr6->sin6_port = addr4->sin_port; + memcpy(&addr6->sin6_addr.u.Byte[12], &addr4->sin_addr, 4); // :::::FFFF:XXXX:XXXX X=IPv4 + pv6.IPVersion = NetworkIPVersion::IPv6; + pv6.Port = endPoint.Port; + + return pv6; +} diff --git a/Source/Engine/Platform/Win32/Win32Network.h b/Source/Engine/Platform/Win32/Win32Network.h new file mode 100644 index 000000000..7a1aaabbf --- /dev/null +++ b/Source/Engine/Platform/Win32/Win32Network.h @@ -0,0 +1,38 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#if PLATFORM_WIN32 + +#include "Engine/Platform/Base/NetworkBase.h" + +class FLAXENGINE_API Win32Network : public NetworkBase +{ + friend NetworkEndPoint; + friend NetworkSocket; + +public: + // [NetworkBase] + static bool CreateSocket(NetworkSocket& socket, NetworkProtocol proto, NetworkIPVersion ipv); + static bool DestroySocket(NetworkSocket& socket); + static bool SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, bool value); + static bool SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32 value); + static bool GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, bool* value); + static bool GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32* value); + static bool ConnectSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); + static bool BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); + static bool Listen(NetworkSocket& socket, uint16 queueSize); + static bool Accept(NetworkSocket& serverSock, NetworkSocket& newSock, NetworkEndPoint& newEndPoint); + static bool IsReadable(NetworkSocket& socket); + static bool IsWriteable(NetworkSocket& socket); + static int32 Poll(NetworkSocketGroup& group); + static bool GetSocketState(NetworkSocketGroup& group, uint32 index, NetworkSocketState& state); + static int32 AddSocketToGroup(NetworkSocketGroup& group, NetworkSocket& socket); + static void ClearGroup(NetworkSocketGroup& group); + static int32 WriteSocket(NetworkSocket socket, byte* data, uint32 length, NetworkEndPoint* endPoint = nullptr); + static int32 ReadSocket(NetworkSocket socket, byte* buffer, uint32 bufferSize, NetworkEndPoint* endPoint = nullptr); + static bool CreateEndPoint(String* address, String* port, NetworkIPVersion ipv, NetworkEndPoint& endPoint, bool bindable = false); + static NetworkEndPoint RemapEndPointToIPv6(NetworkEndPoint endPoint); +}; + +#endif diff --git a/Source/Engine/Platform/Win32/Win32Platform.cpp b/Source/Engine/Platform/Win32/Win32Platform.cpp index fc815b6ba..b01b96feb 100644 --- a/Source/Engine/Platform/Win32/Win32Platform.cpp +++ b/Source/Engine/Platform/Win32/Win32Platform.cpp @@ -5,16 +5,19 @@ #include "Engine/Platform/Platform.h" #include "Engine/Platform/MemoryStats.h" #include "Engine/Platform/CPUInfo.h" +#include "Engine/Platform/BatteryInfo.h" #include "Engine/Core/Types/Guid.h" #include "Engine/Core/Types/String.h" #include "Engine/Core/Math/Math.h" -#include "IncludeWindowsHeaders.h" #include "Engine/Core/Collections/HashFunctions.h" +#include "Engine/Core/Log.h" +#include "IncludeWindowsHeaders.h" #include #include #include #include +#include #pragma comment(lib, "Iphlpapi.lib") namespace @@ -23,6 +26,7 @@ namespace CPUInfo CpuInfo; uint64 ClockFrequency; double CyclesToSeconds; + WSAData WsaData; } // Helper function to count set bits in the processor mask @@ -42,6 +46,18 @@ DWORD CountSetBits(ULONG_PTR bitMask) return bitSetCount; } +static String GetLastErrorMessage() +{ + wchar_t* s = nullptr; + FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, WSAGetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + reinterpret_cast(&s), 0, nullptr); + String str(s); + LocalFree(s); + return str; +} + bool Win32Platform::Init() { if (PlatformBase::Init()) @@ -54,6 +70,7 @@ bool Win32Platform::Init() ClockFrequency = frequency.QuadPart; CyclesToSeconds = 1.0 / static_cast(frequency.QuadPart); + // Count CPUs BOOL done = FALSE; PSYSTEM_LOGICAL_PROCESSOR_INFORMATION buffer = nullptr; PSYSTEM_LOGICAL_PROCESSOR_INFORMATION ptr; @@ -66,12 +83,10 @@ bool Win32Platform::Init() DWORD processorPackageCount = 0; DWORD byteOffset = 0; PCACHE_DESCRIPTOR cache; - while (!done) { DWORD rc = GetLogicalProcessorInformation(buffer, &returnLength); - - if (FALSE == rc) + if (rc == FALSE) { if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { @@ -79,9 +94,7 @@ bool Win32Platform::Init() { free(buffer); } - buffer = static_cast(malloc(returnLength)); - if (buffer == nullptr) { return true; @@ -97,23 +110,16 @@ bool Win32Platform::Init() done = TRUE; } } - ptr = buffer; - while (byteOffset + sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION) <= returnLength) { switch (ptr->Relationship) { case RelationProcessorCore: - processorCoreCount++; - - // A hyper threaded core supplies more than one logical processor logicalProcessorCount += CountSetBits(ptr->ProcessorMask); break; - case RelationCache: - // Cache data is in ptr->Cache, one CACHE_DESCRIPTOR structure for each cache. cache = &ptr->Cache; if (cache->Level == 1) { @@ -128,9 +134,7 @@ bool Win32Platform::Init() processorL3CacheSize += cache->Size; } break; - case RelationProcessorPackage: - // Logical processors share a physical package processorPackageCount++; break; } @@ -210,24 +214,22 @@ bool Win32Platform::Init() DeviceId.D = (uint32)cpuInfo.ClockSpeed * cpuInfo.LogicalProcessorCount * cpuInfo.ProcessorCoreCount * cpuInfo.CacheLineSize; } + // Init networking + if (WSAStartup(MAKEWORD(2, 0), &WsaData) != 0) + LOG(Error, "Unable to initializes native network! Error : {0}", GetLastErrorMessage()); + return false; } +void Win32Platform::Exit() +{ + WSACleanup(); +} + void Win32Platform::MemoryBarrier() { - // NOTE: _ReadWriteBarrier and friends only prevent the - // compiler from reordering loads and stores. To prevent - // the CPU from doing the same, we have to use the - // MemoryBarrier macro which expands to e.g. a serializing - // XCHG instruction on x86. Also note that the MemoryBarrier - // macro does *not* imply _ReadWriteBarrier, so that call - // cannot be eliminated. - _ReadWriteBarrier(); - - // MemoryBarrier macro (we use undef to hide some symbols from Windows headers) #if PLATFORM_64BITS - #ifdef _AMD64_ __faststorefence(); #elif defined(_IA64_) @@ -235,7 +237,6 @@ void Win32Platform::MemoryBarrier() #else #error "Invalid platform." #endif - #else LONG barrier; __asm { @@ -308,6 +309,21 @@ bool Win32Platform::Is64BitPlatform() #endif } +BatteryInfo Win32Platform::GetBatteryInfo() +{ + BatteryInfo info; + SYSTEM_POWER_STATUS status; + GetSystemPowerStatus(&status); + info.BatteryLifePercent = (float)status.BatteryLifePercent / 255.0f; + if (status.BatteryFlag & 8) + info.State = BatteryInfo::States::BatteryCharging; + else if (status.BatteryFlag & 1 || status.BatteryFlag & 2 || status.BatteryFlag & 4) + info.State = BatteryInfo::States::BatteryDischarging; + else if (status.ACLineStatus == 1 || status.BatteryFlag & 128) + info.State = BatteryInfo::States::Connected; + return info; +} + CPUInfo Win32Platform::GetCPUInfo() { return CpuInfo; diff --git a/Source/Engine/Platform/Win32/Win32Platform.h b/Source/Engine/Platform/Win32/Win32Platform.h index 52ddeb25b..cde506278 100644 --- a/Source/Engine/Platform/Win32/Win32Platform.h +++ b/Source/Engine/Platform/Win32/Win32Platform.h @@ -16,6 +16,7 @@ public: // [PlatformBase] static bool Init(); + static void Exit(); static void MemoryBarrier(); static int64 InterlockedExchange(int64 volatile* dst, int64 exchange); static int32 InterlockedCompareExchange(int32 volatile* dst, int32 exchange, int32 comperand); @@ -43,6 +44,7 @@ public: _aligned_free(ptr); } static bool Is64BitPlatform(); + static BatteryInfo GetBatteryInfo(); static CPUInfo GetCPUInfo(); static int32 GetCacheLineSize(); static MemoryStats GetMemoryStats(); diff --git a/Source/Engine/Platform/Windows/WindowsFileSystem.cpp b/Source/Engine/Platform/Windows/WindowsFileSystem.cpp index 0c0d9788c..1816c9552 100644 --- a/Source/Engine/Platform/Windows/WindowsFileSystem.cpp +++ b/Source/Engine/Platform/Windows/WindowsFileSystem.cpp @@ -5,6 +5,7 @@ #include "WindowsFileSystem.h" #include "Engine/Platform/File.h" #include "Engine/Platform/Window.h" +#include "Engine/Platform/Windows/ComPtr.h" #include "Engine/Core/Types/StringView.h" #include "../Win32/IncludeWindowsHeaders.h" @@ -231,7 +232,9 @@ bool WindowsFileSystem::ShowOpenFileDialog(Window* parentWindow, const StringVie while (*ptr) { filenames.Add(directory / ptr); - ptr += (lstrlenW(ptr) + 1); + ptr += lstrlenW(ptr); + if (multiSelect) + ptr++; } result = false; @@ -276,7 +279,9 @@ bool WindowsFileSystem::ShowSaveFileDialog(Window* parentWindow, const StringVie while (*ptr) { filenames.Add(directory / ptr); - ptr += (lstrlenW(ptr) + 1); + ptr += lstrlenW(ptr); + if (multiSelect) + ptr++; } result = false; @@ -289,41 +294,39 @@ bool WindowsFileSystem::ShowBrowseFolderDialog(Window* parentWindow, const Strin { bool result = true; - // Allocate memory for the filenames - int32 maxPathSize = 2 * MAX_PATH; - Array pathBuffer; - pathBuffer.Resize(maxPathSize); - pathBuffer[0] = 0; + // Randomly generated GUID used for storing the last location of this dialog + const Guid folderGuid(0x53890ed9, 0xa55e47ba, 0xa970bdae, 0x72acedff); - // Setup description - BROWSEINFOW bi; - ZeroMemory(&bi, sizeof(bi)); - if (parentWindow) - bi.hwndOwner = static_cast(parentWindow->GetNativePtr()); - bi.lpszTitle = title.HasChars() ? title.Get() : nullptr; - bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE; - bi.lpfn = BrowseCallbackProc; - bi.lParam = (LPARAM)(initialDirectory.HasChars() ? initialDirectory.Get() : nullptr); - - LPITEMIDLIST pidl = SHBrowseForFolder(&bi); - - if (pidl != nullptr) + ComPtr fd; + if (SUCCEEDED(CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&fd)))) { - // Get the name of the folder and put it in path - SHGetPathFromIDList(pidl, pathBuffer.Get()); + DWORD options; + fd->GetOptions(&options); + fd->SetOptions(options | FOS_PICKFOLDERS | FOS_NOCHANGEDIR); - if (pathBuffer[0] != 0) - { - path = pathBuffer.Get(); - result = false; - } + if (title.HasChars()) + fd->SetTitle(title.Get()); - // Free memory used - IMalloc* imalloc = 0; - if (SUCCEEDED(SHGetMalloc(&imalloc))) + // Associate the last selected folder with this GUID instead of overwriting the global one + fd->SetClientGuid(*reinterpret_cast(&folderGuid)); + + ComPtr defaultFolder; + if (SUCCEEDED(SHCreateItemFromParsingName(initialDirectory.Get(), NULL, IID_PPV_ARGS(&defaultFolder)))) + fd->SetFolder(defaultFolder); + + if (SUCCEEDED(fd->Show(parentWindow->GetHWND()))) { - imalloc->Free(pidl); - imalloc->Release(); + ComPtr si; + if (SUCCEEDED(fd->GetResult(&si))) + { + LPWSTR resultPath; + if (SUCCEEDED(si->GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING, &resultPath))) + { + path = resultPath; + CoTaskMemFree(resultPath); + result = false; + } + } } } diff --git a/Source/Engine/Platform/Windows/WindowsPlatform.cpp b/Source/Engine/Platform/Windows/WindowsPlatform.cpp index 0b1f70989..e01ef7a44 100644 --- a/Source/Engine/Platform/Windows/WindowsPlatform.cpp +++ b/Source/Engine/Platform/Windows/WindowsPlatform.cpp @@ -624,14 +624,27 @@ void WindowsPlatform::Exit() // Unregister app class UnregisterClassW(ApplicationWindowClass, nullptr); + + Win32Platform::Exit(); } #if !BUILD_RELEASE void WindowsPlatform::Log(const StringView& msg) { - OutputDebugStringW(msg.Get()); - OutputDebugStringW(TEXT(PLATFORM_LINE_TERMINATOR)); + Char buffer[512]; + Char* str; + if (msg.Length() + 3 < ARRAY_COUNT(buffer)) + str = buffer; + else + str = (Char*)Allocate((msg.Length() + 3) * sizeof(Char), 16); + MemoryCopy(str, msg.Get(), msg.Length() * sizeof(Char)); + str[msg.Length() + 0] = '\r'; + str[msg.Length() + 1] = '\n'; + str[msg.Length() + 2] = 0; + OutputDebugStringW(str); + if (str != buffer) + Free(str); } bool WindowsPlatform::IsDebuggerPresent() diff --git a/Source/Engine/Platform/Windows/WindowsPlatformSettings.h b/Source/Engine/Platform/Windows/WindowsPlatformSettings.h index 454c7d708..48a39444a 100644 --- a/Source/Engine/Platform/Windows/WindowsPlatformSettings.h +++ b/Source/Engine/Platform/Windows/WindowsPlatformSettings.h @@ -9,84 +9,85 @@ /// /// Windows platform settings. /// -/// -class WindowsPlatformSettings : public Settings +API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API WindowsPlatformSettings : public SettingsBase { +DECLARE_SCRIPTING_TYPE_MINIMAL(WindowsPlatformSettings); public: /// /// The default game window mode. /// + API_FIELD(Attributes="EditorOrder(10), DefaultValue(GameWindowMode.Windowed), EditorDisplay(\"Window\")") GameWindowMode WindowMode = GameWindowMode::Windowed; /// /// The default game window width (in pixels). /// + API_FIELD(Attributes="EditorOrder(20), DefaultValue(1280), EditorDisplay(\"Window\")") int32 ScreenWidth = 1280; /// /// The default game window height (in pixels). /// + API_FIELD(Attributes="EditorOrder(30), DefaultValue(720), EditorDisplay(\"Window\")") int32 ScreenHeight = 720; - /// - /// Enables game running when application window loses focus. - /// - bool RunInBackground = false; - /// /// Enables resizing the game window by the user. /// + API_FIELD(Attributes="EditorOrder(40), DefaultValue(false), EditorDisplay(\"Window\")") bool ResizableWindow = false; + /// + /// Enables game running when application window loses focus. + /// + API_FIELD(Attributes="EditorOrder(1010), DefaultValue(false), EditorDisplay(\"Other\", \"Run In Background\")") + bool RunInBackground = false; + /// /// Limits maximum amount of concurrent game instances running to one, otherwise user may launch application more than once. /// + API_FIELD(Attributes="EditorOrder(1020), DefaultValue(false), EditorDisplay(\"Other\")") bool ForceSingleInstance = false; /// /// Custom icon texture (asset id) to use for the application (overrides the default one). /// + API_FIELD(Attributes="EditorOrder(1030), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.AssetRefEditor\"), AssetReference(typeof(Texture)), EditorDisplay(\"Other\")") Guid OverrideIcon; /// /// Enables support for DirectX 12. Disabling it reduces compiled shaders count. /// + API_FIELD(Attributes="EditorOrder(2000), DefaultValue(false), EditorDisplay(\"Graphics\", \"Support DirectX 12\")") bool SupportDX12 = false; /// /// Enables support for DirectX 11. Disabling it reduces compiled shaders count. /// + API_FIELD(Attributes="EditorOrder(2010), DefaultValue(true), EditorDisplay(\"Graphics\", \"Support DirectX 11\")") bool SupportDX11 = true; /// /// Enables support for DirectX 10 and DirectX 10.1. Disabling it reduces compiled shaders count. /// + API_FIELD(Attributes="EditorOrder(2020), DefaultValue(false), EditorDisplay(\"Graphics\", \"Support DirectX 10\")") bool SupportDX10 = false; /// /// Enables support for Vulkan. Disabling it reduces compiled shaders count. /// + API_FIELD(Attributes="EditorOrder(2030), DefaultValue(false), EditorDisplay(\"Graphics\")") bool SupportVulkan = false; public: - // [Settings] - void RestoreDefault() final override - { - WindowMode = GameWindowMode::Windowed; - ScreenWidth = 1280; - ScreenHeight = 720; - RunInBackground = false; - ResizableWindow = false; - ForceSingleInstance = false; - OverrideIcon = Guid::Empty; - SupportDX12 = false; - SupportDX11 = true; - SupportDX10 = false; - SupportVulkan = false; - } + /// + /// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use. + /// + static WindowsPlatformSettings* Get(); + // [SettingsBase] void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override { DESERIALIZE(WindowMode); diff --git a/Source/Engine/Platform/Windows/WindowsWindow.cpp b/Source/Engine/Platform/Windows/WindowsWindow.cpp index dc130949c..2d60cc40a 100644 --- a/Source/Engine/Platform/Windows/WindowsWindow.cpp +++ b/Source/Engine/Platform/Windows/WindowsWindow.cpp @@ -426,8 +426,8 @@ void WindowsWindow::GetScreenInfo(int32& x, int32& y, int32& width, int32& heigh // Calculate result x = monitorInfo.rcMonitor.left; y = monitorInfo.rcMonitor.top; - width = monitorInfo.rcMonitor.right - x; - height = monitorInfo.rcMonitor.bottom - y; + width = monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left; + height = monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top; } float WindowsWindow::GetOpacity() const @@ -479,6 +479,10 @@ void WindowsWindow::StartTrackingMouse(bool useMouseScreenOffset) _trackingMouseOffset = Vector2::Zero; _isUsingMouseOffset = useMouseScreenOffset; + int32 x = 0 , y = 0, width = 0, height = 0; + GetScreenInfo(x, y, width, height); + _mouseOffsetScreenSize = Rectangle((float)x, (float)y, (float)width, (float)height); + SetCapture(_handle); } } @@ -712,18 +716,20 @@ LRESULT WindowsWindow::WndProc(UINT msg, WPARAM wParam, LPARAM lParam) if (_isTrackingMouse && _isUsingMouseOffset) { // Check if move mouse to another edge of the desktop - Vector2 destopSize = Platform::GetVirtualDesktopSize(); + Vector2 desktopLocation = _mouseOffsetScreenSize.Location; + Vector2 destopSize = _mouseOffsetScreenSize.GetBottomRight(); + const Vector2 mousePos(static_cast(WINDOWS_GET_X_LPARAM(lParam)), static_cast(WINDOWS_GET_Y_LPARAM(lParam))); Vector2 mousePosition = ClientToScreen(mousePos); Vector2 newMousePosition = mousePosition; - if (mousePosition.X <= 1) + if (mousePosition.X <= desktopLocation.X + 2) newMousePosition.X = destopSize.X - 2; else if (mousePosition.X >= destopSize.X - 1) - newMousePosition.X = 2; - if (mousePosition.Y <= 1) + newMousePosition.X = desktopLocation.X + 2; + if (mousePosition.Y <= desktopLocation.Y + 2) newMousePosition.Y = destopSize.Y - 2; else if (mousePosition.Y >= destopSize.Y - 1) - newMousePosition.Y = 2; + newMousePosition.Y = desktopLocation.Y + 2; if (!Vector2::NearEqual(mousePosition, newMousePosition)) { _trackingMouseOffset -= newMousePosition - mousePosition; diff --git a/Source/Engine/Render2D/Font.cpp b/Source/Engine/Render2D/Font.cpp index c9dd7b55c..209989d4f 100644 --- a/Source/Engine/Render2D/Font.cpp +++ b/Source/Engine/Render2D/Font.cpp @@ -33,7 +33,7 @@ Font::~Font() void Font::GetCharacter(Char c, FontCharacterEntry& result) { - // Try get character or cache it if cannot find + // Try to get the character or cache it if cannot be found if (!_characters.TryGet(c, result)) { // This thread race condition may happen in editor but in game we usually do all stuff with fonts on main thread (chars caching) diff --git a/Source/Engine/Render2D/FontAsset.cpp b/Source/Engine/Render2D/FontAsset.cpp index cf2571e4a..8b14b1cfc 100644 --- a/Source/Engine/Render2D/FontAsset.cpp +++ b/Source/Engine/Render2D/FontAsset.cpp @@ -55,7 +55,7 @@ void FontAsset::unload(bool isReloading) // Ensure to cleanup child font objects if (_fonts.HasItems()) { - LOG(Warning, "Font asset {0} is unloading but has {1} reaming font objects created", ToString(), _fonts.Count()); + LOG(Warning, "Font asset {0} is unloading but has {1} remaining font objects created", ToString(), _fonts.Count()); for (auto font : _fonts) { font->_asset = nullptr; diff --git a/Source/Engine/Render2D/FontManager.cpp b/Source/Engine/Render2D/FontManager.cpp index f05cd7280..ec7a61257 100644 --- a/Source/Engine/Render2D/FontManager.cpp +++ b/Source/Engine/Render2D/FontManager.cpp @@ -214,7 +214,7 @@ bool FontManager::AddNewEntry(Font* font, Char c, FontCharacterEntry& entry) return false; } - // Copy glyph data after rasterize (row by row) + // Copy glyph data after rasterization (row by row) for (int32 row = 0; row < glyphHeight; row++) { Platform::MemoryCopy(&GlyphImageData[row * glyphWidth], &bitmap->buffer[row * bitmap->pitch], glyphWidth); diff --git a/Source/Engine/Render2D/Render2D.cpp b/Source/Engine/Render2D/Render2D.cpp index 2413686aa..6e6936cc5 100644 --- a/Source/Engine/Render2D/Render2D.cpp +++ b/Source/Engine/Render2D/Render2D.cpp @@ -182,41 +182,44 @@ struct ClipMask Render2D::RenderingFeatures Render2D::Features = RenderingFeatures::VertexSnapping; -// Private Stuff -GPUContext* Context = nullptr; -GPUTextureView* Output = nullptr; -GPUTextureView* DepthBuffer = nullptr; -Viewport View; -Matrix ViewProjection; +namespace +{ + // Private Stuff + GPUContext* Context = nullptr; + GPUTextureView* Output = nullptr; + GPUTextureView* DepthBuffer = nullptr; + Viewport View; + Matrix ViewProjection; -// Drawing -Array DrawCalls; -Array Lines; -Array Lines2; -bool IsScissorsRectEmpty; -bool IsScissorsRectEnabled; + // Drawing + Array DrawCalls; + Array Lines; + Array Lines2; + bool IsScissorsRectEmpty; + bool IsScissorsRectEnabled; -// Transform -// Note: we use Matrix3x3 instead of Matrix because we use only 2D transformations on CPU side -// Matrix layout: -// [ m1, m2, 0 ] -// [ m3, m4, 0 ] -// [ t1, t2, 1 ] -// where 'm' is 2D transformation (scale, shear and rotate), 't' is translation -Array> TransformLayersStack; -Matrix3x3 TransformCached; + // Transform + // Note: we use Matrix3x3 instead of Matrix because we use only 2D transformations on CPU side + // Matrix layout: + // [ m1, m2, 0 ] + // [ m3, m4, 0 ] + // [ t1, t2, 1 ] + // where 'm' is 2D transformation (scale, shear and rotate), 't' is translation + Array> TransformLayersStack; + Matrix3x3 TransformCached; -Array> ClipLayersStack; + Array> ClipLayersStack; -// Shader -AssetReference GUIShader; -CachedPSO PsoDepth; -CachedPSO PsoNoDepth; -CachedPSO* CurrentPso = nullptr; -DynamicVertexBuffer VB(RENDER2D_INITIAL_VB_CAPACITY, (uint32)sizeof(Render2DVertex), TEXT("Render2D.VB")); -DynamicIndexBuffer IB(RENDER2D_INITIAL_IB_CAPACITY, sizeof(uint32), TEXT("Render2D.IB")); -uint32 VBIndex = 0; -uint32 IBIndex = 0; + // Shader + AssetReference GUIShader; + CachedPSO PsoDepth; + CachedPSO PsoNoDepth; + CachedPSO* CurrentPso = nullptr; + DynamicVertexBuffer VB(RENDER2D_INITIAL_VB_CAPACITY, (uint32)sizeof(Render2DVertex), TEXT("Render2D.VB")); + DynamicIndexBuffer IB(RENDER2D_INITIAL_IB_CAPACITY, sizeof(uint32), TEXT("Render2D.IB")); + uint32 VBIndex = 0; + uint32 IBIndex = 0; +} #define RENDER2D_WRITE_IB_QUAD(indices) \ indices[0] = VBIndex + 0; \ @@ -522,7 +525,7 @@ void Render2DService::Dispose() Lines.Resize(0); Lines2.Resize(0); - GUIShader.Unlink(); + GUIShader = nullptr; PsoDepth.Dispose(); PsoNoDepth.Dispose(); @@ -957,8 +960,8 @@ void DrawBatch(int32 startIndex, int32 count) data.Bounds.Y = bounds.Y; data.Bounds.Z = bounds.Z - bounds.X; data.Bounds.W = bounds.W - bounds.Y; - data.InvBufferSize.X = 1.0f / renderTargetWidth; - data.InvBufferSize.Y = 1.0f / renderTargetHeight; + data.InvBufferSize.X = 1.0f / (float)renderTargetWidth; + data.InvBufferSize.Y = 1.0f / (float)renderTargetHeight; data.SampleCount = ComputeBlurWeights(kernelSize, blurStrength, data.WeightAndOffsets); const auto cb = GUIShader->GetShader()->GetCB(1); Context->UpdateCB(cb, &data); @@ -967,7 +970,7 @@ void DrawBatch(int32 startIndex, int32 count) // Downscale (or not) and extract the background image for the blurring Context->ResetRenderTarget(); Context->SetRenderTarget(blurA->View()); - Context->SetViewport((float)renderTargetWidth, (float)renderTargetHeight); + Context->SetViewportAndScissors((float)renderTargetWidth, (float)renderTargetHeight); Context->BindSR(0, Output); Context->SetState(CurrentPso->PS_Downscale); Context->DrawFullscreenTriangle(); @@ -1003,11 +1006,8 @@ void DrawBatch(int32 startIndex, int32 count) break; } case DrawCallType::ClipScissors: - { - Rectangle* scissorsRect = (Rectangle*)&d.AsClipScissors.X; - Context->SetScissor(*scissorsRect); + Context->SetScissor(*(Rectangle*)&d.AsClipScissors.X); return; - } case DrawCallType::LineAA: Context->SetState(CurrentPso->PS_LineAA); break; diff --git a/Source/Engine/Renderer/AmbientOcclusionPass.cpp b/Source/Engine/Renderer/AmbientOcclusionPass.cpp index dfae20792..e72de509b 100644 --- a/Source/Engine/Renderer/AmbientOcclusionPass.cpp +++ b/Source/Engine/Renderer/AmbientOcclusionPass.cpp @@ -196,7 +196,7 @@ void AmbientOcclusionPass::Dispose() SAFE_DELETE_GPU_RESOURCE(_psApplyHalf); // Release asset - _shader.Unlink(); + _shader = nullptr; } void AmbientOcclusionPass::Render(RenderContext& renderContext) diff --git a/Source/Engine/Renderer/AmbientOcclusionPass.h b/Source/Engine/Renderer/AmbientOcclusionPass.h index 81d64a0e4..bfaf4bc19 100644 --- a/Source/Engine/Renderer/AmbientOcclusionPass.h +++ b/Source/Engine/Renderer/AmbientOcclusionPass.h @@ -60,7 +60,7 @@ private: float ShadowMultiplier; // [0.0, 5.0] Effect strength linear multiplier float ShadowPower; // [0.5, 5.0] Effect strength pow modifier float HorizonAngleThreshold; // [0.0, 0.2] Limits self-shadowing (makes the sampling area less of a hemisphere, more of a spherical cone, to avoid self-shadowing and various artifacts due to low tessellation and depth buffer imprecision, etc.) - float FadeOutFrom; // [0.0, ~ ] Distance to start start fading out the effect. + float FadeOutFrom; // [0.0, ~ ] Distance to start fading out the effect. float FadeOutTo; // [0.0, ~ ] Distance at which the effect is faded out. int QualityLevel; // [ 0, ] Effect quality; 0 - low, 1 - medium, 2 - high, 3 - very high; each quality level is roughly 2x more costly than the previous, except the q3 which is variable but, in general, above q2. int BlurPassCount; // [ 0, 6] Number of edge-sensitive smart blur passes to apply. Quality 0 is an exception with only one 'dumb' blur pass used. diff --git a/Source/Engine/Renderer/AntiAliasing/FXAA.cpp b/Source/Engine/Renderer/AntiAliasing/FXAA.cpp index 159e481a6..3dcb6ae71 100644 --- a/Source/Engine/Renderer/AntiAliasing/FXAA.cpp +++ b/Source/Engine/Renderer/AntiAliasing/FXAA.cpp @@ -55,11 +55,9 @@ void FXAA::Dispose() // Base RendererPass::Dispose(); - // Delete pipeline state + // Cleanup _psFXAA.Delete(); - - // Release asset - _shader.Unlink(); + _shader = nullptr; } void FXAA::Render(RenderContext& renderContext, GPUTexture* input, GPUTextureView* output) diff --git a/Source/Engine/Renderer/AntiAliasing/SMAA.cpp b/Source/Engine/Renderer/AntiAliasing/SMAA.cpp index cf7e53f40..7c1ee0c16 100644 --- a/Source/Engine/Renderer/AntiAliasing/SMAA.cpp +++ b/Source/Engine/Renderer/AntiAliasing/SMAA.cpp @@ -82,15 +82,13 @@ void SMAA::Dispose() // Base RendererPass::Dispose(); - // Delete pipeline states + // Cleanup _psEdge.Delete(); _psBlend.Delete(); SAFE_DELETE_GPU_RESOURCE(_psNeighbor); - - // Release assets - _shader.Unlink(); - _areaTex.Unlink(); - _searchTex.Unlink(); + _shader = nullptr; + _areaTex = nullptr; + _searchTex = nullptr; } void SMAA::Render(RenderContext& renderContext, GPUTexture* input, GPUTextureView* output) diff --git a/Source/Engine/Renderer/AntiAliasing/TAA.cpp b/Source/Engine/Renderer/AntiAliasing/TAA.cpp index 7b8dfe17f..a1fee93ad 100644 --- a/Source/Engine/Renderer/AntiAliasing/TAA.cpp +++ b/Source/Engine/Renderer/AntiAliasing/TAA.cpp @@ -42,8 +42,9 @@ void TAA::Dispose() // Base RendererPass::Dispose(); + // Cleanup _psTAA = nullptr; - _shader.Unlink(); + _shader = nullptr; } bool TAA::NeedMotionVectors(RenderContext& renderContext) diff --git a/Source/Engine/Renderer/AtmospherePreCompute.cpp b/Source/Engine/Renderer/AtmospherePreCompute.cpp index 66576e55f..cef9964ca 100644 --- a/Source/Engine/Renderer/AtmospherePreCompute.cpp +++ b/Source/Engine/Renderer/AtmospherePreCompute.cpp @@ -305,7 +305,7 @@ void release() SAFE_DELETE_GPU_RESOURCE(_psInscatterS); SAFE_DELETE_GPU_RESOURCE(_psInscatterN); SAFE_DELETE_GPU_RESOURCE(_psCopyInscatterNAdd); - _shader.Unlink(); + _shader = nullptr; SAFE_DELETE(_task); SAFE_DELETE_GPU_RESOURCE(AtmosphereTransmittance); SAFE_DELETE_GPU_RESOURCE(AtmosphereIrradiance); diff --git a/Source/Engine/Renderer/ColorGradingPass.cpp b/Source/Engine/Renderer/ColorGradingPass.cpp index 94caf2b46..c40248caa 100644 --- a/Source/Engine/Renderer/ColorGradingPass.cpp +++ b/Source/Engine/Renderer/ColorGradingPass.cpp @@ -121,11 +121,9 @@ void ColorGradingPass::Dispose() // Base RendererPass::Dispose(); - // Delete pipeline state + // Cleanup _psLut.Delete(); - - // Release assets - _shader.Unlink(); + _shader = nullptr; } GPUTexture* ColorGradingPass::RenderLUT(RenderContext& renderContext) diff --git a/Source/Engine/Renderer/DepthOfFieldPass.cpp b/Source/Engine/Renderer/DepthOfFieldPass.cpp index b5382ec72..4e4ee7ed1 100644 --- a/Source/Engine/Renderer/DepthOfFieldPass.cpp +++ b/Source/Engine/Renderer/DepthOfFieldPass.cpp @@ -66,21 +66,17 @@ void DepthOfFieldPass::Dispose() // Base RendererPass::Dispose(); - // Delete pipeline states + // Cleanup SAFE_DELETE_GPU_RESOURCE(_psDofDepthBlurGeneration); SAFE_DELETE_GPU_RESOURCE(_psBokehGeneration); SAFE_DELETE_GPU_RESOURCE(_psDoNotGenerateBokeh); SAFE_DELETE_GPU_RESOURCE(_psBokeh); SAFE_DELETE_GPU_RESOURCE(_psBokehComposite); - - // Release assets - _shader.Unlink(); - _defaultBokehHexagon.Unlink(); - _defaultBokehOctagon.Unlink(); - _defaultBokehCircle.Unlink(); - _defaultBokehCross.Unlink(); - - // Release resources + _shader = nullptr; + _defaultBokehHexagon = nullptr; + _defaultBokehOctagon = nullptr; + _defaultBokehCircle = nullptr; + _defaultBokehCross = nullptr; SAFE_DELETE_GPU_RESOURCE(_bokehBuffer); SAFE_DELETE_GPU_RESOURCE(_bokehIndirectArgsBuffer); } diff --git a/Source/Engine/Renderer/DrawCall.h b/Source/Engine/Renderer/DrawCall.h index 7c4f4fe6d..036ba26bd 100644 --- a/Source/Engine/Renderer/DrawCall.h +++ b/Source/Engine/Renderer/DrawCall.h @@ -113,6 +113,11 @@ public: /// struct DrawCall { + /// + /// The material to use for rendering. + /// + IMaterial* Material; + struct { /// @@ -129,16 +134,6 @@ struct DrawCall /// The geometry vertex buffers byte offsets. /// uint32 VertexBuffersOffsets[3]; - - /// - /// The location of the first index read by the GPU from the index buffer. - /// - int32 StartIndex; - - /// - /// The indices count. - /// - int32 IndicesCount; } Geometry; /// @@ -146,66 +141,58 @@ struct DrawCall /// int32 InstanceCount; - /// - /// The indirect draw arguments offset. - /// - uint32 IndirectArgsOffset; - - /// - /// The indirect draw arguments buffer. - /// - GPUBuffer* IndirectArgsBuffer; - - /// - /// The target material to use. - /// - IMaterial* Material; - - // Particles don't use skinning nor lightmaps so pack those stuff together union { struct { /// - /// Pointer to lightmap for static object with prebaked lighting. + /// The location of the first index read by the GPU from the index buffer. /// + int32 StartIndex; + + /// + /// The indices count. + /// + int32 IndicesCount; + }; + + struct + { + /// + /// The indirect draw arguments offset. + /// + uint32 IndirectArgsOffset; + + /// + /// The indirect draw arguments buffer. + /// + GPUBuffer* IndirectArgsBuffer; + }; + } Draw; + + // Per-material shader data packed into union + union + { + struct + { const Lightmap* Lightmap; + Rectangle LightmapUVsArea; + } Features; - /// - /// The skinning data. If set then material should use GPU skinning during rendering. - /// + struct + { + const Lightmap* Lightmap; + Rectangle LightmapUVsArea; SkinnedMeshDrawData* Skinning; - }; - - struct - { - /// - /// The particles data. Used only by the particles shaders. - /// - class ParticleBuffer* Particles; - - /// - /// The particle module to draw. - /// - class ParticleEmitterGraphCPUNode* Module; - }; - }; - - /// - /// Object world transformation matrix. - /// - Matrix World; - - // Terrain and particles don't use previous world matrix so pack those stuff together - union - { - /// - /// Object world transformation matrix using during previous frame. - /// - Matrix PrevWorld; + Vector3 GeometrySize; // Object geometry size in the world (unscaled). + float LODDitherFactor; // The model LOD transition dither progress. + Matrix PrevWorld; + } Surface; struct { + const Lightmap* Lightmap; + Rectangle LightmapUVsArea; Vector4 HeightmapUVScaleBias; Vector4 NeighborLOD; Vector2 OffsetUV; @@ -213,25 +200,47 @@ struct DrawCall float ChunkSizeNextLOD; float TerrainChunkSizeLOD0; const class TerrainPatch* Patch; - } TerrainData; + } Terrain; struct { - int32 RibbonOrderOffset; - float UVTilingDistance; - float UVScaleX; - float UVScaleY; - float UVOffsetX; - float UVOffsetY; - uint32 SegmentCount; - GPUBuffer* SegmentDistances; - } Ribbon; + class ParticleBuffer* Particles; + class ParticleEmitterGraphCPUNode* Module; + + struct + { + int32 OrderOffset; + float UVTilingDistance; + float UVScaleX; + float UVScaleY; + float UVOffsetX; + float UVOffsetY; + uint32 SegmentCount; + GPUBuffer* SegmentDistances; + } Ribbon; + } Particle; + + struct + { + GPUBuffer* SplineDeformation; + Matrix LocalMatrix; // Geometry transformation applied before deformation. + Vector3 GeometrySize; // Object geometry size in the world (unscaled). + float Segment; + float ChunksPerSegment; + float MeshMinZ; + float MeshMaxZ; + } Deformable; + + struct + { + byte Raw[96]; + } Custom; }; /// - /// Lightmap UVs area that entry occupies. + /// Object world transformation matrix. /// - Rectangle LightmapUVsArea; + Matrix World; /// /// Object location in the world used for draw calls sorting. @@ -243,36 +252,17 @@ struct DrawCall /// float WorldDeterminantSign; - /// - /// Object geometry size in the world (unscaled). - /// - Vector3 GeometrySize; - /// /// The random per-instance value (normalized to range 0-1). /// float PerInstanceRandom; - /// - /// The model LOD transition dither factor. - /// - float LODDitherFactor; - /// /// Does nothing. /// DrawCall() { } - - /// - /// Determines whether world transform matrix is performing negative scale (then model culling should be inverted). - /// - /// true if world matrix contains negative scale; otherwise, false. - FORCE_INLINE bool IsNegativeScale() const - { - return WorldDeterminantSign < 0; - } }; template<> @@ -333,6 +323,6 @@ struct TIsPODType #define GEOMETRY_DRAW_STATE_EVENT_END(drawState, worldMatrix) \ if (drawState.PrevFrame != frame) \ { \ - drawState.PrevWorld = _world; \ + drawState.PrevWorld = worldMatrix; \ drawState.PrevFrame = frame; \ } diff --git a/Source/Engine/Renderer/Editor/LightmapUVsDensity.cpp b/Source/Engine/Renderer/Editor/LightmapUVsDensity.cpp index 152b43d50..9ba9f1a0d 100644 --- a/Source/Engine/Renderer/Editor/LightmapUVsDensity.cpp +++ b/Source/Engine/Renderer/Editor/LightmapUVsDensity.cpp @@ -178,7 +178,7 @@ void LightmapUVsDensityMaterialShader::Bind(BindParameters& params) scaleZ > 0.00001f ? 1.0f / scaleZ : 0.0f); data.LightmapTexelsPerWorldUnit = ShadowsOfMordor::LightmapTexelsPerWorldUnit; data.LightmapSize = 1024.0f; - data.LightmapArea = drawCall.LightmapUVsArea; + data.LightmapArea = drawCall.Surface.LightmapUVsArea; if (drawCallModel) { // Calculate current lightmap slot size for the object (matches the ShadowsOfMordor calculations when baking the lighting) diff --git a/Source/Engine/Renderer/EyeAdaptationPass.cpp b/Source/Engine/Renderer/EyeAdaptationPass.cpp index 754c1489f..3bfbb9daa 100644 --- a/Source/Engine/Renderer/EyeAdaptationPass.cpp +++ b/Source/Engine/Renderer/EyeAdaptationPass.cpp @@ -2,6 +2,7 @@ #include "EyeAdaptationPass.h" #include "RenderList.h" +#include "Engine/Core/Math/Int2.h" #include "Engine/Content/Assets/Shader.h" #include "Engine/Content/Content.h" #include "Engine/Graphics/PostProcessBase.h" @@ -243,15 +244,13 @@ void EyeAdaptationPass::Dispose() // Base RendererPass::Dispose(); - // Delete pipeline states + // Cleanup SAFE_DELETE_GPU_RESOURCE(_psManual); SAFE_DELETE_GPU_RESOURCE(_psLuminanceMap); SAFE_DELETE_GPU_RESOURCE(_psBlendLuminance); SAFE_DELETE_GPU_RESOURCE(_psApplyLuminance); SAFE_DELETE_GPU_RESOURCE(_psHistogram); - - // Release asset - _shader.Unlink(); + _shader = nullptr; } bool EyeAdaptationPass::setupResources() diff --git a/Source/Engine/Renderer/ForwardPass.cpp b/Source/Engine/Renderer/ForwardPass.cpp index ad178fe67..93ccf0750 100644 --- a/Source/Engine/Renderer/ForwardPass.cpp +++ b/Source/Engine/Renderer/ForwardPass.cpp @@ -62,11 +62,9 @@ void ForwardPass::Dispose() // Base RendererPass::Dispose(); - // Delete pipeline states + // Cleanup SAFE_DELETE_GPU_RESOURCE(_psApplyDistortion); - - // Release assets - _shader.Unlink(); + _shader = nullptr; } void ForwardPass::Render(RenderContext& renderContext, GPUTexture* input, GPUTexture* output) diff --git a/Source/Engine/Renderer/GBufferPass.cpp b/Source/Engine/Renderer/GBufferPass.cpp index 5c6f76905..2165e25dc 100644 --- a/Source/Engine/Renderer/GBufferPass.cpp +++ b/Source/Engine/Renderer/GBufferPass.cpp @@ -77,14 +77,11 @@ void GBufferPass::Dispose() // Base RendererPass::Dispose(); - // Delete pipeline state + // Cleanup SAFE_DELETE_GPU_RESOURCE(_psDebug); - - // Release assets - _gBufferShader.Unlink(); - _skyModel.Unlink(); - _boxModel.Unlink(); - + _gBufferShader = nullptr; + _skyModel = nullptr; + _boxModel = nullptr; #if USE_EDITOR SAFE_DELETE(_lightmapUVsDensityMaterialShader); SAFE_DELETE(_vertexColorsMaterialShader); @@ -145,7 +142,8 @@ void GBufferPass::Fill(RenderContext& renderContext, GPUTextureView* lightBuffer drawCall.Material = _lightmapUVsDensityMaterialShader; } } - if (!_lightmapUVsDensityMaterialShader->CanUseInstancing()) + IMaterial::InstancingHandler handler; + if (!_lightmapUVsDensityMaterialShader->CanUseInstancing(handler)) { drawCallsList.CanUseInstancing = false; } @@ -166,7 +164,8 @@ void GBufferPass::Fill(RenderContext& renderContext, GPUTextureView* lightBuffer drawCall.Material = _vertexColorsMaterialShader; } } - if (!_vertexColorsMaterialShader->CanUseInstancing()) + IMaterial::InstancingHandler handler; + if (!_vertexColorsMaterialShader->CanUseInstancing(handler)) { drawCallsList.CanUseInstancing = false; } @@ -311,8 +310,6 @@ void GBufferPass::DrawDecals(RenderContext& renderContext, GPUTextureView* light DrawCall drawCall; MaterialBase::BindParameters bindParams(gpuContext, renderContext, drawCall); drawCall.Material = nullptr; - drawCall.Lightmap = nullptr; - drawCall.Skinning = nullptr; drawCall.WorldDeterminantSign = 1.0f; // Draw all decals diff --git a/Source/Engine/Renderer/HistogramPass.cpp b/Source/Engine/Renderer/HistogramPass.cpp index fe16dbd19..e8bfc9fad 100644 --- a/Source/Engine/Renderer/HistogramPass.cpp +++ b/Source/Engine/Renderer/HistogramPass.cpp @@ -102,8 +102,9 @@ void HistogramPass::Dispose() // Base RendererPass::Dispose(); + // Cleanup SAFE_DELETE_GPU_RESOURCE(_histogramBuffer); - _shader.Unlink(); + _shader = nullptr; } bool HistogramPass::setupResources() diff --git a/Source/Engine/Renderer/LightPass.cpp b/Source/Engine/Renderer/LightPass.cpp index d021ede01..c79033c37 100644 --- a/Source/Engine/Renderer/LightPass.cpp +++ b/Source/Engine/Renderer/LightPass.cpp @@ -164,7 +164,7 @@ void LightPass::Dispose() // Base RendererPass::Dispose(); - // Delete pipeline states + // Cleanup _psLightDir.Delete(); _psLightPointNormal.Delete(); _psLightPointInverted.Delete(); @@ -172,9 +172,7 @@ void LightPass::Dispose() _psLightSpotInverted.Delete(); SAFE_DELETE_GPU_RESOURCE(_psLightSkyNormal); SAFE_DELETE_GPU_RESOURCE(_psLightSkyInverted); - - // Release assets - _sphereModel.Unlink(); + _sphereModel = nullptr; } void LightPass::RenderLight(RenderContext& renderContext, GPUTextureView* lightBuffer) diff --git a/Source/Engine/Renderer/MotionBlurPass.cpp b/Source/Engine/Renderer/MotionBlurPass.cpp index f25d14c51..689916708 100644 --- a/Source/Engine/Renderer/MotionBlurPass.cpp +++ b/Source/Engine/Renderer/MotionBlurPass.cpp @@ -57,11 +57,11 @@ bool MotionBlurPass::Init() // Prepare formats for the buffers auto format = MOTION_VECTORS_PIXEL_FORMAT; - if (FORMAT_FEATURES_ARE_NOT_SUPPORTED(GPUDevice::Instance->GetFormatFeatures(format).Support, (FormatSupport::RenderTarget | FormatSupport::ShaderSample | FormatSupport::Texture2D))) + if (FORMAT_FEATURES_ARE_NOT_SUPPORTED(GPUDevice::Instance->GetFormatFeatures(format).Support, FormatSupport::RenderTarget | FormatSupport::ShaderSample | FormatSupport::Texture2D)) { - if (FORMAT_FEATURES_ARE_NOT_SUPPORTED(GPUDevice::Instance->GetFormatFeatures(PixelFormat::R32G32_Float).Support, (FormatSupport::RenderTarget | FormatSupport::ShaderSample | FormatSupport::Texture2D))) + if (FORMAT_FEATURES_ARE_NOT_SUPPORTED(GPUDevice::Instance->GetFormatFeatures(PixelFormat::R32G32_Float).Support, FormatSupport::RenderTarget | FormatSupport::ShaderSample | FormatSupport::Texture2D)) format = PixelFormat::R32G32_Float; - else if (FORMAT_FEATURES_ARE_NOT_SUPPORTED(GPUDevice::Instance->GetFormatFeatures(PixelFormat::R16G16B16A16_Float).Support, (FormatSupport::RenderTarget | FormatSupport::ShaderSample | FormatSupport::Texture2D))) + else if (FORMAT_FEATURES_ARE_NOT_SUPPORTED(GPUDevice::Instance->GetFormatFeatures(PixelFormat::R16G16B16A16_Float).Support, FormatSupport::RenderTarget | FormatSupport::ShaderSample | FormatSupport::Texture2D)) format = PixelFormat::R16G16B16A16_Float; else format = PixelFormat::R32G32B32A32_Float; @@ -134,16 +134,14 @@ void MotionBlurPass::Dispose() // Base RendererPass::Dispose(); - // Delete pipeline state + // Cleanup SAFE_DELETE_GPU_RESOURCE(_psCameraMotionVectors); SAFE_DELETE_GPU_RESOURCE(_psMotionVectorsDebug); SAFE_DELETE_GPU_RESOURCE(_psTileMax); SAFE_DELETE_GPU_RESOURCE(_psTileMaxVariable); SAFE_DELETE_GPU_RESOURCE(_psNeighborMax); SAFE_DELETE_GPU_RESOURCE(_psMotionBlur); - - // Release asset - _shader.Unlink(); + _shader = nullptr; } void MotionBlurPass::RenderMotionVectors(RenderContext& renderContext) @@ -285,7 +283,7 @@ void MotionBlurPass::Render(RenderContext& renderContext, GPUTexture*& input, GP PROFILE_GPU_CPU("Motion Blur"); // Setup shader inputs - const int32 maxBlurSize = (int32)((float)motionVectorsHeight * 0.05f); + const int32 maxBlurSize = Math::Max((int32)((float)motionVectorsHeight * 0.05f), 1); const int32 tileSize = Math::AlignUp(maxBlurSize, 8); const float timeScale = renderContext.Task->View.IsOfflinePass ? 1.0f : 1.0f / Time::Draw.UnscaledDeltaTime.GetTotalSeconds() / 60.0f; // 60fps as a reference Data data; @@ -337,8 +335,8 @@ void MotionBlurPass::Render(RenderContext& renderContext, GPUTexture*& input, GP RenderTargetPool::Release(vMaxBuffer4); // Downscale motion vectors texture down to tileSize/tileSize (with max velocity calculation NxN kernel) - rtDesc.Width = motionVectorsWidth / tileSize; - rtDesc.Height = motionVectorsHeight / tileSize; + rtDesc.Width = Math::Max(motionVectorsWidth / tileSize, 1); + rtDesc.Height = Math::Max(motionVectorsHeight / tileSize, 1); auto vMaxBuffer = RenderTargetPool::Get(rtDesc); context->ResetRenderTarget(); context->SetRenderTarget(vMaxBuffer->View()); diff --git a/Source/Engine/Renderer/PostProcessingPass.cpp b/Source/Engine/Renderer/PostProcessingPass.cpp index 02469aa81..4e5b4d4fb 100644 --- a/Source/Engine/Renderer/PostProcessingPass.cpp +++ b/Source/Engine/Renderer/PostProcessingPass.cpp @@ -167,19 +167,17 @@ void PostProcessingPass::Dispose() // Base RendererPass::Dispose(); - // Delete pipeline states + // Cleanup SAFE_DELETE_GPU_RESOURCE(_psThreshold); SAFE_DELETE_GPU_RESOURCE(_psScale); SAFE_DELETE_GPU_RESOURCE(_psBlurH); SAFE_DELETE_GPU_RESOURCE(_psBlurV); SAFE_DELETE_GPU_RESOURCE(_psGenGhosts); _psComposite.Delete(); - - // Release assets - _shader.Unlink(); - _defaultLensColor.Unlink(); - _defaultLensDirt.Unlink(); - _defaultLensStar.Unlink(); + _shader = nullptr; + _defaultLensColor = nullptr; + _defaultLensDirt = nullptr; + _defaultLensStar = nullptr; } void PostProcessingPass::Render(RenderContext& renderContext, GPUTexture* input, GPUTexture* output, GPUTexture* colorGradingLUT) diff --git a/Source/Engine/Renderer/ProbesRenderer.cpp b/Source/Engine/Renderer/ProbesRenderer.cpp index 6688c4716..3f48f8c6e 100644 --- a/Source/Engine/Renderer/ProbesRenderer.cpp +++ b/Source/Engine/Renderer/ProbesRenderer.cpp @@ -333,7 +333,7 @@ void ProbesRenderer::Release() SAFE_DELETE_GPU_RESOURCE(_psAccDiffuseIrradiance); SAFE_DELETE_GPU_RESOURCE(_psAccumulateCubeFaces); SAFE_DELETE_GPU_RESOURCE(_psCopyFrameLHB); - _shader.Unlink(); + _shader = nullptr; SAFE_DELETE_GPU_RESOURCE(_output); SAFE_DELETE(_task); SAFE_DELETE_GPU_RESOURCE(_probe); diff --git a/Source/Engine/Renderer/ReflectionsPass.cpp b/Source/Engine/Renderer/ReflectionsPass.cpp index 40f3aeb30..c3108cf0d 100644 --- a/Source/Engine/Renderer/ReflectionsPass.cpp +++ b/Source/Engine/Renderer/ReflectionsPass.cpp @@ -324,15 +324,13 @@ void ReflectionsPass::Dispose() // Base RendererPass::Dispose(); - // Delete pipeline states + // Cleanup SAFE_DELETE_GPU_RESOURCE(_psProbeNormal); SAFE_DELETE_GPU_RESOURCE(_psProbeInverted); SAFE_DELETE_GPU_RESOURCE(_psCombinePass); - - // Release assets - _shader.Unlink(); - _sphereModel.Unlink(); - _preIntegratedGF.Unlink(); + _shader = nullptr; + _sphereModel = nullptr; + _preIntegratedGF = nullptr; } bool sortProbes(EnvironmentProbe* const& p1, EnvironmentProbe* const& p2) diff --git a/Source/Engine/Renderer/RenderList.cpp b/Source/Engine/Renderer/RenderList.cpp index c78931416..7056e8737 100644 --- a/Source/Engine/Renderer/RenderList.cpp +++ b/Source/Engine/Renderer/RenderList.cpp @@ -19,6 +19,11 @@ #define BATCH_KEY_BITS 32 #define BATCH_KEY_MASK ((1 << BATCH_KEY_BITS) - 1) +static_assert(sizeof(DrawCall) <= 280, "Too big draw call data size."); +static_assert(sizeof(DrawCall::Surface) >= sizeof(DrawCall::Terrain), "Wrong draw call data size."); +static_assert(sizeof(DrawCall::Surface) >= sizeof(DrawCall::Particle), "Wrong draw call data size."); +static_assert(sizeof(DrawCall::Surface) >= sizeof(DrawCall::Custom), "Wrong draw call data size."); + namespace { // Cached data for the draw calls sorting @@ -495,24 +500,24 @@ end: } } -/// -/// Checks if this draw call be batched together with the other one. -/// -/// The first draw call. -/// The second draw call. -/// True if can merge them, otherwise false. -FORCE_INLINE bool CanBatchWith(const DrawCall& a, const DrawCall& b) +namespace { - return Platform::MemoryCompare(&a.Geometry, &b.Geometry, sizeof(a.Geometry)) == 0 && - a.Material == b.Material && - a.Lightmap == b.Lightmap && - // TODO: add batch.CanBatch flag computed in AddDrawCall to remove those checks here for Skinning and IndirectDrawArgs - a.Skinning == nullptr && - b.Skinning == nullptr && - a.IndirectArgsBuffer == nullptr && - b.IndirectArgsBuffer == nullptr && - a.WorldDeterminantSign == b.WorldDeterminantSign && - a.Material->CanUseInstancing(); + /// + /// Checks if this draw call be batched together with the other one. + /// + /// The first draw call. + /// The second draw call. + /// True if can merge them, otherwise false. + FORCE_INLINE bool CanBatchWith(const DrawCall& a, const DrawCall& b) + { + IMaterial::InstancingHandler handler; + return a.Material == b.Material && + a.Material->CanUseInstancing(handler) && + Platform::MemoryCompare(&a.Geometry, &b.Geometry, sizeof(a.Geometry)) == 0 && + a.InstanceCount != 0 && + b.InstanceCount != 0 && + a.WorldDeterminantSign == b.WorldDeterminantSign; + } } void RenderList::SortDrawCalls(const RenderContext& renderContext, bool reverseDistance, DrawCallsList& list) @@ -540,7 +545,9 @@ void RenderList::SortDrawCalls(const RenderContext& renderContext, bool reverseD batchKey = (batchKey * 397) ^ GetHash(drawCall.Geometry.VertexBuffers[1]); batchKey = (batchKey * 397) ^ GetHash(drawCall.Geometry.VertexBuffers[2]); batchKey = (batchKey * 397) ^ GetHash(drawCall.Material); - batchKey = (batchKey * 397) ^ GetHash(drawCall.Lightmap); + IMaterial::InstancingHandler handler; + if (drawCall.Material->CanUseInstancing(handler)) + handler.GetHash(drawCall, batchKey); batchKey += (int32)(471 * drawCall.WorldDeterminantSign); #if USE_BATCH_KEY_MASK const uint32 batchHashKey = (uint32)batchKey & BATCH_KEY_MASK; @@ -635,14 +642,12 @@ void RenderList::ExecuteDrawCalls(const RenderContext& renderContext, DrawCallsL auto& batch = list.Batches[i]; if (batch.BatchSize > 1) { + IMaterial::InstancingHandler handler; + DrawCalls[list.Indices[batch.StartIndex]].Material->CanUseInstancing(handler); for (int32 j = 0; j < batch.BatchSize; j++) { auto& drawCall = DrawCalls[list.Indices[batch.StartIndex + j]]; - instanceData->InstanceOrigin = Vector4(drawCall.World.M41, drawCall.World.M42, drawCall.World.M43, drawCall.PerInstanceRandom); - instanceData->InstanceTransform1 = Vector4(drawCall.World.M11, drawCall.World.M12, drawCall.World.M13, drawCall.LODDitherFactor); - instanceData->InstanceTransform2 = Vector3(drawCall.World.M21, drawCall.World.M22, drawCall.World.M23); - instanceData->InstanceTransform3 = Vector3(drawCall.World.M31, drawCall.World.M32, drawCall.World.M33); - instanceData->InstanceLightmapArea = Half4(drawCall.LightmapUVsArea); + handler.WriteDrawCall(instanceData, drawCall); instanceData++; } } @@ -685,20 +690,20 @@ DRAW: context->BindIB(drawCall.Geometry.IndexBuffer); - if (drawCall.IndirectArgsBuffer) + if (drawCall.InstanceCount == 0) { // No support for batching indirect draw calls ASSERT(batch.BatchSize == 1); context->BindVB(ToSpan(vb, vbCount), vbOffsets); - context->DrawIndexedInstancedIndirect(drawCall.IndirectArgsBuffer, drawCall.IndirectArgsOffset); + context->DrawIndexedInstancedIndirect(drawCall.Draw.IndirectArgsBuffer, drawCall.Draw.IndirectArgsOffset); } else { if (batch.BatchSize == 1) { context->BindVB(ToSpan(vb, vbCount), vbOffsets); - context->DrawIndexedInstanced(drawCall.Geometry.IndicesCount, batch.InstanceCount, 0, 0, drawCall.Geometry.StartIndex); + context->DrawIndexedInstanced(drawCall.Draw.IndicesCount, batch.InstanceCount, 0, 0, drawCall.Draw.StartIndex); } else { @@ -707,7 +712,7 @@ DRAW: vbOffsets[vbCount] = 0; vbCount++; context->BindVB(ToSpan(vb, vbCount), vbOffsets); - context->DrawIndexedInstanced(drawCall.Geometry.IndicesCount, batch.InstanceCount, instanceBufferOffset, 0, drawCall.Geometry.StartIndex); + context->DrawIndexedInstanced(drawCall.Draw.IndicesCount, batch.InstanceCount, instanceBufferOffset, 0, drawCall.Draw.StartIndex); instanceBufferOffset += batch.BatchSize; } @@ -730,15 +735,38 @@ DRAW: context->BindIB(drawCall.Geometry.IndexBuffer); context->BindVB(ToSpan(drawCall.Geometry.VertexBuffers, 3), drawCall.Geometry.VertexBuffersOffsets); - if (drawCall.IndirectArgsBuffer) + if (drawCall.InstanceCount == 0) { - context->DrawIndexedInstancedIndirect(drawCall.IndirectArgsBuffer, drawCall.IndirectArgsOffset); + context->DrawIndexedInstancedIndirect(drawCall.Draw.IndirectArgsBuffer, drawCall.Draw.IndirectArgsOffset); } else { - context->DrawIndexedInstanced(drawCall.Geometry.IndicesCount, drawCall.InstanceCount, 0, 0, drawCall.Geometry.StartIndex); + context->DrawIndexedInstanced(drawCall.Draw.IndicesCount, drawCall.InstanceCount, 0, 0, drawCall.Draw.StartIndex); } } } } } + +void SurfaceDrawCallHandler::GetHash(const DrawCall& drawCall, int32& batchKey) +{ + batchKey = (batchKey * 397) ^ ::GetHash(drawCall.Surface.Lightmap); +} + +bool SurfaceDrawCallHandler::CanBatch(const DrawCall& a, const DrawCall& b) +{ + return a.Surface.Lightmap == b.Surface.Lightmap && + a.Surface.Skinning == nullptr && + b.Surface.Skinning == nullptr; +} + +void SurfaceDrawCallHandler::WriteDrawCall(InstanceData* instanceData, const DrawCall& drawCall) +{ + instanceData->InstanceOrigin = Vector3(drawCall.World.M41, drawCall.World.M42, drawCall.World.M43); + instanceData->PerInstanceRandom = drawCall.PerInstanceRandom; + instanceData->InstanceTransform1 = Vector3(drawCall.World.M11, drawCall.World.M12, drawCall.World.M13); + instanceData->LODDitherFactor = drawCall.Surface.LODDitherFactor; + instanceData->InstanceTransform2 = Vector3(drawCall.World.M21, drawCall.World.M22, drawCall.World.M23); + instanceData->InstanceTransform3 = Vector3(drawCall.World.M31, drawCall.World.M32, drawCall.World.M33); + instanceData->InstanceLightmapArea = Half4(drawCall.Surface.LightmapUVsArea); +} diff --git a/Source/Engine/Renderer/RenderList.h b/Source/Engine/Renderer/RenderList.h index dffbbd2d1..69ef0c33d 100644 --- a/Source/Engine/Renderer/RenderList.h +++ b/Source/Engine/Renderer/RenderList.h @@ -357,18 +357,6 @@ public: private: - /// - /// Represents data per instance element used for instanced rendering. - /// - struct InstanceData - { - Vector4 InstanceOrigin; // .w contains PerInstanceRandom - Vector4 InstanceTransform1; // .w contains LODDitherFactor - Vector3 InstanceTransform2; - Vector3 InstanceTransform3; - Half4 InstanceLightmapArea; - }; - DynamicVertexBuffer _instanceBuffer; public: @@ -475,3 +463,24 @@ public: /// The collected draw calls list. void ExecuteDrawCalls(const RenderContext& renderContext, DrawCallsList& list); }; + +/// +/// Represents data per instance element used for instanced rendering. +/// +struct FLAXENGINE_API InstanceData +{ + Vector3 InstanceOrigin; + float PerInstanceRandom; + Vector3 InstanceTransform1; + float LODDitherFactor; + Vector3 InstanceTransform2; + Vector3 InstanceTransform3; + Half4 InstanceLightmapArea; +}; + +struct SurfaceDrawCallHandler +{ + static void GetHash(const DrawCall& drawCall, int32& batchKey); + static bool CanBatch(const DrawCall& a, const DrawCall& b); + static void WriteDrawCall(InstanceData* instanceData, const DrawCall& drawCall); +}; diff --git a/Source/Engine/Renderer/Renderer.cpp b/Source/Engine/Renderer/Renderer.cpp index c2a18f519..89efef74c 100644 --- a/Source/Engine/Renderer/Renderer.cpp +++ b/Source/Engine/Renderer/Renderer.cpp @@ -31,9 +31,6 @@ #include "Editor/Editor.h" #endif -// It must use less or the same amount of memory -static_assert(sizeof(DrawCall::TerrainData) <= sizeof(DrawCall::PrevWorld), "Invalid size of the terrain data in the draw call."); - #if USE_EDITOR // Additional options used in editor for lightmaps baking bool IsRunningRadiancePass = false; @@ -199,9 +196,8 @@ void Renderer::Render(SceneRenderTask* task) #endif // Perform the actual rendering + task->OnPreRender(context, renderContext); RenderInner(task, renderContext); - - // Custom additional rendering task->OnPostRender(context, renderContext); #if USE_EDITOR @@ -306,7 +302,6 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext) #endif renderContext.List->Settings.AntiAliasing.Mode = aaMode; - // Prepare renderContext.View.Prepare(renderContext); renderContext.Buffers->Prepare(); diff --git a/Source/Engine/Renderer/ScreenSpaceReflectionsPass.cpp b/Source/Engine/Renderer/ScreenSpaceReflectionsPass.cpp index 491f53262..5e81db45c 100644 --- a/Source/Engine/Renderer/ScreenSpaceReflectionsPass.cpp +++ b/Source/Engine/Renderer/ScreenSpaceReflectionsPass.cpp @@ -152,16 +152,14 @@ void ScreenSpaceReflectionsPass::Dispose() // Base RendererPass::Dispose(); - // Delete pipeline states + // Cleanup SAFE_DELETE_GPU_RESOURCE(_psRayTracePass); SAFE_DELETE_GPU_RESOURCE(_psCombinePass); SAFE_DELETE_GPU_RESOURCE(_psTemporalPass); SAFE_DELETE_GPU_RESOURCE(_psMixPass); _psResolvePass.Delete(); - - // Release assets - _shader.Unlink(); - _preIntegratedGF.Unlink(); + _shader = nullptr; + _preIntegratedGF = nullptr; } void ScreenSpaceReflectionsPass::Render(RenderContext& renderContext, GPUTextureView* reflectionsRT, GPUTextureView* lightBuffer) diff --git a/Source/Engine/Renderer/ShadowsPass.cpp b/Source/Engine/Renderer/ShadowsPass.cpp index 5ec9d6446..4c3260045 100644 --- a/Source/Engine/Renderer/ShadowsPass.cpp +++ b/Source/Engine/Renderer/ShadowsPass.cpp @@ -6,6 +6,7 @@ #include "Engine/Graphics/Graphics.h" #include "Engine/Graphics/RenderBuffers.h" #include "Engine/Content/Content.h" +#include "Engine/Graphics/PixelFormatExtensions.h" #if USE_EDITOR #include "Engine/Renderer/Lightmaps.h" #endif @@ -77,6 +78,19 @@ bool ShadowsPass::Init() _shader.Get()->OnReloading.Bind(this); #endif + // If GPU doesn't support linear sampling for the shadow map then fallback to the single sample on lowest quality + const auto formatTexture = PixelFormatExtensions::FindShaderResourceFormat(SHADOW_MAPS_FORMAT, false); + const auto formatFeaturesDepth = GPUDevice::Instance->GetFormatFeatures(SHADOW_MAPS_FORMAT); + const auto formatFeaturesTexture = GPUDevice::Instance->GetFormatFeatures(formatTexture); + _supportsShadows = FORMAT_FEATURES_ARE_SUPPORTED(formatFeaturesDepth.Support, FormatSupport::DepthStencil | FormatSupport::Texture2D) + && FORMAT_FEATURES_ARE_SUPPORTED(formatFeaturesTexture.Support, FormatSupport::ShaderSample | FormatSupport::ShaderSampleComparison); + if (!_supportsShadows) + { + LOG(Warning, "GPU doesn't support shadows rendering"); + LOG(Warning, "Format: {0} features support: {1}", (int32)SHADOW_MAPS_FORMAT, (uint32)formatFeaturesDepth.Support); + LOG(Warning, "Format: {0} features support: {1}", (int32)formatTexture, (uint32)formatFeaturesTexture.Support); + } + return false; } @@ -130,24 +144,27 @@ void ShadowsPass::updateShadowMapSize() // Select new size _currentShadowMapsQuality = Graphics::ShadowMapsQuality; - switch (_currentShadowMapsQuality) + if (_supportsShadows) { - case Quality::Ultra: - newSizeCSM = 2048; - newSizeCube = 1024; - break; - case Quality::High: - newSizeCSM = 1024; - newSizeCube = 1024; - break; - case Quality::Medium: - newSizeCSM = 1024; - newSizeCube = 512; - break; - case Quality::Low: - newSizeCSM = 512; - newSizeCube = 256; - break; + switch (_currentShadowMapsQuality) + { + case Quality::Ultra: + newSizeCSM = 2048; + newSizeCube = 1024; + break; + case Quality::High: + newSizeCSM = 1024; + newSizeCube = 1024; + break; + case Quality::Medium: + newSizeCSM = 1024; + newSizeCube = 512; + break; + case Quality::Low: + newSizeCSM = 512; + newSizeCube = 256; + break; + } } // Check if size will change @@ -176,11 +193,12 @@ void ShadowsPass::Dispose() // Base RendererPass::Dispose(); + // Cleanup _psShadowDir.Delete(); _psShadowPoint.Delete(); _psShadowSpot.Delete(); - _shader.Unlink(); - _sphereModel.Unlink(); + _shader = nullptr; + _sphereModel = nullptr; SAFE_DELETE_GPU_RESOURCE(_shadowMapCSM); SAFE_DELETE_GPU_RESOURCE(_shadowMapCube); } @@ -194,7 +212,7 @@ bool ShadowsPass::CanRenderShadow(RenderContext& renderContext, const RendererPo const float fadeDistance = Math::Max(light.ShadowsFadeDistance, 0.1f); const float fade = 1 - Math::Saturate((dstLightToView - light.Radius - light.ShadowsDistance + fadeDistance) / fadeDistance); - return fade > ZeroTolerance; + return fade > ZeroTolerance && _supportsShadows; } bool ShadowsPass::CanRenderShadow(RenderContext& renderContext, const RendererSpotLightData& light) @@ -206,12 +224,12 @@ bool ShadowsPass::CanRenderShadow(RenderContext& renderContext, const RendererSp const float fadeDistance = Math::Max(light.ShadowsFadeDistance, 0.1f); const float fade = 1 - Math::Saturate((dstLightToView - light.Radius - light.ShadowsDistance + fadeDistance) / fadeDistance); - return fade > ZeroTolerance; + return fade > ZeroTolerance && _supportsShadows; } bool ShadowsPass::CanRenderShadow(RenderContext& renderContext, const RendererDirectionalLightData& light) { - return true; + return _supportsShadows; } void ShadowsPass::Prepare(RenderContext& renderContext, GPUContext* context) diff --git a/Source/Engine/Renderer/ShadowsPass.h b/Source/Engine/Renderer/ShadowsPass.h index 8deef8258..3d3190670 100644 --- a/Source/Engine/Renderer/ShadowsPass.h +++ b/Source/Engine/Renderer/ShadowsPass.h @@ -25,6 +25,7 @@ private: GPUPipelineStatePermutationsPs(Quality::MAX) * 2 * 2> _psShadowDir; GPUPipelineStatePermutationsPs(Quality::MAX) * 2> _psShadowPoint; GPUPipelineStatePermutationsPs(Quality::MAX) * 2> _psShadowSpot; + bool _supportsShadows; // Shadow maps stuff int32 _shadowMapsSizeCSM; diff --git a/Source/Engine/Renderer/Utils/BitonicSort.cpp b/Source/Engine/Renderer/Utils/BitonicSort.cpp index 5dc4fffee..89313a95d 100644 --- a/Source/Engine/Renderer/Utils/BitonicSort.cpp +++ b/Source/Engine/Renderer/Utils/BitonicSort.cpp @@ -69,7 +69,7 @@ void BitonicSort::Dispose() // Base RendererPass::Dispose(); - // Delete pipeline states + // Cleanup SAFE_DELETE_GPU_RESOURCE(_dispatchArgsBuffer); _cb = nullptr; _indirectArgsCS = nullptr; @@ -77,9 +77,7 @@ void BitonicSort::Dispose() _innerSortCS = nullptr; _outerSortCS = nullptr; _copyIndicesCS = nullptr; - - // Release asset - _shader.Unlink(); + _shader = nullptr; } void BitonicSort::Sort(GPUContext* context, GPUBuffer* sortingKeysBuffer, GPUBuffer* countBuffer, uint32 counterOffset, bool sortAscending, GPUBuffer* sortedIndicesBuffer) diff --git a/Source/Engine/Renderer/Utils/MultiScaler.cpp b/Source/Engine/Renderer/Utils/MultiScaler.cpp index c415c1900..2c6e8ba14 100644 --- a/Source/Engine/Renderer/Utils/MultiScaler.cpp +++ b/Source/Engine/Renderer/Utils/MultiScaler.cpp @@ -81,14 +81,12 @@ void MultiScaler::Dispose() // Base RendererPass::Dispose(); - // Delete pipeline states + // Cleanup SAFE_DELETE_GPU_RESOURCE(_psHalfDepth); _psBlur5.Delete(); _psBlur9.Delete(); _psBlur13.Delete(); - - // Release asset - _shader.Unlink(); + _shader = nullptr; } void MultiScaler::Filter(const FilterMode mode, GPUContext* context, const int32 width, const int32 height, GPUTextureView* src, GPUTextureView* dst, GPUTextureView* tmp) @@ -121,7 +119,7 @@ void MultiScaler::Filter(const FilterMode mode, GPUContext* context, const int32 ps = &_psBlur13; break; default: - CRASH; + CRASH; return; } @@ -177,7 +175,7 @@ void MultiScaler::Filter(const FilterMode mode, GPUContext* context, const int32 ps = &_psBlur13; break; default: - CRASH; + CRASH; return; } diff --git a/Source/Engine/Renderer/VolumetricFogPass.cpp b/Source/Engine/Renderer/VolumetricFogPass.cpp index 0bf8d838e..ff8f469ae 100644 --- a/Source/Engine/Renderer/VolumetricFogPass.cpp +++ b/Source/Engine/Renderer/VolumetricFogPass.cpp @@ -89,26 +89,19 @@ void VolumetricFogPass::Dispose() // Base RendererPass::Dispose(); - // Delete pipeline states + // Cleanup _psInjectLight.Delete(); - _csInitialize = nullptr; _csLightScattering.Clear(); _csFinalIntegration = nullptr; - SAFE_DELETE_GPU_RESOURCE(_vbCircleRasterize); SAFE_DELETE_GPU_RESOURCE(_ibCircleRasterize); - - // Release assets - _shader.Unlink(); + _shader = nullptr; } -float ComputeZSliceFromDepth(float SceneDepth, const VolumetricFogOptions& options, int32 GridSizeZ) +float ComputeZSliceFromDepth(float sceneDepth, const VolumetricFogOptions& options, int32 gridSizeZ) { - // This must match frustum voxels depth distribution - // See ComputeNormalizedZSliceFromDepth() in VolumetricFog.shader - - return SceneDepth / options.Distance * GridSizeZ; + return sceneDepth / options.Distance * (float)gridSizeZ; } bool VolumetricFogPass::Init(RenderContext& renderContext, GPUContext* context, VolumetricFogOptions& options) diff --git a/Source/Engine/Scripting/Attributes/Editor/EditorOrderAttribute.cs b/Source/Engine/Scripting/Attributes/Editor/EditorOrderAttribute.cs index 506666def..5b783b514 100644 --- a/Source/Engine/Scripting/Attributes/Editor/EditorOrderAttribute.cs +++ b/Source/Engine/Scripting/Attributes/Editor/EditorOrderAttribute.cs @@ -27,7 +27,7 @@ namespace FlaxEngine /// /// Current order is resolved runtime, and can change if custom editor class has changed. /// - /// The order order. + /// The order. public EditorOrderAttribute(int order) { Order = order; diff --git a/Source/Engine/Scripting/Attributes/SerializeAttribute.cs b/Source/Engine/Scripting/Attributes/SerializeAttribute.cs index 3cda97473..d120756ea 100644 --- a/Source/Engine/Scripting/Attributes/SerializeAttribute.cs +++ b/Source/Engine/Scripting/Attributes/SerializeAttribute.cs @@ -5,7 +5,7 @@ using System; namespace FlaxEngine { /// - /// Indicates that a field or a property of a serializable class should be be serialized. This class cannot be inherited. + /// Indicates that a field or a property of a serializable class should be serialized. This class cannot be inherited. /// [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] public sealed class SerializeAttribute : Attribute diff --git a/Source/Engine/Scripting/BinaryModule.cpp b/Source/Engine/Scripting/BinaryModule.cpp index e40a7b716..d5df8947e 100644 --- a/Source/Engine/Scripting/BinaryModule.cpp +++ b/Source/Engine/Scripting/BinaryModule.cpp @@ -14,6 +14,10 @@ #include "MException.h" #include "Scripting.h" #include "StdTypesContainer.h" +#include "Events.h" + +Dictionary, void(*)(ScriptingObject*, void*, bool)> ScriptingEvents::EventsTable; +Delegate&, const ScriptingTypeHandle&, const StringView&> ScriptingEvents::Event; ManagedBinaryModule* GetBinaryModuleCorlib() { @@ -60,62 +64,80 @@ ScriptingType::ScriptingType() , Module(nullptr) , InitRuntime(nullptr) , Fullname(nullptr, 0) - , Type(ScriptingTypes::Class) + , Type(ScriptingTypes::Script) , BaseTypePtr(nullptr) + , Interfaces(nullptr) { - Class.Spawn = nullptr; - Class.VTable = nullptr; - Class.ScriptVTable = nullptr; - Class.ScriptVTableBase = nullptr; - Class.SetupScriptVTable = nullptr; - Class.SetupScriptObjectVTable = nullptr; - Class.DefaultInstance = nullptr; + Script.Spawn = nullptr; + Script.VTable = nullptr; + Script.ScriptVTable = nullptr; + Script.ScriptVTableBase = nullptr; + Script.SetupScriptVTable = nullptr; + Script.SetupScriptObjectVTable = nullptr; + Script.DefaultInstance = nullptr; } -ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, SpawnHandler spawn, const ScriptingTypeHandle& baseType, SetupScriptVTableHandler setupScriptVTable, SetupScriptObjectVTableHandler setupScriptObjectVTable) +ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, SpawnHandler spawn, const ScriptingTypeHandle& baseType, SetupScriptVTableHandler setupScriptVTable, SetupScriptObjectVTableHandler setupScriptObjectVTable, const InterfaceImplementation* interfaces) : ManagedClass(nullptr) , Module(module) , InitRuntime(initRuntime) , Fullname(fullname) - , Type(ScriptingTypes::Class) + , Type(ScriptingTypes::Script) , BaseTypeHandle(baseType) , BaseTypePtr(nullptr) + , Interfaces(interfaces) , Size(size) { - Class.Spawn = spawn; - Class.VTable = nullptr; - Class.ScriptVTable = nullptr; - Class.ScriptVTableBase = nullptr; - Class.SetupScriptVTable = setupScriptVTable; - Class.SetupScriptObjectVTable = setupScriptObjectVTable; - Class.DefaultInstance = nullptr; + Script.Spawn = spawn; + Script.VTable = nullptr; + Script.ScriptVTable = nullptr; + Script.ScriptVTableBase = nullptr; + Script.SetupScriptVTable = setupScriptVTable; + Script.SetupScriptObjectVTable = setupScriptObjectVTable; + Script.DefaultInstance = nullptr; } -ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, SpawnHandler spawn, ScriptingTypeInitializer* baseType, SetupScriptVTableHandler setupScriptVTable, SetupScriptObjectVTableHandler setupScriptObjectVTable) +ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, SpawnHandler spawn, ScriptingTypeInitializer* baseType, SetupScriptVTableHandler setupScriptVTable, SetupScriptObjectVTableHandler setupScriptObjectVTable, const InterfaceImplementation* interfaces) + : ManagedClass(nullptr) + , Module(module) + , InitRuntime(initRuntime) + , Fullname(fullname) + , Type(ScriptingTypes::Script) + , BaseTypePtr(baseType) + , Interfaces(interfaces) + , Size(size) +{ + Script.Spawn = spawn; + Script.VTable = nullptr; + Script.ScriptVTable = nullptr; + Script.ScriptVTableBase = nullptr; + Script.SetupScriptVTable = setupScriptVTable; + Script.SetupScriptObjectVTable = setupScriptObjectVTable; + Script.DefaultInstance = nullptr; +} + +ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, Ctor ctor, Dtor dtor, ScriptingTypeInitializer* baseType, const InterfaceImplementation* interfaces) : ManagedClass(nullptr) , Module(module) , InitRuntime(initRuntime) , Fullname(fullname) , Type(ScriptingTypes::Class) , BaseTypePtr(baseType) + , Interfaces(interfaces) , Size(size) { - Class.Spawn = spawn; - Class.VTable = nullptr; - Class.ScriptVTable = nullptr; - Class.ScriptVTableBase = nullptr; - Class.SetupScriptVTable = setupScriptVTable; - Class.SetupScriptObjectVTable = setupScriptObjectVTable; - Class.DefaultInstance = nullptr; + Class.Ctor = ctor; + Class.Dtor = dtor; } -ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, Ctor ctor, Dtor dtor, Copy copy, Box box, Unbox unbox, GetField getField, SetField setField, ScriptingTypeInitializer* baseType) +ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, Ctor ctor, Dtor dtor, Copy copy, Box box, Unbox unbox, GetField getField, SetField setField, ScriptingTypeInitializer* baseType, const InterfaceImplementation* interfaces) : ManagedClass(nullptr) , Module(module) , InitRuntime(initRuntime) , Fullname(fullname) , Type(ScriptingTypes::Structure) , BaseTypePtr(baseType) + , Interfaces(interfaces) , Size(size) { Struct.Ctor = ctor; @@ -127,6 +149,18 @@ ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* modul Struct.SetField = setField; } +ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, InitRuntimeHandler initRuntime, ScriptingTypeInitializer* baseType, const InterfaceImplementation* interfaces) + : ManagedClass(nullptr) + , Module(module) + , InitRuntime(initRuntime) + , Fullname(fullname) + , Type(ScriptingTypes::Interface) + , BaseTypePtr(baseType) + , Interfaces(interfaces) + , Size(0) +{ +} + ScriptingType::ScriptingType(const ScriptingType& other) : ManagedClass(other.ManagedClass) , Module(other.Module) @@ -135,18 +169,19 @@ ScriptingType::ScriptingType(const ScriptingType& other) , Type(other.Type) , BaseTypeHandle(other.BaseTypeHandle) , BaseTypePtr(other.BaseTypePtr) + , Interfaces(other.Interfaces) , Size(other.Size) { switch (other.Type) { - case ScriptingTypes::Class: - Class.Spawn = other.Class.Spawn; - Class.VTable = nullptr; - Class.ScriptVTable = nullptr; - Class.ScriptVTableBase = nullptr; - Class.SetupScriptVTable = other.Class.SetupScriptVTable; - Class.SetupScriptObjectVTable = other.Class.SetupScriptObjectVTable; - Class.DefaultInstance = nullptr; + case ScriptingTypes::Script: + Script.Spawn = other.Script.Spawn; + Script.VTable = nullptr; + Script.ScriptVTable = nullptr; + Script.ScriptVTableBase = nullptr; + Script.SetupScriptVTable = other.Script.SetupScriptVTable; + Script.SetupScriptObjectVTable = other.Script.SetupScriptObjectVTable; + Script.DefaultInstance = nullptr; break; case ScriptingTypes::Structure: Struct.Ctor = other.Struct.Ctor; @@ -157,8 +192,14 @@ ScriptingType::ScriptingType(const ScriptingType& other) Struct.GetField = other.Struct.GetField; Struct.SetField = other.Struct.SetField; break; + case ScriptingTypes::Class: + Class.Ctor = other.Class.Ctor; + Class.Dtor = other.Class.Dtor; + break; case ScriptingTypes::Enum: break; + case ScriptingTypes::Interface: + break; default: ; } } @@ -171,22 +212,23 @@ ScriptingType::ScriptingType(ScriptingType&& other) , Type(other.Type) , BaseTypeHandle(other.BaseTypeHandle) , BaseTypePtr(other.BaseTypePtr) + , Interfaces(other.Interfaces) , Size(other.Size) { switch (other.Type) { - case ScriptingTypes::Class: - Class.Spawn = other.Class.Spawn; - Class.VTable = other.Class.VTable; - other.Class.VTable = nullptr; - Class.ScriptVTable = other.Class.ScriptVTable; - other.Class.ScriptVTable = nullptr; - Class.ScriptVTableBase = other.Class.ScriptVTableBase; - other.Class.ScriptVTableBase = nullptr; - Class.SetupScriptVTable = other.Class.SetupScriptVTable; - Class.SetupScriptObjectVTable = other.Class.SetupScriptObjectVTable; - Class.DefaultInstance = other.Class.DefaultInstance; - other.Class.DefaultInstance = nullptr; + case ScriptingTypes::Script: + Script.Spawn = other.Script.Spawn; + Script.VTable = other.Script.VTable; + other.Script.VTable = nullptr; + Script.ScriptVTable = other.Script.ScriptVTable; + other.Script.ScriptVTable = nullptr; + Script.ScriptVTableBase = other.Script.ScriptVTableBase; + other.Script.ScriptVTableBase = nullptr; + Script.SetupScriptVTable = other.Script.SetupScriptVTable; + Script.SetupScriptObjectVTable = other.Script.SetupScriptObjectVTable; + Script.DefaultInstance = other.Script.DefaultInstance; + other.Script.DefaultInstance = nullptr; break; case ScriptingTypes::Structure: Struct.Ctor = other.Struct.Ctor; @@ -197,8 +239,14 @@ ScriptingType::ScriptingType(ScriptingType&& other) Struct.GetField = other.Struct.GetField; Struct.SetField = other.Struct.SetField; break; + case ScriptingTypes::Class: + Class.Ctor = other.Class.Ctor; + Class.Dtor = other.Class.Dtor; + break; case ScriptingTypes::Enum: break; + case ScriptingTypes::Interface: + break; default: ; } } @@ -207,18 +255,20 @@ ScriptingType::~ScriptingType() { switch (Type) { - case ScriptingTypes::Class: - if (Class.DefaultInstance) - Delete(Class.DefaultInstance); - if (Class.VTable) - Platform::Free((byte*)Class.VTable - GetVTablePrefix()); - Platform::Free(Class.ScriptVTable); - Platform::Free(Class.ScriptVTableBase); + case ScriptingTypes::Script: + if (Script.DefaultInstance) + Delete(Script.DefaultInstance); + if (Script.VTable) + Platform::Free((byte*)Script.VTable - GetVTablePrefix()); + Platform::Free(Script.ScriptVTable); + Platform::Free(Script.ScriptVTableBase); break; case ScriptingTypes::Structure: break; case ScriptingTypes::Enum: break; + case ScriptingTypes::Interface: + break; default: ; } } @@ -235,17 +285,40 @@ ScriptingTypeHandle ScriptingType::GetHandle() const ScriptingObject* ScriptingType::GetDefaultInstance() const { - ASSERT(Type == ScriptingTypes::Class); - if (!Class.DefaultInstance) + ASSERT(Type == ScriptingTypes::Script); + if (!Script.DefaultInstance) { const ScriptingObjectSpawnParams params(Guid::New(), GetHandle()); - Class.DefaultInstance = Class.Spawn(params); - if (!Class.DefaultInstance) + Script.DefaultInstance = Script.Spawn(params); + if (!Script.DefaultInstance) { LOG(Error, "Failed to create default instance of type {0}", ToString()); } } - return Class.DefaultInstance; + return Script.DefaultInstance; +} + +const ScriptingType::InterfaceImplementation* ScriptingType::GetInterface(const ScriptingTypeInitializer* interfaceType) const +{ + const InterfaceImplementation* interfaces = Interfaces; + if (interfaces) + { + while (interfaces->InterfaceType) + { + if (interfaces->InterfaceType == interfaceType) + return interfaces; + interfaces++; + } + } + if (BaseTypeHandle) + { + return BaseTypeHandle.GetType().GetInterface(interfaceType); + } + if (BaseTypePtr) + { + return BaseTypePtr->GetType().GetInterface(interfaceType); + } + return nullptr; } String ScriptingType::ToString() const @@ -253,11 +326,12 @@ String ScriptingType::ToString() const return String(Fullname.Get(), Fullname.Length()); } -ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::SpawnHandler spawn, ScriptingTypeInitializer* baseType, ScriptingType::SetupScriptVTableHandler setupScriptVTable, ScriptingType::SetupScriptObjectVTableHandler setupScriptObjectVTable) +ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::SpawnHandler spawn, ScriptingTypeInitializer* baseType, ScriptingType::SetupScriptVTableHandler setupScriptVTable, ScriptingType::SetupScriptObjectVTableHandler setupScriptObjectVTable, const ScriptingType::InterfaceImplementation* interfaces) : ScriptingTypeHandle(module, module->Types.Count()) { + // Script module->Types.AddUninitialized(); - new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, size, initRuntime, spawn, baseType, setupScriptVTable, setupScriptObjectVTable); + new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, size, initRuntime, spawn, baseType, setupScriptVTable, setupScriptObjectVTable, interfaces); const MString typeName(fullname.Get(), fullname.Length()); #if BUILD_DEBUG if (module->TypeNameToTypeIndex.ContainsKey(typeName)) @@ -268,11 +342,44 @@ ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const S module->TypeNameToTypeIndex[typeName] = TypeIndex; } -ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingType::Copy copy, ScriptingType::Box box, ScriptingType::Unbox unbox, ScriptingType::GetField getField, ScriptingType::SetField setField, ScriptingTypeInitializer* baseType) +ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingTypeInitializer* baseType, const ScriptingType::InterfaceImplementation* interfaces) : ScriptingTypeHandle(module, module->Types.Count()) { + // Class module->Types.AddUninitialized(); - new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, size, initRuntime, ctor, dtor, copy, box, unbox, getField, setField, baseType); + new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, size, initRuntime, ctor, dtor, baseType, interfaces); + const MString typeName(fullname.Get(), fullname.Length()); +#if BUILD_DEBUG + if (module->TypeNameToTypeIndex.ContainsKey(typeName)) + { + LOG(Error, "Duplicated native typename {0} from module {1}.", String(fullname), String(module->GetName())); + } +#endif + module->TypeNameToTypeIndex[typeName] = TypeIndex; +} + +ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingType::Copy copy, ScriptingType::Box box, ScriptingType::Unbox unbox, ScriptingType::GetField getField, ScriptingType::SetField setField, ScriptingTypeInitializer* baseType, const ScriptingType::InterfaceImplementation* interfaces) + : ScriptingTypeHandle(module, module->Types.Count()) +{ + // Structure + module->Types.AddUninitialized(); + new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, size, initRuntime, ctor, dtor, copy, box, unbox, getField, setField, baseType, interfaces); + const MString typeName(fullname.Get(), fullname.Length()); +#if BUILD_DEBUG + if (module->TypeNameToTypeIndex.ContainsKey(typeName)) + { + LOG(Error, "Duplicated native typename {0} from module {1}.", String(fullname), String(module->GetName())); + } +#endif + module->TypeNameToTypeIndex[typeName] = TypeIndex; +} + +ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, ScriptingType::InitRuntimeHandler initRuntime, ScriptingTypeInitializer* baseType, const ScriptingType::InterfaceImplementation* interfaces) + : ScriptingTypeHandle(module, module->Types.Count()) +{ + // Interface + module->Types.AddUninitialized(); + new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, initRuntime, baseType, interfaces); const MString typeName(fullname.Get(), fullname.Length()); #if BUILD_DEBUG if (module->TypeNameToTypeIndex.ContainsKey(typeName)) @@ -368,7 +475,7 @@ ScriptingObject* ManagedBinaryModule::ManagedObjectSpawn(const ScriptingObjectSp // Create native object ScriptingTypeHandle managedTypeHandle = params.Type; const ScriptingType* managedTypePtr = &managedTypeHandle.GetType(); - while (managedTypePtr->Class.Spawn != &ManagedObjectSpawn) + while (managedTypePtr->Script.Spawn != &ManagedObjectSpawn) { managedTypeHandle = managedTypePtr->GetBaseType(); managedTypePtr = &managedTypeHandle.GetType(); @@ -376,12 +483,12 @@ ScriptingObject* ManagedBinaryModule::ManagedObjectSpawn(const ScriptingObjectSp ScriptingType& managedType = (ScriptingType&)*managedTypePtr; ScriptingTypeHandle nativeTypeHandle = managedType.GetBaseType(); const ScriptingType* nativeTypePtr = &nativeTypeHandle.GetType(); - while (nativeTypePtr->Class.Spawn == &ManagedObjectSpawn) + while (nativeTypePtr->Script.Spawn == &ManagedObjectSpawn) { nativeTypeHandle = nativeTypePtr->GetBaseType(); nativeTypePtr = &nativeTypeHandle.GetType(); } - ScriptingObject* object = nativeTypePtr->Class.Spawn(params); + ScriptingObject* object = nativeTypePtr->Script.Spawn(params); if (!object) { return nullptr; @@ -392,9 +499,9 @@ ScriptingObject* ManagedBinaryModule::ManagedObjectSpawn(const ScriptingObjectSp // We create a custom vtable for the C# objects that use a native class object with virtual functions overrides. // To make it easy to use in C++ we inject custom wrapper methods into C++ object vtable to call C# code from them. // Because virtual member functions calls are C++ ABI and impl-defined this is quite hard. But works. - if (managedType.Class.ScriptVTable) + if (managedType.Script.ScriptVTable) { - if (!managedType.Class.VTable) + if (!managedType.Script.VTable) { // Duplicate vtable void** vtable = *(void***)object; @@ -403,23 +510,23 @@ ScriptingObject* ManagedBinaryModule::ManagedObjectSpawn(const ScriptingObjectSp while (vtable[entriesCount] && entriesCount < 200) entriesCount++; const int32 size = entriesCount * sizeof(void*); - managedType.Class.VTable = (void**)((byte*)Platform::Allocate(prefixSize + size, 16) + prefixSize); - Platform::MemoryCopy((byte*)managedType.Class.VTable - prefixSize, (byte*)vtable - prefixSize, prefixSize + size); + managedType.Script.VTable = (void**)((byte*)Platform::Allocate(prefixSize + size, 16) + prefixSize); + Platform::MemoryCopy((byte*)managedType.Script.VTable - prefixSize, (byte*)vtable - prefixSize, prefixSize + size); // Override vtable entries by the class for (ScriptingTypeHandle e = nativeTypeHandle; e;) { const ScriptingType& eType = e.GetType(); - if (eType.Class.SetupScriptObjectVTable) + if (eType.Script.SetupScriptObjectVTable) { - eType.Class.SetupScriptObjectVTable(managedType.Class.ScriptVTable, managedType.Class.ScriptVTableBase, managedType.Class.VTable, entriesCount, 0); + eType.Script.SetupScriptObjectVTable(managedType.Script.ScriptVTable, managedType.Script.ScriptVTableBase, managedType.Script.VTable, entriesCount, 0); } e = eType.GetBaseType(); } } // Override object vtable with hacked one that has C# functions calls - *(void**)object = managedType.Class.VTable; + *(void**)object = managedType.Script.VTable; } // Mark as managed type @@ -499,6 +606,8 @@ void ManagedBinaryModule::OnLoaded(MAssembly* assembly) for (int32 typeIndex = 0; typeIndex < Types.Count(); typeIndex++) { ScriptingType& type = Types[typeIndex]; + if (type.Type == ScriptingTypes::Interface) + continue; // TODO: generate C# class for interfaces in API ASSERT(type.ManagedClass == nullptr); // Cache class @@ -557,7 +666,7 @@ void ManagedBinaryModule::OnLoaded(MAssembly* assembly) if (baseClassModule->TypeNameToTypeIndex.TryGet(baseClass->GetFullName(), typeIndex)) { nativeType = ScriptingTypeHandle(baseClassModule, typeIndex); - if (nativeType.GetType().Class.Spawn != &ManagedObjectSpawn) + if (nativeType.GetType().Script.Spawn != &ManagedObjectSpawn) break; } baseClass = baseClass->GetBaseClass(); @@ -592,14 +701,14 @@ void ManagedBinaryModule::OnLoaded(MAssembly* assembly) for (ScriptingTypeHandle e = nativeType; e;) { const ScriptingType& eType = e.GetType(); - if (eType.Class.SetupScriptVTable) + if (eType.Script.SetupScriptVTable) { ASSERT(eType.ManagedClass); - eType.Class.SetupScriptVTable(eType.ManagedClass, type.Class.ScriptVTable, type.Class.ScriptVTableBase); + eType.Script.SetupScriptVTable(eType.ManagedClass, type.Script.ScriptVTable, type.Script.ScriptVTableBase); } e = eType.GetBaseType(); } - MMethod** scriptVTable = (MMethod**)type.Class.ScriptVTable; + MMethod** scriptVTable = (MMethod**)type.Script.ScriptVTable; while (scriptVTable && *scriptVTable) { const MMethod* referenceMethod = *scriptVTable; @@ -657,10 +766,10 @@ void ManagedBinaryModule::OnUnloading(MAssembly* assembly) for (ScriptingType& type : Types) { type.ManagedClass = nullptr; - if (type.Type == ScriptingTypes::Class && type.Class.ScriptVTable) + if (type.Type == ScriptingTypes::Script && type.Script.ScriptVTable) { - Platform::Free(type.Class.ScriptVTable); - type.Class.ScriptVTable = nullptr; + Platform::Free(type.Script.ScriptVTable); + type.Script.ScriptVTable = nullptr; } } ClassToTypeIndex.Clear(); diff --git a/Source/Engine/Scripting/Events.h b/Source/Engine/Scripting/Events.h new file mode 100644 index 000000000..11f1643ee --- /dev/null +++ b/Source/Engine/Scripting/Events.h @@ -0,0 +1,36 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "ScriptingType.h" +#include "Engine/Core/Delegate.h" +#include "Engine/Core/Types/Span.h" +#include "Engine/Core/Types/Pair.h" +#include "Engine/Core/Types/Variant.h" +#include "Engine/Core/Types/StringView.h" +#include "Engine/Core/Collections/Dictionary.h" + +/// +/// The helper utility for binding and invoking scripting events (eg. used by Visual Scripting). +/// +class FLAXENGINE_API ScriptingEvents +{ +public: + + /// + /// Global table for registered even binder methods (key is pair of type and event name, value is method that takes instance with event, object to bind and flag to bind or unbind). + /// + /// + /// Key: pair of event type name (full), event name. + /// Value: event binder function with parameters: event caller instance (null for static events), object to bind, true to bind/false to unbind. + /// + static Dictionary, void(*)(ScriptingObject*, void*, bool)> EventsTable; + + /// + /// The action called when any scripting event occurs. Can be used to invoke scripting code that binded to this particular event. + /// + /// + /// Delegate parameters: event caller instance (null for static events), event invocation parameters list, event type name (full), event name. + /// + static Delegate&, const ScriptingTypeHandle&, const StringView&> Event; +}; diff --git a/Source/Engine/Scripting/ManagedCLR/MAssembly.Mono.cpp b/Source/Engine/Scripting/ManagedCLR/MAssembly.Mono.cpp index 50a8b689e..e40014e53 100644 --- a/Source/Engine/Scripting/ManagedCLR/MAssembly.Mono.cpp +++ b/Source/Engine/Scripting/ManagedCLR/MAssembly.Mono.cpp @@ -29,7 +29,6 @@ MAssembly::MAssembly(MDomain* domain, const StringAnsiView& name, const MAssembl , _isDependency(false) , _isFileLocked(false) , _hasCachedClasses(false) - , _classes(options.DictionaryInitialSize) , _reloadCount(0) , _name(name) , _options(options) @@ -303,7 +302,7 @@ bool MAssembly::LoadWithImage(const String& assemblyPath) const auto assembly = mono_assembly_load_from_full(assemblyImage, name.Substring(0, name.Length() - 3).Get(), &status, false); if (status != MONO_IMAGE_OK || assembly == nullptr) { - // Close image if error occured + // Close image if error occurred mono_image_close(assemblyImage); Log::CLRInnerException(TEXT("Mono assembly image is corrupted at ") + assemblyPath); @@ -342,6 +341,8 @@ void MAssembly::OnLoading() Loading(this); _isLoading = true; + if (_classes.Capacity() == 0) + _classes.EnsureCapacity(_options.DictionaryInitialSize); // Pick a domain if (_domain == nullptr) diff --git a/Source/Engine/Scripting/ManagedCLR/MAssembly.h b/Source/Engine/Scripting/ManagedCLR/MAssembly.h index ff8d5e8db..17cb8b624 100644 --- a/Source/Engine/Scripting/ManagedCLR/MAssembly.h +++ b/Source/Engine/Scripting/ManagedCLR/MAssembly.h @@ -234,7 +234,7 @@ public: #endif /// - /// Gets the classes lookup cache. Performs ful initialization if not cached. The result cache contains all classes from the assembly. + /// Gets the classes lookup cache. Performs full initialization if not cached. The result cache contains all classes from the assembly. /// /// The cache. const ClassesDictionary& GetClasses() const; diff --git a/Source/Engine/Scripting/ManagedCLR/MClass.h b/Source/Engine/Scripting/ManagedCLR/MClass.h index bfa813fb4..a8d083d7b 100644 --- a/Source/Engine/Scripting/ManagedCLR/MClass.h +++ b/Source/Engine/Scripting/ManagedCLR/MClass.h @@ -193,7 +193,7 @@ public: /// /// Returns an object referencing a method with the specified name and number of parameters. /// - /// If the the type contains more than one method of the given name and parameters count the returned value can be non-deterministic (one of the matching methods). + /// If the type contains more than one method of the given name and parameters count the returned value can be non-deterministic (one of the matching methods). /// The method name. /// The method parameters count. /// The method or null if failed to get it. diff --git a/Source/Engine/Scripting/Object.cs b/Source/Engine/Scripting/Object.cs index 1c649ba9b..a4e2424e9 100644 --- a/Source/Engine/Scripting/Object.cs +++ b/Source/Engine/Scripting/Object.cs @@ -205,6 +205,11 @@ namespace FlaxEngine return obj?.__unmanagedPtr ?? IntPtr.Zero; } + internal static IntPtr GetUnmanagedPtr(SoftObjectReference reference) + { + return GetUnmanagedPtr(reference.Get()); + } + /// public override int GetHashCode() { diff --git a/Source/Engine/Scripting/Script.cpp b/Source/Engine/Scripting/Script.cpp index 51e827ea8..e1f15e44e 100644 --- a/Source/Engine/Scripting/Script.cpp +++ b/Source/Engine/Scripting/Script.cpp @@ -186,9 +186,9 @@ void Script::SetupType() while (typeHandle != Script::TypeInitializer) { auto& type = typeHandle.GetType(); - _tickUpdate |= type.Class.ScriptVTable[8] != nullptr; - _tickLateUpdate |= type.Class.ScriptVTable[9] != nullptr; - _tickFixedUpdate |= type.Class.ScriptVTable[10] != nullptr; + _tickUpdate |= type.Script.ScriptVTable[8] != nullptr; + _tickLateUpdate |= type.Script.ScriptVTable[9] != nullptr; + _tickFixedUpdate |= type.Script.ScriptVTable[10] != nullptr; typeHandle = type.GetBaseType(); } } diff --git a/Source/Engine/Scripting/Script.cs b/Source/Engine/Scripting/Script.cs index 7f8561c68..c1a0894f0 100644 --- a/Source/Engine/Scripting/Script.cs +++ b/Source/Engine/Scripting/Script.cs @@ -2,7 +2,7 @@ namespace FlaxEngine { - partial class Script : ISceneObject + partial class Script { /// /// Gets the scene object which contains this script. diff --git a/Source/Engine/Scripting/Scripting.cpp b/Source/Engine/Scripting/Scripting.cpp index 6f32f8fd1..c8c8184bd 100644 --- a/Source/Engine/Scripting/Scripting.cpp +++ b/Source/Engine/Scripting/Scripting.cpp @@ -56,7 +56,45 @@ namespace MDomain* _monoRootDomain = nullptr; MDomain* _monoScriptsDomain = nullptr; CriticalSection _objectsLocker; +#define USE_OBJECTS_DISPOSE_CRASHES_DEBUGGING 0 +#if USE_OBJECTS_DISPOSE_CRASHES_DEBUGGING + struct ScriptingObjectData + { + ScriptingObject* Ptr; + StringAnsi TypeName; + + ScriptingObjectData() + { + Ptr = nullptr; + } + + ScriptingObjectData(ScriptingObject* ptr) + { + Ptr = ptr; + if (ptr && ptr->GetTypeHandle() && ptr->GetTypeHandle().TypeIndex < ptr->GetTypeHandle().Module->Types.Count()) + TypeName = ptr->GetType().Fullname; + } + + ScriptingObject* operator->() const + { + return Ptr; + } + + explicit operator ScriptingObject*() + { + return Ptr; + } + + operator ScriptingObject*() const + { + return Ptr; + } + }; + + Dictionary _objectsDictionary(1024 * 16); +#else Dictionary _objectsDictionary(1024 * 16); +#endif bool _isEngineAssemblyLoaded = false; bool _hasGameModulesLoaded = false; MMethod* _method_Update = nullptr; @@ -456,6 +494,9 @@ void Scripting::Release() for (auto i = _objectsDictionary.Begin(); i.IsNotEnd(); ++i) { auto obj = i->Value; +#if USE_OBJECTS_DISPOSE_CRASHES_DEBUGGING + LOG(Info, "[OnScriptingDispose] obj = 0x{0:x}, {1}", (uint64)obj.Ptr, String(obj.TypeName)); +#endif obj->OnScriptingDispose(); } } @@ -498,7 +539,9 @@ void Scripting::Release() } } +#if !USE_SINGLE_DOMAIN MCore::Instance()->UnloadDomain("Scripts Domain"); +#endif } #if USE_EDITOR @@ -546,6 +589,24 @@ void Scripting::Reload(bool canTriggerSceneReload) MCore::GC::Collect(); MCore::GC::WaitForPendingFinalizers(); + // Destroy objects from game assemblies (eg. not released objects that might crash if persist in memory after reload) + _objectsLocker.Lock(); + { + const auto flaxModule = GetBinaryModuleFlaxEngine(); + for (auto i = _objectsDictionary.Begin(); i.IsNotEnd(); ++i) + { + auto obj = i->Value; + if (obj->GetTypeHandle().Module == flaxModule) + continue; + +#if USE_OBJECTS_DISPOSE_CRASHES_DEBUGGING + LOG(Info, "[OnScriptingDispose] obj = 0x{0:x}, {1}", (uint64)obj.Ptr, String(obj.TypeName)); +#endif + obj->OnScriptingDispose(); + } + } + _objectsLocker.Unlock(); + // Unload all game modules LOG(Info, "Unloading game binary modules"); auto modules = BinaryModule::GetModules(); @@ -683,10 +744,18 @@ ScriptingObject* Scripting::FindObject(Guid id, MClass* type) } // Try to find it +#if USE_OBJECTS_DISPOSE_CRASHES_DEBUGGING + ScriptingObjectData data; + _objectsLocker.Lock(); + _objectsDictionary.TryGet(id, data); + _objectsLocker.Unlock(); + auto result = data.Ptr; +#else ScriptingObject* result = nullptr; _objectsLocker.Lock(); _objectsDictionary.TryGet(id, result); _objectsLocker.Unlock(); +#endif if (result) { // Check type @@ -718,11 +787,20 @@ ScriptingObject* Scripting::TryFindObject(Guid id, MClass* type) } // Try to find it +#if USE_OBJECTS_DISPOSE_CRASHES_DEBUGGING + ScriptingObjectData data; + _objectsLocker.Lock(); + _objectsDictionary.TryGet(id, data); + _objectsLocker.Unlock(); + auto result = data.Ptr; +#else ScriptingObject* result = nullptr; _objectsLocker.Lock(); _objectsDictionary.TryGet(id, result); _objectsLocker.Unlock(); +#endif + // Check type if (result && !result->Is(type)) { result = nullptr; @@ -753,28 +831,23 @@ ScriptingObject* Scripting::FindObject(const MonoObject* managedInstance) void Scripting::OnManagedInstanceDeleted(ScriptingObject* obj) { + PROFILE_CPU_NAMED("OnManagedInstanceDeleted"); ASSERT(obj); - PROFILE_CPU_NAMED("OnManagedInstanceDeleted"); - - // This is sometimes crashing, probably rawPtr field is not cleared in some cases - // TODO: use faster callback without crashing - //obj->OnManagedInstanceDeleted(); - // Validate if object still exists - bool isValid; - { - ScopeLock lock(_objectsLocker); - isValid = _objectsDictionary.ContainsValue(obj); - } - if (isValid) + _objectsLocker.Lock(); + if (_objectsDictionary.ContainsValue(obj)) { +#if USE_OBJECTS_DISPOSE_CRASHES_DEBUGGING + LOG(Info, "[OnManagedInstanceDeleted] obj = 0x{0:x}, {1}", (uint64)obj, String(ScriptingObjectData(obj).TypeName)); +#endif obj->OnManagedInstanceDeleted(); } else { //LOG(Warning, "Object finalization called for already removed object (address={0:x})", (uint64)obj); } + _objectsLocker.Unlock(); } bool Scripting::HasGameModulesLoaded() @@ -805,18 +878,25 @@ void Scripting::RegisterObject(ScriptingObject* obj) //ASSERT(!_objectsDictionary.ContainsValue(obj)); #if ENABLE_ASSERTION - ScriptingObject* other = nullptr; +#if USE_OBJECTS_DISPOSE_CRASHES_DEBUGGING + ScriptingObjectData other; if (_objectsDictionary.TryGet(obj->GetID(), other)) +#else + ScriptingObject* other; + if (_objectsDictionary.TryGet(obj->GetID(), other)) +#endif { // Something went wrong... - LOG(Error, "Objects registry already contains object with ID={0}! Trying to register object {1} (type '{2}').", obj->GetID(), obj->ToString(), String(obj->GetClass()->GetFullName())); - + LOG(Error, "Objects registry already contains object with ID={0} (type '{3}')! Trying to register object {1} (type '{2}').", obj->GetID(), obj->ToString(), String(obj->GetClass()->GetFullName()), String(other->GetClass()->GetFullName())); _objectsDictionary.Remove(obj->GetID()); } #else ASSERT(!_objectsDictionary.ContainsKey(obj->_id)); #endif +#if USE_OBJECTS_DISPOSE_CRASHES_DEBUGGING + LOG(Info, "[RegisterObject] obj = 0x{0:x}, {1}", (uint64)obj, String(ScriptingObjectData(obj).TypeName)); +#endif _objectsDictionary.Add(obj->GetID(), obj); } @@ -826,6 +906,9 @@ void Scripting::UnregisterObject(ScriptingObject* obj) //ASSERT(!obj->_id.IsValid() || _objectsDictionary.ContainsValue(obj)); +#if USE_OBJECTS_DISPOSE_CRASHES_DEBUGGING + LOG(Info, "[UnregisterObject] obj = 0x{0:x}, {1}", (uint64)obj, String(ScriptingObjectData(obj).TypeName)); +#endif _objectsDictionary.Remove(obj->GetID()); } diff --git a/Source/Engine/Scripting/ScriptingObject.cpp b/Source/Engine/Scripting/ScriptingObject.cpp index 11d6e43e0..dc9e27865 100644 --- a/Source/Engine/Scripting/ScriptingObject.cpp +++ b/Source/Engine/Scripting/ScriptingObject.cpp @@ -11,7 +11,6 @@ #include "ManagedCLR/MClass.h" #include "ManagedCLR/MUtils.h" #include "ManagedCLR/MField.h" -#include "ManagedCLR/MUtils.h" #if PLATFORM_LINUX #include "ManagedCLR/MCore.h" #endif @@ -72,6 +71,12 @@ ScriptingObject* ScriptingObject::ToNative(MonoObject* obj) return ptr; } +bool ScriptingObject::Is(const ScriptingTypeHandle& type) const +{ + CHECK_RETURN(type, false); + return _type == type || CanCast(GetClass(), type.GetType().ManagedClass); +} + void ScriptingObject::ChangeID(const Guid& newId) { ASSERT(newId.IsValid() && newId != _id); @@ -206,6 +211,14 @@ void ScriptingObject::UnregisterObject() Scripting::UnregisterObject(this); } +bool ScriptingObject::CanCast(const ScriptingTypeHandle& from, const ScriptingTypeHandle& to) +{ + if (!from && !to) + return true; + CHECK_RETURN(from && to, false); + return CanCast(from.GetType().ManagedClass, to.GetType().ManagedClass); +} + bool ScriptingObject::CanCast(MClass* from, MClass* to) { if (!from && !to) @@ -357,7 +370,7 @@ public: // Create unmanaged object const ScriptingObjectSpawnParams params(Guid::New(), ScriptingTypeHandle(module, typeIndex)); - ScriptingObject* obj = scriptingType.Class.Spawn(params); + ScriptingObject* obj = scriptingType.Script.Spawn(params); if (obj == nullptr) { LOG(Error, "Failed to spawn object of type \'{0}.{1}\'.", String(mono_class_get_namespace(typeClass)), String(mono_class_get_name(typeClass))); @@ -400,7 +413,7 @@ public: // Create unmanaged object const ScriptingObjectSpawnParams params(Guid::New(), type); - ScriptingObject* obj = type.GetType().Class.Spawn(params); + ScriptingObject* obj = type.GetType().Script.Spawn(params); if (obj == nullptr) { LOG(Error, "Failed to spawn object of type \'{0}\'.", String(typeName)); @@ -454,7 +467,7 @@ public: // Create unmanaged object const ScriptingObjectSpawnParams params(Guid::New(), ScriptingTypeHandle(module, typeIndex)); - ScriptingObject* obj = scriptingType.Class.Spawn(params); + ScriptingObject* obj = scriptingType.Script.Spawn(params); if (obj == nullptr) { LOG(Error, "Failed to spawn object of type \'{0}.{1}\'.", String(mono_class_get_namespace(typeClass)), String(mono_class_get_name(typeClass))); @@ -501,11 +514,6 @@ public: obj->RegisterObject(); } - static void ManagedInstanceDeleted(ScriptingObject* obj) - { - Scripting::OnManagedInstanceDeleted(obj); - } - static void Destroy(ManagedScriptingObject* obj, float timeLeft) { // Use scaled game time for removing actors/scripts by the user (maybe expose it to the api?) @@ -544,7 +552,7 @@ public: ADD_INTERNAL_CALL("FlaxEngine.Object::Internal_Create1", &Create1); ADD_INTERNAL_CALL("FlaxEngine.Object::Internal_Create2", &Create2); ADD_INTERNAL_CALL("FlaxEngine.Object::Internal_ManagedInstanceCreated", &ManagedInstanceCreated); - ADD_INTERNAL_CALL("FlaxEngine.Object::Internal_ManagedInstanceDeleted", &ManagedInstanceDeleted); + ADD_INTERNAL_CALL("FlaxEngine.Object::Internal_ManagedInstanceDeleted", &Scripting::OnManagedInstanceDeleted); ADD_INTERNAL_CALL("FlaxEngine.Object::Internal_Destroy", &Destroy); ADD_INTERNAL_CALL("FlaxEngine.Object::Internal_GetTypeName", &GetTypeName); ADD_INTERNAL_CALL("FlaxEngine.Object::Internal_FindObject", &FindObject); diff --git a/Source/Engine/Scripting/ScriptingObject.h b/Source/Engine/Scripting/ScriptingObject.h index 9c5b74e18..9eb375fc5 100644 --- a/Source/Engine/Scripting/ScriptingObject.h +++ b/Source/Engine/Scripting/ScriptingObject.h @@ -131,12 +131,30 @@ public: return obj ? obj->GetOrCreateManagedInstance() : nullptr; } + /// + /// Checks if can cast one scripting object type into another type. + /// + /// The object type for the cast. + /// The destination type to the cast. + /// True if can, otherwise false. + static bool CanCast(const ScriptingTypeHandle& from, const ScriptingTypeHandle& to); + + /// + /// Checks if can cast one scripting object type into another type. + /// + /// The object class for the cast. + /// The destination class to the cast. + /// True if can, otherwise false. + static bool CanCast(MClass* from, MClass* to); + template static T* Cast(ScriptingObject* obj) { return obj && CanCast(obj->GetClass(), T::GetStaticClass()) ? (T*)obj : nullptr; } + bool Is(const ScriptingTypeHandle& type) const; + bool Is(MClass* type) const { return CanCast(GetClass(), type); @@ -185,10 +203,6 @@ public: /// void UnregisterObject(); -private: - - static bool CanCast(MClass* from, MClass* to); - protected: /// diff --git a/Source/Engine/Scripting/ScriptingType.h b/Source/Engine/Scripting/ScriptingType.h index 40ca2ed10..b27d92f2f 100644 --- a/Source/Engine/Scripting/ScriptingType.h +++ b/Source/Engine/Scripting/ScriptingType.h @@ -93,9 +93,11 @@ inline uint32 GetHash(const ScriptingTypeHandle& key) /// enum class ScriptingTypes { - Class = 0, + Script = 0, Structure = 1, Enum = 2, + Class = 3, + Interface = 4, }; /// @@ -115,6 +117,15 @@ struct FLAXENGINE_API ScriptingType typedef void (*GetField)(void* ptr, const String& name, Variant& value); typedef void (*SetField)(void* ptr, const String& name, const Variant& value); + struct InterfaceImplementation + { + // Pointer to the type of the implemented interface. + const ScriptingTypeInitializer* InterfaceType; + + // The offset (in bytes) from the object pointer to the interface implementation. Used for casting object to the interface. + int16 VTableOffset; + }; + /// /// The managed class (cached, can be null if missing). /// @@ -150,6 +161,11 @@ struct FLAXENGINE_API ScriptingType /// const ScriptingTypeInitializer* BaseTypePtr; + /// + /// The list of interfaces implemented by this type (null if unused, list ends with null entry). + /// + const InterfaceImplementation* Interfaces; + /// /// The native size of the type value (in bytes). /// @@ -190,10 +206,10 @@ struct FLAXENGINE_API ScriptingType SetupScriptObjectVTableHandler SetupScriptObjectVTable; /// - /// The default instance of the scripting type. Used by serialization system for comparision to save only modified properties of the object. + /// The default instance of the scripting type. Used by serialization system for comparison to save only modified properties of the object. /// mutable ScriptingObject* DefaultInstance; - } Class; + } Script; struct { @@ -218,12 +234,23 @@ struct FLAXENGINE_API ScriptingType // Structure field value setter SetField SetField; } Struct; + + struct + { + // Class constructor method pointer + Ctor Ctor; + + // Class destructor method pointer + Dtor Dtor; + } Class; }; ScriptingType(); - ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, SpawnHandler spawn, const ScriptingTypeHandle& baseType, SetupScriptVTableHandler setupScriptVTable = nullptr, SetupScriptObjectVTableHandler setupScriptObjectVTable = nullptr); - ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime = DefaultInitRuntime, SpawnHandler spawn = DefaultSpawn, ScriptingTypeInitializer* baseType = nullptr, SetupScriptVTableHandler setupScriptVTable = nullptr, SetupScriptObjectVTableHandler setupScriptObjectVTable = nullptr); - ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, Ctor ctor, Dtor dtor, Copy copy, Box box, Unbox unbox, GetField getField, SetField setField, ScriptingTypeInitializer* baseType); + ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, SpawnHandler spawn, const ScriptingTypeHandle& baseType, SetupScriptVTableHandler setupScriptVTable = nullptr, SetupScriptObjectVTableHandler setupScriptObjectVTable = nullptr, const InterfaceImplementation* interfaces = nullptr); + ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime = DefaultInitRuntime, SpawnHandler spawn = DefaultSpawn, ScriptingTypeInitializer* baseType = nullptr, SetupScriptVTableHandler setupScriptVTable = nullptr, SetupScriptObjectVTableHandler setupScriptObjectVTable = nullptr, const InterfaceImplementation* interfaces = nullptr); + ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, Ctor ctor, Dtor dtor, ScriptingTypeInitializer* baseType, const InterfaceImplementation* interfaces = nullptr); + ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, Ctor ctor, Dtor dtor, Copy copy, Box box, Unbox unbox, GetField getField, SetField setField, ScriptingTypeInitializer* baseType, const InterfaceImplementation* interfaces = nullptr); + ScriptingType(const StringAnsiView& fullname, BinaryModule* module, InitRuntimeHandler initRuntime, ScriptingTypeInitializer* baseType, const InterfaceImplementation* interfaces = nullptr); ScriptingType(const ScriptingType& other); ScriptingType(ScriptingType&& other); ScriptingType& operator=(ScriptingType&& other) = delete; @@ -255,10 +282,15 @@ struct FLAXENGINE_API ScriptingType } /// - /// Gets the default instance of the scripting type. Used by serialization system for comparision to save only modified properties of the object. + /// Gets the default instance of the scripting type. Used by serialization system for comparison to save only modified properties of the object. /// ScriptingObject* GetDefaultInstance() const; + /// + /// Gets the pointer to the implementation of the given interface type for this scripting type (including base types). Returns null if given interface is not implemented. + /// + const InterfaceImplementation* GetInterface(const ScriptingTypeInitializer* interfaceType) const; + String ToString() const; }; @@ -267,8 +299,10 @@ struct FLAXENGINE_API ScriptingType /// struct FLAXENGINE_API ScriptingTypeInitializer : ScriptingTypeHandle { - ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime = ScriptingType::DefaultInitRuntime, ScriptingType::SpawnHandler spawn = ScriptingType::DefaultSpawn, ScriptingTypeInitializer* baseType = nullptr, ScriptingType::SetupScriptVTableHandler setupScriptVTable = nullptr, ScriptingType::SetupScriptObjectVTableHandler setupScriptObjectVTable = nullptr); - ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingType::Copy copy, ScriptingType::Box box, ScriptingType::Unbox unbox, ScriptingType::GetField getField, ScriptingType::SetField setField, ScriptingTypeInitializer* baseType = nullptr); + ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime = ScriptingType::DefaultInitRuntime, ScriptingType::SpawnHandler spawn = ScriptingType::DefaultSpawn, ScriptingTypeInitializer* baseType = nullptr, ScriptingType::SetupScriptVTableHandler setupScriptVTable = nullptr, ScriptingType::SetupScriptObjectVTableHandler setupScriptObjectVTable = nullptr, const ScriptingType::InterfaceImplementation* interfaces = nullptr); + ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingTypeInitializer* baseType = nullptr, const ScriptingType::InterfaceImplementation* interfaces = nullptr); + ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingType::Copy copy, ScriptingType::Box box, ScriptingType::Unbox unbox, ScriptingType::GetField getField, ScriptingType::SetField setField, ScriptingTypeInitializer* baseType = nullptr, const ScriptingType::InterfaceImplementation* interfaces = nullptr); + ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, ScriptingType::InitRuntimeHandler initRuntime, ScriptingTypeInitializer* baseType = nullptr, const ScriptingType::InterfaceImplementation* interfaces = nullptr); }; /// diff --git a/Source/Engine/Scripting/SoftObjectReference.cs b/Source/Engine/Scripting/SoftObjectReference.cs new file mode 100644 index 000000000..df098d02a --- /dev/null +++ b/Source/Engine/Scripting/SoftObjectReference.cs @@ -0,0 +1,80 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System; + +namespace FlaxEngine +{ + /// + /// The scripting object soft reference. Objects gets referenced on use (ID reference is resolving it). + /// + public struct SoftObjectReference : IComparable, IComparable + { + private Guid _id; + private Object _object; + + /// + /// Gets or sets the object identifier. + /// + public Guid ID + { + get => _id; + set + { + if (_id == value) + return; + _id = value; + _object = null; + } + } + + /// + /// Gets the object reference. + /// + /// The object type. + /// The resolved object or null. + public T Get() where T : Object + { + if (!_object) + _object = Object.Find(ref _id, typeof(T)); + return _object as T; + } + + /// + /// Sets the object reference. + /// + /// The object. + public void Set(Object obj) + { + _object = obj; + _id = obj?.ID ?? Guid.Empty; + } + + /// + public override string ToString() + { + if (_object) + return _object.ToString(); + return _id.ToString(); + } + + /// + public override int GetHashCode() + { + return _id.GetHashCode(); + } + + /// + public int CompareTo(object obj) + { + if (obj is SoftObjectReference other) + return CompareTo(other); + return 0; + } + + /// + public int CompareTo(SoftObjectReference other) + { + return _id.CompareTo(other._id); + } + } +} diff --git a/Source/Engine/Scripting/SoftObjectReference.h b/Source/Engine/Scripting/SoftObjectReference.h new file mode 100644 index 000000000..b6f08db4a --- /dev/null +++ b/Source/Engine/Scripting/SoftObjectReference.h @@ -0,0 +1,328 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Scripting/ScriptingObject.h" + +// Don't include Scripting.h but just FindObject method +extern FLAXENGINE_API ScriptingObject* FindObject(const Guid& id, MClass* type); + +/// +/// The scripting object soft reference. Objects gets referenced on use (ID reference is resolving it). +/// +class FLAXENGINE_API SoftObjectReferenceBase +{ +public: + + typedef Delegate<> EventType; + +protected: + + ScriptingObject* _object = nullptr; + Guid _id = Guid::Empty; + +public: + + /// + /// Action fired when reference gets changed. + /// + EventType Changed; + +public: + + /// + /// Initializes a new instance of the class. + /// + SoftObjectReferenceBase() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The object to link. + SoftObjectReferenceBase(ScriptingObject* obj) + { + OnSet(obj); + } + + /// + /// Finalizes an instance of the class. + /// + ~SoftObjectReferenceBase() + { + if (_object) + _object->Deleted.Unbind(this); + } + +public: + + /// + /// Gets the object ID. + /// + /// The object ID or Guid::Empty if nothing assigned. + Guid GetID() const + { + return _object ? _object->GetID() : _id; + } + +protected: + + /// + /// Sets the object. + /// + /// The object. + void OnSet(ScriptingObject* object) + { + auto e = _object; + if (e != object) + { + if (e) + e->Deleted.Unbind(this); + _object = e = object; + _id = e ? e->GetID() : Guid::Empty; + if (e) + e->Deleted.Bind(this); + Changed(); + } + } + + void OnDeleted(ScriptingObject* obj) + { + ASSERT(_object == obj); + _object->Deleted.Unbind(this); + _object = nullptr; + Changed(); + } +}; + +/// +/// The scripting object soft reference. Objects gets referenced on use (ID reference is resolving it). +/// +template +API_CLASS(InBuild) class SoftObjectReference : public SoftObjectReferenceBase +{ +public: + + typedef SoftObjectReference Type; + +public: + + /// + /// Initializes a new instance of the class. + /// + SoftObjectReference() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The object to link. + SoftObjectReference(T* obj) + : SoftObjectReferenceBase(obj) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The other property. + SoftObjectReference(const SoftObjectReference& other) + : SoftObjectReferenceBase(other.Get()) + { + } + + /// + /// Finalizes an instance of the class. + /// + ~SoftObjectReference() + { + } + +public: + + /// + /// Compares the property value with the given object. + /// + /// The other. + /// True if property object equals the given value. + FORCE_INLINE bool operator==(T* other) + { + return Get() == other; + } + + /// + /// Compares the property value with the other property value. + /// + /// The other property. + /// True if properties are equal. + FORCE_INLINE bool operator==(const SoftObjectReference& other) + { + return _id == other._id; + } + + /// + /// Compares the property value with the given object. + /// + /// The other. + /// True if property object not equals the given value. + FORCE_INLINE bool operator!=(T* other) + { + return Get() != other; + } + + /// + /// Compares the property value with the other property value. + /// + /// The other property. + /// True if properties are not equal. + FORCE_INLINE bool operator!=(const SoftObjectReference& other) + { + return _id != other._id; + } + + /// + /// Sets the property to the given property value. + /// + /// The other property. + /// The reference to this property. + SoftObjectReference& operator=(const SoftObjectReference& other) + { + if (this != &other) + OnSet(other.Get()); + return *this; + } + + /// + /// Sets the property to the given value. + /// + /// The object. + /// The reference to this property. + FORCE_INLINE SoftObjectReference& operator=(const T& other) + { + OnSet(&other); + return *this; + } + + /// + /// Sets the property to the given value. + /// + /// The object. + /// The reference to this property. + FORCE_INLINE SoftObjectReference& operator=(T* other) + { + OnSet(other); + return *this; + } + + /// + /// Sets the property to the object of the given ID. + /// + /// The object ID. + /// The reference to this property. + FORCE_INLINE SoftObjectReference& operator=(const Guid& id) + { + Set(id); + return *this; + } + + /// + /// Implicit conversion to the object. + /// + /// The object reference. + FORCE_INLINE operator T*() const + { + return (T*)Get(); + } + + /// + /// Implicit conversion to boolean value. + /// + /// True if object has been assigned, otherwise false + FORCE_INLINE operator bool() const + { + return _object != nullptr || _id.IsValid(); + } + + /// + /// Object accessor. + /// + /// The object reference. + FORCE_INLINE T* operator->() const + { + return (T*)Get(); + } + + /// + /// Gets the object pointer. + /// + /// The object reference. + T* Get() const + { + if (!_object) + const_cast(this)->OnSet(FindObject(_id, T::GetStaticClass())); + return (T*)_object; + } + + /// + /// Gets the object as a given type (static cast). + /// + /// Asset + template + FORCE_INLINE U* As() const + { + return static_cast(Get()); + } + +public: + + /// + /// Gets managed instance object (or null if no object linked). + /// + /// The managed object instance. + MonoObject* GetManagedInstance() const + { + auto object = Get(); + return object ? object->GetOrCreateManagedInstance() : nullptr; + } + + /// + /// Determines whether object is assigned and managed instance of the object is alive. + /// + /// True if managed object has been created and exists, otherwise false. + bool HasManagedInstance() const + { + auto object = Get(); + return object && object->HasManagedInstance(); + } + + /// + /// Gets the managed instance object or creates it if missing or null if not assigned. + /// + /// The Mono managed object. + MonoObject* GetOrCreateManagedInstance() const + { + auto object = Get(); + return object ? object->GetOrCreateManagedInstance() : nullptr; + } + + /// + /// Sets the object. + /// + /// The object ID. Uses Scripting to find the registered object of the given ID. + void Set(const Guid& id) + { + _id = id; + _object = nullptr; + } + + /// + /// Sets the object. + /// + /// The object. + FORCE_INLINE void Set(T* object) + { + OnSet(object); + } +}; diff --git a/Source/Engine/Serialization/ISerializable.h b/Source/Engine/Serialization/ISerializable.h index d5f21ec44..26678c998 100644 --- a/Source/Engine/Serialization/ISerializable.h +++ b/Source/Engine/Serialization/ISerializable.h @@ -2,57 +2,5 @@ #pragma once -#include "JsonFwd.h" -#include "Engine/Core/Compiler.h" - -class JsonWriter; -class ISerializeModifier; - -/// -/// Interface for objects that can be serialized/deserialized to/from JSON format. -/// -class FLAXENGINE_API ISerializable -{ -public: - - typedef rapidjson_flax::Document SerializeDocument; - - /// - /// Serialization output stream - /// - typedef rapidjson_flax::Value DeserializeStream; - - /// - /// Serialization input stream - /// - typedef JsonWriter SerializeStream; - -public: - - /// - /// Finalizes an instance of the class. - /// - virtual ~ISerializable() = default; - - /// - /// Serialize object to the output stream compared to the values of the other object instance (eg. default class object). If other object is null then serialize all properties. - /// - /// The output stream. - /// The instance of the object to compare with and serialize only the modified properties. If null, then serialize all properties. - virtual void Serialize(SerializeStream& stream, const void* otherObj) = 0; - - /// - /// Deserialize object from the input stream - /// - /// The input stream. - /// The deserialization modifier object. Always valid. - virtual void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) = 0; - - /// - /// Deserialize object from the input stream child member. Won't deserialize it if member is missing. - /// - /// The input stream. - /// The input stream member to lookup. - /// The deserialization modifier object. Always valid. - void DeserializeIfExists(DeserializeStream& stream, const char* memberName, ISerializeModifier* modifier); -}; +// ISerializable moved to Core module +#include "Engine/Core/ISerializable.h" diff --git a/Source/Engine/Serialization/JsonCustomSerializers/ExtendedDefaultContractResolver.cs b/Source/Engine/Serialization/JsonCustomSerializers/ExtendedDefaultContractResolver.cs index f6a62dd54..cc07597e8 100644 --- a/Source/Engine/Serialization/JsonCustomSerializers/ExtendedDefaultContractResolver.cs +++ b/Source/Engine/Serialization/JsonCustomSerializers/ExtendedDefaultContractResolver.cs @@ -118,9 +118,11 @@ namespace FlaxEngine.Json.JsonCustomSerializers if (noSerialize) continue; + var isObsolete = attributes.Any(x => x is ObsoleteAttribute); + var jsonProperty = CreateProperty(p, memberSerialization); jsonProperty.Writable = true; - jsonProperty.Readable = true; + jsonProperty.Readable = !isObsolete; if (_flaxType.IsAssignableFrom(p.PropertyType)) { diff --git a/Source/Engine/Serialization/JsonSerializer.cs b/Source/Engine/Serialization/JsonSerializer.cs index 231831442..d3300dd23 100644 --- a/Source/Engine/Serialization/JsonSerializer.cs +++ b/Source/Engine/Serialization/JsonSerializer.cs @@ -87,6 +87,38 @@ namespace FlaxEngine.Json } } + /// + /// Serialize SoftObjectReference as Guid in internal format. + /// + /// + internal class SoftObjectReferenceConverter : JsonConverter + { + /// + public override unsafe void WriteJson(JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer) + { + var id = ((SoftObjectReference)value).ID; + writer.WriteValue(JsonSerializer.GetStringID(&id)); + } + + /// + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer) + { + var result = new SoftObjectReference(); + if (reader.TokenType == JsonToken.String) + { + JsonSerializer.ParseID((string)reader.Value, out var id); + result.ID = id; + } + return result; + } + + /// + public override bool CanConvert(Type objectType) + { + return objectType == typeof(SoftObjectReference); + } + } + /* /// /// Serialize Guid values using `N` format @@ -120,6 +152,7 @@ namespace FlaxEngine.Json } } */ + /// /// Objects serialization tool (json format). /// @@ -183,6 +216,7 @@ namespace FlaxEngine.Json ObjectConverter = new FlaxObjectConverter(); settings.Converters.Add(ObjectConverter); settings.Converters.Add(new SceneReferenceConverter()); + settings.Converters.Add(new SoftObjectReferenceConverter()); settings.Converters.Add(new VersionConverter()); //settings.Converters.Add(new GuidConverter()); return settings; diff --git a/Source/Engine/Serialization/JsonWriter.cpp b/Source/Engine/Serialization/JsonWriter.cpp index d3f04a684..de90b49b4 100644 --- a/Source/Engine/Serialization/JsonWriter.cpp +++ b/Source/Engine/Serialization/JsonWriter.cpp @@ -195,7 +195,7 @@ void JsonWriter::SceneObject(::SceneObject* obj) auto prefab = Content::Load(obj->GetPrefabID()); if (prefab) { - // Request the prefab to be deserialized to the default instance (used for comparision to generate a diff) + // Request the prefab to be deserialized to the default instance (used for comparison to generate a diff) prefab->GetDefaultInstance(); // Get prefab object instance from the prefab diff --git a/Source/Engine/Serialization/Serialization.h b/Source/Engine/Serialization/Serialization.h index b760e9689..039d9df3e 100644 --- a/Source/Engine/Serialization/Serialization.h +++ b/Source/Engine/Serialization/Serialization.h @@ -9,6 +9,7 @@ #include "Engine/Core/Collections/Array.h" #include "Engine/Core/Collections/Dictionary.h" #include "Engine/Scripting/ScriptingObjectReference.h" +#include "Engine/Scripting/SoftObjectReference.h" #include "Engine/Content/AssetReference.h" #include "Engine/Content/WeakAssetReference.h" @@ -457,6 +458,27 @@ namespace Serialization v = id; } + // Soft Object Reference + + template + inline bool ShouldSerialize(const SoftObjectReference& v, const void* otherObj) + { + return !otherObj || v.Get() != ((SoftObjectReference*)otherObj)->Get(); + } + template + inline void Serialize(ISerializable::SerializeStream& stream, const SoftObjectReference& v, const void* otherObj) + { + stream.Guid(v.GetID()); + } + template + inline void Deserialize(ISerializable::DeserializeStream& stream, SoftObjectReference& v, ISerializeModifier* modifier) + { + Guid id; + Deserialize(stream, id, modifier); + modifier->IdsMapping.TryGet(id, id); + v = id; + } + // Asset Reference template diff --git a/Source/Engine/Serialization/Stream.cpp b/Source/Engine/Serialization/Stream.cpp index e10ae9fd6..269d49562 100644 --- a/Source/Engine/Serialization/Stream.cpp +++ b/Source/Engine/Serialization/Stream.cpp @@ -4,8 +4,9 @@ #include "WriteStream.h" #include "Engine/Core/Types/CommonValue.h" #include "Engine/Core/Types/Variant.h" -#include "Engine/Content/Asset.h" #include "Engine/Core/Collections/Dictionary.h" +#include "Engine/Content/Asset.h" +#include "Engine/Debug/DebugLog.h" #include "Engine/Scripting/ScriptingObject.h" #include "Engine/Scripting/ScriptingObjectReference.h" @@ -227,6 +228,8 @@ void ReadStream::ReadVariantType(VariantType* data) if (typeNameLength == MAX_int32) { ReadInt32(&typeNameLength); + if (typeNameLength == 0) + return; data->TypeName = static_cast(Allocator::Allocate(typeNameLength + 1)); char* ptr = data->TypeName; Read(ptr, typeNameLength); @@ -330,8 +333,19 @@ void ReadStream::ReadVariant(Variant* data) { int32 length; ReadInt32(&length); - ASSERT(data->AsBlob.Length == length); - ReadBytes(data->AsBlob.Data, length); + if (data->AsBlob.Length == length) + { + ReadBytes(data->AsBlob.Data, length); + } + else + { + LOG(Error, "Invalid Variant {2} data length {0}. Expected {1} bytes from stream.", data->AsBlob.Length, length, data->Type.ToString()); + + // Skip those bytes + void* ptr = Allocator::Allocate(length); + ReadBytes(ptr, length); + Allocator::Free(ptr); + } break; } case VariantType::Blob: @@ -590,13 +604,21 @@ void WriteStream::WriteVariant(const Variant& data) break; case VariantType::Structure: case VariantType::Blob: - case VariantType::BoundingBox: - case VariantType::Transform: - case VariantType::Ray: - case VariantType::Matrix: WriteInt32(data.AsBlob.Length); WriteBytes(data.AsBlob.Data, data.AsBlob.Length); break; + case VariantType::BoundingBox: + WriteBytes(data.AsBlob.Data, sizeof(BoundingBox)); + break; + case VariantType::Transform: + WriteBytes(data.AsBlob.Data, sizeof(Transform)); + break; + case VariantType::Ray: + WriteBytes(data.AsBlob.Data, sizeof(Ray)); + break; + case VariantType::Matrix: + WriteBytes(data.AsBlob.Data, sizeof(Matrix)); + break; case VariantType::Asset: id = data.AsAsset ? data.AsAsset->GetID() : Guid::Empty; Write(&id); diff --git a/Source/Engine/Serialization/Stream.h b/Source/Engine/Serialization/Stream.h index de7911fb4..d988a24df 100644 --- a/Source/Engine/Serialization/Stream.h +++ b/Source/Engine/Serialization/Stream.h @@ -37,9 +37,9 @@ public: public: /// - /// Returns true if error occured during reading/writing to the stream + /// Returns true if error occurred during reading/writing to the stream /// - /// True if error occured during reading/writing to the stream + /// True if error occurred during reading/writing to the stream virtual bool HasError() const { return _hasError; diff --git a/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp b/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp index 7ec466eee..20c2fb325 100644 --- a/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp +++ b/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp @@ -173,7 +173,7 @@ void ShadowsOfMordor::Builder::onJobRender(GPUContext* context) context->SetState(_psRenderCacheTerrain); context->BindIB(drawCall.Geometry.IndexBuffer); context->BindVB(ToSpan(drawCall.Geometry.VertexBuffers, 1)); - context->DrawIndexed(drawCall.Geometry.IndicesCount, 0, drawCall.Geometry.StartIndex); + context->DrawIndexed(drawCall.Draw.IndicesCount, 0, drawCall.Draw.StartIndex); break; } diff --git a/Source/Engine/ShadowsOfMordor/Builder.cpp b/Source/Engine/ShadowsOfMordor/Builder.cpp index 715803839..b9cb57376 100644 --- a/Source/Engine/ShadowsOfMordor/Builder.cpp +++ b/Source/Engine/ShadowsOfMordor/Builder.cpp @@ -499,7 +499,7 @@ void ShadowsOfMordor::Builder::releaseResources() SAFE_DELETE_GPU_RESOURCE(_psRenderCacheModel); SAFE_DELETE_GPU_RESOURCE(_psRenderCacheTerrain); SAFE_DELETE_GPU_RESOURCE(_psBlurCache); - _shader.Unlink(); + _shader = nullptr; SAFE_DELETE_GPU_RESOURCE(_irradianceReduction); diff --git a/Source/Engine/Streaming/StreamingManager.cpp b/Source/Engine/Streaming/StreamingManager.cpp index d82a3b5ab..3b557e85f 100644 --- a/Source/Engine/Streaming/StreamingManager.cpp +++ b/Source/Engine/Streaming/StreamingManager.cpp @@ -92,7 +92,7 @@ void UpdateResource(StreamableResource* resource, DateTime now) } } - // Calculate residency level to stream in (resources may want to incease/decrease it's quality in steps rather than at once) + // Calculate residency level to stream in (resources may want to increase/decrease it's quality in steps rather than at once) int32 requestedResidency = handler->CalculateRequestedResidency(resource, targetResidency); // Create streaming task (resource type specific) diff --git a/Source/Engine/Terrain/Terrain.cpp b/Source/Engine/Terrain/Terrain.cpp index d15dd4cfa..e87de12a3 100644 --- a/Source/Engine/Terrain/Terrain.cpp +++ b/Source/Engine/Terrain/Terrain.cpp @@ -36,6 +36,7 @@ Terrain::~Terrain() void Terrain::UpdateBounds() { + PROFILE_CPU(); _box = BoundingBox(_transform.Translation, _transform.Translation); for (int32 i = 0; i < _patches.Count(); i++) { @@ -48,6 +49,7 @@ void Terrain::UpdateBounds() void Terrain::CacheNeighbors() { + PROFILE_CPU(); for (int32 pathIndex = 0; pathIndex < _patches.Count(); pathIndex++) { const auto patch = _patches[pathIndex]; @@ -69,7 +71,7 @@ void Terrain::UpdateLayerBits() filterData.word0 = GetLayerMask(); // Own layer mask - filterData.word1 = PhysicsSettings::Instance()->LayerMasks[GetLayer()]; + filterData.word1 = Physics::LayerMasks[GetLayer()]; // Update the shapes layer bits for (int32 pathIndex = 0; pathIndex < _patches.Count(); pathIndex++) @@ -489,7 +491,7 @@ void Terrain::RemovePatch(const Int2& patchCoord) const auto patch = GetPatch(patchCoord); if (patch == nullptr) { - LOG(Warning, "Cannot remvoe patch at {0}x{1}. It does not exist.", patchCoord.X, patchCoord.Y); + LOG(Warning, "Cannot remove patch at {0}x{1}. It does not exist.", patchCoord.X, patchCoord.Y); return; } diff --git a/Source/Engine/Terrain/TerrainChunk.cpp b/Source/Engine/Terrain/TerrainChunk.cpp index 1241935e5..08e5946ee 100644 --- a/Source/Engine/Terrain/TerrainChunk.cpp +++ b/Source/Engine/Terrain/TerrainChunk.cpp @@ -21,7 +21,7 @@ void TerrainChunk::Init(TerrainPatch* patch, uint16 x, uint16 z) _yHeight = 1; _heightmapUVScaleBias = Vector4(1.0f, 1.0f, _x, _z) * (1.0f / TerrainPatch::CHUNKS_COUNT_EDGE); _perInstanceRandom = (_patch->_terrain->_id.C ^ _x ^ _z) * (1.0f / MAX_uint32); - OverrideMaterial.Unlink(); + OverrideMaterial = nullptr; } bool TerrainChunk::PrepareDraw(const RenderContext& renderContext) @@ -83,38 +83,34 @@ void TerrainChunk::Draw(const RenderContext& renderContext) const if (TerrainManager::GetChunkGeometry(drawCall, chunkSize, lod)) return; drawCall.InstanceCount = 1; - drawCall.IndirectArgsBuffer = nullptr; - drawCall.IndirectArgsOffset = 0; drawCall.Material = _cachedDrawMaterial; drawCall.World = _world; drawCall.ObjectPosition = drawCall.World.GetTranslation(); - drawCall.TerrainData.Patch = _patch; - drawCall.TerrainData.HeightmapUVScaleBias = _heightmapUVScaleBias; - drawCall.TerrainData.OffsetUV = Vector2((float)(_patch->_x * TerrainPatch::CHUNKS_COUNT_EDGE + _x), (float)(_patch->_z * TerrainPatch::CHUNKS_COUNT_EDGE + _z)); - drawCall.TerrainData.CurrentLOD = (float)lod; - drawCall.TerrainData.ChunkSizeNextLOD = (float)(((chunkSize + 1) >> (lod + 1)) - 1); - drawCall.TerrainData.TerrainChunkSizeLOD0 = TERRAIN_UNITS_PER_VERTEX * chunkSize; + drawCall.Terrain.Patch = _patch; + drawCall.Terrain.HeightmapUVScaleBias = _heightmapUVScaleBias; + drawCall.Terrain.OffsetUV = Vector2((float)(_patch->_x * TerrainPatch::CHUNKS_COUNT_EDGE + _x), (float)(_patch->_z * TerrainPatch::CHUNKS_COUNT_EDGE + _z)); + drawCall.Terrain.CurrentLOD = (float)lod; + drawCall.Terrain.ChunkSizeNextLOD = (float)(((chunkSize + 1) >> (lod + 1)) - 1); + drawCall.Terrain.TerrainChunkSizeLOD0 = TERRAIN_UNITS_PER_VERTEX * chunkSize; // TODO: try using SIMD clamping for 4 chunks at once - drawCall.TerrainData.NeighborLOD.X = (float)Math::Clamp(_neighbors[0]->_cachedDrawLOD, lod, minLod); - drawCall.TerrainData.NeighborLOD.Y = (float)Math::Clamp(_neighbors[1]->_cachedDrawLOD, lod, minLod); - drawCall.TerrainData.NeighborLOD.Z = (float)Math::Clamp(_neighbors[2]->_cachedDrawLOD, lod, minLod); - drawCall.TerrainData.NeighborLOD.W = (float)Math::Clamp(_neighbors[3]->_cachedDrawLOD, lod, minLod); + drawCall.Terrain.NeighborLOD.X = (float)Math::Clamp(_neighbors[0]->_cachedDrawLOD, lod, minLod); + drawCall.Terrain.NeighborLOD.Y = (float)Math::Clamp(_neighbors[1]->_cachedDrawLOD, lod, minLod); + drawCall.Terrain.NeighborLOD.Z = (float)Math::Clamp(_neighbors[2]->_cachedDrawLOD, lod, minLod); + drawCall.Terrain.NeighborLOD.W = (float)Math::Clamp(_neighbors[3]->_cachedDrawLOD, lod, minLod); const auto scene = _patch->_terrain->GetScene(); const auto flags = _patch->_terrain->_staticFlags; if (flags & StaticFlags::Lightmap && scene) { - drawCall.Lightmap = scene->LightmapsData.GetReadyLightmap(Lightmap.TextureIndex); - drawCall.LightmapUVsArea = Lightmap.UVsArea; + drawCall.Terrain.Lightmap = scene->LightmapsData.GetReadyLightmap(Lightmap.TextureIndex); + drawCall.Terrain.LightmapUVsArea = Lightmap.UVsArea; } else { - drawCall.Lightmap = nullptr; - drawCall.LightmapUVsArea = Rectangle::Empty; + drawCall.Terrain.Lightmap = nullptr; + drawCall.Terrain.LightmapUVsArea = Rectangle::Empty; } - drawCall.Skinning = nullptr; drawCall.WorldDeterminantSign = Math::FloatSelect(drawCall.World.RotDeterminant(), 1, -1); drawCall.PerInstanceRandom = _perInstanceRandom; - drawCall.LODDitherFactor = 0.0f; // Add half-texel offset for heightmap sampling in vertex shader //const float lodHeightmapSize = Math::Max(1, drawCall.TerrainData.Heightmap->Width() >> lod); @@ -142,37 +138,33 @@ void TerrainChunk::Draw(const RenderContext& renderContext, MaterialBase* materi if (TerrainManager::GetChunkGeometry(drawCall, chunkSize, lod)) return; drawCall.InstanceCount = 1; - drawCall.IndirectArgsBuffer = nullptr; - drawCall.IndirectArgsOffset = 0; drawCall.Material = material; drawCall.World = _world; drawCall.ObjectPosition = drawCall.World.GetTranslation(); - drawCall.TerrainData.Patch = _patch; - drawCall.TerrainData.HeightmapUVScaleBias = _heightmapUVScaleBias; - drawCall.TerrainData.OffsetUV = Vector2((float)(_patch->_x * TerrainPatch::CHUNKS_COUNT_EDGE + _x), (float)(_patch->_z * TerrainPatch::CHUNKS_COUNT_EDGE + _z)); - drawCall.TerrainData.CurrentLOD = (float)lod; - drawCall.TerrainData.ChunkSizeNextLOD = (float)(((chunkSize + 1) >> (lod + 1)) - 1); - drawCall.TerrainData.TerrainChunkSizeLOD0 = TERRAIN_UNITS_PER_VERTEX * chunkSize; - drawCall.TerrainData.NeighborLOD.X = (float)lod; - drawCall.TerrainData.NeighborLOD.Y = (float)lod; - drawCall.TerrainData.NeighborLOD.Z = (float)lod; - drawCall.TerrainData.NeighborLOD.W = (float)lod; + drawCall.Terrain.Patch = _patch; + drawCall.Terrain.HeightmapUVScaleBias = _heightmapUVScaleBias; + drawCall.Terrain.OffsetUV = Vector2((float)(_patch->_x * TerrainPatch::CHUNKS_COUNT_EDGE + _x), (float)(_patch->_z * TerrainPatch::CHUNKS_COUNT_EDGE + _z)); + drawCall.Terrain.CurrentLOD = (float)lod; + drawCall.Terrain.ChunkSizeNextLOD = (float)(((chunkSize + 1) >> (lod + 1)) - 1); + drawCall.Terrain.TerrainChunkSizeLOD0 = TERRAIN_UNITS_PER_VERTEX * chunkSize; + drawCall.Terrain.NeighborLOD.X = (float)lod; + drawCall.Terrain.NeighborLOD.Y = (float)lod; + drawCall.Terrain.NeighborLOD.Z = (float)lod; + drawCall.Terrain.NeighborLOD.W = (float)lod; const auto scene = _patch->_terrain->GetScene(); const auto flags = _patch->_terrain->_staticFlags; if (flags & StaticFlags::Lightmap && scene) { - drawCall.Lightmap = scene->LightmapsData.GetReadyLightmap(Lightmap.TextureIndex); - drawCall.LightmapUVsArea = Lightmap.UVsArea; + drawCall.Terrain.Lightmap = scene->LightmapsData.GetReadyLightmap(Lightmap.TextureIndex); + drawCall.Terrain.LightmapUVsArea = Lightmap.UVsArea; } else { - drawCall.Lightmap = nullptr; - drawCall.LightmapUVsArea = Rectangle::Empty; + drawCall.Terrain.Lightmap = nullptr; + drawCall.Terrain.LightmapUVsArea = Rectangle::Empty; } - drawCall.Skinning = nullptr; drawCall.WorldDeterminantSign = Math::FloatSelect(drawCall.World.RotDeterminant(), 1, -1); drawCall.PerInstanceRandom = _perInstanceRandom; - drawCall.LODDitherFactor = 0.0f; // Add half-texel offset for heightmap sampling in vertex shader //const float lodHeightmapSize = Math::Max(1, drawCall.TerrainData.Heightmap->Width() >> lod); @@ -192,7 +184,7 @@ bool TerrainChunk::Intersects(const Ray& ray, float& distance) void TerrainChunk::UpdateBounds() { const Vector3 boundsExtent = _patch->_terrain->_boundsExtent; - const float size = _patch->_terrain->_chunkSize * TERRAIN_UNITS_PER_VERTEX; + const float size = (float)_patch->_terrain->_chunkSize * TERRAIN_UNITS_PER_VERTEX; Transform terrainTransform = _patch->_terrain->_transform; Transform localTransform; diff --git a/Source/Engine/Terrain/TerrainManager.cpp b/Source/Engine/Terrain/TerrainManager.cpp index 0638acd45..caf620ff0 100644 --- a/Source/Engine/Terrain/TerrainManager.cpp +++ b/Source/Engine/Terrain/TerrainManager.cpp @@ -37,8 +37,8 @@ public: drawCall.Geometry.VertexBuffersOffsets[0] = 0; drawCall.Geometry.VertexBuffersOffsets[1] = 0; drawCall.Geometry.VertexBuffersOffsets[2] = 0; - drawCall.Geometry.StartIndex = 0; - drawCall.Geometry.IndicesCount = IndicesCount; + drawCall.Draw.StartIndex = 0; + drawCall.Draw.IndicesCount = IndicesCount; } }; diff --git a/Source/Engine/Terrain/TerrainPatch.cpp b/Source/Engine/Terrain/TerrainPatch.cpp index d0050d455..133b44c6a 100644 --- a/Source/Engine/Terrain/TerrainPatch.cpp +++ b/Source/Engine/Terrain/TerrainPatch.cpp @@ -5,7 +5,6 @@ #include "Engine/Serialization/Serialization.h" #include "Engine/Graphics/RenderView.h" #include "Engine/Core/Math/Color32.h" -#include "Engine/Core/Math/VectorInt.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Physics/Utilities.h" #include "Engine/Physics/Physics.h" @@ -65,12 +64,12 @@ void TerrainPatch::Init(Terrain* terrain, int16 x, int16 z) { Chunks[i].Init(this, i % CHUNKS_COUNT_EDGE, i / CHUNKS_COUNT_EDGE); } - Heightmap.Unlink(); + Heightmap = nullptr; for (int32 i = 0; i < TERRAIN_MAX_SPLATMAPS_COUNT; i++) { - Splatmap[i].Unlink(); + Splatmap[i] = nullptr; } - _heightfield.Unlink(); + _heightfield = nullptr; #if TERRAIN_UPDATING _cachedHeightMap.Resize(0); _cachedHolesMask.Resize(0); @@ -952,7 +951,7 @@ bool TerrainPatch::SetupHeightMap(int32 heightMapLength, const float* heightMap, chunk._yHeight = chunkHeights[chunkIndex]; chunk.UpdateTransform(); } - UpdateBounds(); + _terrain->UpdateBounds(); UpdateCollision(); #if TERRAIN_UPDATING @@ -1432,7 +1431,7 @@ bool TerrainPatch::ModifyHeightMap(const float* samples, const Int2& modifiedOff chunk._yHeight = chunkHeights[chunkIndex]; chunk.UpdateTransform(); } - UpdateBounds(); + _terrain->UpdateBounds(); return UpdateHeightData(info, modifiedOffset, modifiedSize, wasHeightRangeChanged); } @@ -2109,9 +2108,8 @@ void TerrainPatch::UpdatePostManualDeserialization() { auto& chunk = Chunks[chunkIndex]; chunk.UpdateTransform(); - chunk.UpdateBounds(); } - UpdateBounds(); + _terrain->UpdateBounds(); ScopeLock lock(_collisionLocker); diff --git a/Source/Engine/Terrain/TerrainPatch.h b/Source/Engine/Terrain/TerrainPatch.h index fcd97f4ec..b09848d61 100644 --- a/Source/Engine/Terrain/TerrainPatch.h +++ b/Source/Engine/Terrain/TerrainPatch.h @@ -5,7 +5,7 @@ #include "Terrain.h" #include "TerrainChunk.h" #include "Engine/Core/Math/Color32.h" -#include "Engine/Core/Math/VectorInt.h" +#include "Engine/Core/Math/Int2.h" #include "Engine/Physics/Types.h" #include "Engine/Level/Scene/Lightmap.h" #include "Engine/Content/Assets/RawDataAsset.h" diff --git a/Source/Engine/Threading/ConcurrentBuffer.h b/Source/Engine/Threading/ConcurrentBuffer.h index 99159e206..99ebe5e86 100644 --- a/Source/Engine/Threading/ConcurrentBuffer.h +++ b/Source/Engine/Threading/ConcurrentBuffer.h @@ -382,7 +382,7 @@ public: /// Searches for the specified object and returns the zero-based index of the first occurrence within the entire collection. /// /// The item. - /// The zero-based index of the first occurrence of itm within the entire collection, if found; otherwise, INVALID_INDEX. + /// The zero-based index of the first occurrence of item within the entire collection, if found; otherwise, INVALID_INDEX. int32 IndexOf(const T& item) const { for (int32 i = 0; i < _count; i++) diff --git a/Source/Engine/Threading/IRunnable.h b/Source/Engine/Threading/IRunnable.h index 7dc42f73a..83a2af305 100644 --- a/Source/Engine/Threading/IRunnable.h +++ b/Source/Engine/Threading/IRunnable.h @@ -41,7 +41,7 @@ public: } // Called when thread ends work (via Kill or normally) - // @param wasKilled True if thead has been killed + // @param wasKilled True if thread has been killed virtual void AfterWork(bool wasKilled) { } diff --git a/Source/Engine/Threading/ThreadPool.cpp b/Source/Engine/Threading/ThreadPool.cpp index 56d6f6289..ffd80d655 100644 --- a/Source/Engine/Threading/ThreadPool.cpp +++ b/Source/Engine/Threading/ThreadPool.cpp @@ -52,7 +52,7 @@ bool ThreadPoolService::Init() // Create tread auto runnable = New(true); runnable->OnWork.Bind(ThreadPool::ThreadProc); - auto thread = Thread::Create(runnable, String::Format(TEXT("Therad Pool {0}"), i)); + auto thread = Thread::Create(runnable, String::Format(TEXT("Thread Pool {0}"), i)); if (thread == nullptr) { LOG(Error, "Failed to spawn {0} thread in the Thread Pool", i + 1); diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp index 998c3f3a3..c1f250824 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp @@ -274,8 +274,10 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) // Compute depth difference auto depthDiff = writeLocal(VariantType::Float, String::Format(TEXT("{0} * ViewFar - {1}"), sceneDepth.Value, posVS.Value), node); + auto fadeDistance = tryGetValue(node->GetBox(0), node->Values[0]).AsFloat(); + // Apply smoothing factor and clamp the result - value = writeLocal(VariantType::Float, String::Format(TEXT("saturate({0} / {1})"), depthDiff.Value, node->Values[0].AsFloat), node); + value = writeLocal(VariantType::Float, String::Format(TEXT("saturate({0} / {1})"), depthDiff.Value, fadeDistance.Value), node); break; } // Material Function @@ -337,6 +339,125 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) case 25: value = Value(VariantType::Vector3, TEXT("GetObjectSize(input)")); break; + // Blend Normals + case 26: + { + const auto baseNormal = tryGetValue(node->GetBox(0), getNormalZero).AsVector3(); + const auto additionalNormal = tryGetValue(node->GetBox(1), getNormalZero).AsVector3(); + + const String text1 = String::Format(TEXT("(float2({0}.xy) + float2({1}.xy) * 2.0)"), baseNormal.Value, additionalNormal.Value); + const auto appendXY = writeLocal(ValueType::Vector2, text1, node); + + const String text2 = String::Format(TEXT("float3({0}, sqrt(saturate(1.0 - dot({0}.xy, {0}.xy))))"), appendXY.Value); + value = writeLocal(ValueType::Vector3, text2, node); + break; + } + // Rotator + case 27: + { + const auto uv = tryGetValue(node->GetBox(0), getUVs).AsVector2(); + const auto center = tryGetValue(node->GetBox(1), Value::Zero).AsVector2(); + const auto rotationAngle = tryGetValue(node->GetBox(2), Value::Zero).AsFloat(); + + auto x1 = writeLocal(ValueType::Vector2, String::Format(TEXT("({0} * -1) + {1}"), center.Value, uv.Value), node); + auto raCosSin = writeLocal(ValueType::Vector2, String::Format(TEXT("float2(cos({0}), sin({0}))"), rotationAngle.Value), node); + + auto dotB1 = writeLocal(ValueType::Vector2, String::Format(TEXT("float2({0}.x, {0}.y * -1)"), raCosSin.Value), node); + auto dotB2 = writeLocal(ValueType::Vector2, String::Format(TEXT("float2({0}.y, {0}.x)"), raCosSin.Value), node); + + value = writeLocal(ValueType::Vector2, String::Format(TEXT("{3} + float2(dot({0},{1}), dot({0},{2}))"), x1.Value, dotB1.Value, dotB2.Value, center.Value), node); + break; + } + // Sphere Mask + case 28: + { + const auto a = tryGetValue(node->GetBox(0), 0, Value::Zero); + const auto b = tryGetValue(node->GetBox(1), 1, Value::Zero).Cast(a.Type); + const auto radius = tryGetValue(node->GetBox(2), node->Values[0]).AsFloat(); + const auto hardness = tryGetValue(node->GetBox(3), node->Values[1]).AsFloat(); + const auto invert = tryGetValue(node->GetBox(4), node->Values[2]).AsBool(); + + // Get distance and apply radius + auto x1 = writeLocal(ValueType::Float, String::Format(TEXT("distance({0},{1}) * (1 / {2})"), a.Value, b.Value, radius.Value), node); + + // Apply hardness, use 0.991 as max since any value above will result in harsh aliasing + auto x2 = writeLocal(ValueType::Float, String::Format(TEXT("saturate((1 - {0}) * (1 / (1 - clamp({1}, 0, 0.991f))))"), x1.Value, hardness.Value), node); + + value = writeLocal(ValueType::Float, String::Format(TEXT("{0} ? (1 - {1}) : {1}"), invert.Value, x2.Value), node); + break; + } + // Tiling & Offset + case 29: + { + const auto uv = tryGetValue(node->GetBox(0), getUVs).AsVector2(); + const auto tiling = tryGetValue(node->GetBox(1), node->Values[0]).AsVector2(); + const auto offset = tryGetValue(node->GetBox(2), node->Values[1]).AsVector2(); + + value = writeLocal(ValueType::Vector2, String::Format(TEXT("{0} * {1} + {2}"), uv.Value, tiling.Value, offset.Value), node); + break; + } + // DDX + case 30: + { + const auto inValue = tryGetValue(node->GetBox(0), 0, Value::Zero); + + value = writeLocal(inValue.Type, String::Format(TEXT("ddx({0})"), inValue.Value), node); + break; + } + // DDY + case 31: + { + const auto inValue = tryGetValue(node->GetBox(0), 0, Value::Zero); + + value = writeLocal(inValue.Type, String::Format(TEXT("ddy({0})"), inValue.Value), node); + break; + } + // Sign + case 32: + { + const auto inValue = tryGetValue(node->GetBox(0), 0, Value::Zero); + + value = writeLocal(ValueType::Float, String::Format(TEXT("sign({0})"), inValue.Value), node); + break; + } + // Any + case 33: + { + const auto inValue = tryGetValue(node->GetBox(0), 0, Value::Zero); + + value = writeLocal(ValueType::Bool, String::Format(TEXT("any({0})"), inValue.Value), node); + break; + } + // All + case 34: + { + const auto inValue = tryGetValue(node->GetBox(0), 0, Value::Zero); + + value = writeLocal(ValueType::Bool, String::Format(TEXT("all({0})"), inValue.Value), node); + break; + } + // Blackbody + case 35: + { + // Reference: Mitchell Charity, http://www.vendian.org/mncharity/dir3/blackbody/ + + const auto temperature = tryGetValue(node->GetBox(0), node->Values[0]).AsFloat(); + + // Value X + auto x = writeLocal(ValueType::Float, String::Format(TEXT("56100000.0f * pow({0}, -1) + 148.0f"), temperature.Value), node); + + // Value Y + auto y = writeLocal(ValueType::Float, String::Format(TEXT("{0} > 6500.0f ? 35200000.0f * pow({0}, -1) + 184.0f : 100.04f * log({0}) - 623.6f"), temperature.Value), node); + + // Value Z + auto z = writeLocal(ValueType::Float, String::Format(TEXT("194.18f * log({0}) - 1448.6f"), temperature.Value), node); + + // Final color + auto color = writeLocal(ValueType::Vector3, String::Format(TEXT("float3({0}, {1}, {2})"), x.Value, y.Value, z.Value), node); + color = writeLocal(ValueType::Vector3, String::Format(TEXT("clamp({0}, 0.0f, 255.0f) / 255.0f"), color.Value), node); + value = writeLocal(ValueType::Vector3, String::Format(TEXT("{1} < 1000.0f ? {0} * {1}/1000.0f : {0}"), color.Value, temperature.Value), node); + break; + } default: break; } diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Texture.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Texture.cpp index 90103c165..32af18364 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Texture.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Texture.cpp @@ -163,27 +163,4 @@ void MaterialGenerator::linearizeSceneDepth(Node* caller, const Value& depth, Va value = writeLocal(VariantType::Float, String::Format(TEXT("ViewInfo.w / ({0}.x - ViewInfo.z)"), depth.Value), caller); } -byte MaterialGenerator::getStartSrvRegister(MaterialLayer* baseLayer) -{ - // Note: this must match material templates - switch (baseLayer->Domain) - { - case MaterialDomain::Surface: - return baseLayer->BlendMode == MaterialBlendMode::Transparent ? 3 : 3; - case MaterialDomain::PostProcess: - return 0; - case MaterialDomain::Decal: - return 1; - case MaterialDomain::GUI: - return 0; - case MaterialDomain::Terrain: - return 6; - case MaterialDomain::Particle: - return 5; - default: - CRASH; - return 0; - } -} - #endif diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp index 7abd29af9..23779e3e1 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp @@ -4,7 +4,9 @@ #include "MaterialGenerator.h" #include "Engine/Visject/ShaderGraphUtilities.h" +#include "Engine/Platform/File.h" #include "Engine/Graphics/Materials/MaterialShader.h" +#include "Engine/Graphics/Materials/MaterialShaderFeatures.h" /// /// Material shader source code template has special marks for generated code. @@ -20,13 +22,90 @@ enum MaterialTemplateInputsMapping In_GetMaterialVS = 5, In_GetMaterialDS = 6, In_Includes = 7, + In_Utilities = 8, + In_Shaders = 9, In_MAX }; +/// +/// Material shader feature source code template has special marks for generated code. Each starts with '@' char and index of the mapped string. +/// +enum class FeatureTemplateInputsMapping +{ + Defines = 0, + Includes = 1, + Constants = 2, + Resources = 3, + Utilities = 4, + Shaders = 5, + MAX +}; + +struct FeatureData +{ + MaterialShaderFeature::GeneratorData Data; + String Inputs[(int32)FeatureTemplateInputsMapping::MAX]; + + bool Init(); +}; + +namespace +{ + // Loaded and parsed features data cache + Dictionary Features; +} + +bool FeatureData::Init() +{ + // Load template file + const String path = Globals::EngineContentFolder / TEXT("Editor/MaterialTemplates/") + Data.Template; + String contents; + if (File::ReadAllText(path, contents)) + { + LOG(Error, "Cannot open file {0}", path); + return true; + } + + int32 i = 0; + const int32 length = contents.Length(); + + // Skip until input start + for (; i < length; i++) + { + if (contents[i] == '@') + break; + } + + // Load all inputs + do + { + // Parse input type + i++; + const int32 inIndex = contents[i++] - '0'; + ASSERT_LOW_LAYER(Math::IsInRange(inIndex, 0, (int32)FeatureTemplateInputsMapping::MAX - 1)); + + // Read until next input start + const Char* start = &contents[i]; + for (; i < length; i++) + { + const auto c = contents[i]; + if (c == '@') + break; + } + const Char* end = &contents[i]; + + // Set input + Inputs[inIndex].Set(start, (int32)(end - start)); + } while (i < length); + + return false; +} + MaterialValue MaterialGenerator::getUVs(VariantType::Vector2, TEXT("input.TexCoord")); MaterialValue MaterialGenerator::getTime(VariantType::Float, TEXT("TimeParam")); MaterialValue MaterialGenerator::getNormal(VariantType::Vector3, TEXT("input.TBN[2]")); +MaterialValue MaterialGenerator::getNormalZero(VariantType::Vector3, TEXT("float3(0, 0, 1)")); MaterialValue MaterialGenerator::getVertexColor(VariantType::Vector4, TEXT("GetVertexColor(input)")); MaterialGenerator::MaterialGenerator() @@ -53,6 +132,7 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo ASSERT_LOW_LAYER(_layers.Count() > 0); String inputs[In_MAX]; + Array> features; // Setup and prepare layers _writer.Clear(); @@ -87,6 +167,59 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo const MaterialGraphBox* layerInputBox = baseLayer->Root->GetBox(0); const bool isLayered = layerInputBox->HasConnection(); + // Initialize features +#define ADD_FEATURE(type) \ + { \ + StringAnsiView typeName(#type, ARRAY_COUNT(#type) - 1); \ + features.Add(typeName); \ + if (!Features.ContainsKey(typeName)) \ + { \ + auto& feature = Features[typeName]; \ + type::Generate(feature.Data); \ + if (feature.Init()) \ + return true; \ + } \ + } + switch (baseLayer->Domain) + { + case MaterialDomain::Surface: + if (materialInfo.TessellationMode != TessellationMethod::None) + ADD_FEATURE(TessellationFeature); + if (materialInfo.BlendMode == MaterialBlendMode::Opaque) + ADD_FEATURE(MotionVectorsFeature); + if (materialInfo.BlendMode == MaterialBlendMode::Opaque) + ADD_FEATURE(LightmapFeature); + if (materialInfo.BlendMode == MaterialBlendMode::Opaque) + ADD_FEATURE(DeferredShadingFeature); + if (materialInfo.BlendMode != MaterialBlendMode::Opaque && (materialInfo.FeaturesFlags & MaterialFeaturesFlags::DisableDistortion) == 0) + ADD_FEATURE(DistortionFeature); + if (materialInfo.BlendMode != MaterialBlendMode::Opaque) + ADD_FEATURE(ForwardShadingFeature); + break; + case MaterialDomain::Terrain: + if (materialInfo.TessellationMode != TessellationMethod::None) + ADD_FEATURE(TessellationFeature); + ADD_FEATURE(LightmapFeature); + ADD_FEATURE(DeferredShadingFeature); + break; + case MaterialDomain::Particle: + if (materialInfo.BlendMode != MaterialBlendMode::Opaque && (materialInfo.FeaturesFlags & MaterialFeaturesFlags::DisableDistortion) == 0) + ADD_FEATURE(DistortionFeature); + ADD_FEATURE(ForwardShadingFeature); + break; + case MaterialDomain::Deformable: + if (materialInfo.TessellationMode != TessellationMethod::None) + ADD_FEATURE(TessellationFeature); + if (materialInfo.BlendMode == MaterialBlendMode::Opaque) + ADD_FEATURE(DeferredShadingFeature); + if (materialInfo.BlendMode != MaterialBlendMode::Opaque) + ADD_FEATURE(ForwardShadingFeature); + break; + default: + break; + } +#undef ADD_FEATURE + // Check if material is using special features and update the metadata flags if (!isLayered) { @@ -104,7 +237,7 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo { materialVarPS = Value(VariantType::Void, baseLayer->GetVariableName(nullptr)); _writer.Write(TEXT("\tMaterial {0} = (Material)0;\n"), materialVarPS.Value); - if (baseLayer->Domain == MaterialDomain::Surface || baseLayer->Domain == MaterialDomain::Terrain || baseLayer->Domain == MaterialDomain::Particle) + if (baseLayer->Domain == MaterialDomain::Surface || baseLayer->Domain == MaterialDomain::Terrain || baseLayer->Domain == MaterialDomain::Particle || baseLayer->Domain == MaterialDomain::Deformable) { eatMaterialGraphBox(baseLayer, MaterialGraphBoxes::Emissive); eatMaterialGraphBox(baseLayer, MaterialGraphBoxes::Normal); @@ -240,11 +373,13 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo // Update material usage based on material generator outputs materialInfo.UsageFlags = baseLayer->UsageFlags; +#define WRITE_FEATURES(input) for (auto f : features) _writer.Write(Features[f].Inputs[(int32)FeatureTemplateInputsMapping::input]); // Defines { _writer.Write(TEXT("#define MATERIAL_MASK_THRESHOLD ({0})\n"), baseLayer->MaskThreshold); _writer.Write(TEXT("#define CUSTOM_VERTEX_INTERPOLATORS_COUNT ({0})\n"), _vsToPsInterpolants.Count()); - _writer.Write(TEXT("#define MATERIAL_OPACITY_THRESHOLD ({0})"), baseLayer->OpacityThreshold); + _writer.Write(TEXT("#define MATERIAL_OPACITY_THRESHOLD ({0})\n"), baseLayer->OpacityThreshold); + WRITE_FEATURES(Defines); inputs[In_Defines] = _writer.ToString(); _writer.Clear(); } @@ -252,31 +387,89 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo // Includes { for (auto& include : _includes) - { _writer.Write(TEXT("#include \"{0}\"\n"), include.Item); - } + WRITE_FEATURES(Includes); inputs[In_Includes] = _writer.ToString(); _writer.Clear(); } - // Check if material is using any parameters - if (_parameters.HasItems()) + // Constants { - ShaderGraphUtilities::GenerateShaderConstantBuffer(_writer, _parameters); + WRITE_FEATURES(Constants); + if (_parameters.HasItems()) + ShaderGraphUtilities::GenerateShaderConstantBuffer(_writer, _parameters); inputs[In_Constants] = _writer.ToString(); _writer.Clear(); + } - const int32 startRegister = getStartSrvRegister(baseLayer); - const auto error = ShaderGraphUtilities::GenerateShaderResources(_writer, _parameters, startRegister); - if (error) + // Resources + { + int32 srv = 0; + switch (baseLayer->Domain) { - OnError(nullptr, nullptr, error); - return true; + case MaterialDomain::Surface: + srv = 2; // Skinning Bones + Prev Bones + break; + case MaterialDomain::Decal: + srv = 1; // Depth buffer + break; + case MaterialDomain::Terrain: + srv = 3; // Heightmap + 2 splatmaps + break; + case MaterialDomain::Particle: + srv = 2; // Particles data + Sorted indices/Ribbon segments + break; + case MaterialDomain::Deformable: + srv = 1; // Mesh deformation buffer + break; + } + for (auto f : features) + { + const auto& text = Features[f].Inputs[(int32)FeatureTemplateInputsMapping::Resources]; + const Char* str = text.Get(); + int32 prevIdx = 0, idx = 0; + while (true) + { + idx = text.Find(TEXT("__SRV__"), StringSearchCase::CaseSensitive, prevIdx); + if (idx == -1) + break; + int32 len = idx - prevIdx; + _writer.Write(StringView(str, len)); + str += len; + _writer.Write(StringUtils::ToString(srv)); + srv++; + str += ARRAY_COUNT("__SRV__") - 1; + prevIdx = idx + ARRAY_COUNT("__SRV__") - 1; + } + _writer.Write(StringView(str)); + } + if (_parameters.HasItems()) + { + const auto error = ShaderGraphUtilities::GenerateShaderResources(_writer, _parameters, srv); + if (error) + { + OnError(nullptr, nullptr, error); + return true; + } } inputs[In_ShaderResources] = _writer.ToString(); _writer.Clear(); } + // Utilities + { + WRITE_FEATURES(Utilities); + inputs[In_Utilities] = _writer.ToString(); + _writer.Clear(); + } + + // Shaders + { + WRITE_FEATURES(Shaders); + inputs[In_Shaders] = _writer.ToString(); + _writer.Clear(); + } + // Save material parameters data if (_parameters.HasItems()) MaterialParams::Save(parametersData, &_parameters); @@ -291,10 +484,7 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo switch (materialInfo.Domain) { case MaterialDomain::Surface: - if (materialInfo.BlendMode == MaterialBlendMode::Opaque) - path /= TEXT("SurfaceDeferred.shader"); - else - path /= TEXT("SurfaceForward.shader"); + path /= TEXT("Surface.shader"); break; case MaterialDomain::PostProcess: path /= TEXT("PostProcess.shader"); @@ -311,20 +501,22 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo case MaterialDomain::Particle: path /= TEXT("Particle.shader"); break; + case MaterialDomain::Deformable: + path /= TEXT("Deformable.shader"); + break; default: LOG(Warning, "Unknown material domain."); return true; } - auto file = FileReadStream::Open(path); if (file == nullptr) { - LOG(Warning, "Cannot load material base source code."); + LOG(Error, "Cannot open file {0}", path); return true; } // Format template - uint32 length = file->GetLength(); + const uint32 length = file->GetLength(); Array tmp; for (uint32 i = 0; i < length; i++) { diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.h b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.h index 005b9c58c..bee1efca4 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.h +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.h @@ -205,12 +205,11 @@ public: static MaterialValue getUVs; static MaterialValue getTime; static MaterialValue getNormal; + static MaterialValue getNormalZero; static MaterialValue getVertexColor; static MaterialGraphBoxesMapping MaterialGraphBoxesMappings[]; static const MaterialGraphBoxesMapping& GetMaterialRootNodeBox(MaterialGraphBoxes box); - - static byte getStartSrvRegister(MaterialLayer* baseLayer); }; #endif diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialLayer.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialLayer.cpp index c7f940574..41679a6a3 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialLayer.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialLayer.cpp @@ -64,7 +64,7 @@ void MaterialLayer::Prepare() Guid MaterialLayer::GetMappedParamId(const Guid& id) { - // TODO: test ParamIdsMappings using Dictionary. will performance change? mamybe we don't wont to allocate too much memory + // TODO: test ParamIdsMappings using Dictionary. will performance change? maybe we don't wont to allocate too much memory for (int32 i = 0; i < ParamIdsMappings.Count(); i++) { diff --git a/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp b/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp index fef8d7f3c..50914ea2e 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp @@ -355,7 +355,7 @@ bool ProcessMesh(AssimpImporterData& data, const aiMesh* aMesh, MeshData& mesh, mesh.BlendIndices.SetAll(Int4::Zero); mesh.BlendWeights.SetAll(Vector4::Zero); - // Build skinning clusters and fill controls points data stutcture + // Build skinning clusters and fill controls points data structure for (unsigned boneId = 0; boneId < aMesh->mNumBones; boneId++) { const auto aBone = aMesh->mBones[boneId]; diff --git a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp index 4c2876125..757dfa6ea 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp @@ -744,7 +744,7 @@ bool ProcessMesh(OpenFbxImporterData& data, const ofbx::Mesh* aMesh, MeshData& m auto length = delta.Length(); if (length > ZeroTolerance) delta /= length;*/ - auto delta = Vector3::Zero; // TODO: blend shape normals deltas fix when importing from ofbx + auto delta = Vector3::Zero; // TODO: blend shape normals deltas fix when importing from fbx blendShapeData.Vertices[i].NormalDelta = delta; } } diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index 4e4fb98dd..d452f5f78 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -15,7 +15,8 @@ #include "Engine/Tools/TextureTool/TextureTool.h" #include "Engine/ContentImporters/AssetsImportingManager.h" #include "Engine/ContentImporters/CreateMaterial.h" -#include "ThirdParty/meshoptimizer/meshoptimizer.h" +#include "Editor/Utilities/EditorUtilities.h" +#include void RemoveNamespace(String& name) { @@ -486,6 +487,11 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options opt if (autoImportOutput.IsEmpty() || (data.Types & ImportDataTypes::Textures) == 0 || texture.FilePath.IsEmpty()) continue; auto filename = StringUtils::GetFileNameWithoutExtension(texture.FilePath); + for (int32 j = filename.Length() - 1; j >= 0; j--) + { + if (EditorUtilities::IsInvalidPathChar(filename[j])) + filename[j] = ' '; + } if (importedFileNames.Contains(filename)) { int32 counter = 1; @@ -526,6 +532,11 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options opt if (autoImportOutput.IsEmpty() || (data.Types & ImportDataTypes::Materials) == 0 || !material.UsesProperties()) continue; auto filename = material.Name; + for (int32 j = filename.Length() - 1; j >= 0; j--) + { + if (EditorUtilities::IsInvalidPathChar(filename[j])) + filename[j] = ' '; + } if (importedFileNames.Contains(filename)) { int32 counter = 1; @@ -1131,7 +1142,7 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options opt dstLod.Meshes.RemoveAt(i--); } - LOG(Info, "Generated LOD{0}: triangles: {1} ({2}% of base LOD), verteces: {3} ({4}% of base LOD)", + LOG(Info, "Generated LOD{0}: triangles: {1} ({2}% of base LOD), verticies: {3} ({4}% of base LOD)", lodIndex, lodTriangleCount, (int32)(lodTriangleCount * 100 / baseLodTriangleCount), lodVertexCount, (int32)(lodVertexCount * 100 / baseLodVertexCount)); diff --git a/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp b/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp index dbf51e965..0c423fed1 100644 --- a/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp +++ b/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp @@ -190,6 +190,16 @@ bool TextureTool::ExportTextureDirectXTex(ImageType type, const StringView& path } img = tmp.GetImage(0, 0, 0); } + else if (image.format == DXGI_FORMAT_R10G10B10A2_UNORM || image.format == DXGI_FORMAT_R11G11B10_FLOAT) + { + result = DirectX::Convert(image, DXGI_FORMAT_R8G8B8A8_UNORM, DirectX::TEX_FILTER_DEFAULT, DirectX::TEX_THRESHOLD_DEFAULT, tmp); + if (FAILED(result)) + { + LOG(Error, "Cannot convert texture, error: {0:x}", static_cast(result)); + return true; + } + img = tmp.GetImage(0, 0, 0); + } DirectX::WICCodecs codec; switch (type) diff --git a/Source/Engine/Tools/TextureTool/TextureTool.cpp b/Source/Engine/Tools/TextureTool/TextureTool.cpp index a8af48764..67e4d7679 100644 --- a/Source/Engine/Tools/TextureTool/TextureTool.cpp +++ b/Source/Engine/Tools/TextureTool/TextureTool.cpp @@ -7,7 +7,7 @@ #include "Engine/Core/Types/DateTime.h" #include "Engine/Core/Math/Packed.h" #include "Engine/Core/Math/Color32.h" -#include "Engine/Core/Math/VectorInt.h" +#include "Engine/Core/Math/Int2.h" #include "Engine/Platform/FileSystem.h" #include "Engine/Serialization/JsonWriter.h" #include "Engine/Serialization/JsonTools.h" @@ -316,7 +316,7 @@ bool TextureTool::Convert(TextureData& dst, const TextureData& src, const PixelF } if (src.Format == dstFormat) { - LOG(Warning, "Soure data and destination format are the same. Cannot perform conversion."); + LOG(Warning, "Source data and destination format are the same. Cannot perform conversion."); return true; } if (src.Depth != 1) @@ -343,7 +343,7 @@ bool TextureTool::Resize(TextureData& dst, const TextureData& src, int32 dstWidt } if (src.Width == dstWidth && src.Height == dstHeight) { - LOG(Warning, "Soure data and destination dimensions are the same. Cannot perform resizing."); + LOG(Warning, "Source data and destination dimensions are the same. Cannot perform resizing."); return true; } if (src.Depth != 1) @@ -489,11 +489,11 @@ TextureTool::PixelFormatSampler PixelFormatSamplers[] = sizeof(Half), [](const void* ptr) { - return Color(ConvertHalfToFloat(*(Half*)ptr), 0, 0, 1); + return Color(Float16Compressor::Decompress(*(Half*)ptr), 0, 0, 1); }, [](const void* ptr, const Color& color) { - *(Half*)ptr = ConvertFloatToHalf(color.R); + *(Half*)ptr = Float16Compressor::Compress(color.R); }, }, { diff --git a/Source/Engine/UI/GUI/Common/Dropdown.cs b/Source/Engine/UI/GUI/Common/Dropdown.cs index e78300e1a..917e51f6e 100644 --- a/Source/Engine/UI/GUI/Common/Dropdown.cs +++ b/Source/Engine/UI/GUI/Common/Dropdown.cs @@ -26,7 +26,7 @@ namespace FlaxEngine.GUI public Action ItemClicked; /// - /// Occurs when popup losts focus. + /// Occurs when popup lost focus. /// public Action LostFocus; diff --git a/Source/Engine/UI/GUI/Common/TextBoxBase.cs b/Source/Engine/UI/GUI/Common/TextBoxBase.cs index 107da6f40..ccf346ed6 100644 --- a/Source/Engine/UI/GUI/Common/TextBoxBase.cs +++ b/Source/Engine/UI/GUI/Common/TextBoxBase.cs @@ -942,6 +942,8 @@ namespace FlaxEngine.GUI { base.OnLostFocus(); + if (IsReadOnly) + return; OnEditEnd(); } diff --git a/Source/Engine/UI/GUI/ContainerControl.cs b/Source/Engine/UI/GUI/ContainerControl.cs index 44f8c782a..fb11f2eff 100644 --- a/Source/Engine/UI/GUI/ContainerControl.cs +++ b/Source/Engine/UI/GUI/ContainerControl.cs @@ -469,7 +469,7 @@ namespace FlaxEngine.GUI } /// - /// Checks if given point in thi container control space intersects with the child control content. + /// Checks if given point in this container control space intersects with the child control content. /// Also calculates result location in child control space which can be used to feed control with event at that point. /// /// The child control to check. @@ -636,7 +636,7 @@ namespace FlaxEngine.GUI } /// - /// Draws the children. Can be overriden to provide some customizations. Draw is performed with applied clipping mask for the client area. + /// Draws the children. Can be overridden to provide some customizations. Draw is performed with applied clipping mask for the client area. /// protected virtual void DrawChildren() { diff --git a/Source/Engine/UI/GUI/Control.cs b/Source/Engine/UI/GUI/Control.cs index 74041610c..c2ba34f05 100644 --- a/Source/Engine/UI/GUI/Control.cs +++ b/Source/Engine/UI/GUI/Control.cs @@ -1006,10 +1006,9 @@ namespace FlaxEngine.GUI c = c.Parent; if (c == parent) - return location; + break; } - - throw new ArgumentException(); + return location; } /// @@ -1211,7 +1210,7 @@ namespace FlaxEngine.GUI } /// - /// Action fred when parent control gets changed. + /// Action fired when parent control gets changed. /// protected virtual void OnParentChangedInternal() { @@ -1252,9 +1251,9 @@ namespace FlaxEngine.GUI } /// - /// Helper utility function to sets the update callback to the root. Does nothing if value has not been modified. Handles if control ahs no root or parent. + /// Helper utility function to sets the update callback to the root. Does nothing if value has not been modified. Handles if control has no root or parent. /// - /// The cached update callback delegate (field in teh custom control implementation). + /// The cached update callback delegate (field in the custom control implementation). /// The value to assign. protected void SetUpdate(ref UpdateDelegate onUpdate, UpdateDelegate value) { @@ -1268,7 +1267,7 @@ namespace FlaxEngine.GUI } /// - /// Action fred when parent control gets resized (also when control gets non-null parent). + /// Action fired when parent control gets resized (also when control gets non-null parent). /// public virtual void OnParentResized() { diff --git a/Source/Engine/UI/GUI/Panels/DropPanel.cs b/Source/Engine/UI/GUI/Panels/DropPanel.cs index fa5ff7f59..04493c7ef 100644 --- a/Source/Engine/UI/GUI/Panels/DropPanel.cs +++ b/Source/Engine/UI/GUI/Panels/DropPanel.cs @@ -31,9 +31,14 @@ namespace FlaxEngine.GUI protected bool _mouseOverHeader; /// - /// The 'mouse down' flag (over header). + /// The 'mouse down' flag (over header) for the left mouse button. /// - protected bool _mouseDown; + protected bool _mouseButtonLeftDown; + + /// + /// The 'mouse down' flag (over header) for the right mouse button. + /// + protected bool _mouseButtonRightDown; /// /// The animation progress (normalized). @@ -126,6 +131,11 @@ namespace FlaxEngine.GUI [EditorDisplay("Style"), EditorOrder(2000)] public bool EnableDropDownIcon { get; set; } + /// + /// Occurs when mouse right-clicks over the header. + /// + public event Action MouseButtonRightClicked; + /// /// Occurs when drop panel is opened or closed. /// @@ -430,10 +440,14 @@ namespace FlaxEngine.GUI return true; _mouseOverHeader = HeaderRectangle.Contains(location); - if (button == MouseButton.Left && _mouseOverHeader) { - _mouseDown = true; + _mouseButtonLeftDown = true; + return true; + } + if (button == MouseButton.Right && _mouseOverHeader) + { + _mouseButtonRightDown = true; return true; } @@ -455,16 +469,17 @@ namespace FlaxEngine.GUI return true; _mouseOverHeader = HeaderRectangle.Contains(location); - - if (button == MouseButton.Left && _mouseDown) + if (button == MouseButton.Left && _mouseButtonLeftDown) { - _mouseDown = false; - + _mouseButtonLeftDown = false; if (_mouseOverHeader) - { Toggle(); - } - + return true; + } + if (button == MouseButton.Right && _mouseButtonRightDown) + { + _mouseButtonRightDown = false; + MouseButtonRightClicked?.Invoke(this, location); return true; } @@ -474,7 +489,8 @@ namespace FlaxEngine.GUI /// public override void OnMouseLeave() { - _mouseDown = false; + _mouseButtonLeftDown = false; + _mouseButtonRightDown = false; _mouseOverHeader = false; base.OnMouseLeave(); diff --git a/Source/Engine/UI/GUI/Panels/HorizontalPanel.cs b/Source/Engine/UI/GUI/Panels/HorizontalPanel.cs index 8f8e7f994..d7b54a835 100644 --- a/Source/Engine/UI/GUI/Panels/HorizontalPanel.cs +++ b/Source/Engine/UI/GUI/Panels/HorizontalPanel.cs @@ -42,10 +42,10 @@ namespace FlaxEngine.GUI for (int i = 0; i < _children.Count; i++) { Control c = _children[i]; - if (c.Visible) + if (c.Visible && Mathf.IsZero(c.AnchorMax.X)) { var w = c.Width; - c.Bounds = new Rectangle(x + _offset.X, _margin.Top + _offset.Y, h, w); + c.Bounds = new Rectangle(x + _offset.X, _margin.Top + _offset.Y, w, h); x = c.Right + _spacing; hasAnyItem = true; } diff --git a/Source/Engine/UI/GUI/Panels/SplitPanel.cs b/Source/Engine/UI/GUI/Panels/SplitPanel.cs index 733e0b3a1..04160db25 100644 --- a/Source/Engine/UI/GUI/Panels/SplitPanel.cs +++ b/Source/Engine/UI/GUI/Panels/SplitPanel.cs @@ -12,12 +12,12 @@ namespace FlaxEngine.GUI /// /// The splitter size (in pixels). /// - public const int SpliterSize = 4; + public const int SplitterSize = 4; /// /// The splitter half size (in pixels). /// - private const int SpliterSizeHalf = SpliterSize / 2; + private const int SplitterSizeHalf = SplitterSize / 2; private Orientation _orientation; private float _splitterValue; @@ -105,12 +105,12 @@ namespace FlaxEngine.GUI if (_orientation == Orientation.Horizontal) { var split = Mathf.RoundToInt(_splitterValue * Width); - _splitterRect = new Rectangle(Mathf.Clamp(split - SpliterSizeHalf, 0.0f, Width), 0, SpliterSize, Height); + _splitterRect = new Rectangle(Mathf.Clamp(split - SplitterSizeHalf, 0.0f, Width), 0, SplitterSize, Height); } else { var split = Mathf.RoundToInt(_splitterValue * Height); - _splitterRect = new Rectangle(0, Mathf.Clamp(split - SpliterSizeHalf, 0.0f, Height), Width, SpliterSize); + _splitterRect = new Rectangle(0, Mathf.Clamp(split - SplitterSizeHalf, 0.0f, Height), Width, SplitterSize); } } @@ -226,14 +226,14 @@ namespace FlaxEngine.GUI if (_orientation == Orientation.Horizontal) { var split = Mathf.RoundToInt(_splitterValue * Width); - Panel1.Bounds = new Rectangle(0, 0, split - SpliterSizeHalf, Height); - Panel2.Bounds = new Rectangle(split + SpliterSizeHalf, 0, Width - split - SpliterSizeHalf, Height); + Panel1.Bounds = new Rectangle(0, 0, split - SplitterSizeHalf, Height); + Panel2.Bounds = new Rectangle(split + SplitterSizeHalf, 0, Width - split - SplitterSizeHalf, Height); } else { var split = Mathf.RoundToInt(_splitterValue * Height); - Panel1.Bounds = new Rectangle(0, 0, Width, split - SpliterSizeHalf); - Panel2.Bounds = new Rectangle(0, split + SpliterSizeHalf, Width, Height - split - SpliterSizeHalf); + Panel1.Bounds = new Rectangle(0, 0, Width, split - SplitterSizeHalf); + Panel2.Bounds = new Rectangle(0, split + SplitterSizeHalf, Width, Height - split - SplitterSizeHalf); } } } diff --git a/Source/Engine/UI/GUI/Panels/UniformGridPanel.cs b/Source/Engine/UI/GUI/Panels/UniformGridPanel.cs index 089907901..62c6a9744 100644 --- a/Source/Engine/UI/GUI/Panels/UniformGridPanel.cs +++ b/Source/Engine/UI/GUI/Panels/UniformGridPanel.cs @@ -14,7 +14,7 @@ namespace FlaxEngine.GUI /// /// Gets or sets the padding given to each slot. /// - [EditorOrder(0), Tooltip("The padding margin appied to each item slot.")] + [EditorOrder(0), Tooltip("The padding margin applied to each item slot.")] public Margin SlotPadding { get => _slotPadding; diff --git a/Source/Engine/UI/GUI/Panels/VerticalPanel.cs b/Source/Engine/UI/GUI/Panels/VerticalPanel.cs index f811755ef..2c821bd3f 100644 --- a/Source/Engine/UI/GUI/Panels/VerticalPanel.cs +++ b/Source/Engine/UI/GUI/Panels/VerticalPanel.cs @@ -42,7 +42,7 @@ namespace FlaxEngine.GUI for (int i = 0; i < _children.Count; i++) { Control c = _children[i]; - if (c.Visible) + if (c.Visible && Mathf.IsZero(c.AnchorMax.Y)) { var h = c.Height; c.Bounds = new Rectangle(_margin.Left + _offset.X, y + _offset.Y, w, h); diff --git a/Source/Engine/UI/GUI/WindowRootControl.cs b/Source/Engine/UI/GUI/WindowRootControl.cs index 03a994aa6..02c996b68 100644 --- a/Source/Engine/UI/GUI/WindowRootControl.cs +++ b/Source/Engine/UI/GUI/WindowRootControl.cs @@ -151,7 +151,7 @@ namespace FlaxEngine.GUI } /// - public override Vector2 TrackingMouseOffset => _window.TrackingMouseOffset; + public override Vector2 TrackingMouseOffset => _window.TrackingMouseOffset / _window._dpiScale; /// public override WindowRootControl RootWindow => this; @@ -262,14 +262,14 @@ namespace FlaxEngine.GUI return false; // Change focused control - Control prevous = _focusedControl; + Control previous = _focusedControl; _focusedControl = c; // Fire events - if (prevous != null) + if (previous != null) { - prevous.OnLostFocus(); - Assert.IsFalse(prevous.IsFocused); + previous.OnLostFocus(); + Assert.IsFalse(previous.IsFocused); } if (_focusedControl != null) { diff --git a/Source/Engine/UI/TextRender.cpp b/Source/Engine/UI/TextRender.cpp index 958aced8b..8e00c2cc3 100644 --- a/Source/Engine/UI/TextRender.cpp +++ b/Source/Engine/UI/TextRender.cpp @@ -319,15 +319,15 @@ void TextRender::Draw(RenderContext& renderContext) // Setup draw call DrawCall drawCall; drawCall.World = _world; - drawCall.PrevWorld = _drawState.PrevWorld; drawCall.ObjectPosition = drawCall.World.GetTranslation(); - drawCall.GeometrySize = _localBox.GetSize(); - drawCall.Lightmap = nullptr; - drawCall.LightmapUVsArea = Rectangle::Empty; - drawCall.Skinning = nullptr; + drawCall.Surface.GeometrySize = _localBox.GetSize(); + drawCall.Surface.PrevWorld = _drawState.PrevWorld; + drawCall.Surface.Lightmap = nullptr; + drawCall.Surface.LightmapUVsArea = Rectangle::Empty; + drawCall.Surface.Skinning = nullptr; + drawCall.Surface.LODDitherFactor = 0.0f; drawCall.WorldDeterminantSign = Math::FloatSelect(_world.RotDeterminant(), 1, -1); drawCall.PerInstanceRandom = GetPerInstanceRandom(); - drawCall.LODDitherFactor = 0.0f; drawCall.Geometry.IndexBuffer = _ib.GetBuffer(); drawCall.Geometry.VertexBuffers[0] = _vb0.GetBuffer(); drawCall.Geometry.VertexBuffers[1] = _vb1.GetBuffer(); @@ -336,14 +336,12 @@ void TextRender::Draw(RenderContext& renderContext) drawCall.Geometry.VertexBuffersOffsets[1] = 0; drawCall.Geometry.VertexBuffersOffsets[2] = 0; drawCall.InstanceCount = 1; - drawCall.IndirectArgsBuffer = nullptr; - drawCall.IndirectArgsOffset = 0; // Submit draw calls for (const auto& e : _drawChunks) { - drawCall.Geometry.IndicesCount = e.IndicesCount; - drawCall.Geometry.StartIndex = e.StartIndex; + drawCall.Draw.IndicesCount = e.IndicesCount; + drawCall.Draw.StartIndex = e.StartIndex; drawCall.Material = e.Material; renderContext.List->AddDrawCall(drawModes, GetStaticFlags(), drawCall, true); } diff --git a/Source/Engine/UI/UICanvas.cpp b/Source/Engine/UI/UICanvas.cpp index 67dbaaa5e..49d6ac420 100644 --- a/Source/Engine/UI/UICanvas.cpp +++ b/Source/Engine/UI/UICanvas.cpp @@ -17,13 +17,17 @@ MMethod* UICanvas_OnDisable = nullptr; MMethod* UICanvas_EndPlay = nullptr; #define UICANVAS_INVOKE(event) \ - MonoObject* exception = nullptr; \ - UICanvas_##event->Invoke(GetManagedInstance(), nullptr, &exception); \ - if (exception) \ - { \ - MException ex(exception); \ - ex.Log(LogType::Error, TEXT("UICanvas::" #event)); \ - } + auto instance = GetManagedInstance(); \ + if (instance) \ + { \ + MonoObject* exception = nullptr; \ + UICanvas_##event->Invoke(instance, nullptr, &exception); \ + if (exception) \ + { \ + MException ex(exception); \ + ex.Log(LogType::Error, TEXT("UICanvas::" #event)); \ + } \ + } UICanvas::UICanvas(const SpawnParams& params) : Actor(params) diff --git a/Source/Engine/UI/UICanvas.cs b/Source/Engine/UI/UICanvas.cs index 9ccbef214..426441493 100644 --- a/Source/Engine/UI/UICanvas.cs +++ b/Source/Engine/UI/UICanvas.cs @@ -1,5 +1,6 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. +using System; using System.Globalization; using System.IO; using System.Text; @@ -186,14 +187,14 @@ namespace FlaxEngine public CanvasRootControl GUI => _guiRoot; /// - /// Delegate schema for the callback used to perform custom canvas intersection test. Can be used to implement a canvas tha has a holes or non-rectangular shape. + /// Delegate schema for the callback used to perform custom canvas intersection test. Can be used to implement a canvas that has a holes or non-rectangular shape. /// /// The location of the point to test in coordinates of the canvas root control (see ). /// True if canvas was hit, otherwise false. public delegate bool TestCanvasIntersectionDelegate(ref Vector2 location); /// - /// The callback used to perform custom canvas intersection test. Can be used to implement a canvas tha has a holes or non-rectangular shape. + /// The callback used to perform custom canvas intersection test. Can be used to implement a canvas that has a holes or non-rectangular shape. /// [HideInEditor] public TestCanvasIntersectionDelegate TestCanvasIntersection; @@ -228,7 +229,10 @@ namespace FlaxEngine } } - private UICanvas() + /// + /// Initializes a new instance of the class. + /// + public UICanvas() { _guiRoot = new CanvasRootControl(this); _guiRoot.IsLayoutLocked = false; @@ -276,16 +280,41 @@ namespace FlaxEngine else if (_renderMode == CanvasRenderMode.CameraSpace) { Matrix tmp1, tmp2; + Vector3 viewPos, viewUp, viewForward, pos; + Quaternion viewRot; // Use default camera is not specified var camera = RenderCamera ?? Camera.MainCamera; +#if FLAX_EDITOR + if (_editorTask) + { + // Use editor viewport task to override Camera Space placement + var view = _editorTask.View; + var frustum = view.Frustum; + if (!frustum.IsOrthographic) + _guiRoot.Size = new Vector2(frustum.GetWidthAtDepth(Distance), frustum.GetHeightAtDepth(Distance)); + else + _guiRoot.Size = _editorTask.Viewport.Size; + Matrix.Translation(_guiRoot.Width / -2.0f, _guiRoot.Height / -2.0f, 0, out world); + Matrix.RotationYawPitchRoll(Mathf.Pi, Mathf.Pi, 0, out tmp2); + Matrix.Multiply(ref world, ref tmp2, out tmp1); + viewPos = view.Position; + viewRot = view.Direction != Vector3.Up ? Quaternion.LookRotation(view.Direction, Vector3.Up) : Quaternion.LookRotation(view.Direction, Vector3.Right); + viewUp = Vector3.Up * viewRot; + viewForward = view.Direction; + pos = view.Position + view.Direction * Distance; + Matrix.Billboard(ref pos, ref viewPos, ref viewUp, ref viewForward, out tmp2); + Matrix.Multiply(ref tmp1, ref tmp2, out world); + return; + } +#endif + // Adjust GUI size to the viewport size at the given distance form the camera var viewport = camera.Viewport; if (camera.UsePerspective) { - Matrix tmp3; - camera.GetMatrices(out tmp1, out tmp3, ref viewport); + camera.GetMatrices(out tmp1, out var tmp3, ref viewport); Matrix.Multiply(ref tmp1, ref tmp3, out tmp2); var frustum = new BoundingFrustum(tmp2); _guiRoot.Size = new Vector2(frustum.GetWidthAtDepth(Distance), frustum.GetHeightAtDepth(Distance)); @@ -301,11 +330,11 @@ namespace FlaxEngine Matrix.Multiply(ref world, ref tmp2, out tmp1); // In front of the camera - var viewPos = camera.Position; - var viewRot = camera.Orientation; - var viewUp = Vector3.Up * viewRot; - var viewForward = Vector3.Forward * viewRot; - var pos = viewPos + viewForward * Distance; + viewPos = camera.Position; + viewRot = camera.Orientation; + viewUp = Vector3.Up * viewRot; + viewForward = Vector3.Forward * viewRot; + pos = viewPos + viewForward * Distance; Matrix.Billboard(ref pos, ref viewPos, ref viewUp, ref viewForward, out tmp2); Matrix.Multiply(ref tmp1, ref tmp2, out world); @@ -331,11 +360,18 @@ namespace FlaxEngine _guiRoot.Offsets = Margin.Zero; if (_renderer) { +#if FLAX_EDITOR + _editorTask?.CustomPostFx.Remove(_renderer); +#endif SceneRenderTask.GlobalCustomPostFx.Remove(_renderer); _renderer.Canvas = null; Destroy(_renderer); _renderer = null; } +#if FLAX_EDITOR + if (_editorRoot != null) + _guiRoot.Parent = _editorRoot; +#endif break; } case CanvasRenderMode.CameraSpace: @@ -343,12 +379,31 @@ namespace FlaxEngine { // Render canvas manually _guiRoot.AnchorPreset = AnchorPresets.TopLeft; +#if FLAX_EDITOR + if (_editorRoot != null && _guiRoot != null) + _guiRoot.Parent = null; +#endif if (_renderer == null) { _renderer = New(); _renderer.Canvas = this; if (IsActiveInHierarchy && Scene) + { +#if FLAX_EDITOR + if (_editorTask != null) + { + _editorTask.CustomPostFx.Add(_renderer); + break; + } +#endif SceneRenderTask.GlobalCustomPostFx.Add(_renderer); + } +#if FLAX_EDITOR + else if (_editorTask != null && IsActiveInHierarchy) + { + _editorTask.CustomPostFx.Add(_renderer); + } +#endif } break; } @@ -487,10 +542,21 @@ namespace FlaxEngine internal void OnEnable() { +#if FLAX_EDITOR + _guiRoot.Parent = _editorRoot ?? RootControl.CanvasRoot; +#else _guiRoot.Parent = RootControl.CanvasRoot; +#endif if (_renderer) { +#if FLAX_EDITOR + if (_editorTask != null) + { + _editorTask.CustomPostFx.Add(_renderer); + return; + } +#endif SceneRenderTask.GlobalCustomPostFx.Add(_renderer); } } @@ -515,5 +581,25 @@ namespace FlaxEngine _renderer = null; } } + +#if FLAX_EDITOR + private SceneRenderTask _editorTask; + private ContainerControl _editorRoot; + + internal void EditorOverride(SceneRenderTask task, ContainerControl root) + { + if (_editorTask != null && _renderer != null) + _editorTask.CustomPostFx.Remove(_renderer); + if (_editorRoot != null && _guiRoot != null) + _guiRoot.Parent = null; + + _editorTask = task; + _editorRoot = root; + Setup(); + + if (RenderMode == CanvasRenderMode.ScreenSpace && _editorRoot != null && _guiRoot != null) + _guiRoot.Parent = _editorRoot; + } +#endif } } diff --git a/Source/Engine/UI/UIControl.cpp b/Source/Engine/UI/UIControl.cpp index 131c04298..33fe8a93b 100644 --- a/Source/Engine/UI/UIControl.cpp +++ b/Source/Engine/UI/UIControl.cpp @@ -156,6 +156,9 @@ void UIControl::OnParentChanged() // Base Actor::OnParentChanged(); + if (!IsDuringPlay()) + return; + UICONTROL_INVOKE(ParentChanged); } diff --git a/Source/Engine/UI/UIControl.cs b/Source/Engine/UI/UIControl.cs index e0f15a106..b31d9cab0 100644 --- a/Source/Engine/UI/UIControl.cs +++ b/Source/Engine/UI/UIControl.cs @@ -13,6 +13,7 @@ namespace FlaxEngine partial class UIControl { private Control _control; + private static bool _blockEvents; // Used to ignore internal events from C++ UIControl impl when performing state sync with C# UI /// /// Gets or sets the GUI control used by this actor. @@ -30,10 +31,11 @@ namespace FlaxEngine return; // Cleanup previous - if (_control != null) + var prevControl = _control; + if (prevControl != null) { - _control.LocationChanged -= OnControlLocationChanged; - _control.Dispose(); + prevControl.LocationChanged -= OnControlLocationChanged; + prevControl.Dispose(); } // Set value @@ -42,16 +44,18 @@ namespace FlaxEngine // Link the new one (events and parent) if (_control != null) { + // Setup control + _blockEvents = true; var containerControl = _control as ContainerControl; if (containerControl != null) containerControl.UnlockChildrenRecursive(); - _control.Parent = GetParent(); _control.IndexInParent = OrderInParent; _control.Location = new Vector2(LocalPosition); // TODO: sync control order in parent with actor order in parent (think about special cases like Panel with scroll bars used as internal controls) _control.LocationChanged += OnControlLocationChanged; + // Link children UI controls if (containerControl != null && IsActiveInHierarchy) { var children = ChildrenCount; @@ -64,6 +68,13 @@ namespace FlaxEngine } } } + + // Refresh + _blockEvents = false; + if (prevControl == null && _control.Parent != null) + _control.Parent.PerformLayout(); + else + _control.PerformLayout(); } } } @@ -170,7 +181,9 @@ namespace FlaxEngine private void OnControlLocationChanged(Control control) { + _blockEvents = true; LocalPosition = new Vector3(control.Location, LocalPosition.Z); + _blockEvents = false; } /// @@ -285,7 +298,7 @@ namespace FlaxEngine internal void ParentChanged() { - if (_control != null) + if (_control != null && !_blockEvents) { _control.Parent = GetParent(); _control.IndexInParent = OrderInParent; @@ -294,13 +307,15 @@ namespace FlaxEngine internal void TransformChanged() { - if (_control != null) + if (_control != null && !_blockEvents) + { _control.Location = new Vector2(LocalPosition); + } } internal void ActiveInTreeChanged() { - if (_control != null) + if (_control != null && !_blockEvents) { // Link or unlink control (won't modify Enable/Visible state) _control.Parent = GetParent(); @@ -310,8 +325,10 @@ namespace FlaxEngine internal void OrderInParentChanged() { - if (_control != null) + if (_control != null && !_blockEvents) + { _control.IndexInParent = OrderInParent; + } } internal void BeginPlay() diff --git a/Source/Engine/Utilities/RectPack.h b/Source/Engine/Utilities/RectPack.h index f020b0481..a8ffb0b7e 100644 --- a/Source/Engine/Utilities/RectPack.h +++ b/Source/Engine/Utilities/RectPack.h @@ -25,7 +25,7 @@ struct RectPack SizeType Width; SizeType Height; - // The reaming space amount inside this slot (updated on every insertion, initial it equal to width*height). + // The remaining space amount inside this slot (updated on every insertion, initial it equal to width*height). SizeType SpaceLeft; // True, if slot has been allocated, otherwise it's free. diff --git a/Source/Engine/Utilities/State.cs b/Source/Engine/Utilities/State.cs index f77308878..a0797ec0e 100644 --- a/Source/Engine/Utilities/State.cs +++ b/Source/Engine/Utilities/State.cs @@ -12,9 +12,6 @@ namespace FlaxEngine.Utilities /// /// Gets the state machine. /// - /// - /// The state machine. - /// public StateMachine StateMachine => owner; /// diff --git a/Source/Engine/Utilities/StateMachine.cs b/Source/Engine/Utilities/StateMachine.cs index 9966e3378..250d2fbe6 100644 --- a/Source/Engine/Utilities/StateMachine.cs +++ b/Source/Engine/Utilities/StateMachine.cs @@ -35,6 +35,11 @@ namespace FlaxEngine.Utilities /// public event Action StateChanged; + /// + /// Gets the states (read-only). + /// + public IReadOnlyList States => states; + /// /// Gets state of given type. /// diff --git a/Source/Engine/Utilities/StateMachine.h b/Source/Engine/Utilities/StateMachine.h index 292a67978..38e69a06b 100644 --- a/Source/Engine/Utilities/StateMachine.h +++ b/Source/Engine/Utilities/StateMachine.h @@ -50,7 +50,7 @@ public: /// /// Checks if can exit from that state /// - /// Next state to ener after exit from the current state + /// Next state to enter after exit from the current state /// True if can exit from that state, otherwise false virtual bool CanExit(State* nextState) const { diff --git a/Source/Engine/Utilities/TextWriter.h b/Source/Engine/Utilities/TextWriter.h index d2702bf3c..54f2e996a 100644 --- a/Source/Engine/Utilities/TextWriter.h +++ b/Source/Engine/Utilities/TextWriter.h @@ -4,6 +4,8 @@ #include "Engine/Core/NonCopyable.h" #include "Engine/Core/Formatting.h" +#include "Engine/Core/Types/String.h" +#include "Engine/Core/Types/StringView.h" #include "Engine/Serialization/MemoryWriteStream.h" /// @@ -97,6 +99,24 @@ public: WriteLine(); } + /// + /// Write text to the buffer + /// + /// Data + void Write(const StringViewBase& text) + { + _buffer.WriteBytes((void*)text.Get(), text.Length() * sizeof(CharType)); + } + + /// + /// Write text to the buffer + /// + /// Data + void Write(const StringBase& text) + { + _buffer.WriteBytes((void*)text.Get(), text.Length() * sizeof(CharType)); + } + /// /// Write text to the buffer /// diff --git a/Source/Engine/Visject/ShaderGraph.cpp b/Source/Engine/Visject/ShaderGraph.cpp index 61c609307..c8a8782e9 100644 --- a/Source/Engine/Visject/ShaderGraph.cpp +++ b/Source/Engine/Visject/ShaderGraph.cpp @@ -380,6 +380,15 @@ void ShaderGenerator::ProcessGroupMath(Box* box, Node* node, Value& value) Value v2 = tryGetValue(node->GetBox(1), Value::Zero); value = writeFunction2(node, v1, v2, TEXT("atan2")); break; + } + // Near Equal + case 42: + { + Value v1 = tryGetValue(node->GetBox(0), Value::Zero); + Value v2 = tryGetValue(node->GetBox(1), Value::Zero).Cast(v1.Type); + Value epsilon = tryGetValue(node->GetBox(2), 2, Value::Zero); + value = writeLocal(ValueType::Bool, String::Format(TEXT("distance({0},{1}) < {2}"), v1.Value, v2.Value, epsilon.Value), node); + break; } // Degrees case 43: @@ -392,6 +401,18 @@ void ShaderGenerator::ProcessGroupMath(Box* box, Node* node, Value& value) { value = writeFunction1(node, tryGetValue(node->GetBox(0), Value::Zero), TEXT("radians")); break; + } + // Remap + case 48: + { + const auto inVal = tryGetValue(node->GetBox(0), node->Values[0].AsFloat); + const auto rangeA = tryGetValue(node->GetBox(1), node->Values[1].AsVector2()); + const auto rangeB = tryGetValue(node->GetBox(2), node->Values[2].AsVector2()); + const auto clamp = tryGetValue(node->GetBox(3), node->Values[3]).AsBool(); + + const auto mapFunc = String::Format(TEXT("{2}.x + ({0} - {1}.x) * ({2}.y - {2}.x) / ({1}.y - {1}.x)"), inVal.Value, rangeA.Value, rangeB.Value); + value = writeLocal(ValueType::Float, String::Format(TEXT("{2} ? clamp({0}, {1}.x, {1}.y) : {0}"), mapFunc, rangeB.Value, clamp.Value), node); + break; } default: break; @@ -911,7 +932,7 @@ void ShaderGenerator::ProcessGroupComparisons(Box* box, Node* node, Value& value const Value condition = tryGetValue(node->GetBox(0), Value::False).AsBool(); const Value onTrue = tryGetValue(node->GetBox(2), 1, Value::Zero); const Value onFalse = tryGetValue(node->GetBox(1), 0, Value::Zero).Cast(onTrue.Type); - value = writeLocal(onTrue.Type, String::Format(TEXT("({0}) ? ({1}) : ({2})"), condition.Value, onTrue.Value, onFalse.Value), node); + value = writeLocal(onTrue.Type, String::Format(TEXT("{0} ? {1} : {2}"), condition.Value, onTrue.Value, onFalse.Value), node); break; } } diff --git a/Source/Engine/Visject/ShaderGraphUtilities.cpp b/Source/Engine/Visject/ShaderGraphUtilities.cpp index c84a500e4..079b6e602 100644 --- a/Source/Engine/Visject/ShaderGraphUtilities.cpp +++ b/Source/Engine/Visject/ShaderGraphUtilities.cpp @@ -177,10 +177,6 @@ const Char* ShaderGraphUtilities::GenerateShaderResources(TextWriterUnicode& wri } } } - - if (startRegister != registerIndex) - writer.WriteLine(); - return nullptr; } diff --git a/Source/Engine/Visject/VisjectGraph.cpp b/Source/Engine/Visject/VisjectGraph.cpp index eb2b8bf61..939013ebd 100644 --- a/Source/Engine/Visject/VisjectGraph.cpp +++ b/Source/Engine/Visject/VisjectGraph.cpp @@ -371,6 +371,20 @@ void VisjectExecutor::ProcessGroupMath(Box* box, Node* node, Value& value) if (value.Type.Type == VariantType::Enum) value.AsUint64 = value.AsUint64 | (uint64)tryGetValue(node->GetBox(1), Value::Zero); break; + // Remap + case 48: + { + const float inVal = tryGetValue(node->GetBox(0), node->Values[0]).AsFloat; + const Vector2 rangeA = tryGetValue(node->GetBox(1), node->Values[1]).AsVector2(); + const Vector2 rangeB = tryGetValue(node->GetBox(2), node->Values[2]).AsVector2(); + const bool clamp = tryGetValue(node->GetBox(3), node->Values[3]).AsBool; + + auto mapFunc = rangeB.X + (inVal - rangeA.X) * (rangeB.Y - rangeB.X) / (rangeA.Y - rangeA.X); + + // Clamp value? + value = clamp ? Math::Clamp(mapFunc, rangeB.X, rangeB.Y) : mapFunc; + break; + } default: break; } diff --git a/Source/FlaxEngine.Gen.cs b/Source/FlaxEngine.Gen.cs index 82a54a760..04aef0556 100644 --- a/Source/FlaxEngine.Gen.cs +++ b/Source/FlaxEngine.Gen.cs @@ -12,6 +12,6 @@ using System.Runtime.InteropServices; [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: Guid("b8442186-4a70-7c85-704a-857c262d00f6")] -[assembly: AssemblyVersion("1.0.6215")] -[assembly: AssemblyFileVersion("1.0.6215")] +[assembly: Guid("095aaaed-cc57-6182-57cc-82617b3c2889")] +[assembly: AssemblyVersion("1.0.6216")] +[assembly: AssemblyFileVersion("1.0.6216")] diff --git a/Source/FlaxEngine.Gen.h b/Source/FlaxEngine.Gen.h index 4ceda354d..53a18990c 100644 --- a/Source/FlaxEngine.Gen.h +++ b/Source/FlaxEngine.Gen.h @@ -5,11 +5,11 @@ #include "Engine/Core/Compiler.h" #define FLAXENGINE_NAME "FlaxEngine" -#define FLAXENGINE_VERSION Version(1, 0, 6215) -#define FLAXENGINE_VERSION_TEXT "1.0.6215" +#define FLAXENGINE_VERSION Version(1, 0, 6216) +#define FLAXENGINE_VERSION_TEXT "1.0.6216" #define FLAXENGINE_VERSION_MAJOR 1 #define FLAXENGINE_VERSION_MINOR 0 -#define FLAXENGINE_VERSION_BUILD 6215 +#define FLAXENGINE_VERSION_BUILD 6216 #define FLAXENGINE_COMPANY "Flax" #define FLAXENGINE_COPYRIGHT "Copyright (c) 2012-2021 Wojciech Figat. All rights reserved." diff --git a/Source/Platforms/Windows/Binaries/ThirdParty/x64/MonoPosixHelper.dll b/Source/Platforms/Windows/Binaries/ThirdParty/x64/MonoPosixHelper.dll index 65c280e99..dd082f208 100644 --- a/Source/Platforms/Windows/Binaries/ThirdParty/x64/MonoPosixHelper.dll +++ b/Source/Platforms/Windows/Binaries/ThirdParty/x64/MonoPosixHelper.dll @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9ad92c41f484482e217f791b5ae7963f5d55c2799bc5578125a88f2d40f33262 +oid sha256:35a252fcb61a805c85558646d073e32f7eb54666931a73380b4dea2879c72584 size 157696 diff --git a/Source/Platforms/Windows/Binaries/ThirdParty/x64/libgcmonosgen.pdb b/Source/Platforms/Windows/Binaries/ThirdParty/x64/libgcmonosgen.pdb index 1a80321ba..166a8df92 100644 --- a/Source/Platforms/Windows/Binaries/ThirdParty/x64/libgcmonosgen.pdb +++ b/Source/Platforms/Windows/Binaries/ThirdParty/x64/libgcmonosgen.pdb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8bd411ac3aa425db683905a5fa2445d7d770e5d04821757fcdcaa42e0d0ac986 +oid sha256:d165b1a28ce64c030b4d242b86b813292ce038e0155ad0d6025ed7472e036ce4 size 348160 diff --git a/Source/Platforms/Windows/Binaries/ThirdParty/x64/libmini.pdb b/Source/Platforms/Windows/Binaries/ThirdParty/x64/libmini.pdb index 7c24a2502..719362b2d 100644 --- a/Source/Platforms/Windows/Binaries/ThirdParty/x64/libmini.pdb +++ b/Source/Platforms/Windows/Binaries/ThirdParty/x64/libmini.pdb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:184d1b18bb84c35fa43d878cedb60f53f203207208882ce6518a2d7a35308332 +oid sha256:9cc98ae831f784a7dd6f15a1dbb083c63e6c50100a201501a7825a6d85baf53e size 864256 diff --git a/Source/Platforms/Windows/Binaries/ThirdParty/x64/libmono-static.lib b/Source/Platforms/Windows/Binaries/ThirdParty/x64/libmono-static.lib index 366a28594..1cc28852d 100644 --- a/Source/Platforms/Windows/Binaries/ThirdParty/x64/libmono-static.lib +++ b/Source/Platforms/Windows/Binaries/ThirdParty/x64/libmono-static.lib @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f633de4fbae8e4d65aec49eaaa06f37745bfc28328f4193020434dab398235cc -size 28245086 +oid sha256:2eac1fd52b8d87a0fe4dce3c02e68da8b35bfaa44cb1689d5d8935f2ba09531b +size 28241218 diff --git a/Source/Shaders/BakeLightmap.shader b/Source/Shaders/BakeLightmap.shader index 4f206469e..14f99b4f4 100644 --- a/Source/Shaders/BakeLightmap.shader +++ b/Source/Shaders/BakeLightmap.shader @@ -328,7 +328,7 @@ void CS_BlurEmpty(uint3 GroupID : SV_GroupID, uint3 GroupThreadID : SV_GroupThre const int2 location = int2(GroupID.x, GroupID.y); const uint texelAdress = (location.y * AtlasSize + location.x) * NUM_SH_TARGETS; - // TODO: use more therads to sample lightmap and final therad make it blur + // TODO: use more threads to sample lightmap and final therad make it blur // Simple box filter (using only valid samples) const int blurRadius = 2; diff --git a/Source/Shaders/BitonicSort.shader b/Source/Shaders/BitonicSort.shader index b75f553b1..8a53802d0 100644 --- a/Source/Shaders/BitonicSort.shader +++ b/Source/Shaders/BitonicSort.shader @@ -32,7 +32,7 @@ uint InsertOneBit(uint value, uint oneBitMask) // Determines if two sort keys should be swapped in the list. KeySign is // either 1 or -1. Multiplication with the KeySign will either invert the sign -// (effectively a negation) or leave the value alone. When the the KeySign is +// (effectively a negation) or leave the value alone. When the KeySign is // 1, we are sorting descending, so when A < B, they should swap. For an // ascending sort, -A < -B should swap. bool ShouldSwap(Item a, Item b) diff --git a/Source/Shaders/Lighting.hlsl b/Source/Shaders/Lighting.hlsl index 9838cca52..b5021b711 100644 --- a/Source/Shaders/Lighting.hlsl +++ b/Source/Shaders/Lighting.hlsl @@ -4,7 +4,7 @@ #define __LIGHTING__ #if !defined(USE_GBUFFER_CUSTOM_DATA) -#error "Canot calculate lighting without custom data in GBuffer. Define USE_GBUFFER_CUSTOM_DATA." +#error "Cannot calculate lighting without custom data in GBuffer. Define USE_GBUFFER_CUSTOM_DATA." #endif #include "./Flax/LightingCommon.hlsl" diff --git a/Source/Shaders/MaterialCommon.hlsl b/Source/Shaders/MaterialCommon.hlsl index 8f47124dc..8fc981cd7 100644 --- a/Source/Shaders/MaterialCommon.hlsl +++ b/Source/Shaders/MaterialCommon.hlsl @@ -61,9 +61,6 @@ #ifndef MAX_TESSELLATION_FACTOR #define MAX_TESSELLATION_FACTOR 15 #endif -#ifndef IS_MOTION_VECTORS_PASS - #define IS_MOTION_VECTORS_PASS 0 -#endif #ifndef PER_BONE_MOTION_BLUR #define PER_BONE_MOTION_BLUR 0 #endif @@ -164,6 +161,12 @@ float3x3 CalcTangentBasisFromWorldNormal(float3 normal) return float3x3(tangent, bitangent, normal); } +float3x3 CalcTangentBasis(float3 normal, float4 tangent) +{ + float3 bitangent = cross(normal, tangent.xyz) * tangent.w; + return float3x3(tangent.xyz, bitangent, normal); +} + // [Jimenez et al. 2016, "Practical Realtime Strategies for Accurate Indirect Occlusion"] float3 AOMultiBounce(float visibility, float3 albedo) { @@ -173,24 +176,4 @@ float3 AOMultiBounce(float visibility, float3 albedo) return max(visibility, ((visibility * a + b) * visibility + c) * visibility); } -#if CAN_USE_LIGHTMAP - -// Evaluates the H-Basis coefficients in the tangent space normal direction -float3 GetHBasisIrradiance(in float3 n, in float3 h0, in float3 h1, in float3 h2, in float3 h3) -{ - float3 color = 0.0f; - - // Band 0 - color += h0 * (1.0f / sqrt(2.0f * PI)); - - // Band 1 - color += h1 * -sqrt(1.5f / PI) * n.y; - color += h2 * sqrt(1.5f / PI) * (2 * n.z - 1.0f); - color += h3 * -sqrt(1.5f / PI) * n.x; - - return color; -} - -#endif - #endif diff --git a/Source/ThirdParty/OpenFBX/ofbx.cpp b/Source/ThirdParty/OpenFBX/ofbx.cpp index dc7c92718..36f942cf8 100644 --- a/Source/ThirdParty/OpenFBX/ofbx.cpp +++ b/Source/ThirdParty/OpenFBX/ofbx.cpp @@ -2894,7 +2894,7 @@ static float getFramerateFromTimeMode(FrameRate time_mode, float custom_frame_ra { switch (time_mode) { - case FrameRate_DEFAULT: return 1; + case FrameRate_DEFAULT: return 14; case FrameRate_120: return 120; case FrameRate_100: return 100; case FrameRate_60: return 60; diff --git a/Source/ThirdParty/rapidjson/document.h b/Source/ThirdParty/rapidjson/document.h index d9472ec1e..2691a6386 100644 --- a/Source/ThirdParty/rapidjson/document.h +++ b/Source/ThirdParty/rapidjson/document.h @@ -1679,7 +1679,18 @@ public: //@{ const Ch* GetString() const { RAPIDJSON_ASSERT(IsString()); return (data_.f.flags & kInlineStrFlag) ? data_.ss.str : GetStringPointer(); } - ::String GetText() const { RAPIDJSON_ASSERT(IsString()); return ::String(GetString(), GetStringLength()); } + ::String GetText() const + { + ::String result; + if (IsString()) + { + if (data_.f.flags & kInlineStrFlag) + result.Set(data_.ss.str, data_.ss.GetLength()); + else + result.Set(GetStringPointer(), data_.s.length); + } + return result; + } //! Get the length of string. /*! Since rapidjson permits "\\u0000" in the json string, strlen(v.GetString()) may not equal to v.GetStringLength(). diff --git a/Source/ThirdParty/recastnavigation/DetourNavMeshQuery.h b/Source/ThirdParty/recastnavigation/DetourNavMeshQuery.h index 1c23e4857..d9f940e2d 100644 --- a/Source/ThirdParty/recastnavigation/DetourNavMeshQuery.h +++ b/Source/ThirdParty/recastnavigation/DetourNavMeshQuery.h @@ -34,6 +34,7 @@ /// @ingroup detour class dtQueryFilter { +public: float m_areaCost[DT_MAX_AREAS]; ///< Cost per area type. (Used by default implementation.) unsigned short m_includeFlags; ///< Flags for polygons that can be visited. (Used by default implementation.) unsigned short m_excludeFlags; ///< Flags for polygons that should not be visted. (Used by default implementation.) diff --git a/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs b/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs index 60074883c..3e2ad1b31 100644 --- a/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs @@ -1,16 +1,17 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System.Collections.Generic; +using System.IO; namespace Flax.Build.Bindings { /// /// The native type information for bindings generator. /// - public class ApiTypeInfo + public class ApiTypeInfo : IBindingsCache { public ApiTypeInfo Parent; - public List Children; + public List Children = new List(); public string NativeName; public string Name; public string Namespace; @@ -22,6 +23,7 @@ namespace Flax.Build.Bindings public virtual bool IsClass => false; public virtual bool IsStruct => false; public virtual bool IsEnum => false; + public virtual bool IsInterface => false; public virtual bool IsValueType => false; public virtual bool IsScriptingObject => false; public virtual bool IsPod => false; @@ -90,6 +92,35 @@ namespace Flax.Build.Bindings Children.Add(apiTypeInfo); } + public virtual void Write(BinaryWriter writer) + { + BindingsGenerator.Write(writer, NativeName); + BindingsGenerator.Write(writer, Name); + BindingsGenerator.Write(writer, Namespace); + BindingsGenerator.Write(writer, Attributes); + BindingsGenerator.Write(writer, Comment); + writer.Write(IsInBuild); + BindingsGenerator.Write(writer, Children); + } + + public virtual void Read(BinaryReader reader) + { + NativeName = BindingsGenerator.Read(reader, NativeName); + Name = BindingsGenerator.Read(reader, Name); + Namespace = BindingsGenerator.Read(reader, Namespace); + Attributes = BindingsGenerator.Read(reader, Attributes); + Comment = BindingsGenerator.Read(reader, Comment); + IsInBuild = reader.ReadBoolean(); + Children = BindingsGenerator.Read(reader, Children); + + // Fix hierarchy + for (int i = 0; i < Children.Count; i++) + { + var child = Children[i]; + child.Parent = this; + } + } + public override string ToString() { return Name; diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs index d3ba0d0a9..82128eaa7 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs @@ -14,6 +14,7 @@ namespace Flax.Build.Bindings public const string Enum = "API_ENUM"; public const string Class = "API_CLASS"; public const string Struct = "API_STRUCT"; + public const string Interface = "API_INTERFACE"; public const string Function = "API_FUNCTION"; public const string Property = "API_PROPERTY"; public const string Field = "API_FIELD"; @@ -27,6 +28,7 @@ namespace Flax.Build.Bindings Enum, Class, Struct, + Interface, }; } diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index 6184b9005..dc829912c 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -66,6 +66,14 @@ namespace Flax.Build.Bindings return null; } + // Special case for Engine TEXT macro + if (value.Contains("TEXT(\"")) + return value.Replace("TEXT(\"", "(\""); + + // Special case for value constructors + if (value.Contains('(') && value.Contains(')')) + return "new " + value; + // Convert from C++ to C# switch (value) { @@ -124,8 +132,8 @@ namespace Flax.Build.Bindings return result; } - // ScriptingObjectReference or AssetReference or WeakAssetReference - if ((typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference") && typeInfo.GenericArgs != null) + // ScriptingObjectReference or AssetReference or WeakAssetReference or SoftObjectReference + if ((typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference" || typeInfo.Type == "SoftObjectReference") && typeInfo.GenericArgs != null) return typeInfo.GenericArgs[0].Type.Replace("::", "."); // Array or Span @@ -180,8 +188,8 @@ namespace Flax.Build.Bindings return "IntPtr"; } - // ScriptingObjectReference or AssetReference or WeakAssetReference - if ((typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference") && typeInfo.GenericArgs != null) + // ScriptingObjectReference or AssetReference or WeakAssetReference or SoftObjectReference + if ((typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference" || typeInfo.Type == "SoftObjectReference") && typeInfo.GenericArgs != null) return "IntPtr"; return GenerateCSharpNativeToManaged(buildData, typeInfo, caller); @@ -223,8 +231,8 @@ namespace Flax.Build.Bindings return "FlaxEngine.Object.GetUnmanagedPtr"; } - // ScriptingObjectReference or AssetReference or WeakAssetReference - if ((typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference") && typeInfo.GenericArgs != null) + // ScriptingObjectReference or AssetReference or WeakAssetReference or SoftObjectReference + if ((typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference" || typeInfo.Type == "SoftObjectReference") && typeInfo.GenericArgs != null) return "FlaxEngine.Object.GetUnmanagedPtr"; // Default @@ -386,14 +394,36 @@ namespace Flax.Build.Bindings } } - private static void GenerateCSharpAttributes(BuildData buildData, StringBuilder contents, string indent, string attributes, string[] comment, bool canUseTooltip, bool useUnmanaged) + private static bool IsDefaultValueSupported(string value) + { + // TEXT macro (eg. TEXT("text")) + // TODO: support string for default value attribute + if (value.Contains("TEXT(\"")) + return false; + + // Value constructors (eg. Vector2(1, 2)) + // TODO: support value constructors for default value attribute + if (value.Contains('(') && value.Contains(')')) + return false; + + // Constants (eg. Vector2::Zero) + // TODO: support constants for default value attribute + if (value.Contains("::")) + return false; + + return true; + } + + private static void GenerateCSharpAttributes(BuildData buildData, StringBuilder contents, string indent, ApiTypeInfo apiTypeInfo, string attributes, string[] comment, bool canUseTooltip, bool useUnmanaged, string defaultValue = null) { var writeTooltip = true; + var writeDefaultValue = true; if (!string.IsNullOrEmpty(attributes)) { // Write attributes contents.Append(indent).Append('[').Append(attributes).Append(']').AppendLine(); - writeTooltip = !attributes.Contains("Tooltip("); + writeTooltip = !attributes.Contains("Tooltip(") && !attributes.Contains("HideInEditor"); + writeDefaultValue = !attributes.Contains("DefaultValue("); } if (useUnmanaged) @@ -417,20 +447,27 @@ namespace Flax.Build.Bindings contents.Append(indent).Append("[Tooltip(\"").Append(tooltip).Append("\")]").AppendLine(); } } + if (!string.IsNullOrEmpty(defaultValue) && writeDefaultValue && IsDefaultValueSupported(defaultValue)) + { + // Write default value attribute + defaultValue = GenerateCSharpDefaultValueNativeToManaged(buildData, defaultValue, apiTypeInfo); + contents.Append(indent).Append("[DefaultValue(").Append(defaultValue).Append(")]").AppendLine(); + } } - private static void GenerateCSharpAttributes(BuildData buildData, StringBuilder contents, string indent, ApiTypeInfo apiTypeInfo, bool useUnmanaged) + private static void GenerateCSharpAttributes(BuildData buildData, StringBuilder contents, string indent, ApiTypeInfo apiTypeInfo, bool useUnmanaged, string defaultValue = null) { - GenerateCSharpAttributes(buildData, contents, indent, apiTypeInfo.Attributes, apiTypeInfo.Comment, true, useUnmanaged); + GenerateCSharpAttributes(buildData, contents, indent, apiTypeInfo, apiTypeInfo.Attributes, apiTypeInfo.Comment, true, useUnmanaged, defaultValue); } - private static void GenerateCSharpAttributes(BuildData buildData, StringBuilder contents, string indent, MemberInfo memberInfo, bool useUnmanaged) + private static void GenerateCSharpAttributes(BuildData buildData, StringBuilder contents, string indent, ApiTypeInfo apiTypeInfo, MemberInfo memberInfo, bool useUnmanaged, string defaultValue = null) { - GenerateCSharpAttributes(buildData, contents, indent, memberInfo.Attributes, memberInfo.Comment, true, useUnmanaged); + GenerateCSharpAttributes(buildData, contents, indent, apiTypeInfo, memberInfo.Attributes, memberInfo.Comment, true, useUnmanaged, defaultValue); } private static void GenerateCSharpClass(BuildData buildData, StringBuilder contents, string indent, ClassInfo classInfo) { + var useUnmanaged = classInfo.IsStatic || classInfo.IsScriptingObject; contents.AppendLine(); // Namespace begin @@ -446,7 +483,7 @@ namespace Flax.Build.Bindings GenerateCSharpComment(contents, indent, classInfo.Comment); // Class begin - GenerateCSharpAttributes(buildData, contents, indent, classInfo, true); + GenerateCSharpAttributes(buildData, contents, indent, classInfo, useUnmanaged); contents.Append(indent); if (classInfo.Access == AccessLevel.Public) contents.Append("public "); @@ -461,7 +498,7 @@ namespace Flax.Build.Bindings else if (classInfo.IsAbstract) contents.Append("abstract "); contents.Append("unsafe partial class ").Append(classInfo.Name); - if (classInfo.BaseType != null && classInfo.BaseTypeInheritance != AccessLevel.Private) + if (classInfo.BaseType != null && !classInfo.IsBaseTypeHidden) contents.Append(" : ").Append(GenerateCSharpNativeToManaged(buildData, classInfo.BaseType, classInfo)); contents.AppendLine(); contents.Append(indent + "{"); @@ -482,19 +519,20 @@ namespace Flax.Build.Bindings // Events foreach (var eventInfo in classInfo.Events) { + if (!useUnmanaged) + throw new NotImplementedException("TODO: support events inside non-static and non-scripting API class types."); + var paramsCount = eventInfo.Type.GenericArgs?.Count ?? 0; + contents.AppendLine(); foreach (var comment in eventInfo.Comment) { if (comment.Contains("/// ")) continue; - var c = comment.Replace("::", "."); - contents.Append(indent); - contents.Append(c); - contents.AppendLine(); + contents.Append(indent).Append(comment.Replace("::", ".")).AppendLine(); } - GenerateCSharpAttributes(buildData, contents, indent, eventInfo, true); + GenerateCSharpAttributes(buildData, contents, indent, classInfo, eventInfo, useUnmanaged); contents.Append(indent); if (eventInfo.Access == AccessLevel.Public) contents.Append("public "); @@ -505,10 +543,10 @@ namespace Flax.Build.Bindings if (eventInfo.IsStatic) contents.Append("static "); string eventSignature = "event Action"; - if (eventInfo.Type.GenericArgs.Count != 0) + if (paramsCount != 0) { eventSignature += '<'; - for (var i = 0; i < eventInfo.Type.GenericArgs.Count; i++) + for (var i = 0; i < paramsCount; i++) { if (i != 0) eventSignature += ", "; @@ -546,7 +584,7 @@ namespace Flax.Build.Bindings if (eventInfo.IsStatic) contents.Append("static "); contents.Append($"void Internal_{eventInfo.Name}_Invoke("); - for (var i = 0; i < eventInfo.Type.GenericArgs.Count; i++) + for (var i = 0; i < paramsCount; i++) { if (i != 0) contents.Append(", "); @@ -557,7 +595,7 @@ namespace Flax.Build.Bindings contents.Append(indent).Append('{').AppendLine(); contents.Append(indent).Append(" Internal_").Append(eventInfo.Name); contents.Append('('); - for (var i = 0; i < eventInfo.Type.GenericArgs.Count; i++) + for (var i = 0; i < paramsCount; i++) { if (i != 0) contents.Append(", "); @@ -586,14 +624,10 @@ namespace Flax.Build.Bindings { if (comment.Contains("/// ")) continue; - - var c = comment.Replace("::", "."); - contents.Append(indent); - contents.Append(c); - contents.AppendLine(); + contents.Append(indent).Append(comment.Replace("::", ".")).AppendLine(); } - GenerateCSharpAttributes(buildData, contents, indent, fieldInfo, true); + GenerateCSharpAttributes(buildData, contents, indent, classInfo, fieldInfo, useUnmanaged, fieldInfo.DefaultValue); contents.Append(indent); if (fieldInfo.Access == AccessLevel.Public) contents.Append("public "); @@ -604,8 +638,19 @@ namespace Flax.Build.Bindings if (fieldInfo.IsStatic) contents.Append("static "); var returnValueType = GenerateCSharpNativeToManaged(buildData, fieldInfo.Type, classInfo); - contents.Append(returnValueType).Append(' ').AppendLine(fieldInfo.Name); - contents.AppendLine(indent + "{"); + contents.Append(returnValueType).Append(' ').Append(fieldInfo.Name); + if (!useUnmanaged) + { + if (fieldInfo.DefaultValue != null) + { + var defaultValue = GenerateCSharpDefaultValueNativeToManaged(buildData, fieldInfo.DefaultValue, classInfo); + if (!string.IsNullOrEmpty(defaultValue)) + contents.Append(" = ").Append(defaultValue); + } + contents.AppendLine(";"); + continue; + } + contents.AppendLine().AppendLine(indent + "{"); indent += " "; contents.Append(indent).Append("get { "); @@ -615,7 +660,7 @@ namespace Flax.Build.Bindings if (!fieldInfo.IsReadOnly) { contents.Append(indent).Append("set { "); - GenerateCSharpWrapperFunctionCall(buildData, contents, classInfo, fieldInfo.Setter, true); + GenerateCSharpWrapperFunctionCall(buildData, contents, classInfo, fieldInfo.Setter, useUnmanaged); contents.Append(" }").AppendLine(); } @@ -630,23 +675,19 @@ namespace Flax.Build.Bindings // Properties foreach (var propertyInfo in classInfo.Properties) { + if (!useUnmanaged) + throw new NotImplementedException("TODO: support properties inside non-static and non-scripting API class types."); + contents.AppendLine(); foreach (var comment in propertyInfo.Comment) { if (comment.Contains("/// ") || comment.Contains(" _typeCache = new Dictionary(); + + internal static void Write(BinaryWriter writer, string e) + { + var valid = e != null; + writer.Write(valid); + if (valid) + writer.Write(e); + } + + internal static void Write(BinaryWriter writer, string[] list) + { + if (list != null) + { + writer.Write(list.Length); + for (int i = 0; i < list.Length; i++) + writer.Write(list[i]); + } + else + { + writer.Write(0); + } + } + + internal static void Write(BinaryWriter writer, IEnumerable list) + { + if (list != null) + { + writer.Write(list.Count()); + foreach (var e in list) + writer.Write(e); + } + else + { + writer.Write(0); + } + } + + internal static void Write(BinaryWriter writer, T e) where T : IBindingsCache + { + if (e != null) + { + writer.Write(e.GetType().FullName); + e.Write(writer); + } + else + { + writer.Write(string.Empty); + } + } + + internal static void Write(BinaryWriter writer, IList list) where T : IBindingsCache + { + if (list != null) + { + var count = list.Count; + writer.Write(count); + for (int i = 0; i < count; i++) + { + var e = list[i]; + writer.Write(e.GetType().FullName); + e.Write(writer); + } + } + else + { + writer.Write(0); + } + } + + internal static string Read(BinaryReader reader, string e) + { + var valid = reader.ReadBoolean(); + if (valid) + e = reader.ReadString(); + return e; + } + + internal static string[] Read(BinaryReader reader, string[] list) + { + var count = reader.ReadInt32(); + if (count != 0) + { + list = new string[count]; + for (int i = 0; i < count; i++) + list[i] = reader.ReadString(); + } + return list; + } + + internal static T Read(BinaryReader reader, T e) where T : IBindingsCache + { + var typename = reader.ReadString(); + if (string.IsNullOrEmpty(typename)) + return e; + if (!_typeCache.TryGetValue(typename, out var type)) + { + type = Builder.BuildTypes.FirstOrDefault(x => x.FullName == typename); + if (type == null) + { + var msg = $"Missing type {typename}."; + Log.Error(msg); + throw new Exception(msg); + } + _typeCache.Add(typename, type); + } + e = (T)Activator.CreateInstance(type); + e.Read(reader); + return e; + } + + internal static List Read(BinaryReader reader, List list) where T : IBindingsCache + { + var count = reader.ReadInt32(); + if (count != 0) + { + list = new List(count); + for (int i = 0; i < count; i++) + { + var typename = reader.ReadString(); + if (!_typeCache.TryGetValue(typename, out var type)) + { + type = Builder.BuildTypes.FirstOrDefault(x => x.FullName == typename); + if (type == null) + { + var msg = $"Missing type {typename}."; + Log.Error(msg); + throw new Exception(msg); + } + _typeCache.Add(typename, type); + } + var e = (T)Activator.CreateInstance(type); + e.Read(reader); + list.Add(e); + } + } + return list; + } + + private static string GetCachePath(Module module, BuildOptions moduleOptions) + { + return Path.Combine(moduleOptions.IntermediateFolder, module.Name + ".Bindings.cache"); + } + + private static void SaveCache(ModuleInfo moduleInfo, BuildOptions moduleOptions, List headerFiles) + { + var path = GetCachePath(moduleInfo.Module, moduleOptions); + using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read)) + using (var writer = new BinaryWriter(stream, Encoding.UTF8)) + { + // Version + writer.Write(4); + writer.Write(File.GetLastWriteTime(Assembly.GetExecutingAssembly().Location).Ticks); + + // Build options + writer.Write(moduleOptions.IntermediateFolder); + writer.Write((int)moduleOptions.Platform.Target); + writer.Write((int)moduleOptions.Architecture); + writer.Write((int)moduleOptions.Configuration); + Write(writer, moduleOptions.PublicDefinitions); + Write(writer, moduleOptions.PrivateDefinitions); + Write(writer, moduleOptions.CompileEnv.PreprocessorDefinitions); + + // Header files + writer.Write(headerFiles.Count); + for (int i = 0; i < headerFiles.Count; i++) + { + var headerFile = headerFiles[i]; + writer.Write(headerFile); + writer.Write(File.GetLastWriteTime(headerFile).Ticks); + } + + // Info + moduleInfo.Write(writer); + } + } + + private static bool LoadCache(ref ModuleInfo moduleInfo, BuildOptions moduleOptions, List headerFiles) + { + var path = GetCachePath(moduleInfo.Module, moduleOptions); + if (!File.Exists(path)) + return false; + using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) + using (var reader = new BinaryReader(stream, Encoding.UTF8)) + { + // Version + var version = reader.ReadInt32(); + if (version != 4) + return false; + if (File.GetLastWriteTime(Assembly.GetExecutingAssembly().Location).Ticks != reader.ReadInt64()) + return false; + + // Build options + if (reader.ReadString() != moduleOptions.IntermediateFolder || + reader.ReadInt32() != (int)moduleOptions.Platform.Target || + reader.ReadInt32() != (int)moduleOptions.Architecture || + reader.ReadInt32() != (int)moduleOptions.Configuration) + return false; + var publicDefinitions = Read(reader, Utilities.GetEmptyArray()); + if (publicDefinitions.Length != moduleOptions.PublicDefinitions.Count || publicDefinitions.Any(x => !moduleOptions.PublicDefinitions.Contains(x))) + return false; + var privateDefinitions = Read(reader, Utilities.GetEmptyArray()); + if (privateDefinitions.Length != moduleOptions.PrivateDefinitions.Count || privateDefinitions.Any(x => !moduleOptions.PrivateDefinitions.Contains(x))) + return false; + var preprocessorDefinitions = Read(reader, Utilities.GetEmptyArray()); + if (preprocessorDefinitions.Length != moduleOptions.CompileEnv.PreprocessorDefinitions.Count || preprocessorDefinitions.Any(x => !moduleOptions.CompileEnv.PreprocessorDefinitions.Contains(x))) + return false; + + // Header files + var headerFilesCount = reader.ReadInt32(); + if (headerFilesCount != headerFiles.Count) + return false; + for (int i = 0; i < headerFilesCount; i++) + { + var headerFile = headerFiles[i]; + if (headerFile != reader.ReadString()) + return false; + if (File.GetLastWriteTime(headerFile).Ticks > reader.ReadInt64()) + return false; + } + + // Info + var newModuleInfo = new ModuleInfo + { + Module = moduleInfo.Module, + Name = moduleInfo.Name, + Namespace = moduleInfo.Namespace, + IsFromCache = true, + }; + try + { + newModuleInfo.Read(reader); + + // Skip parsing and use data loaded from cache + moduleInfo = newModuleInfo; + return true; + } + catch + { + // Skip loading cache + return false; + } + } + } + } +} diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index 9ea37c01e..2c5149914 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -100,7 +100,7 @@ namespace Flax.Build.Bindings return value; if (typeInfo.Type == "String") return $"Variant(StringView({value}))"; - if (typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference" || typeInfo.Type == "ScriptingObjectReference") + if (typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference" || typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "SoftObjectReference") return $"Variant({value}.Get())"; if (typeInfo.IsArray) { @@ -149,7 +149,7 @@ namespace Flax.Build.Bindings return $"((StringView){value}).GetText()"; if (typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference") return $"ScriptingObject::Cast<{typeInfo.GenericArgs[0].Type}>((Asset*){value})"; - if (typeInfo.Type == "ScriptingObjectReference") + if (typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "SoftObjectReference") return $"ScriptingObject::Cast<{typeInfo.GenericArgs[0].Type}>((ScriptingObject*){value})"; if (typeInfo.IsArray) throw new Exception($"Not supported type to convert from the Variant to fixed-size array '{typeInfo}[{typeInfo.ArraySize}]'."); @@ -340,8 +340,8 @@ namespace Flax.Build.Bindings } } - // ScriptingObjectReference or AssetReference or WeakAssetReference - if ((typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference") && typeInfo.GenericArgs != null) + // ScriptingObjectReference or AssetReference or WeakAssetReference or SoftObjectReference + if ((typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference" || typeInfo.Type == "SoftObjectReference") && typeInfo.GenericArgs != null) { type = "MonoObject*"; return "{0}.GetManagedInstance()"; @@ -448,8 +448,8 @@ namespace Flax.Build.Bindings type = "MonoReflectionType*"; return "MUtils::UnboxVariantType({0})"; default: - // ScriptingObjectReference or AssetReference or WeakAssetReference - if ((typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference") && typeInfo.GenericArgs != null) + // ScriptingObjectReference or AssetReference or WeakAssetReference or SoftObjectReference + if ((typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference" || typeInfo.Type == "SoftObjectReference") && typeInfo.GenericArgs != null) { // For non-pod types converting only, other API converts managed to unmanaged object in C# wrapper code) if (CppNonPodTypesConvertingGeneration) @@ -832,7 +832,7 @@ namespace Flax.Build.Bindings contents.AppendLine(" ScriptingTypeHandle managedTypeHandle = object->GetTypeHandle();"); contents.AppendLine(" const ScriptingType* managedTypePtr = &managedTypeHandle.GetType();"); - contents.AppendLine(" while (managedTypePtr->Class.Spawn != &ManagedBinaryModule::ManagedObjectSpawn)"); + contents.AppendLine(" while (managedTypePtr->Script.Spawn != &ManagedBinaryModule::ManagedObjectSpawn)"); contents.AppendLine(" {"); contents.AppendLine(" managedTypeHandle = managedTypePtr->GetBaseType();"); contents.AppendLine(" managedTypePtr = &managedTypeHandle.GetType();"); @@ -841,7 +841,7 @@ namespace Flax.Build.Bindings contents.AppendLine(" if (IsDuringWrapperCall)"); contents.AppendLine(" {"); contents.AppendLine(" // Prevent stack overflow by calling native base method"); - contents.AppendLine(" const auto scriptVTableBase = managedTypePtr->Class.ScriptVTableBase;"); + contents.AppendLine(" const auto scriptVTableBase = managedTypePtr->Script.ScriptVTableBase;"); contents.Append($" return (object->**({functionInfo.UniqueName}_Signature*)&scriptVTableBase[{scriptVTableIndex} + 2])("); separator = false; for (var i = 0; i < functionInfo.Parameters.Count; i++) @@ -854,7 +854,7 @@ namespace Flax.Build.Bindings } contents.AppendLine(");"); contents.AppendLine(" }"); - contents.AppendLine(" auto scriptVTable = (MMethod**)managedTypePtr->Class.ScriptVTable;"); + contents.AppendLine(" auto scriptVTable = (MMethod**)managedTypePtr->Script.ScriptVTable;"); contents.AppendLine($" ASSERT(scriptVTable && scriptVTable[{scriptVTableIndex}]);"); contents.AppendLine($" auto method = scriptVTable[{scriptVTableIndex}];"); contents.AppendLine(" MonoObject* exception = nullptr;"); @@ -961,7 +961,7 @@ namespace Flax.Build.Bindings var classInfo = typeInfo as ClassInfo; var structureInfo = typeInfo as StructureInfo; var baseType = classInfo?.BaseType ?? structureInfo?.BaseType; - if (baseType != null && baseType.Type == "ISerializable") + if (classInfo != null && classInfo.IsBaseTypeHidden) baseType = null; CppAutoSerializeFields.Clear(); CppAutoSerializeProperties.Clear(); @@ -1050,6 +1050,25 @@ namespace Flax.Build.Bindings contents.Append('}').AppendLine(); } + private static string GenerateCppInterfaceInheritanceTable(BuildData buildData, StringBuilder contents, ModuleInfo moduleInfo, ClassStructInfo typeInfo, string typeNameNative) + { + var interfacesPtr = "nullptr"; + var interfaces = typeInfo.Interfaces; + if (interfaces != null) + { + interfacesPtr = typeNameNative + "_Interfaces"; + contents.Append("static const ScriptingType::InterfaceImplementation ").Append(interfacesPtr).AppendLine("[] = {"); + for (int i = 0; i < interfaces.Count; i++) + { + var interfaceInfo = interfaces[i]; + contents.Append(" { &").Append(interfaceInfo.NativeName).Append("::TypeInitializer, (int16)VTABLE_OFFSET(").Append(typeInfo.NativeName).Append(", ").Append(interfaceInfo.NativeName).AppendLine(") },"); + } + contents.AppendLine(" { nullptr, 0 },"); + contents.AppendLine("};"); + } + return interfacesPtr; + } + private static void GenerateCppClass(BuildData buildData, StringBuilder contents, ModuleInfo moduleInfo, ClassInfo classInfo) { var classTypeNameNative = classInfo.FullNameNative; @@ -1058,6 +1077,7 @@ namespace Flax.Build.Bindings var classTypeNameInternal = classInfo.NativeName; if (classInfo.Parent != null && !(classInfo.Parent is FileInfo)) classTypeNameInternal = classInfo.Parent.FullNameNative + '_' + classTypeNameInternal; + var useScripting = classInfo.IsStatic || classInfo.IsScriptingObject; if (classInfo.IsAutoSerialization) GenerateCppAutoSerialization(buildData, contents, moduleInfo, classInfo, classTypeNameNative); @@ -1070,32 +1090,34 @@ namespace Flax.Build.Bindings // Events foreach (var eventInfo in classInfo.Events) { - CppIncludeFiles.Add("Engine/Scripting/ManagedCLR/MEvent.h"); + if (!useScripting) + continue; + var paramsCount = eventInfo.Type.GenericArgs?.Count ?? 0; // C# event invoking wrapper (calls C# event from C++ delegate) + CppIncludeFiles.Add("Engine/Scripting/ManagedCLR/MEvent.h"); contents.Append(" "); if (eventInfo.IsStatic) contents.Append("static "); contents.AppendFormat("void {0}_ManagedWrapper(", eventInfo.Name); - for (var i = 0; i < eventInfo.Type.GenericArgs.Count; i++) + for (var i = 0; i < paramsCount; i++) { if (i != 0) contents.Append(", "); - contents.Append(eventInfo.Type.GenericArgs[i]); - contents.Append(" arg" + i); + contents.Append(eventInfo.Type.GenericArgs[i]).Append(" arg" + i); } contents.Append(')').AppendLine(); contents.Append(" {").AppendLine(); contents.Append(" static MMethod* mmethod = nullptr;").AppendLine(); contents.Append(" if (!mmethod)").AppendLine(); - contents.AppendFormat(" mmethod = {1}::GetStaticClass()->GetMethod(\"Internal_{0}_Invoke\", {2});", eventInfo.Name, classTypeNameNative, eventInfo.Type.GenericArgs.Count).AppendLine(); + contents.AppendFormat(" mmethod = {1}::GetStaticClass()->GetMethod(\"Internal_{0}_Invoke\", {2});", eventInfo.Name, classTypeNameNative, paramsCount).AppendLine(); contents.Append(" CHECK(mmethod);").AppendLine(); contents.Append(" MonoObject* exception = nullptr;").AppendLine(); - if (eventInfo.Type.GenericArgs.Count == 0) + if (paramsCount == 0) contents.AppendLine(" void** params = nullptr;"); else - contents.AppendLine($" void* params[{eventInfo.Type.GenericArgs.Count}];"); - for (var i = 0; i < eventInfo.Type.GenericArgs.Count; i++) + contents.AppendLine($" void* params[{paramsCount}];"); + for (var i = 0; i < paramsCount; i++) { var paramType = eventInfo.Type.GenericArgs[i]; var paramName = "arg" + i; @@ -1118,7 +1140,7 @@ namespace Flax.Build.Bindings contents.Append("bool bind)").AppendLine(); contents.Append(" {").AppendLine(); contents.Append(" Function(params, {paramsCount}), {classTypeNameNative}::TypeInitializer, StringView(TEXT(\"{eventInfo.Name}\"), {eventInfo.Name.Length}));"); + contents.Append(" }").AppendLine().AppendLine(); + + // Scripting event wrapper binding method (binds/unbinds generic wrapper to C++ delegate) + contents.AppendFormat(" static void {0}_Bind(", eventInfo.Name); + contents.AppendFormat("{0}* obj, void* instance, bool bind)", classTypeNameNative).AppendLine(); + contents.Append(" {").AppendLine(); + contents.Append(" Function f;").AppendLine(); + if (eventInfo.IsStatic) + contents.AppendFormat(" f.Bind<{0}_Wrapper>();", eventInfo.Name).AppendLine(); + else + contents.AppendFormat(" f.Bind<{1}Internal, &{1}Internal::{0}_Wrapper>(({1}Internal*)instance);", eventInfo.Name, classTypeNameInternal).AppendLine(); + contents.Append(" if (bind)").AppendLine(); + contents.AppendFormat(" {0}{1}.Bind(f);", bindPrefix, eventInfo.Name).AppendLine(); + contents.Append(" else").AppendLine(); + contents.AppendFormat(" {0}{1}.Unbind(f);", bindPrefix, eventInfo.Name).AppendLine(); + contents.Append(" }").AppendLine().AppendLine(); } // Fields foreach (var fieldInfo in classInfo.Fields) { + if (!useScripting) + continue; if (fieldInfo.Getter != null) GenerateCppWrapperFunction(buildData, contents, classInfo, fieldInfo.Getter, "{0}"); if (fieldInfo.Setter != null) @@ -1149,6 +1223,8 @@ namespace Flax.Build.Bindings // Properties foreach (var propertyInfo in classInfo.Properties) { + if (!useScripting) + continue; if (propertyInfo.Getter != null) GenerateCppWrapperFunction(buildData, contents, classInfo, propertyInfo.Getter); if (propertyInfo.Setter != null) @@ -1158,6 +1234,8 @@ namespace Flax.Build.Bindings // Functions foreach (var functionInfo in classInfo.Functions) { + if (!useScripting) + throw new Exception($"Not supported function {functionInfo.Name} inside non-static and non-scripting class type {classInfo.Name}."); GenerateCppWrapperFunction(buildData, contents, classInfo, functionInfo); } @@ -1267,50 +1345,88 @@ namespace Flax.Build.Bindings // Runtime initialization (internal methods binding) contents.AppendLine(" static void InitRuntime()"); contents.AppendLine(" {"); - foreach (var eventInfo in classInfo.Events) + if (useScripting) { - contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{eventInfo.Name}_Bind\", &{eventInfo.Name}_ManagedBind);"); - } - foreach (var fieldInfo in classInfo.Fields) - { - if (fieldInfo.Getter != null) - contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{fieldInfo.Getter.UniqueName}\", &{fieldInfo.Getter.UniqueName});"); - if (fieldInfo.Setter != null) - contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{fieldInfo.Setter.UniqueName}\", &{fieldInfo.Setter.UniqueName});"); - } - foreach (var propertyInfo in classInfo.Properties) - { - if (propertyInfo.Getter != null) - contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{propertyInfo.Getter.UniqueName}\", &{propertyInfo.Getter.UniqueName});"); - if (propertyInfo.Setter != null) - contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{propertyInfo.Setter.UniqueName}\", &{propertyInfo.Setter.UniqueName});"); - } - foreach (var functionInfo in classInfo.Functions) - { - contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{functionInfo.UniqueName}\", &{functionInfo.UniqueName});"); + foreach (var eventInfo in classInfo.Events) + { + contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{eventInfo.Name}_Bind\", &{eventInfo.Name}_ManagedBind);"); + + // Register scripting event binder + contents.AppendLine($" ScriptingEvents::EventsTable[Pair({classTypeNameNative}::TypeInitializer, StringView(TEXT(\"{eventInfo.Name}\"), {eventInfo.Name.Length}))] = (void(*)(ScriptingObject*, void*, bool)){classTypeNameInternal}Internal::{eventInfo.Name}_Bind;"); + } + foreach (var fieldInfo in classInfo.Fields) + { + if (fieldInfo.Getter != null) + contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{fieldInfo.Getter.UniqueName}\", &{fieldInfo.Getter.UniqueName});"); + if (fieldInfo.Setter != null) + contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{fieldInfo.Setter.UniqueName}\", &{fieldInfo.Setter.UniqueName});"); + } + foreach (var propertyInfo in classInfo.Properties) + { + if (propertyInfo.Getter != null) + contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{propertyInfo.Getter.UniqueName}\", &{propertyInfo.Getter.UniqueName});"); + if (propertyInfo.Setter != null) + contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{propertyInfo.Setter.UniqueName}\", &{propertyInfo.Setter.UniqueName});"); + } + foreach (var functionInfo in classInfo.Functions) + { + contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{functionInfo.UniqueName}\", &{functionInfo.UniqueName});"); + } } GenerateCppClassInitRuntime?.Invoke(buildData, classInfo, contents); - contents.AppendLine(" }"); - contents.Append('}'); - contents.Append(';'); - contents.AppendLine(); + contents.AppendLine(" }").AppendLine(); + + if (!useScripting && !classInfo.IsAbstract) + { + // Constructor + contents.AppendLine(" static void Ctor(void* ptr)"); + contents.AppendLine(" {"); + contents.AppendLine($" new(ptr){classTypeNameNative}();"); + contents.AppendLine(" }").AppendLine(); + + // Destructor + contents.AppendLine(" static void Dtor(void* ptr)"); + contents.AppendLine(" {"); + contents.AppendLine($" (({classTypeNameNative}*)ptr)->~{classInfo.NativeName}();"); + contents.AppendLine(" }").AppendLine(); + } + + contents.Append('}').Append(';').AppendLine(); contents.AppendLine(); + // Interfaces + var interfacesTable = GenerateCppInterfaceInheritanceTable(buildData, contents, moduleInfo, classInfo, classTypeNameNative); + // Type initializer contents.Append($"ScriptingTypeInitializer {classTypeNameNative}::TypeInitializer((BinaryModule*)GetBinaryModule{moduleInfo.Name}(), "); contents.Append($"StringAnsiView(\"{classTypeNameManaged}\", {classTypeNameManaged.Length}), "); contents.Append($"sizeof({classTypeNameNative}), "); contents.Append($"&{classTypeNameInternal}Internal::InitRuntime, "); - if (!classInfo.IsStatic && !classInfo.NoSpawn) - contents.Append($"(ScriptingType::SpawnHandler)&{classTypeNameNative}::Spawn, "); + if (useScripting) + { + if (classInfo.IsStatic || classInfo.NoSpawn) + contents.Append("&ScriptingType::DefaultSpawn, "); + else + contents.Append($"(ScriptingType::SpawnHandler)&{classTypeNameNative}::Spawn, "); + if (classInfo.BaseType != null && useScripting) + contents.Append($"&{classInfo.BaseType}::TypeInitializer, "); + else + contents.Append("nullptr, "); + contents.Append(setupScriptVTable); + } else - contents.Append("&ScriptingType::DefaultSpawn, "); - if (classInfo.BaseType != null) - contents.Append($"&{classInfo.BaseType}::TypeInitializer, "); - else - contents.Append("nullptr, "); - contents.Append(setupScriptVTable); + { + if (classInfo.IsAbstract) + contents.Append("nullptr, nullptr, "); + else + contents.Append($"&{classTypeNameInternal}Internal::Ctor, &{classTypeNameInternal}Internal::Dtor, "); + if (classInfo.BaseType != null) + contents.Append($"&{classInfo.BaseType}::TypeInitializer"); + else + contents.Append("nullptr"); + } + contents.Append(", ").Append(interfacesTable); contents.Append(");"); contents.AppendLine(); @@ -1505,10 +1621,46 @@ namespace Flax.Build.Bindings } } - private static bool GenerateCppType(BuildData buildData, StringBuilder contents, ModuleInfo moduleInfo, object type) + private static void GenerateCppInterface(BuildData buildData, StringBuilder contents, ModuleInfo moduleInfo, InterfaceInfo interfaceInfo) + { + var interfaceTypeNameNative = interfaceInfo.FullNameNative; + var interfaceTypeNameManaged = interfaceInfo.FullNameManaged; + var interfaceTypeNameManagedInternalCall = interfaceTypeNameManaged.Replace('+', '/'); + var interfaceTypeNameInternal = interfaceInfo.NativeName; + if (interfaceInfo.Parent != null && !(interfaceInfo.Parent is FileInfo)) + interfaceTypeNameInternal = interfaceInfo.Parent.FullNameNative + '_' + interfaceTypeNameInternal; + + contents.AppendLine(); + contents.AppendFormat("class {0}Internal", interfaceTypeNameInternal).AppendLine(); + contents.Append('{').AppendLine(); + contents.AppendLine("public:"); + + // Runtime initialization (internal methods binding) + contents.AppendLine(" static void InitRuntime()"); + contents.AppendLine(" {"); + contents.AppendLine(" }").AppendLine(); + + contents.Append('}').Append(';').AppendLine(); + contents.AppendLine(); + + // Type initializer + contents.Append($"ScriptingTypeInitializer {interfaceTypeNameNative}::TypeInitializer((BinaryModule*)GetBinaryModule{moduleInfo.Name}(), "); + contents.Append($"StringAnsiView(\"{interfaceTypeNameManaged}\", {interfaceTypeNameManaged.Length}), "); + contents.Append($"&{interfaceTypeNameInternal}Internal::InitRuntime"); + contents.Append(");"); + contents.AppendLine(); + + // Nested types + foreach (var apiTypeInfo in interfaceInfo.Children) + { + GenerateCppType(buildData, contents, moduleInfo, apiTypeInfo); + } + } + + private static void GenerateCppType(BuildData buildData, StringBuilder contents, ModuleInfo moduleInfo, object type) { if (type is ApiTypeInfo apiTypeInfo && apiTypeInfo.IsInBuild) - return false; + return; try { @@ -1516,18 +1668,16 @@ namespace Flax.Build.Bindings GenerateCppClass(buildData, contents, moduleInfo, classInfo); else if (type is StructureInfo structureInfo) GenerateCppStruct(buildData, contents, moduleInfo, structureInfo); + else if (type is InterfaceInfo interfaceInfo) + GenerateCppInterface(buildData, contents, moduleInfo, interfaceInfo); else if (type is InjectCppCodeInfo injectCppCodeInfo) contents.AppendLine(injectCppCodeInfo.Code); - else - return false; } catch { Log.Error($"Failed to generate C++ bindings for {type}."); throw; } - - return true; } private static void GenerateCppCppUsedNonPodTypes(BuildData buildData, ApiTypeInfo apiType) @@ -1575,21 +1725,16 @@ namespace Flax.Build.Bindings CppReferencesFiles.Add(fileInfo); } } - var headerPos = contents.Length; foreach (var child in moduleInfo.Children) { foreach (var apiTypeInfo in child.Children) { - if (GenerateCppType(buildData, contents, moduleInfo, apiTypeInfo)) - bindings.UseBindings = true; + GenerateCppType(buildData, contents, moduleInfo, apiTypeInfo); } } - if (!bindings.UseBindings) - return; - GenerateCppModuleSource?.Invoke(buildData, moduleInfo, contents); { @@ -1883,7 +2028,7 @@ namespace Flax.Build.Bindings var binaryModuleSourcePath = Path.Combine(project.ProjectFolderPath, "Source", binaryModuleName + ".Gen.cpp"); contents.AppendLine("// This code was auto-generated. Do not modify it."); contents.AppendLine(); - contents.AppendLine($"#include \"Engine/Scripting/BinaryModule.h\""); + contents.AppendLine("#include \"Engine/Scripting/BinaryModule.h\""); contents.AppendLine($"#include \"{binaryModuleName}.Gen.h\""); contents.AppendLine(); contents.AppendLine($"StaticallyLinkedBinaryModuleInitializer StaticallyLinkedBinaryModule{binaryModuleName}(GetBinaryModule{binaryModuleName});"); diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs index b39f2f066..7714e6abd 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs @@ -17,6 +17,7 @@ namespace Flax.Build.Bindings public Stack ScopeTypeStack; public Stack ScopeAccessStack; public Dictionary PreprocessorDefines; + public List StringCache; public ApiTypeInfo ValidScopeInfoFromStack { @@ -46,14 +47,12 @@ namespace Flax.Build.Bindings } } - private static List _commentCache; - private static string[] ParseComment(ref ParsingContext context) { - if (_commentCache == null) - _commentCache = new List(); + if (context.StringCache == null) + context.StringCache = new List(); else - _commentCache.Clear(); + context.StringCache.Clear(); int tokensCount = 0; bool isValid = true; @@ -77,7 +76,7 @@ namespace Flax.Build.Bindings if (commentLine.StartsWith("// ")) commentLine = "/// " + commentLine.Substring(3); - _commentCache.Insert(0, commentLine); + context.StringCache.Insert(0, commentLine); break; } default: @@ -90,14 +89,14 @@ namespace Flax.Build.Bindings for (var i = 0; i < tokensCount; i++) context.Tokenizer.NextToken(true, true); - if (_commentCache.Count == 1) + if (context.StringCache.Count == 1) { // Ensure to have summary begin/end pair - _commentCache.Insert(0, "/// "); - _commentCache.Add("/// "); + context.StringCache.Insert(0, "/// "); + context.StringCache.Add("/// "); } - return _commentCache.ToArray(); + return context.StringCache.ToArray(); } private struct TagParameter @@ -383,17 +382,101 @@ namespace Flax.Build.Bindings return name; } + private static void ParseInheritance(ref ParsingContext context, ClassStructInfo desc, out bool isFinal) + { + desc.BaseType = null; + desc.BaseTypeInheritance = AccessLevel.Private; + + var token = context.Tokenizer.NextToken(); + isFinal = token.Value == "final"; + if (isFinal) + token = context.Tokenizer.NextToken(); + + if (token.Type == TokenType.Colon) + { + while (token.Type != TokenType.LeftCurlyBrace) + { + var accessToken = context.Tokenizer.ExpectToken(TokenType.Identifier); + switch (accessToken.Value) + { + case "public": + desc.BaseTypeInheritance = AccessLevel.Public; + token = context.Tokenizer.ExpectToken(TokenType.Identifier); + break; + case "protected": + desc.BaseTypeInheritance = AccessLevel.Protected; + token = context.Tokenizer.ExpectToken(TokenType.Identifier); + break; + case "private": + token = context.Tokenizer.ExpectToken(TokenType.Identifier); + break; + default: + token = accessToken; + break; + } + + var baseTypeInfo = new TypeInfo + { + Type = token.Value, + }; + if (token.Value.Length > 2 && token.Value[0] == 'I' && char.IsUpper(token.Value[1])) + { + // Interface + if (desc.InterfaceNames == null) + desc.InterfaceNames = new List(); + desc.InterfaceNames.Add(baseTypeInfo); + token = context.Tokenizer.NextToken(); + continue; + } + + if (desc.BaseType != null) + { + // Allow for multiple base classes, just the first one needs to be a valid base type + break; + throw new Exception($"Invalid '{desc.Name}' inheritance (only single base class is allowed for scripting types, excluding interfaces)."); + } + desc.BaseType = baseTypeInfo; + token = context.Tokenizer.NextToken(); + if (token.Type == TokenType.LeftCurlyBrace) + { + break; + } + if (token.Type == TokenType.LeftAngleBracket) + { + var genericType = context.Tokenizer.ExpectToken(TokenType.Identifier); + token = context.Tokenizer.ExpectToken(TokenType.RightAngleBracket); + desc.BaseType.GenericArgs = new List + { + new TypeInfo + { + Type = genericType.Value, + } + }; + + // TODO: find better way to resolve this (custom base type attribute?) + if (desc.BaseType.Type == "ShaderAssetTypeBase") + { + desc.BaseType = desc.BaseType.GenericArgs[0]; + } + + token = context.Tokenizer.NextToken(); + } + } + token = context.Tokenizer.PreviousToken(); + } + else + { + // No base type + token = context.Tokenizer.PreviousToken(); + } + } + private static ClassInfo ParseClass(ref ParsingContext context) { var desc = new ClassInfo { - Children = new List(), Access = context.CurrentAccessLevel, BaseTypeInheritance = AccessLevel.Private, - Functions = new List(), - Properties = new List(), - Fields = new List(), - Events = new List(), }; // Read the documentation comment @@ -410,64 +493,8 @@ namespace Flax.Build.Bindings // Read name desc.Name = desc.NativeName = ParseName(ref context); - // Read class inheritance - token = context.Tokenizer.NextToken(); - var isFinal = token.Value == "final"; - if (isFinal) - token = context.Tokenizer.NextToken(); - if (token.Type == TokenType.Colon) - { - // Current class does have inheritance defined - var accessToken = context.Tokenizer.ExpectToken(TokenType.Identifier); - switch (accessToken.Value) - { - case "public": - desc.BaseTypeInheritance = AccessLevel.Public; - token = context.Tokenizer.ExpectToken(TokenType.Identifier); - break; - case "protected": - desc.BaseTypeInheritance = AccessLevel.Protected; - token = context.Tokenizer.ExpectToken(TokenType.Identifier); - break; - case "private": - token = context.Tokenizer.ExpectToken(TokenType.Identifier); - break; - } - - desc.BaseType = new TypeInfo - { - Type = token.Value, - }; - token = context.Tokenizer.NextToken(); - if (token.Type == TokenType.LeftAngleBracket) - { - var genericType = context.Tokenizer.ExpectToken(TokenType.Identifier); - context.Tokenizer.ExpectToken(TokenType.RightAngleBracket); - desc.BaseType.GenericArgs = new List - { - new TypeInfo - { - Type = genericType.Value, - } - }; - - // TODO: find better way to resolve this (custom base type attribute?) - if (desc.BaseType.Type == "ShaderAssetTypeBase") - { - desc.BaseType = desc.BaseType.GenericArgs[0]; - } - } - else - { - token = context.Tokenizer.PreviousToken(); - } - } - else - { - // No base type - token = context.Tokenizer.PreviousToken(); - desc.BaseType = null; - } + // Read inheritance + ParseInheritance(ref context, desc, out var isFinal); // Process tag parameters foreach (var tag in tagParams) @@ -523,12 +550,72 @@ namespace Flax.Build.Bindings return desc; } + private static InterfaceInfo ParseInterface(ref ParsingContext context) + { + var desc = new InterfaceInfo + { + Access = context.CurrentAccessLevel, + }; + + // Read the documentation comment + desc.Comment = ParseComment(ref context); + + // Read parameters from the tag + var tagParams = ParseTagParameters(ref context); + + // Read 'class' keyword + var token = context.Tokenizer.NextToken(); + if (token.Value != "class") + throw new Exception($"Invalid API_INTERFACE usage (expected 'class' keyword but got '{token.Value} {context.Tokenizer.NextToken().Value}')."); + + // Read name + desc.Name = desc.NativeName = ParseName(ref context); + if (desc.Name.Length < 2 || desc.Name[0] != 'I' || !char.IsUpper(desc.Name[1])) + throw new Exception($"Invalid API_INTERFACE name '{desc.Name}' (it must start with 'I' character followed by the uppercase character)."); + + // Read inheritance + ParseInheritance(ref context, desc, out _); + + // Process tag parameters + foreach (var tag in tagParams) + { + switch (tag.Tag.ToLower()) + { + case "public": + desc.Access = AccessLevel.Public; + break; + case "protected": + desc.Access = AccessLevel.Protected; + break; + case "private": + desc.Access = AccessLevel.Private; + break; + case "inbuild": + desc.IsInBuild = true; + break; + case "attributes": + desc.Attributes = tag.Value; + break; + case "name": + desc.Name = tag.Value; + break; + case "namespace": + desc.Namespace = tag.Value; + break; + default: + Log.Warning($"Unknown or not supported tag parameter {tag} used on interface {desc.Name} at line {context.Tokenizer.CurrentLine}"); + break; + } + } + + return desc; + } + private static FunctionInfo ParseFunction(ref ParsingContext context) { var desc = new FunctionInfo { Access = context.CurrentAccessLevel, - Parameters = new List(), }; // Read the documentation comment @@ -691,6 +778,35 @@ namespace Flax.Build.Bindings else propertyInfo.Setter = functionInfo; + if (propertyInfo.Getter != null && propertyInfo.Setter != null) + { + // Check if getter and setter types are matching (const and ref specifiers are skipped) + var getterType = propertyInfo.Getter.ReturnType; + var setterType = propertyInfo.Setter.Parameters[0].Type; + if (!string.Equals(getterType.Type, setterType.Type) || + getterType.IsPtr != setterType.IsPtr || + getterType.IsArray != setterType.IsArray || + getterType.IsBitField != setterType.IsBitField || + getterType.ArraySize != setterType.ArraySize || + getterType.BitSize != setterType.BitSize || + !TypeInfo.Equals(getterType.GenericArgs, setterType.GenericArgs)) + { + // Skip compatible types + if (getterType.Type == "String" && setterType.Type == "StringView") + return propertyInfo; + if (getterType.Type == "Array" && setterType.Type == "Span" && getterType.GenericArgs?.Count == 1 && setterType.GenericArgs?.Count == 1 && getterType.GenericArgs[0].Equals(setterType.GenericArgs[0])) + return propertyInfo; + throw new Exception($"Property {propertyName} in class {classInfo.Name} (line {context.Tokenizer.CurrentLine}) has mismatching getter return type ({getterType}) and setter parameter type ({setterType}). Both getter and setter methods must use the same value type used for property."); + } + + // Fix documentation comment to reflect both getter and setters available + for (var i = 0; i < propertyInfo.Comment.Length; i++) + { + ref var comment = ref propertyInfo.Comment[i]; + comment = comment.Replace("/// Gets ", "/// Gets or sets "); + } + } + return propertyInfo; } @@ -698,9 +814,7 @@ namespace Flax.Build.Bindings { var desc = new EnumInfo { - Children = new List(), Access = context.CurrentAccessLevel, - Entries = new List(), }; // Read the documentation comment @@ -855,10 +969,7 @@ namespace Flax.Build.Bindings { var desc = new StructureInfo { - Children = new List(), Access = context.CurrentAccessLevel, - Fields = new List(), - Functions = new List(), }; // Read the documentation comment @@ -875,55 +986,8 @@ namespace Flax.Build.Bindings // Read name desc.Name = desc.NativeName = ParseName(ref context); - // Read structure inheritance - token = context.Tokenizer.NextToken(); - if (token.Type == TokenType.Colon) - { - // Current class does have inheritance defined - var accessToken = context.Tokenizer.ExpectToken(TokenType.Identifier); - switch (accessToken.Value) - { - case "public": - token = context.Tokenizer.ExpectToken(TokenType.Identifier); - break; - case "protected": - token = context.Tokenizer.ExpectToken(TokenType.Identifier); - break; - case "private": - token = context.Tokenizer.ExpectToken(TokenType.Identifier); - break; - default: - token = accessToken; - break; - } - - desc.BaseType = new TypeInfo - { - Type = token.Value, - }; - token = context.Tokenizer.NextToken(); - if (token.Type == TokenType.LeftAngleBracket) - { - var genericType = context.Tokenizer.ExpectToken(TokenType.Identifier); - context.Tokenizer.ExpectToken(TokenType.RightAngleBracket); - desc.BaseType.GenericArgs = new List - { - new TypeInfo - { - Type = genericType.Value, - } - }; - } - else - { - token = context.Tokenizer.PreviousToken(); - } - } - else - { - // No base type - token = context.Tokenizer.PreviousToken(); - } + // Read inheritance + ParseInheritance(ref context, desc, out _); // Process tag parameters foreach (var tag in tagParams) @@ -974,11 +1038,30 @@ namespace Flax.Build.Bindings // Read parameters from the tag var tagParams = ParseTagParameters(ref context); + context.Tokenizer.SkipUntil(TokenType.Identifier); - // Read 'static' keyword - desc.IsStatic = context.Tokenizer.NextToken().Value == "static"; - if (!desc.IsStatic) - context.Tokenizer.PreviousToken(); + // Read 'static' or 'mutable' + Token token; + var isMutable = false; + while (true) + { + token = context.Tokenizer.CurrentToken; + if (!desc.IsStatic && token.Value == "static") + { + desc.IsStatic = true; + context.Tokenizer.NextToken(); + } + else if (!isMutable && token.Value == "mutable") + { + isMutable = true; + context.Tokenizer.NextToken(); + } + else + { + context.Tokenizer.PreviousToken(); + break; + } + } // Read type desc.Type = ParseType(ref context); @@ -987,11 +1070,10 @@ namespace Flax.Build.Bindings desc.Name = ParseName(ref context); // Read ';' or default value or array size or bit-field size - var token = context.Tokenizer.ExpectAnyTokens(new[] { TokenType.SemiColon, TokenType.Equal, TokenType.LeftBracket, TokenType.Colon }); + token = context.Tokenizer.ExpectAnyTokens(new[] { TokenType.SemiColon, TokenType.Equal, TokenType.LeftBracket, TokenType.Colon }); if (token.Type == TokenType.Equal) { - //context.Tokenizer.SkipUntil(TokenType.SemiColon, out var defaultValue); - context.Tokenizer.SkipUntil(TokenType.SemiColon); + context.Tokenizer.SkipUntil(TokenType.SemiColon, out desc.DefaultValue, false); } else if (token.Type == TokenType.LeftBracket) { @@ -1106,7 +1188,6 @@ namespace Flax.Build.Bindings context.Tokenizer.ExpectToken(TokenType.LeftParent); var desc = new InjectCppCodeInfo { - Children = new List(), Code = context.Tokenizer.ExpectToken(TokenType.String).Value.Replace("\\\"", "\""), }; desc.Code = desc.Code.Substring(1, desc.Code.Length - 2); diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs index 45394b510..a7e1508eb 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs @@ -4,6 +4,8 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using Flax.Build.NativeCpp; using BuildData = Flax.Build.Builder.BuildData; @@ -58,16 +60,12 @@ namespace Flax.Build.Bindings private static ModuleInfo ParseModuleInner(BuildData buildData, Module module, BuildOptions moduleOptions = null) { - if (buildData.ModulesInfo.TryGetValue(module, out var moduleInfo)) - return moduleInfo; - // Setup bindings module info descriptor - moduleInfo = new ModuleInfo + var moduleInfo = new ModuleInfo { Module = module, Name = module.BinaryModuleName, Namespace = string.Empty, - Children = new List(), }; if (string.IsNullOrEmpty(moduleInfo.Name)) throw new Exception("Module name cannot be empty."); @@ -81,320 +79,375 @@ namespace Flax.Build.Bindings throw new Exception($"Cannot parse module {module.Name} without options."); // Collect all header files that can have public symbols for API - var headerFiles = moduleOptions.SourceFiles.Where(x => x.EndsWith(".h")).ToList(); - - // Skip if no header files to process + var headerFiles = new List(moduleOptions.SourceFiles.Count / 2); + for (int i = 0; i < moduleOptions.SourceFiles.Count; i++) + { + if (moduleOptions.SourceFiles[i].EndsWith(".h", StringComparison.OrdinalIgnoreCase)) + headerFiles.Add(moduleOptions.SourceFiles[i]); + } if (headerFiles.Count == 0) return moduleInfo; - - // Find and load files with API tags - string[] headerFilesContents = null; - //using (new ProfileEventScope("LoadHeaderFiles")) + if (module.Name == "Core") { - var anyApi = false; + // Special case for Core module to ignore API tags defines for (int i = 0; i < headerFiles.Count; i++) { - // Skip scripting types definitions file - if (headerFiles[i].Replace('\\', '/').EndsWith("Engine/Core/Config.h", StringComparison.Ordinal) || - headerFiles[i].EndsWith("EditorContextAPI.h", StringComparison.Ordinal)) - continue; - - // Check if file contains any valid API tag - var contents = File.ReadAllText(headerFiles[i]); - for (int j = 0; j < ApiTokens.SearchTags.Length; j++) + if (headerFiles[i].EndsWith("Config.h", StringComparison.Ordinal)) { - if (contents.Contains(ApiTokens.SearchTags[j])) - { - if (headerFilesContents == null) - headerFilesContents = new string[headerFiles.Count]; - headerFilesContents[i] = contents; - anyApi = true; - break; - } - } - } - - if (!anyApi) - return moduleInfo; - } - - // Skip if none of the headers was modified after last time generated C++ file was edited - // TODO: skip parsing if module has API cache file -> then load it from disk - /*if (!forceGenerate) - { - var lastGenerateTime = File.GetLastWriteTime(bindings.GeneratedCppFilePath); - var anyModified = false; - for (int i = 0; i < headerFiles.Count; i++) - { - if (File.GetLastWriteTime(headerFiles[i]) > lastGenerateTime) - { - anyModified = true; + headerFiles.RemoveAt(i); break; } } + } - if (!anyModified) - return; - }*/ + // Sort file paths to have stable results + headerFiles.Sort(); - Log.Verbose($"Parsing API bindings for {module.Name} ({moduleInfo.Name})"); - - // Process all header files to generate the module API code reflection - var context = new ParsingContext + // Load cache + using (new ProfileEventScope("LoadCache")) { - CurrentAccessLevel = AccessLevel.Public, - ScopeTypeStack = new Stack(), - ScopeAccessStack = new Stack(), - PreprocessorDefines = new Dictionary(), - }; - for (int i = 0; i < headerFiles.Count; i++) - { - if (headerFilesContents[i] == null) - continue; - var fileInfo = new FileInfo + if (LoadCache(ref moduleInfo, moduleOptions, headerFiles)) { - Parent = null, - Children = new List(), - Name = headerFiles[i], - Namespace = moduleInfo.Name, - }; - moduleInfo.AddChild(fileInfo); + buildData.ModulesInfo[module] = moduleInfo; - try - { - // Tokenize the source - var tokenizer = new Tokenizer(); - tokenizer.Tokenize(headerFilesContents[i]); - - // Init the context - context.Tokenizer = tokenizer; - context.File = fileInfo; - context.ScopeInfo = null; - context.ScopeTypeStack.Clear(); - context.ScopeAccessStack.Clear(); - context.PreprocessorDefines.Clear(); - context.EnterScope(fileInfo); - - // Process the source code - ApiTypeInfo scopeType = null; - Token prevToken = null; - while (true) + // Initialize API + using (new ProfileEventScope("Init")) { - // Move to the next token - var token = tokenizer.NextToken(); - if (token == null) - continue; - if (token.Type == TokenType.EndOfFile) - break; - - // Parse API_.. tags in source code - if (token.Type == TokenType.Identifier && token.Value.StartsWith("API_", StringComparison.Ordinal)) - { - if (string.Equals(token.Value, ApiTokens.Class, StringComparison.Ordinal)) - { - if (!(context.ScopeInfo is FileInfo)) - throw new NotImplementedException("TODO: add support for nested classes in scripting API"); - - var classInfo = ParseClass(ref context); - scopeType = classInfo; - context.ScopeInfo.AddChild(scopeType); - context.CurrentAccessLevel = AccessLevel.Public; - } - else if (string.Equals(token.Value, ApiTokens.Property, StringComparison.Ordinal)) - { - var propertyInfo = ParseProperty(ref context); - } - else if (string.Equals(token.Value, ApiTokens.Function, StringComparison.Ordinal)) - { - var functionInfo = ParseFunction(ref context); - - if (context.ScopeInfo is ClassInfo classInfo) - classInfo.Functions.Add(functionInfo); - else if (context.ScopeInfo is StructureInfo structureInfo) - structureInfo.Functions.Add(functionInfo); - else - throw new Exception($"Not supported free-function {functionInfo.Name} at line {tokenizer.CurrentLine}. Place it in the class to use API bindings for it."); - } - else if (string.Equals(token.Value, ApiTokens.Enum, StringComparison.Ordinal)) - { - var enumInfo = ParseEnum(ref context); - context.ScopeInfo.AddChild(enumInfo); - } - else if (string.Equals(token.Value, ApiTokens.Struct, StringComparison.Ordinal)) - { - var structureInfo = ParseStructure(ref context); - scopeType = structureInfo; - context.ScopeInfo.AddChild(scopeType); - context.CurrentAccessLevel = AccessLevel.Public; - } - else if (string.Equals(token.Value, ApiTokens.Field, StringComparison.Ordinal)) - { - var fieldInfo = ParseField(ref context); - var scopeInfo = context.ValidScopeInfoFromStack; - - if (scopeInfo is ClassInfo classInfo) - classInfo.Fields.Add(fieldInfo); - else if (scopeInfo is StructureInfo structureInfo) - structureInfo.Fields.Add(fieldInfo); - else - throw new Exception($"Not supported location for field {fieldInfo.Name} at line {tokenizer.CurrentLine}. Place it in the class or structure to use API bindings for it."); - } - else if (string.Equals(token.Value, ApiTokens.Event, StringComparison.Ordinal)) - { - var eventInfo = ParseEvent(ref context); - var scopeInfo = context.ValidScopeInfoFromStack; - - if (scopeInfo is ClassInfo classInfo) - classInfo.Events.Add(eventInfo); - else - throw new Exception($"Not supported location for event {eventInfo.Name} at line {tokenizer.CurrentLine}. Place it in the class to use API bindings for it."); - } - else if (string.Equals(token.Value, ApiTokens.InjectCppCode, StringComparison.Ordinal)) - { - var injectCppCodeInfo = ParseInjectCppCode(ref context); - fileInfo.AddChild(injectCppCodeInfo); - } - else if (string.Equals(token.Value, ApiTokens.AutoSerialization, StringComparison.Ordinal)) - { - if (context.ScopeInfo is ClassInfo classInfo) - classInfo.IsAutoSerialization = true; - else if (context.ScopeInfo is StructureInfo structureInfo) - structureInfo.IsAutoSerialization = true; - else - throw new Exception($"Not supported location for {ApiTokens.AutoSerialization} at line {tokenizer.CurrentLine}. Place it in the class or structure that uses API bindings."); - } - } - - // Track access level inside class - if (context.ScopeInfo != null && token.Type == TokenType.Colon && prevToken != null && prevToken.Type == TokenType.Identifier) - { - if (string.Equals(prevToken.Value, "public", StringComparison.Ordinal)) - { - context.CurrentAccessLevel = AccessLevel.Public; - } - else if (string.Equals(prevToken.Value, "protected", StringComparison.Ordinal)) - { - context.CurrentAccessLevel = AccessLevel.Protected; - } - else if (string.Equals(prevToken.Value, "private", StringComparison.Ordinal)) - { - context.CurrentAccessLevel = AccessLevel.Private; - } - } - - // Handle preprocessor blocks - if (token.Type == TokenType.Preprocessor) - { - token = tokenizer.NextToken(); - switch (token.Value) - { - case "define": - { - token = tokenizer.NextToken(); - var name = token.Value; - var value = string.Empty; - token = tokenizer.NextToken(true); - while (token.Type != TokenType.Newline) - { - value += token.Value; - token = tokenizer.NextToken(true); - } - value = value.Trim(); - context.PreprocessorDefines[name] = value; - break; - } - case "if": - { - // Parse condition - var condition = string.Empty; - token = tokenizer.NextToken(true); - while (token.Type != TokenType.Newline) - { - condition += token.Value; - token = tokenizer.NextToken(true); - } - - // Replace contents with defines - condition = condition.Trim(); - condition = ReplacePreProcessorDefines(condition, context.PreprocessorDefines.Keys); - condition = ReplacePreProcessorDefines(condition, moduleOptions.PublicDefinitions); - condition = ReplacePreProcessorDefines(condition, moduleOptions.PrivateDefinitions); - condition = ReplacePreProcessorDefines(condition, moduleOptions.CompileEnv.PreprocessorDefinitions); - condition = condition.Replace("false", "0"); - condition = condition.Replace("true", "1"); - - // Check condition - // TODO: support expressions in preprocessor defines in API headers? - if (condition != "1") - { - // Skip chunk of code - ParsePreprocessorIf(fileInfo, tokenizer, ref token); - } - - break; - } - case "ifdef": - { - // Parse condition - var define = string.Empty; - token = tokenizer.NextToken(true); - while (token.Type != TokenType.Newline) - { - define += token.Value; - token = tokenizer.NextToken(true); - } - - // Check condition - define = define.Trim(); - if (!context.PreprocessorDefines.ContainsKey(define) && !moduleOptions.CompileEnv.PreprocessorDefinitions.Contains(define)) - { - ParsePreprocessorIf(fileInfo, tokenizer, ref token); - } - - break; - } - } - } - - // Scope tracking - if (token.Type == TokenType.LeftCurlyBrace) - { - context.ScopeTypeStack.Push(scopeType); - context.ScopeInfo = context.ScopeTypeStack.Peek(); - scopeType = null; - } - else if (token.Type == TokenType.RightCurlyBrace) - { - context.ScopeTypeStack.Pop(); - if (context.ScopeTypeStack.Count == 0) - throw new Exception($"Mismatch of the {{}} braces pair in file '{fileInfo.Name}' at line {tokenizer.CurrentLine}."); - context.ScopeInfo = context.ScopeTypeStack.Peek(); - if (context.ScopeInfo is FileInfo) - context.CurrentAccessLevel = AccessLevel.Public; - } - - prevToken = token; + moduleInfo.Init(buildData); } - } - catch (Exception ex) - { - Log.Error($"Failed to parse '{fileInfo.Name}' file to generate bindings."); - Log.Exception(ex); - throw; + + return moduleInfo; } } + // Parse bindings + Log.Verbose($"Parsing API bindings for {module.Name} ({moduleInfo.Name})"); + int concurrency = Math.Min(Math.Max(1, (int)(Environment.ProcessorCount * Configuration.ConcurrencyProcessorScale)), Configuration.MaxConcurrency); + concurrency = 1; // Disable concurrency for parsing (the gain is unnoticeable or even worse in some cases) + if (concurrency == 1 || headerFiles.Count < 2 * concurrency) + { + // Single-threaded + for (int i = 0; i < headerFiles.Count; i++) + { + using (new ProfileEventScope(Path.GetFileName(headerFiles[i]))) + { + ParseModuleInnerAsync(moduleInfo, moduleOptions, headerFiles, i); + } + } + } + else + { + // Multi-threaded + ThreadPool.GetMinThreads(out var workerThreads, out var completionPortThreads); + if (workerThreads != concurrency) + ThreadPool.SetMaxThreads(concurrency, completionPortThreads); + Parallel.For(0, headerFiles.Count, (i, state) => + { + using (new ProfileEventScope(Path.GetFileName(headerFiles[i]))) + { + ParseModuleInnerAsync(moduleInfo, moduleOptions, headerFiles, i); + } + }); + } + + // Save cache + using (new ProfileEventScope("SaveCache")) + { + SaveCache(moduleInfo, moduleOptions, headerFiles); + } + // Initialize API - moduleInfo.Init(buildData); + using (new ProfileEventScope("Init")) + { + moduleInfo.Init(buildData); + } return moduleInfo; } + private static void ParseModuleInnerAsync(ModuleInfo moduleInfo, BuildOptions moduleOptions, List headerFiles, int workIndex) + { + // Find and load files with API tags + bool hasApi = false; + string headerFileContents = File.ReadAllText(headerFiles[workIndex]); + for (int j = 0; j < ApiTokens.SearchTags.Length; j++) + { + if (headerFileContents.Contains(ApiTokens.SearchTags[j])) + { + hasApi = true; + break; + } + } + if (!hasApi) + return; + + // Process header file to generate the module API code reflection + var fileInfo = new FileInfo + { + Parent = null, + Name = headerFiles[workIndex], + Namespace = moduleInfo.Name, + }; + lock (moduleInfo) + { + moduleInfo.AddChild(fileInfo); + } + + try + { + // Tokenize the source + var tokenizer = new Tokenizer(); + tokenizer.Tokenize(headerFileContents); + + // Init the context + var context = new ParsingContext + { + File = fileInfo, + Tokenizer = tokenizer, + ScopeInfo = null, + CurrentAccessLevel = AccessLevel.Public, + ScopeTypeStack = new Stack(), + ScopeAccessStack = new Stack(), + PreprocessorDefines = new Dictionary(), + }; + context.EnterScope(fileInfo); + + // Process the source code + ApiTypeInfo scopeType = null; + Token prevToken = null; + while (true) + { + // Move to the next token + var token = tokenizer.NextToken(); + if (token == null) + continue; + if (token.Type == TokenType.EndOfFile) + break; + + // Parse API_.. tags in source code + if (token.Type == TokenType.Identifier && token.Value.StartsWith("API_", StringComparison.Ordinal)) + { + if (string.Equals(token.Value, ApiTokens.Class, StringComparison.Ordinal)) + { + if (!(context.ScopeInfo is FileInfo)) + throw new NotImplementedException("TODO: add support for nested classes in scripting API"); + + var classInfo = ParseClass(ref context); + scopeType = classInfo; + context.ScopeInfo.AddChild(scopeType); + context.CurrentAccessLevel = AccessLevel.Public; + } + else if (string.Equals(token.Value, ApiTokens.Property, StringComparison.Ordinal)) + { + var propertyInfo = ParseProperty(ref context); + } + else if (string.Equals(token.Value, ApiTokens.Function, StringComparison.Ordinal)) + { + var functionInfo = ParseFunction(ref context); + + if (context.ScopeInfo is ClassInfo classInfo) + classInfo.Functions.Add(functionInfo); + else if (context.ScopeInfo is StructureInfo structureInfo) + structureInfo.Functions.Add(functionInfo); + else + throw new Exception($"Not supported free-function {functionInfo.Name} at line {tokenizer.CurrentLine}. Place it in the class to use API bindings for it."); + } + else if (string.Equals(token.Value, ApiTokens.Enum, StringComparison.Ordinal)) + { + var enumInfo = ParseEnum(ref context); + context.ScopeInfo.AddChild(enumInfo); + } + else if (string.Equals(token.Value, ApiTokens.Struct, StringComparison.Ordinal)) + { + var structureInfo = ParseStructure(ref context); + scopeType = structureInfo; + context.ScopeInfo.AddChild(scopeType); + context.CurrentAccessLevel = AccessLevel.Public; + } + else if (string.Equals(token.Value, ApiTokens.Field, StringComparison.Ordinal)) + { + var fieldInfo = ParseField(ref context); + var scopeInfo = context.ValidScopeInfoFromStack; + + if (scopeInfo is ClassInfo classInfo) + classInfo.Fields.Add(fieldInfo); + else if (scopeInfo is StructureInfo structureInfo) + structureInfo.Fields.Add(fieldInfo); + else + throw new Exception($"Not supported location for field {fieldInfo.Name} at line {tokenizer.CurrentLine}. Place it in the class or structure to use API bindings for it."); + } + else if (string.Equals(token.Value, ApiTokens.Event, StringComparison.Ordinal)) + { + var eventInfo = ParseEvent(ref context); + var scopeInfo = context.ValidScopeInfoFromStack; + + if (scopeInfo is ClassInfo classInfo) + classInfo.Events.Add(eventInfo); + else + throw new Exception($"Not supported location for event {eventInfo.Name} at line {tokenizer.CurrentLine}. Place it in the class to use API bindings for it."); + } + else if (string.Equals(token.Value, ApiTokens.InjectCppCode, StringComparison.Ordinal)) + { + var injectCppCodeInfo = ParseInjectCppCode(ref context); + fileInfo.AddChild(injectCppCodeInfo); + } + else if (string.Equals(token.Value, ApiTokens.Interface, StringComparison.Ordinal)) + { + if (!(context.ScopeInfo is FileInfo)) + throw new NotImplementedException("TODO: add support for nested interfaces in scripting API"); + + var interfaceInfo = ParseInterface(ref context); + scopeType = interfaceInfo; + context.ScopeInfo.AddChild(scopeType); + context.CurrentAccessLevel = AccessLevel.Public; + } + else if (string.Equals(token.Value, ApiTokens.AutoSerialization, StringComparison.Ordinal)) + { + if (context.ScopeInfo is ClassInfo classInfo) + classInfo.IsAutoSerialization = true; + else if (context.ScopeInfo is StructureInfo structureInfo) + structureInfo.IsAutoSerialization = true; + else + throw new Exception($"Not supported location for {ApiTokens.AutoSerialization} at line {tokenizer.CurrentLine}. Place it in the class or structure that uses API bindings."); + } + } + + // Track access level inside class + if (context.ScopeInfo != null && token.Type == TokenType.Colon && prevToken != null && prevToken.Type == TokenType.Identifier) + { + if (string.Equals(prevToken.Value, "public", StringComparison.Ordinal)) + { + context.CurrentAccessLevel = AccessLevel.Public; + } + else if (string.Equals(prevToken.Value, "protected", StringComparison.Ordinal)) + { + context.CurrentAccessLevel = AccessLevel.Protected; + } + else if (string.Equals(prevToken.Value, "private", StringComparison.Ordinal)) + { + context.CurrentAccessLevel = AccessLevel.Private; + } + } + + // Handle preprocessor blocks + if (token.Type == TokenType.Preprocessor) + { + token = tokenizer.NextToken(); + switch (token.Value) + { + case "define": + { + token = tokenizer.NextToken(); + var name = token.Value; + var value = string.Empty; + token = tokenizer.NextToken(true); + while (token.Type != TokenType.Newline) + { + value += token.Value; + token = tokenizer.NextToken(true); + } + value = value.Trim(); + context.PreprocessorDefines[name] = value; + break; + } + case "if": + { + // Parse condition + var condition = string.Empty; + token = tokenizer.NextToken(true); + while (token.Type != TokenType.Newline) + { + var tokenValue = token.Value.Trim(); + if (tokenValue.Length == 0) + { + token = tokenizer.NextToken(true); + continue; + } + + // Very simple defines processing + tokenValue = ReplacePreProcessorDefines(tokenValue, context.PreprocessorDefines.Keys); + tokenValue = ReplacePreProcessorDefines(tokenValue, moduleOptions.PublicDefinitions); + tokenValue = ReplacePreProcessorDefines(tokenValue, moduleOptions.PrivateDefinitions); + tokenValue = ReplacePreProcessorDefines(tokenValue, moduleOptions.CompileEnv.PreprocessorDefinitions); + tokenValue = tokenValue.Replace("false", "0"); + tokenValue = tokenValue.Replace("true", "1"); + tokenValue = tokenValue.Replace("||", "|"); + if (tokenValue.Length != 0 && tokenValue != "1" && tokenValue != "0" && tokenValue != "|") + tokenValue = "0"; + + condition += tokenValue; + token = tokenizer.NextToken(true); + } + + // Filter condition + condition = condition.Replace("1|1", "1"); + condition = condition.Replace("1|0", "1"); + condition = condition.Replace("0|1", "1"); + + // Skip chunk of code of condition fails + if (condition != "1") + { + ParsePreprocessorIf(fileInfo, tokenizer, ref token); + } + + break; + } + case "ifdef": + { + // Parse condition + var define = string.Empty; + token = tokenizer.NextToken(true); + while (token.Type != TokenType.Newline) + { + define += token.Value; + token = tokenizer.NextToken(true); + } + + // Check condition + define = define.Trim(); + if (!context.PreprocessorDefines.ContainsKey(define) && !moduleOptions.CompileEnv.PreprocessorDefinitions.Contains(define)) + { + ParsePreprocessorIf(fileInfo, tokenizer, ref token); + } + + break; + } + } + } + + // Scope tracking + if (token.Type == TokenType.LeftCurlyBrace) + { + context.ScopeTypeStack.Push(scopeType); + context.ScopeInfo = context.ScopeTypeStack.Peek(); + scopeType = null; + } + else if (token.Type == TokenType.RightCurlyBrace) + { + context.ScopeTypeStack.Pop(); + if (context.ScopeTypeStack.Count == 0) + throw new Exception($"Mismatch of the {{}} braces pair in file '{fileInfo.Name}' at line {tokenizer.CurrentLine}."); + context.ScopeInfo = context.ScopeTypeStack.Peek(); + if (context.ScopeInfo is FileInfo) + context.CurrentAccessLevel = AccessLevel.Public; + } + + prevToken = token; + } + } + catch (Exception ex) + { + Log.Error($"Failed to parse '{fileInfo.Name}' file to generate bindings."); + Log.Exception(ex); + throw; + } + } + private static string ReplacePreProcessorDefines(string text, IEnumerable defines) { foreach (var define in defines) { - if (text.Contains(define)) + if (string.Equals(text, define, StringComparison.Ordinal)) text = text.Replace(define, "1"); } return text; @@ -426,41 +479,82 @@ namespace Flax.Build.Bindings throw new Exception($"Invalid #if/endif pairing in file '{fileInfo.Name}'. Failed to generate API bindings for it."); } + private static bool UseBindings(object type) + { + var apiTypeInfo = type as ApiTypeInfo; + if (apiTypeInfo != null && apiTypeInfo.IsInBuild) + return false; + if ((type is ModuleInfo || type is FileInfo) && apiTypeInfo != null) + { + foreach (var child in apiTypeInfo.Children) + { + if (UseBindings(child)) + return true; + } + } + return type is ClassInfo || + type is StructureInfo || + type is InterfaceInfo || + type is InjectCppCodeInfo; + } + /// /// The API bindings generation utility that can produce scripting bindings for another languages to the native code. /// public static void GenerateBindings(BuildData buildData, Module module, ref BuildOptions moduleOptions, out BindingsResult bindings) { // Parse module (or load from cache) + var moduleInfo = ParseModule(buildData, module, moduleOptions); bindings = new BindingsResult { + UseBindings = UseBindings(moduleInfo), GeneratedCppFilePath = Path.Combine(moduleOptions.IntermediateFolder, module.Name + ".Bindings.Gen.cpp"), GeneratedCSharpFilePath = Path.Combine(moduleOptions.IntermediateFolder, module.Name + ".Bindings.Gen.cs"), }; - var moduleInfo = ParseModule(buildData, module, moduleOptions); + + if (bindings.UseBindings) + { + buildData.Modules.TryGetValue(moduleInfo.Module, out var moduleBuildInfo); + + // Ensure that generated files are included into build + if (!moduleBuildInfo.SourceFiles.Contains(bindings.GeneratedCSharpFilePath)) + moduleBuildInfo.SourceFiles.Add(bindings.GeneratedCSharpFilePath); + } + + // Skip if module is cached (no scripting API changed) + if (moduleInfo.IsFromCache) + return; // Process parsed API - foreach (var child in moduleInfo.Children) + using (new ProfileEventScope("Process")) { - try + foreach (var child in moduleInfo.Children) { - foreach (var apiTypeInfo in child.Children) - ProcessAndValidate(buildData, apiTypeInfo); - } - catch (Exception) - { - if (child is FileInfo fileInfo) - Log.Error($"Failed to validate '{fileInfo.Name}' file to generate bindings."); - throw; + try + { + foreach (var apiTypeInfo in child.Children) + ProcessAndValidate(buildData, apiTypeInfo); + } + catch (Exception) + { + if (child is FileInfo fileInfo) + Log.Error($"Failed to validate '{fileInfo.Name}' file to generate bindings."); + throw; + } } } // Generate bindings for scripting - Log.Verbose($"Generating API bindings for {module.Name} ({moduleInfo.Name})"); - GenerateCpp(buildData, moduleInfo, ref bindings); - GenerateCSharp(buildData, moduleInfo, ref bindings); + if (bindings.UseBindings) + { + Log.Verbose($"Generating API bindings for {module.Name} ({moduleInfo.Name})"); + using (new ProfileEventScope("Cpp")) + GenerateCpp(buildData, moduleInfo, ref bindings); + using (new ProfileEventScope("CSharp")) + GenerateCSharp(buildData, moduleInfo, ref bindings); - // TODO: add support for extending this code and support generating bindings for other scripting languages + // TODO: add support for extending this code and support generating bindings for other scripting languages + } } /// @@ -568,7 +662,6 @@ namespace Flax.Build.Bindings { foreach (var fieldInfo in structureInfo.Fields) { - // TODO: support bit-fields in structure fields if (fieldInfo.Type.IsBitField) throw new NotImplementedException($"TODO: support bit-fields in structure fields (found field {fieldInfo} in structure {structureInfo.Name})"); diff --git a/Source/Tools/Flax.Build/Bindings/ClassInfo.cs b/Source/Tools/Flax.Build/Bindings/ClassInfo.cs index 1a62132b2..c68ff1a59 100644 --- a/Source/Tools/Flax.Build/Bindings/ClassInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/ClassInfo.cs @@ -1,6 +1,7 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System.Collections.Generic; +using System.IO; using System.Linq; namespace Flax.Build.Bindings @@ -8,7 +9,7 @@ namespace Flax.Build.Bindings /// /// The native class information for bindings generator. /// - public class ClassInfo : ApiTypeInfo + public class ClassInfo : ClassStructInfo { private static readonly HashSet InBuildScriptingObjectTypes = new HashSet { @@ -22,19 +23,17 @@ namespace Flax.Build.Bindings "Actor", }; - public AccessLevel Access; - public TypeInfo BaseType; - public AccessLevel BaseTypeInheritance; + public bool IsBaseTypeHidden; public bool IsStatic; public bool IsSealed; public bool IsAbstract; public bool IsAutoSerialization; public bool NoSpawn; public bool NoConstructor; - public List Functions; - public List Properties; - public List Fields; - public List Events; + public List Functions = new List(); + public List Properties = new List(); + public List Fields = new List(); + public List Events = new List(); internal HashSet UniqueFunctionNames; @@ -50,6 +49,9 @@ namespace Flax.Build.Bindings { base.Init(buildData); + // Internal base types are usually hidden from bindings (used in core-only internally) + IsBaseTypeHidden = BaseTypeInheritance == AccessLevel.Private || BaseType == null; + // Cache if it it Scripting Object type if (InBuildScriptingObjectTypes.Contains(Name)) _isScriptingObject = true; @@ -73,6 +75,40 @@ namespace Flax.Build.Bindings } } + public override void Write(BinaryWriter writer) + { + // TODO: convert into flags + writer.Write(IsStatic); + writer.Write(IsSealed); + writer.Write(IsAbstract); + writer.Write(IsAutoSerialization); + writer.Write(NoSpawn); + writer.Write(NoConstructor); + BindingsGenerator.Write(writer, Functions); + BindingsGenerator.Write(writer, Properties); + BindingsGenerator.Write(writer, Fields); + BindingsGenerator.Write(writer, Events); + + base.Write(writer); + } + + public override void Read(BinaryReader reader) + { + // TODO: convert into flags + IsStatic = reader.ReadBoolean(); + IsSealed = reader.ReadBoolean(); + IsAbstract = reader.ReadBoolean(); + IsAutoSerialization = reader.ReadBoolean(); + NoSpawn = reader.ReadBoolean(); + NoConstructor = reader.ReadBoolean(); + Functions = BindingsGenerator.Read(reader, Functions); + Properties = BindingsGenerator.Read(reader, Properties); + Fields = BindingsGenerator.Read(reader, Fields); + Events = BindingsGenerator.Read(reader, Events); + + base.Read(reader); + } + public int GetScriptVTableSize(Builder.BuildData buildData, out int offset) { if (_scriptVTableSize == -1) diff --git a/Source/Tools/Flax.Build/Bindings/ClassStructInfo.cs b/Source/Tools/Flax.Build/Bindings/ClassStructInfo.cs new file mode 100644 index 000000000..2f0dad003 --- /dev/null +++ b/Source/Tools/Flax.Build/Bindings/ClassStructInfo.cs @@ -0,0 +1,60 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System.Collections.Generic; +using System.IO; + +namespace Flax.Build.Bindings +{ + /// + /// The native class/structure information for bindings generator. + /// + public abstract class ClassStructInfo : ApiTypeInfo + { + public AccessLevel Access; + public AccessLevel BaseTypeInheritance; + public TypeInfo BaseType; + public List Interfaces; // Optional + public List InterfaceNames; // Optional + + public override void Init(Builder.BuildData buildData) + { + base.Init(buildData); + + if (Interfaces == null && InterfaceNames != null && InterfaceNames.Count != 0) + { + Interfaces = new List(); + for (var i = 0; i < InterfaceNames.Count; i++) + { + var interfaceName = InterfaceNames[i]; + var apiTypeInfo = BindingsGenerator.FindApiTypeInfo(buildData, interfaceName, this); + if (apiTypeInfo is InterfaceInfo interfaceInfo) + { + Interfaces.Add(interfaceInfo); + } + } + if (Interfaces.Count == 0) + Interfaces = null; + } + } + + public override void Write(BinaryWriter writer) + { + writer.Write((byte)Access); + writer.Write((byte)BaseTypeInheritance); + BindingsGenerator.Write(writer, BaseType); + BindingsGenerator.Write(writer, InterfaceNames); + + base.Write(writer); + } + + public override void Read(BinaryReader reader) + { + Access = (AccessLevel)reader.ReadByte(); + BaseTypeInheritance = (AccessLevel)reader.ReadByte(); + BaseType = BindingsGenerator.Read(reader, BaseType); + InterfaceNames = BindingsGenerator.Read(reader, InterfaceNames); + + base.Read(reader); + } + } +} diff --git a/Source/Tools/Flax.Build/Bindings/EnumInfo.cs b/Source/Tools/Flax.Build/Bindings/EnumInfo.cs index eeb750255..ddfcb3a38 100644 --- a/Source/Tools/Flax.Build/Bindings/EnumInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/EnumInfo.cs @@ -1,7 +1,8 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; using System.Collections.Generic; +using System.IO; namespace Flax.Build.Bindings { @@ -10,7 +11,7 @@ namespace Flax.Build.Bindings /// public class EnumInfo : ApiTypeInfo { - public struct EntryInfo + public struct EntryInfo : IBindingsCache { public string Name; public string[] Comment; @@ -21,11 +22,27 @@ namespace Flax.Build.Bindings { return Name + (string.IsNullOrEmpty(Value) ? string.Empty : " = " + Value); } + + public void Write(BinaryWriter writer) + { + writer.Write(Name); + BindingsGenerator.Write(writer, Comment); + BindingsGenerator.Write(writer, Value); + BindingsGenerator.Write(writer, Attributes); + } + + public void Read(BinaryReader reader) + { + Name = reader.ReadString(); + Comment = BindingsGenerator.Read(reader, Comment); + Value = BindingsGenerator.Read(reader, Value); + Attributes = BindingsGenerator.Read(reader, Attributes); + } } public AccessLevel Access; public TypeInfo UnderlyingType; - public List Entries; + public List Entries = new List(); public override bool IsValueType => true; public override bool IsEnum => true; @@ -36,6 +53,24 @@ namespace Flax.Build.Bindings throw new NotSupportedException("API enums cannot have sub-types."); } + public override void Write(BinaryWriter writer) + { + writer.Write((byte)Access); + BindingsGenerator.Write(writer, UnderlyingType); + BindingsGenerator.Write(writer, Entries); + + base.Write(writer); + } + + public override void Read(BinaryReader reader) + { + Access = (AccessLevel)reader.ReadByte(); + UnderlyingType = BindingsGenerator.Read(reader, UnderlyingType); + Entries = BindingsGenerator.Read(reader, Entries); + + base.Read(reader); + } + public override string ToString() { return "enum " + Name; diff --git a/Source/Tools/Flax.Build/Bindings/EventInfo.cs b/Source/Tools/Flax.Build/Bindings/EventInfo.cs index 7d98979b4..905bee803 100644 --- a/Source/Tools/Flax.Build/Bindings/EventInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/EventInfo.cs @@ -1,4 +1,6 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System.IO; namespace Flax.Build.Bindings { @@ -9,6 +11,20 @@ namespace Flax.Build.Bindings { public TypeInfo Type; + public override void Write(BinaryWriter writer) + { + BindingsGenerator.Write(writer, Type); + + base.Write(writer); + } + + public override void Read(BinaryReader reader) + { + Type = BindingsGenerator.Read(reader, Type); + + base.Read(reader); + } + public override string ToString() { var result = string.Empty; diff --git a/Source/Tools/Flax.Build/Bindings/FieldInfo.cs b/Source/Tools/Flax.Build/Bindings/FieldInfo.cs index 29c8b0617..d01cde5e7 100644 --- a/Source/Tools/Flax.Build/Bindings/FieldInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/FieldInfo.cs @@ -1,4 +1,6 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System.IO; namespace Flax.Build.Bindings { @@ -12,6 +14,31 @@ namespace Flax.Build.Bindings public bool NoArray; public FunctionInfo Getter; public FunctionInfo Setter; + public string DefaultValue; + + public bool HasDefaultValue => !string.IsNullOrEmpty(DefaultValue); + + public override void Write(BinaryWriter writer) + { + BindingsGenerator.Write(writer, Type); + // TODO: convert into flags + writer.Write(IsReadOnly); + writer.Write(NoArray); + BindingsGenerator.Write(writer, DefaultValue); + + base.Write(writer); + } + + public override void Read(BinaryReader reader) + { + Type = BindingsGenerator.Read(reader, Type); + // TODO: convert into flags + IsReadOnly = reader.ReadBoolean(); + NoArray = reader.ReadBoolean(); + DefaultValue = BindingsGenerator.Read(reader, DefaultValue); + + base.Read(reader); + } public override string ToString() { @@ -19,6 +46,8 @@ namespace Flax.Build.Bindings if (IsStatic) result += "static "; result += Type + " " + Name; + if (HasDefaultValue) + result += " = " + DefaultValue; return result; } } diff --git a/Source/Tools/Flax.Build/Bindings/FileInfo.cs b/Source/Tools/Flax.Build/Bindings/FileInfo.cs index 38f2e491f..6c4b4dcfe 100644 --- a/Source/Tools/Flax.Build/Bindings/FileInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/FileInfo.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; @@ -7,7 +7,7 @@ namespace Flax.Build.Bindings /// /// The native file information for bindings generator. /// - public class FileInfo : ApiTypeInfo, IComparable + public class FileInfo : ApiTypeInfo, IComparable, IComparable { public override void AddChild(ApiTypeInfo apiTypeInfo) { @@ -26,5 +26,12 @@ namespace Flax.Build.Bindings { return System.IO.Path.GetFileName(Name); } + + public int CompareTo(object obj) + { + if (obj is ApiTypeInfo apiTypeInfo) + return Name.CompareTo(apiTypeInfo.Name); + return 0; + } } } diff --git a/Source/Tools/Flax.Build/Bindings/FunctionInfo.cs b/Source/Tools/Flax.Build/Bindings/FunctionInfo.cs index 81ea02796..a78c9242a 100644 --- a/Source/Tools/Flax.Build/Bindings/FunctionInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/FunctionInfo.cs @@ -1,6 +1,7 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System.Collections.Generic; +using System.IO; namespace Flax.Build.Bindings { @@ -9,7 +10,7 @@ namespace Flax.Build.Bindings /// public class FunctionInfo : MemberInfo { - public struct ParameterInfo + public struct ParameterInfo : IBindingsCache { public string Name; public TypeInfo Type; @@ -25,6 +26,28 @@ namespace Flax.Build.Bindings return Attributes != null && Attributes.Contains(name); } + public void Write(BinaryWriter writer) + { + writer.Write(Name); + BindingsGenerator.Write(writer, Type); + BindingsGenerator.Write(writer, DefaultValue); + BindingsGenerator.Write(writer, Attributes); + // TODO: convert into flags + writer.Write(IsRef); + writer.Write(IsOut); + } + + public void Read(BinaryReader reader) + { + Name = reader.ReadString(); + Type = BindingsGenerator.Read(reader, Type); + DefaultValue = BindingsGenerator.Read(reader, DefaultValue); + Attributes = BindingsGenerator.Read(reader, Attributes); + // TODO: convert into flags + IsRef = reader.ReadBoolean(); + IsOut = reader.ReadBoolean(); + } + public override string ToString() { var result = Type + " " + Name; @@ -42,12 +65,36 @@ namespace Flax.Build.Bindings public string UniqueName; public TypeInfo ReturnType; - public List Parameters; + public List Parameters = new List(); public bool IsVirtual; public bool IsConst; public bool NoProxy; public GlueInfo Glue; + public override void Write(BinaryWriter writer) + { + BindingsGenerator.Write(writer, ReturnType); + BindingsGenerator.Write(writer, Parameters); + // TODO: convert into flags + writer.Write(IsVirtual); + writer.Write(IsConst); + writer.Write(NoProxy); + + base.Write(writer); + } + + public override void Read(BinaryReader reader) + { + ReturnType = BindingsGenerator.Read(reader, ReturnType); + Parameters = BindingsGenerator.Read(reader, Parameters); + // TODO: convert into flags + IsVirtual = reader.ReadBoolean(); + IsConst = reader.ReadBoolean(); + NoProxy = reader.ReadBoolean(); + + base.Read(reader); + } + public override string ToString() { var result = string.Empty; diff --git a/Source/Tools/Flax.Build/Bindings/InheritanceInfo.cs b/Source/Tools/Flax.Build/Bindings/InheritanceInfo.cs index 01ab2e292..4a8673660 100644 --- a/Source/Tools/Flax.Build/Bindings/InheritanceInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/InheritanceInfo.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. namespace Flax.Build.Bindings { diff --git a/Source/Tools/Flax.Build/Bindings/InjectCppCodeInfo.cs b/Source/Tools/Flax.Build/Bindings/InjectCppCodeInfo.cs index 7deee98c6..0bb53fc99 100644 --- a/Source/Tools/Flax.Build/Bindings/InjectCppCodeInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/InjectCppCodeInfo.cs @@ -1,4 +1,6 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System.IO; namespace Flax.Build.Bindings { @@ -9,6 +11,20 @@ namespace Flax.Build.Bindings { public string Code; + public override void Write(BinaryWriter writer) + { + writer.Write(Code); + + base.Write(writer); + } + + public override void Read(BinaryReader reader) + { + Code = reader.ReadString(); + + base.Read(reader); + } + /// public override string ToString() { diff --git a/Source/Tools/Flax.Build/Bindings/InterfaceInfo.cs b/Source/Tools/Flax.Build/Bindings/InterfaceInfo.cs new file mode 100644 index 000000000..e0225f288 --- /dev/null +++ b/Source/Tools/Flax.Build/Bindings/InterfaceInfo.cs @@ -0,0 +1,24 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +namespace Flax.Build.Bindings +{ + /// + /// The native class/structure interface information for bindings generator. + /// + public class InterfaceInfo : ClassStructInfo + { + public override bool IsInterface => true; + + public override void AddChild(ApiTypeInfo apiTypeInfo) + { + apiTypeInfo.Namespace = null; + + base.AddChild(apiTypeInfo); + } + + public override string ToString() + { + return "interface " + Name; + } + } +} diff --git a/Source/Tools/Flax.Build/Bindings/LangType.cs b/Source/Tools/Flax.Build/Bindings/LangType.cs index d74560bc6..7f5f2da03 100644 --- a/Source/Tools/Flax.Build/Bindings/LangType.cs +++ b/Source/Tools/Flax.Build/Bindings/LangType.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; diff --git a/Source/Tools/Flax.Build/Bindings/MemberInfo.cs b/Source/Tools/Flax.Build/Bindings/MemberInfo.cs index 63034f57a..49187c101 100644 --- a/Source/Tools/Flax.Build/Bindings/MemberInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/MemberInfo.cs @@ -1,11 +1,13 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System.IO; namespace Flax.Build.Bindings { /// /// The native member information for bindings generator. /// - public class MemberInfo + public class MemberInfo : IBindingsCache { public string Name; public string[] Comment; @@ -17,5 +19,23 @@ namespace Flax.Build.Bindings { return Attributes != null && Attributes.Contains(name); } + + public virtual void Write(BinaryWriter writer) + { + writer.Write(Name); + BindingsGenerator.Write(writer, Comment); + writer.Write(IsStatic); + writer.Write((byte)Access); + BindingsGenerator.Write(writer, Attributes); + } + + public virtual void Read(BinaryReader reader) + { + Name = reader.ReadString(); + Comment = BindingsGenerator.Read(reader, Comment); + IsStatic = reader.ReadBoolean(); + Access = (AccessLevel)reader.ReadByte(); + Attributes = BindingsGenerator.Read(reader, Attributes); + } } } diff --git a/Source/Tools/Flax.Build/Bindings/ModuleInfo.cs b/Source/Tools/Flax.Build/Bindings/ModuleInfo.cs index 554d46c01..68742b554 100644 --- a/Source/Tools/Flax.Build/Bindings/ModuleInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/ModuleInfo.cs @@ -1,4 +1,7 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System; +using System.IO; namespace Flax.Build.Bindings { @@ -8,10 +11,41 @@ namespace Flax.Build.Bindings public class ModuleInfo : ApiTypeInfo { public Module Module; + public bool IsFromCache; public override string ToString() { return "module " + Name; } + + /// + public override void Init(Builder.BuildData buildData) + { + base.Init(buildData); + + // Sort module files to prevent bindings rebuild due to order changes (list might be created in async) + Children.Sort(); + } + + public override void Write(BinaryWriter writer) + { + writer.Write(Module.Name); + writer.Write(Module.FilePath); + BindingsGenerator.Write(writer, Module.BinaryModuleName); + writer.Write(Module.BuildNativeCode); + + base.Write(writer); + } + + public override void Read(BinaryReader reader) + { + if (reader.ReadString() != Module.Name || + reader.ReadString() != Module.FilePath || + BindingsGenerator.Read(reader, Module.BinaryModuleName) != Module.BinaryModuleName || + reader.ReadBoolean() != Module.BuildNativeCode) + throw new Exception(); + + base.Read(reader); + } } } diff --git a/Source/Tools/Flax.Build/Bindings/PropertyInfo.cs b/Source/Tools/Flax.Build/Bindings/PropertyInfo.cs index b36821511..96b0f8b93 100644 --- a/Source/Tools/Flax.Build/Bindings/PropertyInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/PropertyInfo.cs @@ -1,4 +1,6 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System.IO; namespace Flax.Build.Bindings { @@ -11,6 +13,24 @@ namespace Flax.Build.Bindings public FunctionInfo Getter; public FunctionInfo Setter; + public override void Write(BinaryWriter writer) + { + BindingsGenerator.Write(writer, Type); + BindingsGenerator.Write(writer, Getter); + BindingsGenerator.Write(writer, Setter); + + base.Write(writer); + } + + public override void Read(BinaryReader reader) + { + Type = BindingsGenerator.Read(reader, Type); + Getter = BindingsGenerator.Read(reader, Getter); + Setter = BindingsGenerator.Read(reader, Setter); + + base.Read(reader); + } + public override string ToString() { return Type + " " + Name; diff --git a/Source/Tools/Flax.Build/Bindings/StructureInfo.cs b/Source/Tools/Flax.Build/Bindings/StructureInfo.cs index ca0a02247..882ed6295 100644 --- a/Source/Tools/Flax.Build/Bindings/StructureInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/StructureInfo.cs @@ -1,19 +1,18 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; using System.Collections.Generic; +using System.IO; namespace Flax.Build.Bindings { /// /// The native structure information for bindings generator. /// - public class StructureInfo : ApiTypeInfo + public class StructureInfo : ClassStructInfo { - public AccessLevel Access; - public TypeInfo BaseType; - public List Fields; - public List Functions; + public List Fields = new List(); + public List Functions = new List(); public bool IsAutoSerialization; public bool ForceNoPod; @@ -27,7 +26,7 @@ namespace Flax.Build.Bindings { base.Init(buildData); - if (ForceNoPod) + if (ForceNoPod || (InterfaceNames != null && InterfaceNames.Count != 0)) { _isPod = false; return; @@ -45,6 +44,26 @@ namespace Flax.Build.Bindings } } + public override void Write(BinaryWriter writer) + { + BindingsGenerator.Write(writer, Fields); + BindingsGenerator.Write(writer, Functions); + writer.Write(IsAutoSerialization); + writer.Write(ForceNoPod); + + base.Write(writer); + } + + public override void Read(BinaryReader reader) + { + Fields = BindingsGenerator.Read(reader, Fields); + Functions = BindingsGenerator.Read(reader, Functions); + IsAutoSerialization = reader.ReadBoolean(); + ForceNoPod = reader.ReadBoolean(); + + base.Read(reader); + } + public override void AddChild(ApiTypeInfo apiTypeInfo) { if (apiTypeInfo is EnumInfo) diff --git a/Source/Tools/Flax.Build/Bindings/TypeInfo.cs b/Source/Tools/Flax.Build/Bindings/TypeInfo.cs index 8af061200..2d15f6651 100644 --- a/Source/Tools/Flax.Build/Bindings/TypeInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/TypeInfo.cs @@ -1,7 +1,8 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; @@ -10,7 +11,7 @@ namespace Flax.Build.Bindings /// /// The native type information for bindings generator. /// - public class TypeInfo : IEquatable + public class TypeInfo : IEquatable, IBindingsCache { public string Type; public bool IsConst; @@ -51,6 +52,34 @@ namespace Flax.Build.Bindings return true; } + public void Write(BinaryWriter writer) + { + BindingsGenerator.Write(writer, Type); + // TODO: pack as flags + writer.Write(IsConst); + writer.Write(IsRef); + writer.Write(IsPtr); + writer.Write(IsArray); + writer.Write(IsBitField); + writer.Write(ArraySize); + writer.Write(BitSize); + BindingsGenerator.Write(writer, GenericArgs); + } + + public void Read(BinaryReader reader) + { + Type = BindingsGenerator.Read(reader, Type); + // TODO: convert into flags + IsConst = reader.ReadBoolean(); + IsRef = reader.ReadBoolean(); + IsPtr = reader.ReadBoolean(); + IsArray = reader.ReadBoolean(); + IsBitField = reader.ReadBoolean(); + ArraySize = reader.ReadInt32(); + BitSize = reader.ReadInt32(); + GenericArgs = BindingsGenerator.Read(reader, GenericArgs); + } + public override string ToString() { var sb = new StringBuilder(64); @@ -83,7 +112,7 @@ namespace Flax.Build.Bindings return sb.ToString(); } - private static bool Equals(List a, List b) + public static bool Equals(List a, List b) { if (a == null && b == null) return true; diff --git a/Source/Tools/Flax.Build/Build/Builder.Rules.cs b/Source/Tools/Flax.Build/Build/Builder.Rules.cs index 9982e3601..1c4609c2e 100644 --- a/Source/Tools/Flax.Build/Build/Builder.Rules.cs +++ b/Source/Tools/Flax.Build/Build/Builder.Rules.cs @@ -12,12 +12,31 @@ namespace Flax.Build partial class Builder { private static RulesAssembly _rules; + private static Type[] _buildTypes; /// /// The build configuration files postfix. /// public static string BuildFilesPostfix = ".Build.cs"; + /// + /// The cached list of types from Flax.Build assembly. Reused by other build tool utilities to improve performance. + /// + internal static Type[] BuildTypes + { + get + { + if (_buildTypes == null) + { + using (new ProfileEventScope("CacheBuildTypes")) + { + _buildTypes = typeof(Program).Assembly.GetTypes(); + } + } + return _buildTypes; + } + } + /// /// The rules assembly data. /// @@ -128,7 +147,7 @@ namespace Flax.Build using (new ProfileEventScope("InitInBuildPlugins")) { - foreach (var type in typeof(Program).Assembly.GetTypes().Where(x => !x.IsAbstract && x.IsSubclassOf(typeof(Plugin)))) + foreach (var type in BuildTypes.Where(x => x.IsClass && !x.IsAbstract && x.IsSubclassOf(typeof(Plugin)))) { var plugin = (Plugin)Activator.CreateInstance(type); plugin.Init(); @@ -176,67 +195,74 @@ namespace Flax.Build // Prepare targets and modules objects Type[] types; - Target[] targetObjects; - Module[] moduleObjects; - Plugin[] pluginObjects; + var targetObjects = new List(16); + var moduleObjects = new List(256); + var pluginObjects = new List(); using (new ProfileEventScope("GetTypes")) { types = assembly.GetTypes(); - targetObjects = types.Where(x => !x.IsAbstract && x.IsSubclassOf(typeof(Target))).Select(type => + for (var i = 0; i < types.Length; i++) { - var target = (Target)Activator.CreateInstance(type); - - var targetFilename = target.Name + BuildFilesPostfix; - target.FilePath = files.FirstOrDefault(path => string.Equals(Path.GetFileName(path), targetFilename, StringComparison.OrdinalIgnoreCase)); - if (target.FilePath == null) + var type = types[i]; + if (!type.IsClass || type.IsAbstract) + continue; + if (type.IsSubclassOf(typeof(Target))) { - targetFilename = target.Name + "Target" + BuildFilesPostfix; + var target = (Target)Activator.CreateInstance(type); + + var targetFilename = target.Name + BuildFilesPostfix; target.FilePath = files.FirstOrDefault(path => string.Equals(Path.GetFileName(path), targetFilename, StringComparison.OrdinalIgnoreCase)); if (target.FilePath == null) { - if (target.Name.EndsWith("Target")) - { - targetFilename = target.Name.Substring(0, target.Name.Length - "Target".Length) + BuildFilesPostfix; - target.FilePath = files.FirstOrDefault(path => string.Equals(Path.GetFileName(path), targetFilename, StringComparison.OrdinalIgnoreCase)); - } + targetFilename = target.Name + "Target" + BuildFilesPostfix; + target.FilePath = files.FirstOrDefault(path => string.Equals(Path.GetFileName(path), targetFilename, StringComparison.OrdinalIgnoreCase)); if (target.FilePath == null) { - throw new Exception(string.Format("Failed to find source file path for {0}", target)); + if (target.Name.EndsWith("Target")) + { + targetFilename = target.Name.Substring(0, target.Name.Length - "Target".Length) + BuildFilesPostfix; + target.FilePath = files.FirstOrDefault(path => string.Equals(Path.GetFileName(path), targetFilename, StringComparison.OrdinalIgnoreCase)); + } + if (target.FilePath == null) + { + throw new Exception(string.Format("Failed to find source file path for {0}", target)); + } } } + target.FolderPath = Path.GetDirectoryName(target.FilePath); + target.Init(); + targetObjects.Add(target); } - target.FolderPath = Path.GetDirectoryName(target.FilePath); - target.Init(); - return target; - }).ToArray(); - moduleObjects = types.Where(x => !x.IsAbstract && x.IsSubclassOf(typeof(Module))).Select(type => - { - var module = (Module)Activator.CreateInstance(type); - - var moduleFilename = module.Name + BuildFilesPostfix; - module.FilePath = files.FirstOrDefault(path => string.Equals(Path.GetFileName(path), moduleFilename, StringComparison.OrdinalIgnoreCase)); - if (module.FilePath == null) + else if (type.IsSubclassOf(typeof(Module))) { - moduleFilename = module.Name + "Module" + BuildFilesPostfix; + var module = (Module)Activator.CreateInstance(type); + + var moduleFilename = module.Name + BuildFilesPostfix; module.FilePath = files.FirstOrDefault(path => string.Equals(Path.GetFileName(path), moduleFilename, StringComparison.OrdinalIgnoreCase)); if (module.FilePath == null) { - throw new Exception(string.Format("Failed to find source file path for {0}", module)); + moduleFilename = module.Name + "Module" + BuildFilesPostfix; + module.FilePath = files.FirstOrDefault(path => string.Equals(Path.GetFileName(path), moduleFilename, StringComparison.OrdinalIgnoreCase)); + if (module.FilePath == null) + { + throw new Exception(string.Format("Failed to find source file path for {0}", module)); + } } + module.FolderPath = Path.GetDirectoryName(module.FilePath); + module.Init(); + moduleObjects.Add(module); } - module.FolderPath = Path.GetDirectoryName(module.FilePath); - module.Init(); - return module; - }).ToArray(); - pluginObjects = types.Where(x => !x.IsAbstract && x.IsSubclassOf(typeof(Plugin))).Select(type => - { - var plugin = (Plugin)Activator.CreateInstance(type); - plugin.Init(); - return plugin; - }).ToArray(); + else if (type.IsSubclassOf(typeof(Plugin))) + { + var plugin = (Plugin)Activator.CreateInstance(type); + + plugin.Init(); + pluginObjects.Add(plugin); + } + } } - _rules = new RulesAssembly(assembly, targetObjects, moduleObjects, pluginObjects); + _rules = new RulesAssembly(assembly, targetObjects.ToArray(), moduleObjects.ToArray(), pluginObjects.ToArray()); } return _rules; diff --git a/Source/Tools/Flax.Build/Build/Graph/LocalExecutor.cs b/Source/Tools/Flax.Build/Build/Graph/LocalExecutor.cs index c874dba51..391b6952f 100644 --- a/Source/Tools/Flax.Build/Build/Graph/LocalExecutor.cs +++ b/Source/Tools/Flax.Build/Build/Graph/LocalExecutor.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Threading; using Flax.Build.Graph; @@ -27,12 +28,12 @@ namespace Flax.Build.BuildSystem.Graph /// /// The maximum amount of threads to be used for the parallel execution. /// - public int ThreadCountMax = 1410; + public int ThreadCountMax = Configuration.MaxConcurrency; /// /// The amount of threads to allocate per processor. Use it to allocate more threads for faster execution or use less to keep reduce CPU usage during build. /// - public float ProcessorCountScale = 1.0f; + public float ProcessorCountScale = Configuration.ConcurrencyProcessorScale; /// public override int Execute(List tasks) @@ -178,7 +179,12 @@ namespace Flax.Build.BuildSystem.Graph private int ExecuteTask(Task task) { - ProcessStartInfo startInfo = new ProcessStartInfo + string name = "Task"; + if (task.ProducedFiles != null && task.ProducedFiles.Count != 0) + name = Path.GetFileName(task.ProducedFiles[0]); + var profilerEvent = Profiling.Begin(name); + + var startInfo = new ProcessStartInfo { WorkingDirectory = task.WorkingDirectory, FileName = task.CommandPath, @@ -230,10 +236,13 @@ namespace Flax.Build.BuildSystem.Graph // Hang until process end process.WaitForExit(); + Profiling.End(profilerEvent); return process.ExitCode; } finally { + Profiling.End(profilerEvent); + // Ensure to cleanup data process?.Close(); } diff --git a/Source/Tools/Flax.Build/Build/Module.cs b/Source/Tools/Flax.Build/Build/Module.cs index dae700728..726cbf946 100644 --- a/Source/Tools/Flax.Build/Build/Module.cs +++ b/Source/Tools/Flax.Build/Build/Module.cs @@ -111,5 +111,16 @@ namespace Flax.Build // By default deploy all C++ header files files.AddRange(Directory.GetFiles(FolderPath, "*.h", SearchOption.AllDirectories)); } + + /// + /// Adds the file to the build sources if exists. + /// + /// The options. + /// The source file path. + protected void AddSourceFileIfExists(BuildOptions options, string path) + { + if (File.Exists(path)) + options.SourceFiles.Add(path); + } } } diff --git a/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs b/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs index 70d3722fb..af50cfe1e 100644 --- a/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs +++ b/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs @@ -330,7 +330,12 @@ namespace Flax.Build } // Collect all files to compile - var cppFiles = moduleOptions.SourceFiles.Where(x => x.EndsWith(".cpp")).ToList(); + var cppFiles = new List(moduleOptions.SourceFiles.Count / 2); + for (int i = 0; i < moduleOptions.SourceFiles.Count; i++) + { + if (moduleOptions.SourceFiles[i].EndsWith(".cpp", StringComparison.OrdinalIgnoreCase)) + cppFiles.Add(moduleOptions.SourceFiles[i]); + } if (!string.IsNullOrEmpty(module.BinaryModuleName)) { diff --git a/Source/Tools/Flax.Build/Build/NativeCpp/CompileEnvironment.cs b/Source/Tools/Flax.Build/Build/NativeCpp/CompileEnvironment.cs index b1159db1c..9939cea53 100644 --- a/Source/Tools/Flax.Build/Build/NativeCpp/CompileEnvironment.cs +++ b/Source/Tools/Flax.Build/Build/NativeCpp/CompileEnvironment.cs @@ -56,6 +56,11 @@ namespace Flax.Build.NativeCpp /// public bool Optimization = false; + /// + /// Enables the whole program optimization. + /// + public bool WholeProgramOptimization = false; + /// /// Enables functions level linking support. /// @@ -131,6 +136,7 @@ namespace Flax.Build.NativeCpp RuntimeTypeInfo = RuntimeTypeInfo, Inlining = Inlining, Optimization = Optimization, + WholeProgramOptimization = WholeProgramOptimization, FunctionLevelLinking = FunctionLevelLinking, DebugInformation = DebugInformation, UseDebugCRT = UseDebugCRT, diff --git a/Source/Tools/Flax.Build/Build/Platform.cs b/Source/Tools/Flax.Build/Build/Platform.cs index 07a18a2a9..15addce27 100644 --- a/Source/Tools/Flax.Build/Build/Platform.cs +++ b/Source/Tools/Flax.Build/Build/Platform.cs @@ -157,7 +157,7 @@ namespace Flax.Build if (_platforms == null) { using (new ProfileEventScope("GetPlatforms")) - _platforms = typeof(Platform).Assembly.GetTypes().Where(x => !x.IsAbstract && x.IsSubclassOf(typeof(Platform))).Select(Activator.CreateInstance).Cast().ToArray(); + _platforms = Builder.BuildTypes.Where(x => x.IsClass && !x.IsAbstract && x.IsSubclassOf(typeof(Platform))).Select(Activator.CreateInstance).Cast().ToArray(); } foreach (var platform in _platforms) diff --git a/Source/Tools/Flax.Build/Build/Plugins/VisualScriptingPlugin.cs b/Source/Tools/Flax.Build/Build/Plugins/VisualScriptingPlugin.cs index 8ebe13518..b2656ce0d 100644 --- a/Source/Tools/Flax.Build/Build/Plugins/VisualScriptingPlugin.cs +++ b/Source/Tools/Flax.Build/Build/Plugins/VisualScriptingPlugin.cs @@ -22,7 +22,7 @@ namespace Flax.Build.Plugins private void OnGenerateCppScriptWrapperFunction(Builder.BuildData buildData, ClassInfo classInfo, FunctionInfo functionInfo, int scriptVTableSize, int scriptVTableIndex, StringBuilder contents) { - // Generate C++ wrapper function to invoke Visual Script instead of overriden native function (with support for base method callback) + // Generate C++ wrapper function to invoke Visual Script instead of overridden native function (with support for base method callback) BindingsGenerator.CppIncludeFiles.Add("Engine/Content/Assets/VisualScript.h"); @@ -48,7 +48,7 @@ namespace Flax.Build.Plugins contents.AppendLine(" if (IsDuringWrapperCall)"); contents.AppendLine(" {"); contents.AppendLine(" // Prevent stack overflow by calling base method"); - contents.AppendLine(" const auto scriptVTableBase = object->GetType().Class.ScriptVTableBase;"); + contents.AppendLine(" const auto scriptVTableBase = object->GetType().Script.ScriptVTableBase;"); contents.Append($" return (object->**({functionInfo.UniqueName}_Signature*)&scriptVTableBase[{scriptVTableIndex} + 2])("); separator = false; for (var i = 0; i < functionInfo.Parameters.Count; i++) @@ -61,7 +61,7 @@ namespace Flax.Build.Plugins } contents.AppendLine(");"); contents.AppendLine(" }"); - contents.AppendLine(" auto scriptVTable = (VisualScript::Method**)object->GetType().Class.ScriptVTable;"); + contents.AppendLine(" auto scriptVTable = (VisualScript::Method**)object->GetType().Script.ScriptVTable;"); contents.AppendLine($" ASSERT(scriptVTable && scriptVTable[{scriptVTableIndex}]);"); if (functionInfo.Parameters.Count != 0) diff --git a/Source/Tools/Flax.Build/Build/Profiling.cs b/Source/Tools/Flax.Build/Build/Profiling.cs index 98bc2c62a..c5cc89bcb 100644 --- a/Source/Tools/Flax.Build/Build/Profiling.cs +++ b/Source/Tools/Flax.Build/Build/Profiling.cs @@ -4,6 +4,7 @@ using System; using System.IO; using System.Collections.Generic; using System.Text; +using System.Threading; namespace Flax.Build { @@ -12,7 +13,7 @@ namespace Flax.Build /// public class ProfileEventScope : IDisposable { - private int _id; + private readonly int _id; /// /// Initializes a new instance of the class. @@ -61,6 +62,11 @@ namespace Flax.Build /// The event call depth (for parent-children events evaluation). /// public int Depth; + + /// + /// The calling thread id. + /// + public int ThreadId; } private static int _depth; @@ -78,6 +84,7 @@ namespace Flax.Build e.StartTime = DateTime.Now; e.Duration = TimeSpan.Zero; e.Depth = _depth++; + e.ThreadId = Thread.CurrentThread.ManagedThreadId; _events.Add(e); return _events.Count - 1; } @@ -147,8 +154,8 @@ namespace Flax.Build for (int i = 0; i < _events.Count; i++) { var e = _events[i]; - - contents.Append($"{{ \"pid\":1, \"tid\":1, \"ts\":{(int)((e.StartTime - startTime).TotalMilliseconds * 1000.0)}, \"dur\":{(int)(e.Duration.TotalMilliseconds * 1000.0)}, \"ph\":\"X\", \"name\":\"{e.Name}\", \"args\":{{ \"startTime\":\"{e.StartTime.ToShortTimeString()}\" }} }}\n"); + + contents.Append($"{{ \"pid\":{e.ThreadId}, \"tid\":1, \"ts\":{(int)((e.StartTime - startTime).TotalMilliseconds * 1000.0)}, \"dur\":{(int)(e.Duration.TotalMilliseconds * 1000.0)}, \"ph\":\"X\", \"name\":\"{e.Name}\", \"args\":{{ \"startTime\":\"{e.StartTime.ToShortTimeString()}\" }} }}\n"); if (i + 1 != _events.Count) contents.Append(","); diff --git a/Source/Tools/Flax.Build/Build/Sdk.cs b/Source/Tools/Flax.Build/Build/Sdk.cs index 816404679..5c7044705 100644 --- a/Source/Tools/Flax.Build/Build/Sdk.cs +++ b/Source/Tools/Flax.Build/Build/Sdk.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; using System.Collections.Generic; @@ -68,7 +68,7 @@ namespace Flax.Build using (new ProfileEventScope("GetSdks")) { _sdks = new Dictionary(); - var types = typeof(Sdk).Assembly.GetTypes().Where(x => !x.IsAbstract && x.IsSubclassOf(typeof(Sdk))); + var types = Builder.BuildTypes.Where(x => !x.IsAbstract && x.IsSubclassOf(typeof(Sdk))); foreach (var type in types) { object instance = null; diff --git a/Source/Tools/Flax.Build/Build/Target.cs b/Source/Tools/Flax.Build/Build/Target.cs index b7a6b2fbe..239578d51 100644 --- a/Source/Tools/Flax.Build/Build/Target.cs +++ b/Source/Tools/Flax.Build/Build/Target.cs @@ -225,7 +225,7 @@ namespace Flax.Build } /// - /// Setups the target building environment (native C++). Allows to modify compiler and linker options. Options applied here are used by all modules included into this target (can be overriden per module). + /// Setups the target building environment (native C++). Allows to modify compiler and linker options. Options applied here are used by all modules included into this target (can be overridden per module). /// /// The build options. public virtual void SetupTargetEnvironment(BuildOptions options) @@ -256,6 +256,7 @@ namespace Flax.Build options.CompileEnv.IntrinsicFunctions = false; options.CompileEnv.BufferSecurityCheck = true; options.CompileEnv.Inlining = false; + options.CompileEnv.WholeProgramOptimization = false; options.LinkEnv.DebugInformation = true; options.LinkEnv.LinkTimeCodeGeneration = false; @@ -273,11 +274,11 @@ namespace Flax.Build options.CompileEnv.IntrinsicFunctions = true; options.CompileEnv.BufferSecurityCheck = true; options.CompileEnv.Inlining = true; - //options.CompileEnv.WholeProgramOptimization = true; + options.CompileEnv.WholeProgramOptimization = false; options.LinkEnv.DebugInformation = true; - options.LinkEnv.LinkTimeCodeGeneration = true; - options.LinkEnv.UseIncrementalLinking = false; + options.LinkEnv.LinkTimeCodeGeneration = false; + options.LinkEnv.UseIncrementalLinking = true; options.LinkEnv.Optimization = true; break; case TargetConfiguration.Release: @@ -291,7 +292,7 @@ namespace Flax.Build options.CompileEnv.IntrinsicFunctions = true; options.CompileEnv.BufferSecurityCheck = false; options.CompileEnv.Inlining = true; - //options.CompileEnv.WholeProgramOptimization = true; + options.CompileEnv.WholeProgramOptimization = true; options.LinkEnv.DebugInformation = false; options.LinkEnv.LinkTimeCodeGeneration = true; diff --git a/Source/Tools/Flax.Build/Configuration.cs b/Source/Tools/Flax.Build/Configuration.cs index 4fb58c4f2..28970f289 100644 --- a/Source/Tools/Flax.Build/Configuration.cs +++ b/Source/Tools/Flax.Build/Configuration.cs @@ -1,5 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. +using System.Collections.Generic; + namespace Flax.Build { /// @@ -133,6 +135,18 @@ namespace Flax.Build [CommandLine("logfile", "", "The log file path relative to the working directory. Set to empty to disable it/")] public static string LogFile = "Cache/Intermediate/Log.txt"; + /// + /// The maximum allowed concurrency for a build system (maximum active worker threads count). + /// + [CommandLine("maxConcurrency", "", "The maximum allowed concurrency for a build system (maximum active worker threads count).")] + public static int MaxConcurrency = 1410; + + /// + /// The concurrency scale for a build system that specifies how many worker threads allocate per-logical processor. + /// + [CommandLine("concurrencyProcessorScale", "", "The concurrency scale for a build system that specifies how many worker threads allocate per-logical processor.")] + public static float ConcurrencyProcessorScale = 1.0f; + /// /// The output binaries folder path relative to the working directory. /// @@ -186,5 +200,10 @@ namespace Flax.Build /// [CommandLine("customProjectFormat", "", "Generates code project files for a custom project format type. Valid only with -genproject option.")] public static string ProjectFormatCustom = null; + + /// + /// Custom configuration defines provided via command line for the build tool. + /// + public static List CustomDefines = new List(); } } diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/PhysX.cs b/Source/Tools/Flax.Build/Deps/Dependencies/PhysX.cs index 3415501bd..32a408828 100644 --- a/Source/Tools/Flax.Build/Deps/Dependencies/PhysX.cs +++ b/Source/Tools/Flax.Build/Deps/Dependencies/PhysX.cs @@ -312,7 +312,7 @@ namespace Flax.Deps.Dependencies } // Get the source - //CloneGitRepoSingleBranch(root, "https://github.com/NVIDIAGameWorks/PhysX.git", "4.1"); + CloneGitRepoSingleBranch(root, "https://github.com/NVIDIAGameWorks/PhysX.git", "4.1"); foreach (var platform in options.Platforms) { diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/mono.cs b/Source/Tools/Flax.Build/Deps/Dependencies/mono.cs index 18fb44e9d..7cd7649b1 100644 --- a/Source/Tools/Flax.Build/Deps/Dependencies/mono.cs +++ b/Source/Tools/Flax.Build/Deps/Dependencies/mono.cs @@ -258,9 +258,8 @@ namespace Flax.Deps.Dependencies "mono_type_normalize", }; - private void BuildMsvc(BuildOptions options, TargetPlatform platform, TargetArchitecture architecture) + private void BuildMsvc(BuildOptions options, TargetPlatform platform, TargetArchitecture architecture, string configuration = "Release") { - var configuration = "Release"; string buildPlatform; switch (architecture) { @@ -491,12 +490,13 @@ namespace Flax.Deps.Dependencies { case TargetPlatform.Windows: { - BuildMsvc(options, platform, TargetArchitecture.x64); + var configuration = "Release"; + BuildMsvc(options, platform, TargetArchitecture.x64, configuration); //BuildBcl(options, platform); // Export header files - Deploy.VCEnvironment.BuildSolution(Path.Combine(root, "msvc", "libmono-dynamic.vcxproj"), "Release", "x64"); - Deploy.VCEnvironment.BuildSolution(Path.Combine(root, "msvc", "build-install.vcxproj"), "Release", "x64"); + Deploy.VCEnvironment.BuildSolution(Path.Combine(root, "msvc", "libmono-dynamic.vcxproj"), configuration, "x64"); + Deploy.VCEnvironment.BuildSolution(Path.Combine(root, "msvc", "build-install.vcxproj"), configuration, "x64"); // Get exported mono methods to forward them in engine module (on Win32 platforms) GetMonoExports(options); diff --git a/Source/Tools/Flax.Build/Deps/DepsBuilder.cs b/Source/Tools/Flax.Build/Deps/DepsBuilder.cs index 7ac4b0d69..6d6eaf5b3 100644 --- a/Source/Tools/Flax.Build/Deps/DepsBuilder.cs +++ b/Source/Tools/Flax.Build/Deps/DepsBuilder.cs @@ -51,7 +51,7 @@ namespace Flax.Deps } // Get all deps - var dependencies = typeof(DepsBuilder).Assembly.GetTypes().Where(x => x.IsSubclassOf(typeof(Dependency))).Select(Activator.CreateInstance).Cast().ToArray(); + var dependencies = Builder.BuildTypes.Where(x => x.IsSubclassOf(typeof(Dependency))).Select(Activator.CreateInstance).Cast().ToArray(); if (dependencies.Length == 0) Log.Warning("No dependencies found!"); for (var i = 0; i < dependencies.Length; i++) diff --git a/Source/Tools/Flax.Build/Flax.Build.csproj b/Source/Tools/Flax.Build/Flax.Build.csproj index a8221455f..236348032 100644 --- a/Source/Tools/Flax.Build/Flax.Build.csproj +++ b/Source/Tools/Flax.Build/Flax.Build.csproj @@ -64,17 +64,20 @@ + + + diff --git a/Source/Tools/Flax.Build/Log.cs b/Source/Tools/Flax.Build/Log.cs index ba948d7ca..3bd972f42 100644 --- a/Source/Tools/Flax.Build/Log.cs +++ b/Source/Tools/Flax.Build/Log.cs @@ -36,7 +36,7 @@ namespace Flax.Build var path = Path.GetDirectoryName(Configuration.LogFile); if (!string.IsNullOrEmpty(path) && !Directory.Exists(path)) Directory.CreateDirectory(path); - _logFile = new FileStream(Configuration.LogFile, FileMode.Create); + _logFile = new FileStream(Configuration.LogFile, FileMode.Create, FileAccess.Write, FileShare.Read); _logFileWriter = new StreamWriter(_logFile); } } diff --git a/Source/Tools/Flax.Build/Platforms/Android/AndroidNdk.cs b/Source/Tools/Flax.Build/Platforms/Android/AndroidNdk.cs index f6a7816cf..d02f440ae 100644 --- a/Source/Tools/Flax.Build/Platforms/Android/AndroidNdk.cs +++ b/Source/Tools/Flax.Build/Platforms/Android/AndroidNdk.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; using System.IO; diff --git a/Source/Tools/Flax.Build/Platforms/Android/AndroidSdk.cs b/Source/Tools/Flax.Build/Platforms/Android/AndroidSdk.cs index d2d0278bf..ec83fb90f 100644 --- a/Source/Tools/Flax.Build/Platforms/Android/AndroidSdk.cs +++ b/Source/Tools/Flax.Build/Platforms/Android/AndroidSdk.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; using System.IO; diff --git a/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs b/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs index 0917b5027..4d3289a86 100644 --- a/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs +++ b/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs @@ -467,8 +467,11 @@ namespace Flax.Build.Platforms // Frame-Pointer Omission commonArgs.Add("/Oy"); - // Whole Program Optimization - commonArgs.Add("/GL"); + if (compileEnvironment.WholeProgramOptimization) + { + // Whole Program Optimization + commonArgs.Add("/GL"); + } } else { @@ -721,7 +724,7 @@ namespace Flax.Build.Platforms args.Add("/PDBALTPATH:%_PDB%"); // Optimize - if (linkEnvironment.Optimization) + if (linkEnvironment.Optimization && !linkEnvironment.UseIncrementalLinking) { // Generate an EXE checksum args.Add("/RELEASE"); diff --git a/Source/Tools/Flax.Build/Program.cs b/Source/Tools/Flax.Build/Program.cs index fc6d86b3b..89e71ace0 100644 --- a/Source/Tools/Flax.Build/Program.cs +++ b/Source/Tools/Flax.Build/Program.cs @@ -32,6 +32,16 @@ namespace Flax.Build { // Setup CommandLine.Configure(typeof(Configuration)); + foreach (var option in CommandLine.GetOptions()) + { + if (option.Name.Length > 1 && option.Name[0] == 'D') + { + var define = option.Name.Substring(1); + if (!string.IsNullOrEmpty(option.Value)) + define += "=" + option.Value; + Configuration.CustomDefines.Add(define); + } + } if (Configuration.CurrentDirectory != null) Environment.CurrentDirectory = Configuration.CurrentDirectory; Globals.Root = Directory.GetCurrentDirectory(); diff --git a/Source/Tools/Flax.Build/Utilities/Tokenizer.cs b/Source/Tools/Flax.Build/Utilities/Tokenizer.cs index 70d0d7fc1..e73ddcbca 100644 --- a/Source/Tools/Flax.Build/Utilities/Tokenizer.cs +++ b/Source/Tools/Flax.Build/Utilities/Tokenizer.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; using System.Collections.Generic; @@ -460,6 +460,24 @@ namespace Flax.Build } } + /// + /// Skips all tokens until the tokenizer steps into token of given type (and it is also skipped, so, NextToken will give the next token). + /// + /// The expected token type. + /// The output contents of the skipped tokens. + /// When false, all white-space tokens will be ignored. + public void SkipUntil(TokenType tokenType, out string context, bool includeWhitespaces) + { + context = string.Empty; + while (NextToken(true).Type != tokenType) + { + var token = CurrentToken; + if (!includeWhitespaces && (token.Type == TokenType.Newline || token.Type == TokenType.Whitespace)) + continue; + context += token.Value; + } + } + /// /// Disposes the . /// diff --git a/Source/Tools/Flax.Build/Utilities/TwoWayEnumerator.cs b/Source/Tools/Flax.Build/Utilities/TwoWayEnumerator.cs index 0347a88a8..ec9f71091 100644 --- a/Source/Tools/Flax.Build/Utilities/TwoWayEnumerator.cs +++ b/Source/Tools/Flax.Build/Utilities/TwoWayEnumerator.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; using System.Collections.Generic; diff --git a/Source/Tools/Flax.Build/Utilities/Utilities.cs b/Source/Tools/Flax.Build/Utilities/Utilities.cs index 760aafbe5..8390377a8 100644 --- a/Source/Tools/Flax.Build/Utilities/Utilities.cs +++ b/Source/Tools/Flax.Build/Utilities/Utilities.cs @@ -19,7 +19,7 @@ namespace Flax.Build /// /// The input string. /// The file size text. - internal static uint GetHashCode(string str) + public static uint GetHashCode(string str) { uint hash = 5381; if (str != null) @@ -33,6 +33,16 @@ namespace Flax.Build return hash; } + /// + /// Gets the empty array of the given type (shared one). + /// + /// The type. + /// The empty array object. + public static T[] GetEmptyArray() + { + return Enumerable.Empty() as T[]; + } + /// /// Gets the size of the file as a readable string. /// diff --git a/Source/Tools/Flax.Stats/CodeFrameNode.cs b/Source/Tools/Flax.Stats/CodeFrameNode.cs index 25d326ff4..e42bdcfef 100644 --- a/Source/Tools/Flax.Stats/CodeFrameNode.cs +++ b/Source/Tools/Flax.Stats/CodeFrameNode.cs @@ -75,14 +75,14 @@ namespace Flax.Stats /// /// Gets total amount of memory used by that node and all child nodes /// - public long TotaSizeOnDisk + public long TotalSizeOnDisk { get { long result = SizeOnDisk; for (int i = 0; i < Children.Length; i++) { - result += Children[i].TotaSizeOnDisk; + result += Children[i].TotalSizeOnDisk; } return result; } @@ -153,15 +153,15 @@ namespace Flax.Stats /// /// Gets total amount of lines of code per language /// - /// Language + /// Language /// Result amount of lines - public long GetTotalLinesOfCode(Languages languge) + public long GetTotalLinesOfCode(Languages language) { long result = 0; - result += LinesOfCode[(int)languge]; + result += LinesOfCode[(int)language]; for (int i = 0; i < Children.Length; i++) { - result += Children[i].GetTotalLinesOfCode(languge); + result += Children[i].GetTotalLinesOfCode(language); } return result; } @@ -270,9 +270,9 @@ namespace Flax.Stats public void CleanupDirectories() { - var chld = Children.ToList(); - chld.RemoveAll(e => ignoredFolders.Contains(e.ShortName.ToLower())); - Children = chld.ToArray(); + var child = Children.ToList(); + child.RemoveAll(e => ignoredFolders.Contains(e.ShortName.ToLower())); + Children = child.ToArray(); foreach (var a in Children) { diff --git a/Source/Tools/Flax.Stats/Tools.cs b/Source/Tools/Flax.Stats/Tools.cs index d947ae758..1e3ba1f74 100644 --- a/Source/Tools/Flax.Stats/Tools.cs +++ b/Source/Tools/Flax.Stats/Tools.cs @@ -103,7 +103,7 @@ namespace Flax.Stats } /// - /// Write string in UTF-8 encoding to the stream and ofset data + /// Write string in UTF-8 encoding to the stream and offset data /// /// File stream /// Data to write @@ -292,7 +292,7 @@ namespace Flax.Stats } /// - /// Write arry of Guids to the stream + /// Write array of Guids to the stream /// /// File stream /// Value to write diff --git a/Source/Tools/FlaxEngine.Tests/TestTransform.cs b/Source/Tools/FlaxEngine.Tests/TestTransform.cs index d947c295e..38f71e217 100644 --- a/Source/Tools/FlaxEngine.Tests/TestTransform.cs +++ b/Source/Tools/FlaxEngine.Tests/TestTransform.cs @@ -143,7 +143,29 @@ namespace FlaxEngine.Tests Transform ab = a.LocalToWorld(b); Transform ba = a.WorldToLocal(ab); - Assert.IsTrue(Transform.NearEqual(ref b, ref ba, 0.00001f)); + Assert.IsTrue(Transform.NearEqual(ref b, ref ba, 0.00001f), $"Got: {b} but expected {ba}"); + } + } + + /// + /// Test conversions between transform local/world space + /// + [Test] + public void TestAddSubtract() + { + var rand = new Random(10); + for (int i = 0; i < 10; i++) + { + Transform a = new Transform(rand.NextVector3(), Quaternion.Euler(i * 10, 0, i), rand.NextVector3() * 10.0f); + Transform b = new Transform(rand.NextVector3(), Quaternion.Euler(i, 1, 22), rand.NextVector3() * 0.3f); + + Transform ab = a + b; + Transform newA = ab - b; + Assert.IsTrue(Transform.NearEqual(ref a, ref newA, 0.00001f), $"Got: {newA} but expected {a}"); + + Transform ba = b + a; + Transform newB = ba - a; + Assert.IsTrue(Transform.NearEqual(ref b, ref newB, 0.00001f), $"Got: {newB} but expected {b}"); } } } diff --git a/Source/flax.natvis b/Source/flax.natvis index fb3ef1228..550d8a4d4 100644 --- a/Source/flax.natvis +++ b/Source/flax.natvis @@ -52,7 +52,7 @@ _elementsCount - + _table[i] i++ @@ -72,7 +72,7 @@ _elementsCount - + _table[i] i++ @@ -206,4 +206,13 @@ + + + Null + Type={Module->Types._allocation._data[TypeIndex].Fullname} + + Module->Types._allocation._data[TypeIndex] + + +