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