diff --git a/.github/ISSUE_TEMPLATE/1-bug.yaml b/.github/ISSUE_TEMPLATE/1-bug.yaml new file mode 100644 index 000000000..2e2c65485 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/1-bug.yaml @@ -0,0 +1,42 @@ +name: Bug Report +description: File a bug report. +title: "[Bug]: " +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this bug report! Please attach any minimal reproduction projects! + - type: textarea + id: description-area + attributes: + label: Description + description: Please provide a description of the bug and what you expected to happen. + validations: + required: true + - type: textarea + id: steps-area + attributes: + label: Steps to reproduce + description: Please provide reproduction steps if possible. + validations: + required: true + - type: dropdown + id: version + attributes: + label: Version + description: What version of Flax are you running? + options: + - '1.8' + - '1.9' + - '1.10' + - '1.11' + - master branch + default: 2 + validations: + required: true + - type: textarea + id: logs + attributes: + label: Relevant logs + description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. + render: shell \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/2-feature-request.yaml b/.github/ISSUE_TEMPLATE/2-feature-request.yaml new file mode 100644 index 000000000..338c9aea0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/2-feature-request.yaml @@ -0,0 +1,22 @@ +name: Feature Request +description: File a feature request. +title: "[Request]: " +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out a feature request! + - type: textarea + id: description-area + attributes: + label: Description + description: Please provide a description of the feature! + validations: + required: true + - type: textarea + id: benefits-area + attributes: + label: Benefits + description: Please provide what benefits this feature would provide to the engine! + validations: + required: true \ No newline at end of file diff --git a/Content/Editor/Camera/M_Camera.flax b/Content/Editor/Camera/M_Camera.flax index 5012b16e9..25b184d86 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:6a2936be1789e6a7c663f84ddfea8c897fe8273cd2d29910ac37720907d7b930 +oid sha256:5ef316cb161204b2c7a9dd4746c6c7281507a1e26c4c48c1a917b7645ac611f6 size 29533 diff --git a/Content/Editor/CubeTexturePreviewMaterial.flax b/Content/Editor/CubeTexturePreviewMaterial.flax index 973a01177..3d7014519 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:6ecf44ea82025d0f491c68b0470e1704ca5f385bd54ad196d6912aeb2f3aee0f +oid sha256:99287e35e0c8ed22fd336cce66d8a675ad43fb318d431e63baf23670bc1212a7 size 31125 diff --git a/Content/Editor/DebugMaterials/DDGIDebugProbes.flax b/Content/Editor/DebugMaterials/DDGIDebugProbes.flax index d598ac9bb..a5c8ccda0 100644 --- a/Content/Editor/DebugMaterials/DDGIDebugProbes.flax +++ b/Content/Editor/DebugMaterials/DDGIDebugProbes.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f6e897a2fcbbb21efdd589604b4e3fc657f457ea124384842b380295af66cf13 +oid sha256:83aee3f2d5088930b7395099150a1bc062584996082cc1e13465c546d6f90fa3 size 40358 diff --git a/Content/Editor/DebugMaterials/SingleColor/Decal.flax b/Content/Editor/DebugMaterials/SingleColor/Decal.flax index 6024d3961..a85229155 100644 --- a/Content/Editor/DebugMaterials/SingleColor/Decal.flax +++ b/Content/Editor/DebugMaterials/SingleColor/Decal.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d8301da35f0b45f58f5c59221bd22bf0a8500555d31ab84ec1c8cd5eca9fc101 +oid sha256:aa1b7b7f37b73cbcdef2faa6a6a1a1ce11c08033233606dd34e558195f4718cd size 9973 diff --git a/Content/Editor/DebugMaterials/SingleColor/Particle.flax b/Content/Editor/DebugMaterials/SingleColor/Particle.flax index 9c2c0755a..423396308 100644 --- a/Content/Editor/DebugMaterials/SingleColor/Particle.flax +++ b/Content/Editor/DebugMaterials/SingleColor/Particle.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4e432328bb19eaa58caf35f60cd6495a46cca694314828010f3be401d7de9434 -size 32108 +oid sha256:5096f5c6aff954618c4fc863e59a76ce880879695bac1abc990fbabd0b8d330b +size 32699 diff --git a/Content/Editor/DebugMaterials/SingleColor/Surface.flax b/Content/Editor/DebugMaterials/SingleColor/Surface.flax index 0fb0d1c6f..f405ecd8b 100644 --- a/Content/Editor/DebugMaterials/SingleColor/Surface.flax +++ b/Content/Editor/DebugMaterials/SingleColor/Surface.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1688861997cc2e8a433cdea81ee62662f45b261bc863cdb9431beed612f0aad7 +oid sha256:0c1c2e4e2324de02092f73a0477cd909e400e99a400ce255c59751b21af3ed8e size 29306 diff --git a/Content/Editor/DebugMaterials/SingleColor/SurfaceAdditive.flax b/Content/Editor/DebugMaterials/SingleColor/SurfaceAdditive.flax index e5bda89f6..98a9221f6 100644 --- a/Content/Editor/DebugMaterials/SingleColor/SurfaceAdditive.flax +++ b/Content/Editor/DebugMaterials/SingleColor/SurfaceAdditive.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:42363fad30e29b0e0c4cf7ad9e02dc91040eb6821c3c28bd49996771e65893c4 -size 31559 +oid sha256:7b8794eaf03955f5afb9cf8a56a6327d7fe3b7c287e2eeb50c95d1a78dd2c7c4 +size 32150 diff --git a/Content/Editor/DebugMaterials/SingleColor/Terrain.flax b/Content/Editor/DebugMaterials/SingleColor/Terrain.flax index 09a253a8d..2ab956aad 100644 --- a/Content/Editor/DebugMaterials/SingleColor/Terrain.flax +++ b/Content/Editor/DebugMaterials/SingleColor/Terrain.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eb957ea2ee358b0e611d6612703261c9837099b6d03d48484cdab1151a461d8f +oid sha256:dfd8cdbfe96f53c2795b7c408a5f6c09fbf67b0d3ded6c34aacf2e34313e1925 size 21004 diff --git a/Content/Editor/DefaultFontMaterial.flax b/Content/Editor/DefaultFontMaterial.flax index b4f659e34..71b91f9a6 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:7a46410b49a7e38c4c2569e4d8d8c8f4744cf26f79c632a53899ce753ab7c88a +oid sha256:9a5f051b559c86cd397dda551afd4f3b751a99bc9cb14de989a80c2a04651d82 size 29627 diff --git a/Content/Editor/Gizmo/FoliageBrushMaterial.flax b/Content/Editor/Gizmo/FoliageBrushMaterial.flax index 1d8e89a65..7af84241d 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:f377cc4e05d89e4edbec6382a052abe571f3261bc273cd4475541b7b7051cffb -size 37586 +oid sha256:3d2bb53a8b94d596738a6a1527d2516e032a287a7a6de6fab472be7a28978de2 +size 38177 diff --git a/Content/Editor/Gizmo/Material.flax b/Content/Editor/Gizmo/Material.flax index cce270410..2d52c87a9 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:271c80d9d9971d96d6ea430dfaf8f8f57d9b5f9fe1770b387f426d3c8721c3d8 -size 32713 +oid sha256:261de215acfeee3d2e37bfea0c4f807c7f685f20719032f1706f82b07ae52992 +size 33304 diff --git a/Content/Editor/Gizmo/MaterialAxisLocked.flax b/Content/Editor/Gizmo/MaterialAxisLocked.flax deleted file mode 100644 index be44ece86..000000000 --- a/Content/Editor/Gizmo/MaterialAxisLocked.flax +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7518765847301a4b13625fb05d542fab4fc924190a7414d39227db817a0e29cb -size 661 diff --git a/Content/Editor/Gizmo/MaterialWire.flax b/Content/Editor/Gizmo/MaterialWire.flax index 4b30df5f5..4b90f0311 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:ff3e7b3e77afa7191f1db9cf12f21908b80bb8f71e832c37e55547dc9dcab31c -size 31410 +oid sha256:072c5ae6e0248b8f48bb933a8bd51935218afe425ab9f6df5fb853003e545b52 +size 32001 diff --git a/Content/Editor/Gizmo/SelectionOutlineMaterial.flax b/Content/Editor/Gizmo/SelectionOutlineMaterial.flax index 962179da0..7dc84ec92 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:fc2facc8fa980e5baa399fa7510a87d33d21bbd4c97eaab24856f6db49b13172 +oid sha256:87f9eee9186d2862aa04c837d05e4cffeb1eff8be706ea856bd74a179ad591bb size 16212 diff --git a/Content/Editor/Gizmo/VertexColorsPreviewMaterial.flax b/Content/Editor/Gizmo/VertexColorsPreviewMaterial.flax index 305c6a4c2..b6e40d5b4 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:4458a9483e81fb0526cc395f93eeae238f4f91fa5d4889e3196b6530a8f17ec2 +oid sha256:c8b6d69f23dc6dd94e03180127e1b5eafb9bb610a392d41b6aee94d3e03bebef size 30419 diff --git a/Content/Editor/Highlight Material.flax b/Content/Editor/Highlight Material.flax index e944ae62d..4371e5753 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:391606b1f7563d9a8e414baf6c19f3d99694a28f3f574b7aca64a325294d8e39 -size 30104 +oid sha256:bde47005982dbcb96abb900d5773d5f6d707ea0eee94b7b385945e3b3b350372 +size 30695 diff --git a/Content/Editor/Icons/IconsMaterial.flax b/Content/Editor/Icons/IconsMaterial.flax index 320547f9a..dfcfed0b6 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:7951a44b138e60aa9dee4fdaf000eba8a7faef7b31c2e387f78b4a393d0cd0bc -size 30021 +oid sha256:c6524caaaf72bc30bc32bffcfdf26b4d1d2adc466dd5143c4b1980e62b14b97f +size 30612 diff --git a/Content/Editor/IesProfilePreviewMaterial.flax b/Content/Editor/IesProfilePreviewMaterial.flax index c7a025108..9f23ac666 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:41210c6c490513503f01e8a628d80dd98e58fc0482f9966f9342700118ccd04c +oid sha256:f796e403ee85d33eb2be0a371e18085a31ca7bf970e26c19c26f9ea2b0224a33 size 20445 diff --git a/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl b/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl index 22d9ca9b4..bb18b3011 100644 --- a/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl +++ b/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl @@ -27,6 +27,7 @@ TextureCube EnvProbe : register(t__SRV__); TextureCube SkyLightTexture : register(t__SRV__); Buffer ShadowsBuffer : register(t__SRV__); Texture2D ShadowMap : register(t__SRV__); +Texture3D VolumetricFogTexture : register(t__SRV__); @4// Forward Shading: Utilities @5// Forward Shading: Shaders @@ -147,6 +148,18 @@ void PS_Forward( // Calculate exponential height fog float4 fog = GetExponentialHeightFog(ExponentialHeightFog, materialInput.WorldPosition, ViewPos, 0, gBuffer.ViewPos.z); + if (ExponentialHeightFog.VolumetricFogMaxDistance > 0) + { + // Sample volumetric fog and mix it in + float2 screenUV = materialInput.SvPosition.xy * ScreenSize.zw; + float3 viewVector = materialInput.WorldPosition - ViewPos; + float sceneDepth = length(viewVector); + float depthSlice = sceneDepth / ExponentialHeightFog.VolumetricFogMaxDistance; + float3 volumeUV = float3(screenUV, depthSlice); + float4 volumetricFog = VolumetricFogTexture.SampleLevel(SamplerLinearClamp, volumeUV, 0); + fog = CombineVolumetricFog(fog, volumetricFog); + } + // Apply fog to the output color #if MATERIAL_BLEND == MATERIAL_BLEND_OPAQUE output = float4(output.rgb * fog.a + fog.rgb, output.a); diff --git a/Content/Editor/MaterialTemplates/Particle.shader b/Content/Editor/MaterialTemplates/Particle.shader index 6f0be21e0..661a8f69b 100644 --- a/Content/Editor/MaterialTemplates/Particle.shader +++ b/Content/Editor/MaterialTemplates/Particle.shader @@ -645,7 +645,7 @@ VertexOutput VS_Ribbon(RibbonInput input, uint vertexIndex : SV_VertexID) materialInput.TBN = output.TBN; materialInput.TwoSidedSign = 1; materialInput.SvPosition = output.Position; - materialInput.PreSkinnedPosition = Position; + materialInput.PreSkinnedPosition = position; materialInput.PreSkinnedNormal = tangentToLocal[2].xyz; materialInput.InstanceOrigin = output.InstanceOrigin; materialInput.InstanceParams = output.InstanceParams; diff --git a/Content/Editor/Particles/Particle Material Color.flax b/Content/Editor/Particles/Particle Material Color.flax index bde834456..50cf3b09f 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:b600cd725f5550de72d5a2544571ca2c1ea8de1a3d45038bac273d2b6f3b04c2 -size 30429 +oid sha256:d4fcde3b4d440561997706160c0f10e4b82e30b7c2f8aa732fd1439d9bae160e +size 31020 diff --git a/Content/Editor/Particles/Smoke Material.flax b/Content/Editor/Particles/Smoke Material.flax index 7a8fdeb1a..38b79ec48 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:96d3865771cfa47ad59e0493c8f1b6e9fd9950593b2b929c91ea2692eca71efd -size 38495 +oid sha256:1a1fdeeb738daa07787556cac664214c33633a1f9a3021aff4dc0727e105ea4c +size 39087 diff --git a/Content/Editor/SpriteMaterial.flax b/Content/Editor/SpriteMaterial.flax index 876a38a56..2c98b951e 100644 --- a/Content/Editor/SpriteMaterial.flax +++ b/Content/Editor/SpriteMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5d02a6d11ea83ea6c519a1644950750e58433e6a2aea9a2daa646db1d2b0c293 +oid sha256:3059c52ee632be69bab545192e170b61ddabcf67d4848bbdb46f4c1da5414072 size 30502 diff --git a/Content/Editor/Terrain/Circle Brush Material.flax b/Content/Editor/Terrain/Circle Brush Material.flax index af191fdaa..71a54e7f6 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:8e9ef5186642a38af8ebb5856a891215686576b23841aabe16b7dde7f2bcb57f +oid sha256:bd3172fc12d5ad7c6cc862f4b62a77b781974242e14518dbf81805605941cd55 size 27676 diff --git a/Content/Editor/Terrain/Highlight Terrain Material.flax b/Content/Editor/Terrain/Highlight Terrain Material.flax index 14bd86c35..863c238b7 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:5f1a9524d9bc7ee41df761b9fb34613fc04357377a81836fb28b3ee5a6f2dcf4 +oid sha256:fbdc0533affafaaf7f8813cfdbe5a9d5596038e0e5e46ad3e970ec7ffd924fab size 21179 diff --git a/Content/Editor/TexturePreviewMaterial.flax b/Content/Editor/TexturePreviewMaterial.flax index fac30b4f1..2ab0e2c23 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:c178081b59417439b2d523486f3c7e4f7f391ff57e5ab5a565aadf3fd31f5488 +oid sha256:b5d4eda6497493dabf6697a9df34ec59b11183648e481399a7b1beae3c8596e1 size 10744 diff --git a/Content/Editor/Wires Debug Material.flax b/Content/Editor/Wires Debug Material.flax index 8fff1a174..8c5a07c5d 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:c31daed51b38e6aa9eeceaad85498d6ae7079f7b125c6f71f036799278c34e22 -size 30104 +oid sha256:037ea2b610ee9c7ceb2cdb680d2a5d898a0600a896df93036565e11aef02a17c +size 30695 diff --git a/Content/Engine/DefaultDeformableMaterial.flax b/Content/Engine/DefaultDeformableMaterial.flax index 8af3db999..b3be3fe58 100644 --- a/Content/Engine/DefaultDeformableMaterial.flax +++ b/Content/Engine/DefaultDeformableMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:df767eeb060d058d502271f1cd89581c57ad339cd690cc9c588f9a34cc9344b1 +oid sha256:4591783ed6e6cca6b8639dd3ebb624101b623c3813b80f75e22ce606d914b8db size 18985 diff --git a/Content/Engine/DefaultMaterial.flax b/Content/Engine/DefaultMaterial.flax index a253452df..51e67d79f 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:b00388390410aabb11f1d9b032361902d2f284daa765d536c8f2a821f659effe +oid sha256:f1906849094ff4ad6022e14762961e2a235160d29427f82724d91ca8b8cfb320 size 31331 diff --git a/Content/Engine/DefaultRadialMenu.flax b/Content/Engine/DefaultRadialMenu.flax index 2b3d7f0de..8175a130a 100644 --- a/Content/Engine/DefaultRadialMenu.flax +++ b/Content/Engine/DefaultRadialMenu.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2b0272c8f2df095e6609f49845a3d329daaf634e0776ca764e4c51596cac60ff +oid sha256:5ab4c3c9d3f425b136eb0cdee3342b5c99f9372e691d5bf0b1f85c7654362805 size 20514 diff --git a/Content/Engine/DefaultTerrainMaterial.flax b/Content/Engine/DefaultTerrainMaterial.flax index 8d195ab98..cd1a11e93 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:a86caeef4de5a84783ba34208701c0f272f3b4b3ff82c64c2553d6aec631e07b +oid sha256:04a300665f72ea9543edb0419f660c09bd6d41eb46021a52be4b5bb14fa257a1 size 23301 diff --git a/Content/Engine/SingleColorMaterial.flax b/Content/Engine/SingleColorMaterial.flax index b6906b930..a9534c2db 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:74d77dbfdf72c9281b0760a266adac7f1eb849f9656ea8da5cd8951f2fab5343 +oid sha256:5da0e59ceaa1921fce54a8602837fe4782436ba67af4c945b2827cbc672cb362 size 29507 diff --git a/Content/Engine/SkyboxMaterial.flax b/Content/Engine/SkyboxMaterial.flax index 8faccf8c0..7f9545007 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:8149367ccbef36932866e6af53fedf79931f26677db5dfcce71ba33caeff5980 -size 31070 +oid sha256:98a0d6b06a90d753e68638ac64836cb96e6edc90e5b13770b722f33512034c09 +size 31650 diff --git a/Content/Shaders/Fog.flax b/Content/Shaders/Fog.flax index 3f934412c..9bce56170 100644 --- a/Content/Shaders/Fog.flax +++ b/Content/Shaders/Fog.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e83f9dbbcf84550de09e7c63bbdd3acc6591cf6ba1bcce2a2699772122ae07f4 -size 2633 +oid sha256:72daf0834367b96cc0732e6b3a5d8944c1048f516a52146bfd47f8ad83c95b4a +size 2590 diff --git a/Content/Shaders/Sky.flax b/Content/Shaders/Sky.flax index 87a5b6b3a..a568db451 100644 --- a/Content/Shaders/Sky.flax +++ b/Content/Shaders/Sky.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:28d44ef295d989d49ad4d59d6581eee921bc5fa5237e046d271a433454496388 -size 3463 +oid sha256:8541d1f0590167498f44da231f6de4f2938e15b26a5eb58e27b5067e1c882be8 +size 2252 diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md deleted file mode 100644 index 798f210ef..000000000 --- a/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,19 +0,0 @@ - - -**Issue description:** - - - -**Steps to reproduce:** - - - -**Minimal reproduction project:** - - - -**Flax version:** - - diff --git a/Source/Editor/Content/Create/PrefabCreateEntry.cs b/Source/Editor/Content/Create/PrefabCreateEntry.cs index 90cca263d..e9ceaaa68 100644 --- a/Source/Editor/Content/Create/PrefabCreateEntry.cs +++ b/Source/Editor/Content/Create/PrefabCreateEntry.cs @@ -117,7 +117,8 @@ namespace FlaxEditor.Content.Create private static bool IsValid(Type type) { - return (type.IsPublic || type.IsNestedPublic) && !type.IsAbstract && !type.IsGenericType; + var controlTypes = Editor.Instance.CodeEditing.Controls.Get(); + return (type.IsPublic || type.IsNestedPublic) && !type.IsAbstract && !type.IsGenericType && controlTypes.Contains(new ScriptType(type)); } } diff --git a/Source/Editor/CustomEditors/CustomEditorsUtil.cs b/Source/Editor/CustomEditors/CustomEditorsUtil.cs index 7d7ef123d..476219960 100644 --- a/Source/Editor/CustomEditors/CustomEditorsUtil.cs +++ b/Source/Editor/CustomEditors/CustomEditorsUtil.cs @@ -87,8 +87,11 @@ namespace FlaxEditor.CustomEditors var targetTypeType = TypeUtils.GetType(targetType); if (canUseRefPicker) { + // TODO: add generic way of CustomEditor for ref pickers (use it on AssetRefEditor/GPUTextureEditor/...) if (typeof(Asset).IsAssignableFrom(targetTypeType)) return new AssetRefEditor(); + if (typeof(GPUTexture).IsAssignableFrom(targetTypeType)) + return new GPUTextureEditor(); if (typeof(FlaxEngine.Object).IsAssignableFrom(targetTypeType)) return new FlaxObjectRefEditor(); } diff --git a/Source/Editor/CustomEditors/Dedicated/AudioSourceEditor.cs b/Source/Editor/CustomEditors/Dedicated/AudioSourceEditor.cs index 1ddc1c144..0b15773b8 100644 --- a/Source/Editor/CustomEditors/Dedicated/AudioSourceEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/AudioSourceEditor.cs @@ -13,6 +13,8 @@ namespace FlaxEditor.CustomEditors.Dedicated public class AudioSourceEditor : ActorEditor { private Label _infoLabel; + private Slider _slider; + private AudioSource.States _slideStartState; /// public override void Initialize(LayoutElementsContainer layout) @@ -28,6 +30,13 @@ namespace FlaxEditor.CustomEditors.Dedicated _infoLabel = playbackGroup.Label(string.Empty).Label; _infoLabel.AutoHeight = true; + // Play back slider + var sliderElement = playbackGroup.CustomContainer(); + _slider = sliderElement.CustomControl; + _slider.ThumbSize = new Float2(_slider.ThumbSize.X * 0.5f, _slider.ThumbSize.Y); + _slider.SlidingStart += OnSlidingStart; + _slider.SlidingEnd += OnSlidingEnd; + var grid = playbackGroup.UniformGrid(); var gridControl = grid.CustomControl; gridControl.ClipChildren = false; @@ -40,6 +49,38 @@ namespace FlaxEditor.CustomEditors.Dedicated } } + private void OnSlidingEnd() + { + foreach (var value in Values) + { + if (value is AudioSource audioSource && audioSource.Clip) + { + switch (_slideStartState) + { + case AudioSource.States.Playing: + audioSource.Play(); + break; + case AudioSource.States.Paused: + case AudioSource.States.Stopped: + audioSource.Pause(); + break; + default: break; + } + } + } + } + + private void OnSlidingStart() + { + foreach (var value in Values) + { + if (value is AudioSource audioSource && audioSource.Clip) + { + _slideStartState = audioSource.State; + } + } + } + /// public override void Refresh() { @@ -51,7 +92,29 @@ namespace FlaxEditor.CustomEditors.Dedicated foreach (var value in Values) { if (value is AudioSource audioSource && audioSource.Clip) + { text += $"Time: {audioSource.Time:##0.0}s / {audioSource.Clip.Length:##0.0}s\n"; + _slider.Maximum = audioSource.Clip.Length; + _slider.Minimum = 0; + if (_slider.IsSliding) + { + if (audioSource.State != AudioSource.States.Playing) + { + // Play to move slider correctly + audioSource.Play(); + audioSource.Time = _slider.Value; + } + else + { + audioSource.Time = _slider.Value; + } + } + else + { + _slider.Value = audioSource.Time; + } + + } } _infoLabel.Text = text; } diff --git a/Source/Editor/CustomEditors/Dedicated/GPUTextureEditor.cs b/Source/Editor/CustomEditors/Dedicated/GPUTextureEditor.cs new file mode 100644 index 000000000..2daa3c0a5 --- /dev/null +++ b/Source/Editor/CustomEditors/Dedicated/GPUTextureEditor.cs @@ -0,0 +1,68 @@ +// Copyright (c) Wojciech Figat. All rights reserved. + +using FlaxEditor.GUI.ContextMenu; +using FlaxEngine; +using FlaxEngine.GUI; + +namespace FlaxEditor.CustomEditors.Dedicated +{ + /// + /// Basic editor/viewer for . + /// + [CustomEditor(typeof(GPUTexture)), DefaultEditor] + public class GPUTextureEditor : CustomEditor + { + private Image _image; + + /// + public override DisplayStyle Style => DisplayStyle.Inline; + + /// + public override void Initialize(LayoutElementsContainer layout) + { + _image = new Image + { + Brush = new GPUTextureBrush(), + Size = new Float2(200, 100), + Parent = layout.ContainerControl, + }; + _image.Clicked += OnImageClicked; + } + + private void OnImageClicked(Image image, MouseButton button) + { + var texture = Values[0] as GPUTexture; + if (!texture || button != MouseButton.Right) + return; + var menu = new ContextMenu(); + menu.AddButton("Save...", () => Screenshot.Capture(Values[0] as GPUTexture)); + menu.AddButton("Enlarge", () => _image.Size *= 2); + menu.AddButton("Shrink", () => _image.Size /= 2).Enabled = _image.Height > 32; + var location = image.PointFromScreen(Input.MouseScreenPosition); + menu.Show(image, location); + } + + /// + public override void Refresh() + { + base.Refresh(); + + var texture = Values[0] as GPUTexture; + ((GPUTextureBrush)_image.Brush).Texture = texture; + if (texture) + { + var desc = texture.Description; +#if BUILD_RELEASE + var name = string.Empty; +#else + var name = texture.Name; +#endif + _image.TooltipText = $"{name}\nType: {texture.ResourceType}\nSize: {desc.Width}x{desc.Height}\nFormat: {desc.Format}\nMemory: {Utilities.Utils.FormatBytesCount(texture.MemoryUsage)}"; + } + else + { + _image.TooltipText = "None"; + } + } + } +} diff --git a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs index 123c7252a..356ae5ee4 100644 --- a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs @@ -36,6 +36,7 @@ namespace FlaxEditor.CustomEditors.Dedicated { ScriptName = scriptName; TooltipText = "Create a new script"; + DrawHighlights = false; } } @@ -70,7 +71,7 @@ namespace FlaxEditor.CustomEditors.Dedicated var buttonHeight = (textSize.Y < 18) ? 18 : textSize.Y + 4; _addScriptsButton = new Button { - TooltipText = "Add new scripts to the actor", + TooltipText = "Add new scripts to the actor.", AnchorPreset = AnchorPresets.MiddleCenter, Text = buttonText, Parent = this, @@ -114,7 +115,16 @@ namespace FlaxEditor.CustomEditors.Dedicated cm.TextChanged += text => { if (!IsValidScriptName(text)) + { + // Remove NewScriptItems + List newScriptItems = cm.ItemsPanel.Children.FindAll(c => c is NewScriptItem); + foreach (var item in newScriptItems) + { + cm.ItemsPanel.RemoveChild(item); + } + return; + } if (!cm.ItemsPanel.Children.Any(x => x.Visible && x is not NewScriptItem)) { // If there are no visible items, that means the search failed so we can find the create script button or create one if it's the first time @@ -876,7 +886,7 @@ namespace FlaxEditor.CustomEditors.Dedicated // Add drag button to the group var scriptDrag = new DragImage { - TooltipText = "Script reference", + TooltipText = "Script reference.", AutoFocus = true, IsScrollable = false, Color = FlaxEngine.GUI.Style.Current.ForegroundGrey, diff --git a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs index a0ee8d3dc..b977dab63 100644 --- a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs +++ b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs @@ -71,7 +71,7 @@ namespace FlaxEditor.CustomEditors.Editors menu.AddButton("Copy", linkedEditor.Copy); var b = menu.AddButton("Duplicate", () => Editor.Duplicate(Index)); - b.Enabled = linkedEditor.CanPaste && !Editor._readOnly; + b.Enabled = linkedEditor.CanPaste && !Editor._readOnly && Editor._canResize; b = menu.AddButton("Paste", linkedEditor.Paste); b.Enabled = linkedEditor.CanPaste && !Editor._readOnly; @@ -407,7 +407,7 @@ namespace FlaxEditor.CustomEditors.Editors menu.AddButton("Copy", linkedEditor.Copy); var b = menu.AddButton("Duplicate", () => Editor.Duplicate(Index)); - b.Enabled = linkedEditor.CanPaste && !Editor._readOnly; + b.Enabled = linkedEditor.CanPaste && !Editor._readOnly && Editor._canResize; var paste = menu.AddButton("Paste", linkedEditor.Paste); paste.Enabled = linkedEditor.CanPaste && !Editor._readOnly; @@ -422,7 +422,8 @@ namespace FlaxEditor.CustomEditors.Editors moveDownButton.Enabled = Index + 1 < Editor.Count; } - menu.AddButton("Remove", OnRemoveClicked); + b = menu.AddButton("Remove", OnRemoveClicked); + b.Enabled = !Editor._readOnly && Editor._canResize; menu.Show(panel, location); } diff --git a/Source/Editor/CustomEditors/Editors/ObjectSwitcherEditor.cs b/Source/Editor/CustomEditors/Editors/ObjectSwitcherEditor.cs index 85efbadc3..09670f1b2 100644 --- a/Source/Editor/CustomEditors/Editors/ObjectSwitcherEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ObjectSwitcherEditor.cs @@ -3,6 +3,7 @@ using System; using FlaxEditor.GUI; using FlaxEditor.Scripting; +using FlaxEngine; using FlaxEngine.Utilities; namespace FlaxEditor.CustomEditors.Editors @@ -81,9 +82,13 @@ namespace FlaxEditor.CustomEditors.Editors private OptionType[] _options; private ScriptType _type; + private Elements.PropertiesListElement _typeItem; private ScriptType Type => Values[0] == null ? Values.Type : TypeUtils.GetObjectType(Values[0]); + /// + public override bool RevertValueWithChildren => false; // Always revert value for a whole object + /// public override void Initialize(LayoutElementsContainer layout) { @@ -98,7 +103,8 @@ namespace FlaxEditor.CustomEditors.Editors _type = type; // Type - var typeEditor = layout.ComboBox(TypeComboBoxName, "Type of the object value. Use it to change the object."); + _typeItem = layout.AddPropertyItem(TypeComboBoxName, "Type of the object value. Use it to change the object."); + var typeEditor = _typeItem.ComboBox(); for (int i = 0; i < _options.Length; i++) { typeEditor.ComboBox.AddItem(_options[i].Name); @@ -126,6 +132,8 @@ namespace FlaxEditor.CustomEditors.Editors // Value var values = new CustomValueContainer(type, (instance, index) => instance); + if (Values.HasReferenceValue) + values.SetReferenceValue(Values.ReferenceValue); values.AddRange(Values); var editor = CustomEditorsUtil.CreateEditor(type); var style = editor.Style; @@ -170,6 +178,12 @@ namespace FlaxEditor.CustomEditors.Editors { base.Refresh(); + // Show prefab diff when reference value type is different + var color = Color.Transparent; + if (Values.HasReferenceValue && CanRevertReferenceValue && Values[0]?.GetType() != Values.ReferenceValue?.GetType()) + color = FlaxEngine.GUI.Style.Current.BackgroundSelected; + _typeItem.Labels[0].HighlightStripColor = color; + // Check if type has been modified outside the editor (eg. from code) if (Type != _type) { diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs index 853a6eb7d..85e579ec9 100644 --- a/Source/Editor/Editor.cs +++ b/Source/Editor/Editor.cs @@ -51,6 +51,7 @@ namespace FlaxEditor private readonly List _modules = new List(16); private bool _isAfterInit, _areModulesInited, _areModulesAfterInitEnd, _isHeadlessMode, _autoExit; private string _projectToOpen; + private bool _projectIsNew; private float _lastAutoSaveTimer, _autoExitTimeout = 0.1f; private Button _saveNowButton; private Button _cancelSaveButton; @@ -737,11 +738,12 @@ namespace FlaxEditor var procSettings = new CreateProcessSettings { FileName = Platform.ExecutableFilePath, - Arguments = string.Format("-project \"{0}\"", _projectToOpen), + Arguments = string.Format("-project \"{0}\"" + (_projectIsNew ? " -new" : string.Empty), _projectToOpen), ShellExecute = true, WaitForEnd = false, HiddenWindow = false, }; + _projectIsNew = false; _projectToOpen = null; Platform.CreateProcess(ref procSettings); } @@ -790,6 +792,24 @@ namespace FlaxEditor } } + /// + /// Creates the given project. Afterwards closes this project with running editor and opens the given project. + /// + /// The project file path. + public void NewProject(string projectFilePath) + { + if (projectFilePath == null) + { + MessageBox.Show("Missing project"); + return; + } + + // Cache project path and start editor exit (it will open new instance on valid closing) + _projectToOpen = StringUtils.NormalizePath(Path.GetDirectoryName(projectFilePath)); + _projectIsNew = true; + Windows.MainWindow.Close(ClosingReason.User); + } + /// /// Closes this project with running editor and opens the given project. /// diff --git a/Source/Editor/GUI/ItemsListContextMenu.cs b/Source/Editor/GUI/ItemsListContextMenu.cs index 8fdf21e4c..033af782f 100644 --- a/Source/Editor/GUI/ItemsListContextMenu.cs +++ b/Source/Editor/GUI/ItemsListContextMenu.cs @@ -51,6 +51,11 @@ namespace FlaxEditor.GUI /// public float SortScore; + /// + /// Wether the query highlights should be draw. + /// + public bool DrawHighlights = true; + /// /// Occurs when items gets clicked by the user. /// @@ -165,7 +170,7 @@ namespace FlaxEditor.GUI Render2D.FillRectangle(new Rectangle(Float2.Zero, Size), style.BackgroundHighlighted); // Draw all highlights - if (_highlights != null) + if (DrawHighlights && _highlights != null) { var color = style.ProgressNormal * 0.6f; for (int i = 0; i < _highlights.Count; i++) diff --git a/Source/Editor/GUI/Timeline/Tracks/EventTrack.cs b/Source/Editor/GUI/Timeline/Tracks/EventTrack.cs index c18c64ac5..f7aba94b3 100644 --- a/Source/Editor/GUI/Timeline/Tracks/EventTrack.cs +++ b/Source/Editor/GUI/Timeline/Tracks/EventTrack.cs @@ -10,6 +10,7 @@ using System.Text; using FlaxEditor.GUI.Timeline.Undo; using FlaxEngine; using FlaxEngine.GUI; +using FlaxEngine.Json; using FlaxEngine.Utilities; namespace FlaxEditor.GUI.Timeline.Tracks @@ -54,7 +55,10 @@ namespace FlaxEditor.GUI.Timeline.Tracks var paramTypeName = LoadName(stream); e.EventParamsTypes[i] = TypeUtils.GetManagedType(paramTypeName); if (e.EventParamsTypes[i] == null) + { + Editor.LogError($"Unknown type {paramTypeName}."); isInvalid = true; + } } if (isInvalid) @@ -82,7 +86,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks for (int j = 0; j < paramsCount; j++) { stream.Read(dataBuffer, 0, e.EventParamsSizes[j]); - key.Parameters[j] = Marshal.PtrToStructure(handle.AddrOfPinnedObject(), e.EventParamsTypes[j]); + key.Parameters[j] = Utilities.Utils.ByteArrayToStructure(handle.AddrOfPinnedObject(), e.EventParamsTypes[j], e.EventParamsSizes[j]); } events[i] = new KeyframesEditor.Keyframe @@ -125,8 +129,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks for (int j = 0; j < paramsCount; j++) { - Marshal.StructureToPtr(key.Parameters[j], ptr, true); - Marshal.Copy(ptr, dataBuffer, 0, e.EventParamsSizes[j]); + Utilities.Utils.StructureToByteArray(key.Parameters[j], e.EventParamsSizes[j], ptr, dataBuffer); stream.Write(dataBuffer, 0, e.EventParamsSizes[j]); } } @@ -153,7 +156,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks /// /// The event key data. /// - public struct EventKey + public struct EventKey : ICloneable { /// /// The parameters values. @@ -178,6 +181,26 @@ namespace FlaxEditor.GUI.Timeline.Tracks sb.Append(')'); return sb.ToString(); } + + /// + public object Clone() + { + if (Parameters == null) + return new EventKey(); + + // Deep clone parameter values (especially boxed value types need to be duplicated to avoid referencing the same ones) + var parameters = new object[Parameters.Length]; + for (int i = 0; i < parameters.Length; i++) + { + var p = Parameters[i]; + if (p == null || p is FlaxEngine.Object) + parameters[i] = Parameters[i]; + else + parameters[i] = JsonSerializer.Deserialize(JsonSerializer.Serialize(p), p.GetType()); + } + + return new EventKey { Parameters = parameters }; + } } /// @@ -234,6 +257,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks var time = Timeline.CurrentTime; if (!TryGetValue(out var value)) value = Events.Evaluate(time); + value = ((ICloneable)value).Clone(); // Find event at the current location for (int i = Events.Keyframes.Count - 1; i >= 0; i--) diff --git a/Source/Editor/GUI/Timeline/Tracks/KeyframesPropertyTrack.cs b/Source/Editor/GUI/Timeline/Tracks/KeyframesPropertyTrack.cs index 389041381..32f1575ec 100644 --- a/Source/Editor/GUI/Timeline/Tracks/KeyframesPropertyTrack.cs +++ b/Source/Editor/GUI/Timeline/Tracks/KeyframesPropertyTrack.cs @@ -77,7 +77,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks { var time = stream.ReadSingle(); stream.Read(dataBuffer, 0, e.ValueSize); - var value = Marshal.PtrToStructure(handle.AddrOfPinnedObject(), propertyType); + var value = Utilities.Utils.ByteArrayToStructure(handle.AddrOfPinnedObject(), propertyType, e.ValueSize); keyframes[i] = new KeyframesEditor.Keyframe { @@ -142,8 +142,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks for (int i = 0; i < keyframes.Count; i++) { var keyframe = keyframes[i]; - Marshal.StructureToPtr(keyframe.Value, ptr, true); - Marshal.Copy(ptr, dataBuffer, 0, e.ValueSize); + Utilities.Utils.StructureToByteArray(keyframe.Value, e.ValueSize, ptr, dataBuffer); stream.Write(keyframe.Time); stream.Write(dataBuffer); } diff --git a/Source/Editor/Gizmo/TransformGizmoBase.Draw.cs b/Source/Editor/Gizmo/TransformGizmoBase.Draw.cs index bb9684869..03c18268d 100644 --- a/Source/Editor/Gizmo/TransformGizmoBase.Draw.cs +++ b/Source/Editor/Gizmo/TransformGizmoBase.Draw.cs @@ -1,9 +1,7 @@ // Copyright (c) Wojciech Figat. All rights reserved. using FlaxEditor.Options; -using FlaxEditor.SceneGraph; using FlaxEngine; -using System; namespace FlaxEditor.Gizmo { @@ -21,12 +19,16 @@ namespace FlaxEditor.Gizmo private MaterialInstance _materialAxisY; private MaterialInstance _materialAxisZ; private MaterialInstance _materialAxisFocus; - private MaterialInstance _materialAxisLocked; private MaterialBase _materialSphere; // Material Parameter Names - const String _brightnessParamName = "Brightness"; - const String _opacityParamName = "Opacity"; + private const string _brightnessParamName = "Brightness"; + private const string _opacityParamName = "Opacity"; + + /// + /// Used for example when the selection can't be moved because one actor is static. + /// + private bool _isDisabled; private void InitDrawing() { @@ -42,7 +44,6 @@ namespace FlaxEditor.Gizmo _materialAxisY = FlaxEngine.Content.LoadAsyncInternal("Editor/Gizmo/MaterialAxisY"); _materialAxisZ = FlaxEngine.Content.LoadAsyncInternal("Editor/Gizmo/MaterialAxisZ"); _materialAxisFocus = FlaxEngine.Content.LoadAsyncInternal("Editor/Gizmo/MaterialAxisFocus"); - _materialAxisLocked = FlaxEngine.Content.LoadAsyncInternal("Editor/Gizmo/MaterialAxisLocked"); _materialSphere = FlaxEngine.Content.LoadAsyncInternal("Editor/Gizmo/MaterialSphere"); // Ensure that every asset was loaded @@ -67,17 +68,42 @@ namespace FlaxEditor.Gizmo private void OnEditorOptionsChanged(EditorOptions options) { - float brightness = options.Visual.TransformGizmoBrightness; - _materialAxisX.SetParameterValue(_brightnessParamName, brightness); - _materialAxisY.SetParameterValue(_brightnessParamName, brightness); - _materialAxisZ.SetParameterValue(_brightnessParamName, brightness); - _materialAxisLocked.SetParameterValue(_brightnessParamName, brightness); - + UpdateGizmoBrightness(options); + float opacity = options.Visual.TransformGizmoOpacity; _materialAxisX.SetParameterValue(_opacityParamName, opacity); _materialAxisY.SetParameterValue(_opacityParamName, opacity); _materialAxisZ.SetParameterValue(_opacityParamName, opacity); - _materialAxisLocked.SetParameterValue(_opacityParamName, opacity); + } + + private void UpdateGizmoBrightness(EditorOptions options) + { + _isDisabled = ShouldGizmoBeLocked(); + + float brightness = _isDisabled ? options.Visual.TransformGizmoBrightnessDisabled : options.Visual.TransformGizmoBrightness; + if (Mathf.NearEqual(brightness, (float)_materialAxisX.GetParameterValue(_brightnessParamName))) + return; + _materialAxisX.SetParameterValue(_brightnessParamName, brightness); + _materialAxisY.SetParameterValue(_brightnessParamName, brightness); + _materialAxisZ.SetParameterValue(_brightnessParamName, brightness); + } + + private bool ShouldGizmoBeLocked() + { + bool gizmoLocked = false; + if (Editor.Instance.StateMachine.IsPlayMode && Owner is Viewport.EditorGizmoViewport) + { + // Block editing static scene objects in main view during play mode + foreach (var obj in Editor.Instance.SceneEditing.Selection) + { + if (obj.CanTransform == false) + { + gizmoLocked = true; + break; + } + } + } + return gizmoLocked; } /// @@ -88,20 +114,8 @@ namespace FlaxEditor.Gizmo if (!_modelCube || !_modelCube.IsLoaded) return; - // Find out if any of the selected objects can not be moved - bool gizmoLocked = false; - if (Editor.Instance.StateMachine.IsPlayMode) - { - for (int i = 0; i < SelectionCount; i++) - { - var obj = GetSelectedObject(i); - if (obj.CanTransform == false) - { - gizmoLocked = true; - break; - } - } - } + // Update the gizmo brightness every frame to ensure it updates correctly + UpdateGizmoBrightness(Editor.Instance.Options.Options); // As all axisMesh have the same pivot, add a little offset to the x axisMesh, this way SortDrawCalls is able to sort the draw order // https://github.com/FlaxEngine/FlaxEngine/issues/680 @@ -136,37 +150,37 @@ namespace FlaxEditor.Gizmo // X axis Matrix.RotationY(-Mathf.PiOverTwo, out m2); Matrix.Multiply(ref m2, ref m1, out m3); - MaterialInstance xAxisMaterialTransform = gizmoLocked ? _materialAxisLocked : (isXAxis ? _materialAxisFocus : _materialAxisX); + MaterialInstance xAxisMaterialTransform = (isXAxis && !_isDisabled) ? _materialAxisFocus : _materialAxisX; transAxisMesh.Draw(ref renderContext, xAxisMaterialTransform, ref m3, StaticFlags.None, true, DrawPass.Default, 0.0f, sortOrder); // Y axis Matrix.RotationX(Mathf.PiOverTwo, out m2); Matrix.Multiply(ref m2, ref m1, out m3); - MaterialInstance yAxisMaterialTransform = gizmoLocked ? _materialAxisLocked : (isYAxis ? _materialAxisFocus : _materialAxisY); + MaterialInstance yAxisMaterialTransform = (isYAxis && !_isDisabled) ? _materialAxisFocus : _materialAxisY; transAxisMesh.Draw(ref renderContext, yAxisMaterialTransform, ref m3, StaticFlags.None, true, DrawPass.Default, 0.0f, sortOrder); // Z axis Matrix.RotationX(Mathf.Pi, out m2); Matrix.Multiply(ref m2, ref m1, out m3); - MaterialInstance zAxisMaterialTransform = gizmoLocked ? _materialAxisLocked : (isZAxis ? _materialAxisFocus : _materialAxisZ); + MaterialInstance zAxisMaterialTransform = (isZAxis && !_isDisabled) ? _materialAxisFocus : _materialAxisZ; transAxisMesh.Draw(ref renderContext, zAxisMaterialTransform, ref m3, StaticFlags.None, true, DrawPass.Default, 0.0f, sortOrder); // XY plane m2 = Matrix.Transformation(new Vector3(boxSize, boxSize * 0.1f, boxSize), Quaternion.RotationX(Mathf.PiOverTwo), new Vector3(boxSize * boxScale, boxSize * boxScale, 0.0f)); Matrix.Multiply(ref m2, ref m1, out m3); - MaterialInstance xyPlaneMaterialTransform = gizmoLocked ? _materialAxisLocked : (_activeAxis == Axis.XY ? _materialAxisFocus : _materialAxisX); + MaterialInstance xyPlaneMaterialTransform = (_activeAxis == Axis.XY && !_isDisabled) ? _materialAxisFocus : _materialAxisX; cubeMesh.Draw(ref renderContext, xyPlaneMaterialTransform, ref m3, StaticFlags.None, true, DrawPass.Default, 0.0f, sortOrder); // ZX plane m2 = Matrix.Transformation(new Vector3(boxSize, boxSize * 0.1f, boxSize), Quaternion.Identity, new Vector3(boxSize * boxScale, 0.0f, boxSize * boxScale)); Matrix.Multiply(ref m2, ref m1, out m3); - MaterialInstance zxPlaneMaterialTransform = gizmoLocked ? _materialAxisLocked : (_activeAxis == Axis.ZX ? _materialAxisFocus : _materialAxisY); + MaterialInstance zxPlaneMaterialTransform = (_activeAxis == Axis.ZX && !_isDisabled) ? _materialAxisFocus : _materialAxisY; cubeMesh.Draw(ref renderContext, zxPlaneMaterialTransform, ref m3, StaticFlags.None, true, DrawPass.Default, 0.0f, sortOrder); // YZ plane m2 = Matrix.Transformation(new Vector3(boxSize, boxSize * 0.1f, boxSize), Quaternion.RotationZ(Mathf.PiOverTwo), new Vector3(0.0f, boxSize * boxScale, boxSize * boxScale)); Matrix.Multiply(ref m2, ref m1, out m3); - MaterialInstance yzPlaneMaterialTransform = gizmoLocked ? _materialAxisLocked : (_activeAxis == Axis.YZ ? _materialAxisFocus : _materialAxisZ); + MaterialInstance yzPlaneMaterialTransform = (_activeAxis == Axis.YZ && !_isDisabled) ? _materialAxisFocus : _materialAxisZ; cubeMesh.Draw(ref renderContext, yzPlaneMaterialTransform, ref m3, StaticFlags.None, true, DrawPass.Default, 0.0f, sortOrder); // Center sphere @@ -186,17 +200,17 @@ namespace FlaxEditor.Gizmo // X axis Matrix.RotationZ(Mathf.PiOverTwo, out m2); Matrix.Multiply(ref m2, ref m1, out m3); - MaterialInstance xAxisMaterialRotate = gizmoLocked ? _materialAxisLocked : (isXAxis ? _materialAxisFocus : _materialAxisX); + MaterialInstance xAxisMaterialRotate = (isXAxis && !_isDisabled) ? _materialAxisFocus : _materialAxisX; rotationAxisMesh.Draw(ref renderContext, xAxisMaterialRotate, ref m3, StaticFlags.None, true, DrawPass.Default, 0.0f, sortOrder); // Y axis - MaterialInstance yAxisMaterialRotate = gizmoLocked ? _materialAxisLocked : (isYAxis ? _materialAxisFocus : _materialAxisY); + MaterialInstance yAxisMaterialRotate = (isYAxis && !_isDisabled) ? _materialAxisFocus : _materialAxisY; rotationAxisMesh.Draw(ref renderContext, yAxisMaterialRotate, ref m1, StaticFlags.None, true, DrawPass.Default, 0.0f, sortOrder); // Z axis Matrix.RotationX(-Mathf.PiOverTwo, out m2); Matrix.Multiply(ref m2, ref m1, out m3); - MaterialInstance zAxisMaterialRotate = gizmoLocked ? _materialAxisLocked : (isZAxis ? _materialAxisFocus : _materialAxisZ); + MaterialInstance zAxisMaterialRotate = (isZAxis && !_isDisabled) ? _materialAxisFocus : _materialAxisZ; rotationAxisMesh.Draw(ref renderContext, zAxisMaterialRotate, ref m3, StaticFlags.None, true, DrawPass.Default, 0.0f, sortOrder); // Center box @@ -216,37 +230,37 @@ namespace FlaxEditor.Gizmo // X axis Matrix.RotationY(-Mathf.PiOverTwo, out m2); Matrix.Multiply(ref m2, ref mx1, out m3); - MaterialInstance xAxisMaterialRotate = gizmoLocked ? _materialAxisLocked : (isXAxis ? _materialAxisFocus : _materialAxisX); + MaterialInstance xAxisMaterialRotate = (isXAxis && !_isDisabled) ? _materialAxisFocus : _materialAxisX; scaleAxisMesh.Draw(ref renderContext, xAxisMaterialRotate, ref m3, StaticFlags.None, true, DrawPass.Default, 0.0f, sortOrder); // Y axis Matrix.RotationX(Mathf.PiOverTwo, out m2); Matrix.Multiply(ref m2, ref m1, out m3); - MaterialInstance yAxisMaterialRotate = gizmoLocked ? _materialAxisLocked : (isYAxis ? _materialAxisFocus : _materialAxisY); + MaterialInstance yAxisMaterialRotate = (isYAxis && !_isDisabled) ? _materialAxisFocus : _materialAxisY; scaleAxisMesh.Draw(ref renderContext, yAxisMaterialRotate, ref m3, StaticFlags.None, true, DrawPass.Default, 0.0f, sortOrder); // Z axis Matrix.RotationX(Mathf.Pi, out m2); Matrix.Multiply(ref m2, ref m1, out m3); - MaterialInstance zAxisMaterialRotate = gizmoLocked ? _materialAxisLocked : (isZAxis ? _materialAxisFocus : _materialAxisZ); + MaterialInstance zAxisMaterialRotate = (isZAxis && !_isDisabled) ? _materialAxisFocus : _materialAxisZ; scaleAxisMesh.Draw(ref renderContext, zAxisMaterialRotate, ref m3, StaticFlags.None, true, DrawPass.Default, 0.0f, sortOrder); // XY plane m2 = Matrix.Transformation(new Vector3(boxSize, boxSize * 0.1f, boxSize), Quaternion.RotationX(Mathf.PiOverTwo), new Vector3(boxSize * boxScale, boxSize * boxScale, 0.0f)); Matrix.Multiply(ref m2, ref m1, out m3); - MaterialInstance xyPlaneMaterialScale = gizmoLocked ? _materialAxisLocked : (_activeAxis == Axis.XY ? _materialAxisFocus : _materialAxisX); + MaterialInstance xyPlaneMaterialScale = (_activeAxis == Axis.XY && !_isDisabled) ? _materialAxisFocus : _materialAxisX; cubeMesh.Draw(ref renderContext, xyPlaneMaterialScale, ref m3, StaticFlags.None, true, DrawPass.Default, 0.0f, sortOrder); // ZX plane m2 = Matrix.Transformation(new Vector3(boxSize, boxSize * 0.1f, boxSize), Quaternion.Identity, new Vector3(boxSize * boxScale, 0.0f, boxSize * boxScale)); Matrix.Multiply(ref m2, ref m1, out m3); - MaterialInstance zxPlaneMaterialScale = gizmoLocked ? _materialAxisLocked : (_activeAxis == Axis.ZX ? _materialAxisFocus : _materialAxisZ); + MaterialInstance zxPlaneMaterialScale = (_activeAxis == Axis.ZX && !_isDisabled) ? _materialAxisFocus : _materialAxisZ; cubeMesh.Draw(ref renderContext, zxPlaneMaterialScale, ref m3, StaticFlags.None, true, DrawPass.Default, 0.0f, sortOrder); // YZ plane m2 = Matrix.Transformation(new Vector3(boxSize, boxSize * 0.1f, boxSize), Quaternion.RotationZ(Mathf.PiOverTwo), new Vector3(0.0f, boxSize * boxScale, boxSize * boxScale)); Matrix.Multiply(ref m2, ref m1, out m3); - MaterialInstance yzPlaneMaterialScale = gizmoLocked ? _materialAxisLocked : (_activeAxis == Axis.YZ ? _materialAxisFocus : _materialAxisY); + MaterialInstance yzPlaneMaterialScale = (_activeAxis == Axis.YZ && !_isDisabled) ? _materialAxisFocus : _materialAxisY; cubeMesh.Draw(ref renderContext, yzPlaneMaterialScale, ref m3, StaticFlags.None, true, DrawPass.Default, 0.0f, sortOrder); // Center box diff --git a/Source/Editor/Gizmo/UIEditorGizmo.cs b/Source/Editor/Gizmo/UIEditorGizmo.cs index bdc1c56ca..765893bed 100644 --- a/Source/Editor/Gizmo/UIEditorGizmo.cs +++ b/Source/Editor/Gizmo/UIEditorGizmo.cs @@ -155,6 +155,16 @@ namespace FlaxEditor private List _widgets; private Widget _activeWidget; + /// + /// Sets the view size. + /// + /// The new size. + public void SetViewSize(Float2 size) + { + _view.Size = size; + _view.PerformLayout(); + } + /// /// True if enable displaying UI editing background and grid elements. /// diff --git a/Source/Editor/Modules/SceneEditingModule.cs b/Source/Editor/Modules/SceneEditingModule.cs index 3c7130615..c96e79ed9 100644 --- a/Source/Editor/Modules/SceneEditingModule.cs +++ b/Source/Editor/Modules/SceneEditingModule.cs @@ -673,6 +673,7 @@ namespace FlaxEditor.Modules pasteAction.Do(out _, out var nodeParents); // Select spawned objects (parents only) + newSelection.Clear(); newSelection.AddRange(nodeParents); var selectAction = new SelectionChangeAction(Selection.ToArray(), newSelection.ToArray(), OnSelectionUndo); selectAction.Do(); diff --git a/Source/Editor/Modules/SourceCodeEditing/InBuildSourceCodeEditor.cs b/Source/Editor/Modules/SourceCodeEditing/InBuildSourceCodeEditor.cs index 5aba20b65..a2a333805 100644 --- a/Source/Editor/Modules/SourceCodeEditing/InBuildSourceCodeEditor.cs +++ b/Source/Editor/Modules/SourceCodeEditing/InBuildSourceCodeEditor.cs @@ -54,6 +54,9 @@ namespace FlaxEditor.Modules.SourceCodeEditing case CodeEditorTypes.VS2022: Name = "Visual Studio 2022"; break; + case CodeEditorTypes.VS2026: + Name = "Visual Studio 2026"; + break; case CodeEditorTypes.VSCode: Name = "Visual Studio Code"; break; @@ -110,6 +113,7 @@ namespace FlaxEditor.Modules.SourceCodeEditing case CodeEditorTypes.VS2017: case CodeEditorTypes.VS2019: case CodeEditorTypes.VS2022: + case CodeEditorTypes.VS2026: // TODO: finish dynamic files adding to the project //Editor.Instance.ProgressReporting.GenerateScriptsProjectFiles.RunAsync(); break; diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs index cae120ab6..f581af81e 100644 --- a/Source/Editor/Modules/UIModule.cs +++ b/Source/Editor/Modules/UIModule.cs @@ -70,6 +70,53 @@ namespace FlaxEditor.Modules private bool _progressFailed; ContextMenuSingleSelectGroup _numberOfClientsGroup = new ContextMenuSingleSelectGroup(); + + /// + /// Defines a viewport scaling option. + /// + public class ViewportScaleOption + { + /// + /// Defines the viewport scale type. + /// + public enum ViewportScaleType + { + /// + /// Resolution. + /// + Resolution = 0, + + /// + /// Aspect Ratio. + /// + Aspect = 1, + } + + /// + /// The name. + /// + public string Label; + + /// + /// The Type of scaling to do. + /// + public ViewportScaleType ScaleType; + + /// + /// The width and height to scale by. + /// + public Int2 Size; + } + + /// + /// The default viewport scaling options. + /// + public List DefaultViewportScaleOptions = new List(); + + /// + /// The user defined viewport scaling options. + /// + public List CustomViewportScaleOptions = new List(); private ContextMenuButton _menuFileSaveScenes; private ContextMenuButton _menuFileReloadScenes; @@ -409,6 +456,8 @@ namespace FlaxEditor.Modules // Update window background mainWindow.BackgroundColor = Style.Current.Background; + + InitViewportScaleOptions(); InitSharedMenus(); InitMainMenu(mainWindow); @@ -422,6 +471,57 @@ namespace FlaxEditor.Modules mainWindow.PerformLayout(true); } + private void InitViewportScaleOptions() + { + if (DefaultViewportScaleOptions.Count == 0) + { + DefaultViewportScaleOptions.Add(new ViewportScaleOption + { + Label = "Free Aspect", + ScaleType = ViewportScaleOption.ViewportScaleType.Aspect, + Size = new Int2(1, 1), + }); + DefaultViewportScaleOptions.Add(new ViewportScaleOption + { + Label = "16:9 Aspect", + ScaleType = ViewportScaleOption.ViewportScaleType.Aspect, + Size = new Int2(16, 9), + }); + DefaultViewportScaleOptions.Add(new ViewportScaleOption + { + Label = "16:10 Aspect", + ScaleType = ViewportScaleOption.ViewportScaleType.Aspect, + Size = new Int2(16, 10), + }); + DefaultViewportScaleOptions.Add(new ViewportScaleOption + { + Label = "1920x1080 Resolution (Full HD)", + ScaleType = ViewportScaleOption.ViewportScaleType.Resolution, + Size = new Int2(1920, 1080), + }); + DefaultViewportScaleOptions.Add(new ViewportScaleOption + { + Label = "2560x1440 Resolution (2K)", + ScaleType = ViewportScaleOption.ViewportScaleType.Resolution, + Size = new Int2(2560, 1440), + }); + } + + if (Editor.Instance.ProjectCache.TryGetCustomData("CustomViewportScalingOptions", out string data)) + { + CustomViewportScaleOptions = JsonSerializer.Deserialize>(data); + } + } + + /// + /// Saves the custom viewport scaling options. + /// + public void SaveCustomViewportScalingOptions() + { + var customOptions = JsonSerializer.Serialize(CustomViewportScaleOptions); + Editor.Instance.ProjectCache.SetCustomData("CustomViewportScalingOptions", customOptions); + } + /// public override void OnUpdate() { @@ -546,6 +646,7 @@ namespace FlaxEditor.Modules _menuFileGenerateScriptsProjectFiles = cm.AddButton("Generate scripts project files", inputOptions.GenerateScriptsProject, Editor.ProgressReporting.GenerateScriptsProjectFiles.RunAsync); _menuFileRecompileScripts = cm.AddButton("Recompile scripts", inputOptions.RecompileScripts, ScriptsBuilder.Compile); cm.AddSeparator(); + cm.AddButton("New project", NewProject); cm.AddButton("Open project...", OpenProject); cm.AddButton("Reload project", ReloadProject); cm.AddButton("Open project folder", () => FileSystem.ShowFileExplorer(Editor.Instance.GameProject.ProjectFolderPath)); @@ -576,7 +677,7 @@ namespace FlaxEditor.Modules if (item != null) Editor.ContentEditing.Open(item); }); - cm.AddButton("Editor Options", () => Editor.Windows.EditorOptionsWin.Show()); + cm.AddButton("Editor Options", inputOptions.EditorOptionsWindow, () => Editor.Windows.EditorOptionsWin.Show()); // Scene MenuScene = MainMenu.AddButton("Scene"); @@ -851,6 +952,17 @@ namespace FlaxEditor.Modules MasterPanel.Offsets = new Margin(0, 0, ToolStrip.Bottom, StatusBar.Height); } + private void NewProject() + { + // Ask user to create project file + if (FileSystem.ShowSaveFileDialog(Editor.Windows.MainWindow, null, "Project files (*.flaxproj)\0*.flaxproj\0All files (*.*)\0*.*\0", false, "Create project file", out var files)) + return; + if (files != null && files.Length > 0) + { + Editor.NewProject(files[0]); + } + } + private void OpenProject() { // Ask user to select project file @@ -1108,5 +1220,267 @@ namespace FlaxEditor.Modules MenuTools = null; MenuHelp = null; } + + internal void CreateViewportSizingContextMenu(ContextMenu vsMenu, int defaultScaleActiveIndex, int customScaleActiveIndex, bool prefabViewport, Action changeView, Action changeActiveIndices) + { + // Add default viewport sizing options + var defaultOptions = DefaultViewportScaleOptions; + for (int i = 0; i < defaultOptions.Count; i++) + { + var viewportScale = defaultOptions[i]; + if (prefabViewport && viewportScale.ScaleType == ViewportScaleOption.ViewportScaleType.Aspect) + continue; // Skip aspect ratio types in prefab + var button = vsMenu.AddButton(viewportScale.Label); + button.CloseMenuOnClick = false; + button.Tag = viewportScale; + + // No default index is active + if (defaultScaleActiveIndex == -1) + { + button.Icon = SpriteHandle.Invalid; + } + // This is the active index + else if (defaultScaleActiveIndex == i) + { + button.Icon = Style.Current.CheckBoxTick; + changeView(viewportScale); + } + + button.Clicked += () => + { + if (button.Tag == null) + return; + + // Reset selected icon on all buttons + foreach (var child in vsMenu.Items) + { + if (child is ContextMenuButton cmb && cmb.Tag is UIModule.ViewportScaleOption v) + { + if (cmb == button) + { + button.Icon = Style.Current.CheckBoxTick; + var index = defaultOptions.FindIndex(x => x == v); + changeActiveIndices(index, -1); // Reset custom index because default was chosen + changeView(v); + } + else if (cmb.Icon != SpriteHandle.Invalid) + { + cmb.Icon = SpriteHandle.Invalid; + } + } + } + }; + } + if (defaultOptions.Count != 0) + vsMenu.AddSeparator(); + + // Add custom viewport options + var customOptions = CustomViewportScaleOptions; + for (int i = 0; i < customOptions.Count; i++) + { + var viewportScale = customOptions[i]; + if (prefabViewport && viewportScale.ScaleType == ViewportScaleOption.ViewportScaleType.Aspect) + continue; // Skip aspect ratio types in prefab + var childCM = vsMenu.AddChildMenu(viewportScale.Label); + childCM.CloseMenuOnClick = false; + childCM.Tag = viewportScale; + + // No custom index is active + if (customScaleActiveIndex == -1) + { + childCM.Icon = SpriteHandle.Invalid; + } + // This is the active index + else if (customScaleActiveIndex == i) + { + childCM.Icon = Style.Current.CheckBoxTick; + changeView(viewportScale); + } + + var applyButton = childCM.ContextMenu.AddButton("Apply"); + applyButton.Tag = childCM.Tag = viewportScale; + applyButton.CloseMenuOnClick = false; + applyButton.Clicked += () => + { + if (childCM.Tag == null) + return; + + // Reset selected icon on all buttons + foreach (var child in vsMenu.Items) + { + if (child is ContextMenuButton cmb && cmb.Tag is UIModule.ViewportScaleOption v) + { + if (child == childCM) + { + childCM.Icon = Style.Current.CheckBoxTick; + var index = customOptions.FindIndex(x => x == v); + changeActiveIndices(-1, index); // Reset default index because custom was chosen + changeView(v); + } + else if (cmb.Icon != SpriteHandle.Invalid) + { + cmb.Icon = SpriteHandle.Invalid; + } + } + } + }; + + var deleteButton = childCM.ContextMenu.AddButton("Delete"); + deleteButton.CloseMenuOnClick = false; + deleteButton.Clicked += () => + { + if (childCM.Tag == null) + return; + + var v = (ViewportScaleOption)childCM.Tag; + if (childCM.Icon != SpriteHandle.Invalid) + { + changeActiveIndices(-1, 0); + changeView(defaultOptions[0]); + } + customOptions.Remove(v); + SaveCustomViewportScalingOptions(); + vsMenu.DisposeAllItems(); + CreateViewportSizingContextMenu(vsMenu, defaultScaleActiveIndex, customScaleActiveIndex, prefabViewport, changeView, changeActiveIndices); + vsMenu.PerformLayout(); + }; + } + if (customOptions.Count != 0) + vsMenu.AddSeparator(); + + // Add button + var add = vsMenu.AddButton("Add..."); + add.CloseMenuOnClick = false; + add.Clicked += () => + { + var popup = new ContextMenuBase + { + Size = new Float2(230, 125), + ClipChildren = false, + CullChildren = false, + }; + popup.Show(add, new Float2(add.Width, 0)); + + var nameLabel = new Label + { + Parent = popup, + AnchorPreset = AnchorPresets.TopLeft, + Text = "Name", + HorizontalAlignment = TextAlignment.Near, + }; + nameLabel.LocalX += 10; + nameLabel.LocalY += 10; + + var nameTextBox = new TextBox + { + Parent = popup, + AnchorPreset = AnchorPresets.TopLeft, + IsMultiline = false, + }; + nameTextBox.LocalX += 100; + nameTextBox.LocalY += 10; + + var typeLabel = new Label + { + Parent = popup, + AnchorPreset = AnchorPresets.TopLeft, + Text = "Type", + HorizontalAlignment = TextAlignment.Near, + }; + typeLabel.LocalX += 10; + typeLabel.LocalY += 35; + + var typeDropdown = new Dropdown + { + Parent = popup, + AnchorPreset = AnchorPresets.TopLeft, + Items = { "Aspect", "Resolution" }, + SelectedItem = "Aspect", + Visible = !prefabViewport, + Width = nameTextBox.Width + }; + typeDropdown.LocalY += 35; + typeDropdown.LocalX += 100; + + var whLabel = new Label + { + Parent = popup, + AnchorPreset = AnchorPresets.TopLeft, + Text = "Width & Height", + HorizontalAlignment = TextAlignment.Near, + }; + whLabel.LocalX += 10; + whLabel.LocalY += 60; + + var wValue = new IntValueBox(16) + { + Parent = popup, + AnchorPreset = AnchorPresets.TopLeft, + MinValue = 1, + Width = 55, + }; + wValue.LocalY += 60; + wValue.LocalX += 100; + + var hValue = new IntValueBox(9) + { + Parent = popup, + AnchorPreset = AnchorPresets.TopLeft, + MinValue = 1, + Width = 55, + }; + hValue.LocalY += 60; + hValue.LocalX += 165; + + var submitButton = new Button + { + Parent = popup, + AnchorPreset = AnchorPresets.TopLeft, + Text = "Submit", + Width = 70, + }; + submitButton.LocalX += 40; + submitButton.LocalY += 90; + submitButton.Clicked += () => + { + Enum.TryParse(typeDropdown.SelectedItem, out ViewportScaleOption.ViewportScaleType type); + if (prefabViewport) + type = ViewportScaleOption.ViewportScaleType.Resolution; + + var combineString = type == ViewportScaleOption.ViewportScaleType.Aspect ? ":" : "x"; + var name = nameTextBox.Text + " (" + wValue.Value + combineString + hValue.Value + ") " + typeDropdown.SelectedItem; + var newViewportOption = new ViewportScaleOption + { + ScaleType = type, + Label = name, + Size = new Int2(wValue.Value, hValue.Value), + }; + + customOptions.Add(newViewportOption); + SaveCustomViewportScalingOptions(); + vsMenu.DisposeAllItems(); + CreateViewportSizingContextMenu(vsMenu, defaultScaleActiveIndex, customScaleActiveIndex, prefabViewport, changeView, changeActiveIndices); + vsMenu.PerformLayout(); + }; + + var cancelButton = new Button + { + Parent = popup, + AnchorPreset = AnchorPresets.TopLeft, + Text = "Cancel", + Width = 70, + }; + cancelButton.LocalX += 120; + cancelButton.LocalY += 90; + cancelButton.Clicked += () => + { + nameTextBox.Clear(); + typeDropdown.SelectedItem = "Aspect"; + hValue.Value = 9; + wValue.Value = 16; + popup.Hide(); + }; + }; + } } } diff --git a/Source/Editor/Options/InputOptions.cs b/Source/Editor/Options/InputOptions.cs index af919c1f3..ab473ebed 100644 --- a/Source/Editor/Options/InputOptions.cs +++ b/Source/Editor/Options/InputOptions.cs @@ -650,6 +650,10 @@ namespace FlaxEditor.Options [EditorDisplay("Windows"), EditorOrder(4020)] public InputBinding VisualScriptDebuggerWindow = new InputBinding(KeyboardKeys.None); + [DefaultValue(typeof(InputBinding), "Control+Comma")] + [EditorDisplay("Windows"), EditorOrder(4030)] + public InputBinding EditorOptionsWindow = new InputBinding(KeyboardKeys.Comma, KeyboardKeys.Control); + #endregion #region Node Editors diff --git a/Source/Editor/Options/ViewportOptions.cs b/Source/Editor/Options/ViewportOptions.cs index 13ab2a8b0..aed633672 100644 --- a/Source/Editor/Options/ViewportOptions.cs +++ b/Source/Editor/Options/ViewportOptions.cs @@ -150,5 +150,26 @@ namespace FlaxEditor.Options [DefaultValue(typeof(Color), "0.5,0.5,0.5,1.0")] [EditorDisplay("Grid"), EditorOrder(310), Tooltip("The color for the viewport grid.")] public Color ViewportGridColor { get; set; } = new Color(0.5f, 0.5f, 0.5f, 1.0f); + + /// + /// Gets or sets the minimum size used for viewport icons. + /// + [DefaultValue(7.0f), Limit(1.0f, 1000.0f, 5.0f)] + [EditorDisplay("Viewport Icons"), EditorOrder(400)] + public float IconsMinimumSize { get; set; } = 7.0f; + + /// + /// Gets or sets the maximum size used for viewport icons. + /// + [DefaultValue(30.0f), Limit(1.0f, 1000.0f, 5.0f)] + [EditorDisplay("Viewport Icons"), EditorOrder(410)] + public float IconsMaximumSize { get; set; } = 30.0f; + + /// + /// Gets or sets the distance towards the camera at which the max icon scale will be applied. Set to 0 to disable scaling the icons based on the distance to the camera. + /// + [DefaultValue(1000.0f), Limit(0.0f, 20000.0f, 5.0f)] + [EditorDisplay("Viewport Icons"), EditorOrder(410)] + public float MaxSizeDistance { get; set; } = 1000.0f; } } diff --git a/Source/Editor/Options/VisualOptions.cs b/Source/Editor/Options/VisualOptions.cs index 3a12dd0c5..e0f0982f4 100644 --- a/Source/Editor/Options/VisualOptions.cs +++ b/Source/Editor/Options/VisualOptions.cs @@ -81,6 +81,13 @@ namespace FlaxEditor.Options [EditorDisplay("Transform Gizmo", "Gizmo Opacity"), EditorOrder(211)] public float TransformGizmoOpacity { get; set; } = 1f; + /// + /// Gets or set a value indicating how bright the transform gizmo is when it is disabled, for example when one of the selected actors is static in play mode. Use a value of 0 to make the gizmo fully gray. Value over 1 will result in the gizmo emitting light. + /// + [DefaultValue(0.25f), Range(0f, 5f)] + [EditorDisplay("Transform Gizmo", "Disabled Gizmo Brightness"), EditorOrder(212)] + public float TransformGizmoBrightnessDisabled { get; set; } = 0.25f; + /// /// Gets or sets a value indicating whether enable MSAA for DebugDraw primitives rendering. Helps with pixel aliasing but reduces performance. /// diff --git a/Source/Editor/Scripting/CodeEditor.h b/Source/Editor/Scripting/CodeEditor.h index 13edd6af1..9cc71977b 100644 --- a/Source/Editor/Scripting/CodeEditor.h +++ b/Source/Editor/Scripting/CodeEditor.h @@ -62,6 +62,11 @@ API_ENUM(Namespace="FlaxEditor", Attributes="HideInEditor") enum class CodeEdito /// VS2022, + /// + /// Visual Studio 2026 + /// + VS2026, + /// /// Visual Studio Code /// diff --git a/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioEditor.cpp b/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioEditor.cpp index cebdc681d..5c06eec9c 100644 --- a/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioEditor.cpp +++ b/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioEditor.cpp @@ -43,6 +43,9 @@ VisualStudioEditor::VisualStudioEditor(VisualStudioVersion version, const String case VisualStudioVersion::VS2022: _type = CodeEditorTypes::VS2022; break; + case VisualStudioVersion::VS2026: + _type = CodeEditorTypes::VS2026; + break; default: CRASH; break; } @@ -70,6 +73,9 @@ void VisualStudioEditor::FindEditors(Array* output) VisualStudioVersion version; switch (info.VersionMajor) { + case 18: + version = VisualStudioVersion::VS2026; + break; case 17: version = VisualStudioVersion::VS2022; break; diff --git a/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioEditor.h b/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioEditor.h index 78f069897..1bf1f1433 100644 --- a/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioEditor.h +++ b/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioEditor.h @@ -10,7 +10,7 @@ /// /// Microsoft Visual Studio version types /// -DECLARE_ENUM_8(VisualStudioVersion, VS2008, VS2010, VS2012, VS2013, VS2015, VS2017, VS2019, VS2022); +DECLARE_ENUM_9(VisualStudioVersion, VS2008, VS2010, VS2012, VS2013, VS2015, VS2017, VS2019, VS2022, VS2026); /// /// Implementation of code editor utility that is using Microsoft Visual Studio. diff --git a/Source/Editor/Surface/AnimGraphSurface.cs b/Source/Editor/Surface/AnimGraphSurface.cs index 40cb1fd9a..237e9d019 100644 --- a/Source/Editor/Surface/AnimGraphSurface.cs +++ b/Source/Editor/Surface/AnimGraphSurface.cs @@ -229,20 +229,20 @@ namespace FlaxEditor.Surface } /// - protected override void OnShowPrimaryMenu(VisjectCM activeCM, Float2 location, Box startBox) + protected override void OnShowPrimaryMenu(VisjectCM activeCM, Float2 location, List startBoxes) { // Check if show additional nodes in the current surface context if (activeCM != _cmStateMachineMenu) { _nodesCache.Get(activeCM); - base.OnShowPrimaryMenu(activeCM, location, startBox); + base.OnShowPrimaryMenu(activeCM, location, startBoxes); activeCM.VisibleChanged += OnActiveContextMenuVisibleChanged; } else { - base.OnShowPrimaryMenu(activeCM, location, startBox); + base.OnShowPrimaryMenu(activeCM, location, startBoxes); } } diff --git a/Source/Editor/Surface/BehaviorTreeSurface.cs b/Source/Editor/Surface/BehaviorTreeSurface.cs index 9adcc9949..cf6d7b19a 100644 --- a/Source/Editor/Surface/BehaviorTreeSurface.cs +++ b/Source/Editor/Surface/BehaviorTreeSurface.cs @@ -101,12 +101,12 @@ namespace FlaxEditor.Surface } /// - protected override void OnShowPrimaryMenu(VisjectCM activeCM, Float2 location, Box startBox) + protected override void OnShowPrimaryMenu(VisjectCM activeCM, Float2 location, List startBoxes) { activeCM.ShowExpanded = true; _nodesCache.Get(activeCM); - base.OnShowPrimaryMenu(activeCM, location, startBox); + base.OnShowPrimaryMenu(activeCM, location, startBoxes); activeCM.VisibleChanged += OnActiveContextMenuVisibleChanged; } diff --git a/Source/Editor/Surface/ContextMenu/VisjectCM.cs b/Source/Editor/Surface/ContextMenu/VisjectCM.cs index 94b411fc2..42c863789 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCM.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCM.cs @@ -24,8 +24,8 @@ namespace FlaxEditor.Surface.ContextMenu /// Visject context menu item clicked delegate. /// /// The item that was clicked - /// The currently user-selected box. Can be null. - public delegate void ItemClickedDelegate(VisjectCMItem clickedItem, Elements.Box selectedBox); + /// The currently user-selected boxes. Can be empty/ null. + public delegate void ItemClickedDelegate(VisjectCMItem clickedItem, List selectedBoxes); /// /// Visject Surface node archetype spawn ability checking delegate. @@ -53,7 +53,7 @@ namespace FlaxEditor.Surface.ContextMenu private Panel _panel1; private VerticalPanel _groupsPanel; private readonly ParameterGetterDelegate _parametersGetter; - private Elements.Box _selectedBox; + private List _selectedBoxes = new List(); private NodeArchetype _parameterGetNodeArchetype; private NodeArchetype _parameterSetNodeArchetype; @@ -411,7 +411,8 @@ namespace FlaxEditor.Surface.ContextMenu if (!IsLayoutLocked) { group.UnlockChildrenRecursive(); - if (_contextSensitiveSearchEnabled && _selectedBox != null) + // TODO: Improve filtering to be based on boxes with the most common things instead of first box + if (_contextSensitiveSearchEnabled && _selectedBoxes[0] != null) UpdateFilters(); else SortGroups(); @@ -425,7 +426,8 @@ namespace FlaxEditor.Surface.ContextMenu } else if (_contextSensitiveSearchEnabled) { - group.EvaluateVisibilityWithBox(_selectedBox); + // TODO: Filtering could be improved here as well + group.EvaluateVisibilityWithBox(_selectedBoxes[0]); } Profiler.EndEvent(); @@ -461,7 +463,7 @@ namespace FlaxEditor.Surface.ContextMenu }; } if (_contextSensitiveSearchEnabled) - group.EvaluateVisibilityWithBox(_selectedBox); + group.EvaluateVisibilityWithBox(_selectedBoxes[0]); group.SortChildren(); if (ShowExpanded) group.Open(false); @@ -474,7 +476,7 @@ namespace FlaxEditor.Surface.ContextMenu if (!isLayoutLocked) { - if (_contextSensitiveSearchEnabled && _selectedBox != null) + if (_contextSensitiveSearchEnabled && _selectedBoxes[0] != null) UpdateFilters(); else SortGroups(); @@ -583,7 +585,7 @@ namespace FlaxEditor.Surface.ContextMenu private void UpdateFilters() { - if (string.IsNullOrEmpty(_searchBox.Text) && _selectedBox == null) + if (string.IsNullOrEmpty(_searchBox.Text) && _selectedBoxes[0] == null) { ResetView(); Profiler.EndEvent(); @@ -592,7 +594,7 @@ namespace FlaxEditor.Surface.ContextMenu // Update groups LockChildrenRecursive(); - var contextSensitiveSelectedBox = _contextSensitiveSearchEnabled ? _selectedBox : null; + var contextSensitiveSelectedBox = _contextSensitiveSearchEnabled && _selectedBoxes.Count > 0 ? _selectedBoxes[0] : null; for (int i = 0; i < _groups.Count; i++) { _groups[i].UpdateFilter(_searchBox.Text, contextSensitiveSelectedBox); @@ -640,7 +642,7 @@ namespace FlaxEditor.Surface.ContextMenu public void OnClickItem(VisjectCMItem item) { Hide(); - ItemClicked?.Invoke(item, _selectedBox); + ItemClicked?.Invoke(item, _selectedBoxes); } /// @@ -666,12 +668,12 @@ namespace FlaxEditor.Surface.ContextMenu for (int i = 0; i < _groups.Count; i++) { _groups[i].ResetView(); - if (_contextSensitiveSearchEnabled) - _groups[i].EvaluateVisibilityWithBox(_selectedBox); + if (_contextSensitiveSearchEnabled && _selectedBoxes.Count > 0) + _groups[i].EvaluateVisibilityWithBox(_selectedBoxes[0]); } UnlockChildrenRecursive(); - if (_contextSensitiveSearchEnabled && _selectedBox != null) + if (_contextSensitiveSearchEnabled && _selectedBoxes.Count > 0 && _selectedBoxes[0] != null) UpdateFilters(); else SortGroups(); @@ -772,10 +774,10 @@ namespace FlaxEditor.Surface.ContextMenu /// /// Parent control to attach to it. /// Popup menu origin location in parent control coordinates. - /// The currently selected box that the new node will get connected to. Can be null - public void Show(Control parent, Float2 location, Elements.Box startBox) + /// The currently selected boxes that the new node will get connected to. Can be empty/ null + public void Show(Control parent, Float2 location, List startBoxes) { - _selectedBox = startBox; + _selectedBoxes = startBoxes; base.Show(parent, location); } diff --git a/Source/Editor/Surface/Elements/Box.cs b/Source/Editor/Surface/Elements/Box.cs index 16b6b3c16..964b3cc69 100644 --- a/Source/Editor/Surface/Elements/Box.cs +++ b/Source/Editor/Surface/Elements/Box.cs @@ -544,35 +544,39 @@ namespace FlaxEditor.Surface.Elements public override void OnMouseLeave() { if (_originalTooltipText != null) - { TooltipText = _originalTooltipText; - } if (_isMouseDown) { _isMouseDown = false; if (Surface.CanEdit) { - if (!IsOutput && HasSingleConnection) + if (IsOutput && Input.GetKey(KeyboardKeys.Control)) { - var connectedBox = Connections[0]; + List connectedBoxes = new List(Connections); + + for (int i = 0; i < connectedBoxes.Count; i++) + { + BreakConnection(connectedBoxes[i]); + Surface.ConnectingStart(connectedBoxes[i], true); + } + } + else if (!IsOutput && HasSingleConnection) + { + var otherBox = Connections[0]; if (Surface.Undo != null && Surface.Undo.Enabled) { - var action = new ConnectBoxesAction((InputBox)this, (OutputBox)connectedBox, false); - BreakConnection(connectedBox); + var action = new ConnectBoxesAction((InputBox)this, (OutputBox)otherBox, false); + BreakConnection(otherBox); action.End(); Surface.AddBatchedUndoAction(action); Surface.MarkAsEdited(); } else - { - BreakConnection(connectedBox); - } - Surface.ConnectingStart(connectedBox); + BreakConnection(otherBox); + Surface.ConnectingStart(otherBox); } else - { Surface.ConnectingStart(this); - } } } base.OnMouseLeave(); diff --git a/Source/Editor/Surface/SurfaceNode.cs b/Source/Editor/Surface/SurfaceNode.cs index 97ecab1d0..e1d7c131f 100644 --- a/Source/Editor/Surface/SurfaceNode.cs +++ b/Source/Editor/Surface/SurfaceNode.cs @@ -151,6 +151,8 @@ namespace FlaxEditor.Surface /// protected virtual Color FooterColor => GroupArchetype.Color; + private Float2 mouseDownMousePosition; + /// /// Calculates the size of the node including header, footer, and margins. /// @@ -917,7 +919,7 @@ namespace FlaxEditor.Surface /// public override bool OnTestTooltipOverControl(ref Float2 location) { - return _headerRect.Contains(ref location) && ShowTooltip && !Surface.IsConnecting && !Surface.IsBoxSelecting; + return _headerRect.Contains(ref location) && ShowTooltip && !Surface.IsConnecting && !Surface.IsSelecting; } /// @@ -1075,7 +1077,7 @@ namespace FlaxEditor.Surface // Header var headerColor = style.BackgroundHighlighted; - if (_headerRect.Contains(ref _mousePosition) && !Surface.IsConnecting && !Surface.IsBoxSelecting) + if (_headerRect.Contains(ref _mousePosition) && !Surface.IsConnecting && !Surface.IsSelecting) headerColor *= 1.07f; Render2D.FillRectangle(_headerRect, headerColor); Render2D.DrawText(style.FontLarge, Title, _headerRect, style.Foreground, TextAlignment.Center, TextAlignment.Center); @@ -1083,7 +1085,7 @@ namespace FlaxEditor.Surface // Close button if ((Archetype.Flags & NodeFlags.NoCloseButton) == 0 && Surface.CanEdit) { - bool highlightClose = _closeButtonRect.Contains(_mousePosition) && !Surface.IsConnecting && !Surface.IsBoxSelecting; + bool highlightClose = _closeButtonRect.Contains(_mousePosition) && !Surface.IsConnecting && !Surface.IsSelecting; Render2D.DrawSprite(style.Cross, _closeButtonRect, highlightClose ? style.Foreground : style.ForegroundGrey); } @@ -1121,7 +1123,7 @@ namespace FlaxEditor.Surface if (button == MouseButton.Left && (Archetype.Flags & NodeFlags.NoCloseButton) == 0 && _closeButtonRect.Contains(ref location)) return true; if (button == MouseButton.Right) - return true; + mouseDownMousePosition = Input.Mouse.Position; return false; } @@ -1133,7 +1135,7 @@ namespace FlaxEditor.Surface return true; // Close/ delete - bool canDelete = !Surface.IsConnecting && !Surface.WasBoxSelecting && !Surface.WasMovingSelection; + bool canDelete = !Surface.IsConnecting && !Surface.WasSelecting && !Surface.WasMovingSelection; if (button == MouseButton.Left && canDelete && (Archetype.Flags & NodeFlags.NoCloseButton) == 0 && _closeButtonRect.Contains(ref location)) { Surface.Delete(this); @@ -1143,6 +1145,10 @@ namespace FlaxEditor.Surface // Secondary Context Menu if (button == MouseButton.Right) { + float distance = Float2.Distance(mouseDownMousePosition, Input.Mouse.Position); + if (distance > 2.5f) + return true; + if (!IsSelected) Surface.Select(this); var tmp = PointToParent(ref location); diff --git a/Source/Editor/Surface/VisjectSurface.Connecting.cs b/Source/Editor/Surface/VisjectSurface.Connecting.cs index dbc2b7fb5..6f60ca2e2 100644 --- a/Source/Editor/Surface/VisjectSurface.Connecting.cs +++ b/Source/Editor/Surface/VisjectSurface.Connecting.cs @@ -1,6 +1,8 @@ // Copyright (c) Wojciech Figat. All rights reserved. +using System.Collections.Generic; using FlaxEditor.Scripting; +using FlaxEditor.Surface.Elements; using FlaxEngine; namespace FlaxEditor.Surface @@ -233,11 +235,15 @@ namespace FlaxEditor.Surface /// Begins connecting surface objects action. /// /// The connection instigator (eg. start box). - public void ConnectingStart(IConnectionInstigator instigator) + /// If the instigator should be added to the list of instigators. + public void ConnectingStart(IConnectionInstigator instigator, bool additive = false) { - if (instigator != null && instigator != _connectionInstigator) + if (instigator != null && instigator != _connectionInstigators) { - _connectionInstigator = instigator; + if (!additive) + _connectionInstigators.Clear(); + + _connectionInstigators.Add(instigator); StartMouseCapture(); } } @@ -257,22 +263,30 @@ namespace FlaxEditor.Surface /// The end object (eg. end box). public void ConnectingEnd(IConnectionInstigator end) { - // Ensure that there was a proper start box - if (_connectionInstigator == null) + // Ensure that there is at least one connection instigator + if (_connectionInstigators.Count == 0) return; - var start = _connectionInstigator; - _connectionInstigator = null; - - // Check if boxes are different and end box is specified - if (start == end || end == null) - return; - - // Connect them - if (start.CanConnectWith(end)) + List instigators = new List(_connectionInstigators); + for (int i = 0; i < instigators.Count; i++) { - start.Connect(end); + var start = instigators[i]; + + // Check if boxes are different and end box is specified + if (start == end || end == null) + return; + + // Properly handle connecting to a socket that already has a connection + if (end is Box e && !e.IsOutput && start is Box s && e.AreConnected(s)) + e.BreakConnection(s); + + // Connect them + if (start.CanConnectWith(end)) + start.Connect(end); } + + // Reset instigator list + _connectionInstigators.Clear(); } } } diff --git a/Source/Editor/Surface/VisjectSurface.ContextMenu.cs b/Source/Editor/Surface/VisjectSurface.ContextMenu.cs index 2cd46593b..d8dfb8ad3 100644 --- a/Source/Editor/Surface/VisjectSurface.ContextMenu.cs +++ b/Source/Editor/Surface/VisjectSurface.ContextMenu.cs @@ -261,10 +261,10 @@ namespace FlaxEditor.Surface /// /// The active context menu to show. /// The display location on the surface control. - /// The start box. - protected virtual void OnShowPrimaryMenu(VisjectCM activeCM, Float2 location, Box startBox) + /// The start boxes. + protected virtual void OnShowPrimaryMenu(VisjectCM activeCM, Float2 location, List startBoxes) { - activeCM.Show(this, location, startBox); + activeCM.Show(this, location, startBoxes); } /// @@ -298,8 +298,10 @@ namespace FlaxEditor.Surface _cmStartPos = location; - // Offset added in case the user doesn't like the box and wants to quickly get rid of it by clicking - OnShowPrimaryMenu(_activeVisjectCM, _cmStartPos + ContextMenuOffset, _connectionInstigator as Box); + List startBoxes = new List(_connectionInstigators.Where(c => c is Box).Cast()); + + // Position offset added so the user can quickly close the menu by clicking + OnShowPrimaryMenu(_activeVisjectCM, _cmStartPos + ContextMenuOffset, startBoxes); if (!string.IsNullOrEmpty(input)) { @@ -513,17 +515,15 @@ namespace FlaxEditor.Surface private void OnPrimaryMenuVisibleChanged(Control primaryMenu) { if (!primaryMenu.Visible) - { - _connectionInstigator = null; - } + _connectionInstigators.Clear(); } /// /// Handles Visject CM item click event by spawning the selected item. /// /// The item. - /// The selected box. - protected virtual void OnPrimaryMenuButtonClick(VisjectCMItem visjectCmItem, Box selectedBox) + /// The selected boxes. + protected virtual void OnPrimaryMenuButtonClick(VisjectCMItem visjectCmItem, List selectedBoxes) { if (!CanEdit) return; @@ -550,34 +550,36 @@ namespace FlaxEditor.Surface // Auto select new node Select(node); - if (selectedBox != null) + for (int i = 0; i < selectedBoxes.Count; i++) { - Box endBox = null; - foreach (var box in node.GetBoxes().Where(box => box.IsOutput != selectedBox.IsOutput)) + Box currentBox = selectedBoxes[i]; + if (currentBox != null) { - if (selectedBox.IsOutput) + Box endBox = null; + foreach (var box in node.GetBoxes().Where(box => box.IsOutput != currentBox.IsOutput)) { - if (box.CanUseType(selectedBox.CurrentType)) + if (currentBox.IsOutput) { - endBox = box; - break; + if (box.CanUseType(currentBox.CurrentType)) + { + endBox = box; + break; + } } - } - else - { - if (selectedBox.CanUseType(box.CurrentType)) + else { - endBox = box; - break; + if (currentBox.CanUseType(box.CurrentType)) + { + endBox = box; + break; + } } - } - if (endBox == null && selectedBox.CanUseType(box.CurrentType)) - { - endBox = box; + if (endBox == null && currentBox.CanUseType(box.CurrentType)) + endBox = box; } + TryConnect(currentBox, endBox); } - TryConnect(selectedBox, endBox); } } @@ -593,13 +595,8 @@ namespace FlaxEditor.Surface } // If the user is patiently waiting for his box to get connected to the newly created one fulfill his wish! - - _connectionInstigator = startBox; - if (!IsConnecting) - { ConnectingStart(startBox); - } ConnectingEnd(endBox); // Smart-Select next box diff --git a/Source/Editor/Surface/VisjectSurface.Draw.cs b/Source/Editor/Surface/VisjectSurface.Draw.cs index f60c19d21..af5893907 100644 --- a/Source/Editor/Surface/VisjectSurface.Draw.cs +++ b/Source/Editor/Surface/VisjectSurface.Draw.cs @@ -1,5 +1,6 @@ // Copyright (c) Wojciech Figat. All rights reserved. +using System.Collections.Generic; using FlaxEditor.Surface.Elements; using FlaxEngine; @@ -126,40 +127,45 @@ namespace FlaxEditor.Surface /// Called only when user is connecting nodes. protected virtual void DrawConnectingLine() { - // Get start position - var startPos = _connectionInstigator.ConnectionOrigin; - - // Check if mouse is over any of box var cmVisible = _activeVisjectCM != null && _activeVisjectCM.Visible; var endPos = cmVisible ? _rootControl.PointFromParent(ref _cmStartPos) : _rootControl.PointFromParent(ref _mousePos); Color lineColor = Style.Colors.Connecting; - if (_lastInstigatorUnderMouse != null && !cmVisible) - { - // Check if can connect objects - bool canConnect = _connectionInstigator.CanConnectWith(_lastInstigatorUnderMouse); - lineColor = canConnect ? Style.Colors.ConnectingValid : Style.Colors.ConnectingInvalid; - endPos = _lastInstigatorUnderMouse.ConnectionOrigin; - } - Float2 actualStartPos = startPos; - Float2 actualEndPos = endPos; - - if (_connectionInstigator is Archetypes.Tools.RerouteNode) + List instigators = new List(_connectionInstigators); + for (int i = 0; i < instigators.Count; i++) { - if (endPos.X < startPos.X && _lastInstigatorUnderMouse is null or Box { IsOutput: true }) + IConnectionInstigator currentInstigator = instigators[i]; + Float2 currentStartPosition = currentInstigator.ConnectionOrigin; + + // Check if mouse is over any box + if (_lastInstigatorUnderMouse != null && !cmVisible) + { + // Check if can connect objects + bool canConnect = currentInstigator.CanConnectWith(_lastInstigatorUnderMouse); + lineColor = canConnect ? Style.Colors.ConnectingValid : Style.Colors.ConnectingInvalid; + endPos = _lastInstigatorUnderMouse.ConnectionOrigin; + } + + Float2 actualStartPos = currentStartPosition; + Float2 actualEndPos = endPos; + + if (currentInstigator is Archetypes.Tools.RerouteNode) + { + if (endPos.X < currentStartPosition.X && _lastInstigatorUnderMouse is null or Box { IsOutput: true }) + { + actualStartPos = endPos; + actualEndPos = currentStartPosition; + } + } + else if (currentInstigator is Box { IsOutput: false }) { actualStartPos = endPos; - actualEndPos = startPos; + actualEndPos = currentStartPosition; } - } - else if (_connectionInstigator is Box { IsOutput: false }) - { - actualStartPos = endPos; - actualEndPos = startPos; - } - // Draw connection - _connectionInstigator.DrawConnectingLine(ref actualStartPos, ref actualEndPos, ref lineColor); + // Draw connection + currentInstigator.DrawConnectingLine(ref actualStartPos, ref actualEndPos, ref lineColor); + } } /// @@ -226,10 +232,10 @@ namespace FlaxEditor.Surface _rootControl.DrawComments(); // Reset input flags here because this is the closest to Update we have - WasBoxSelecting = IsBoxSelecting; + WasSelecting = IsSelecting; WasMovingSelection = IsMovingSelection; - if (IsBoxSelecting) + if (IsSelecting) { DrawSelection(); } diff --git a/Source/Editor/Surface/VisjectSurface.Formatting.cs b/Source/Editor/Surface/VisjectSurface.Formatting.cs index 39ac58242..e1b9a6777 100644 --- a/Source/Editor/Surface/VisjectSurface.Formatting.cs +++ b/Source/Editor/Surface/VisjectSurface.Formatting.cs @@ -176,10 +176,10 @@ namespace FlaxEditor.Surface if (connectedNodes.Count == 0) return; - for (int i = 0; i < connectedNodes.Count - 1; i++) + for (int i = 0; i < connectedNodes.Count; i++) { SurfaceNode nodeA = connectedNodes[i]; - List connectedOutputBoxes = nodeA.GetBoxes().Where(b => b.IsOutput && b.HasAnyConnection).ToList(); + List connectedOutputBoxes = nodeA.GetBoxes().Where(b => b.HasAnyConnection).ToList(); for (int j = 0; j < connectedOutputBoxes.Count; j++) { diff --git a/Source/Editor/Surface/VisjectSurface.Input.cs b/Source/Editor/Surface/VisjectSurface.Input.cs index 09df195eb..ba6596476 100644 --- a/Source/Editor/Surface/VisjectSurface.Input.cs +++ b/Source/Editor/Surface/VisjectSurface.Input.cs @@ -290,7 +290,7 @@ namespace FlaxEditor.Surface if (_leftMouseDown) { // Connecting - if (_connectionInstigator != null) + if (_connectionInstigators.Count > 0) { } // Moving @@ -460,7 +460,7 @@ namespace FlaxEditor.Surface public override bool OnMouseDown(Float2 location, MouseButton button) { // Check if user is connecting boxes - if (_connectionInstigator != null) + if (_connectionInstigators.Count > 0) return true; // Base @@ -606,7 +606,7 @@ namespace FlaxEditor.Surface _movingNodesDelta = Float2.Zero; } // Connecting - else if (_connectionInstigator != null) + else if (_connectionInstigators.Count > 0) { } // Selecting @@ -678,7 +678,7 @@ namespace FlaxEditor.Surface ShowPrimaryMenu(_cmStartPos); } // Letting go of a connection or right clicking while creating a connection - else if (!_isMovingSelection && _connectionInstigator != null && !IsPrimaryMenuOpened) + else if (!_isMovingSelection && _connectionInstigators.Count > 0 && !IsPrimaryMenuOpened) { _cmStartPos = location; Cursor = CursorType.Default; diff --git a/Source/Editor/Surface/VisjectSurface.Serialization.cs b/Source/Editor/Surface/VisjectSurface.Serialization.cs index e490d1550..5bf7a9e34 100644 --- a/Source/Editor/Surface/VisjectSurface.Serialization.cs +++ b/Source/Editor/Surface/VisjectSurface.Serialization.cs @@ -33,7 +33,7 @@ namespace FlaxEditor.Surface Enabled = false; // Clean data - _connectionInstigator = null; + _connectionInstigators.Clear(); _lastInstigatorUnderMouse = null; var failed = RootContext.Load(); diff --git a/Source/Editor/Surface/VisjectSurface.cs b/Source/Editor/Surface/VisjectSurface.cs index 8cbcb4a21..e3bb94bcc 100644 --- a/Source/Editor/Surface/VisjectSurface.cs +++ b/Source/Editor/Surface/VisjectSurface.cs @@ -121,7 +121,7 @@ namespace FlaxEditor.Surface /// /// The connection start. /// - protected IConnectionInstigator _connectionInstigator; + protected List _connectionInstigators = new List(); /// /// The last connection instigator under mouse. @@ -232,19 +232,19 @@ namespace FlaxEditor.Surface } /// - /// Gets a value indicating whether user is box selecting nodes. + /// Gets a value indicating whether user is selecting nodes. /// - public bool IsBoxSelecting => _leftMouseDown && !_isMovingSelection && _connectionInstigator == null; + public bool IsSelecting => _leftMouseDown && !_isMovingSelection && _connectionInstigators.Count == 0; /// - /// Gets a value indicating whether user was previously box selecting nodes. + /// Gets a value indicating whether user was previously selecting nodes. /// - public bool WasBoxSelecting { get; private set; } + public bool WasSelecting { get; private set; } /// /// Gets a value indicating whether user is moving selected nodes. /// - public bool IsMovingSelection => _leftMouseDown && _isMovingSelection && _connectionInstigator == null; + public bool IsMovingSelection => _leftMouseDown && _isMovingSelection && _connectionInstigators.Count == 0; /// /// Gets a value indicating whether user was previously moving selected nodes. @@ -254,7 +254,7 @@ namespace FlaxEditor.Surface /// /// Gets a value indicating whether user is connecting nodes. /// - public bool IsConnecting => _connectionInstigator != null; + public bool IsConnecting => _connectionInstigators.Count > 0; /// /// Gets a value indicating whether the left mouse button is down. diff --git a/Source/Editor/Surface/VisualScriptSurface.cs b/Source/Editor/Surface/VisualScriptSurface.cs index 0c11e54ff..2f70ee340 100644 --- a/Source/Editor/Surface/VisualScriptSurface.cs +++ b/Source/Editor/Surface/VisualScriptSurface.cs @@ -212,7 +212,7 @@ namespace FlaxEditor.Surface } /// - protected override void OnShowPrimaryMenu(VisjectCM activeCM, Float2 location, Box startBox) + protected override void OnShowPrimaryMenu(VisjectCM activeCM, Float2 location, List startBoxes) { // Update nodes for method overrides Profiler.BeginEvent("Overrides"); @@ -268,7 +268,7 @@ namespace FlaxEditor.Surface // Update nodes for invoke methods (async) _nodesCache.Get(activeCM); - base.OnShowPrimaryMenu(activeCM, location, startBox); + base.OnShowPrimaryMenu(activeCM, location, startBoxes); activeCM.VisibleChanged += OnActiveContextMenuVisibleChanged; } diff --git a/Source/Editor/Tools/Terrain/TerrainTools.cpp b/Source/Editor/Tools/Terrain/TerrainTools.cpp index 2a17f7960..065f7ffeb 100644 --- a/Source/Editor/Tools/Terrain/TerrainTools.cpp +++ b/Source/Editor/Tools/Terrain/TerrainTools.cpp @@ -149,13 +149,13 @@ bool GetTextureDataForSampling(Texture* texture, TextureDataResult& data, bool h bool TerrainTools::GenerateTerrain(Terrain* terrain, const Int2& numberOfPatches, Texture* heightmap, float heightmapScale, Texture* splatmap1, Texture* splatmap2) { + PROFILE_CPU_NAMED("Terrain.GenerateTerrain"); CHECK_RETURN(terrain && terrain->GetChunkSize() != 0, true); if (numberOfPatches.X < 1 || numberOfPatches.Y < 1) { - LOG(Warning, "Cannot setup terain with no patches."); + LOG(Warning, "Cannot setup terrain with no patches."); return false; } - PROFILE_CPU_NAMED("Terrain.GenerateTerrain"); // Wait for assets to be loaded if (heightmap && heightmap->WaitForLoaded()) @@ -178,7 +178,9 @@ bool TerrainTools::GenerateTerrain(Terrain* terrain, const Int2& numberOfPatches terrain->AddPatches(numberOfPatches); // Prepare data - const auto heightmapSize = terrain->GetChunkSize() * Terrain::ChunksCountEdge + 1; + const int32 heightmapSize = terrain->GetChunkSize() * Terrain::ChunksCountEdge + 1; + const float heightmapSizeInv = 1.0f / (float)(heightmapSize - 1); + const Float2 uvPerPatch = Float2::One / Float2(numberOfPatches); Array heightmapData; heightmapData.Resize(heightmapSize * heightmapSize); @@ -192,19 +194,17 @@ bool TerrainTools::GenerateTerrain(Terrain* terrain, const Int2& numberOfPatches const auto sampler = PixelFormatSampler::Get(dataHeightmap.Format); // Initialize with sub-range of the input heightmap - const Vector2 uvPerPatch = Vector2::One / Vector2(numberOfPatches); - const float heightmapSizeInv = 1.0f / (heightmapSize - 1); for (int32 patchIndex = 0; patchIndex < terrain->GetPatchesCount(); patchIndex++) { auto patch = terrain->GetPatch(patchIndex); - const Vector2 uvStart = Vector2((float)patch->GetX(), (float)patch->GetZ()) * uvPerPatch; + const Float2 uvStart = Float2((float)patch->GetX(), (float)patch->GetZ()) * uvPerPatch; // Sample heightmap pixels with interpolation to get actual heightmap vertices locations for (int32 z = 0; z < heightmapSize; z++) { for (int32 x = 0; x < heightmapSize; x++) { - const Vector2 uv = uvStart + Vector2(x * heightmapSizeInv, z * heightmapSizeInv) * uvPerPatch; + const Float2 uv = uvStart + Float2(x * heightmapSizeInv, z * heightmapSizeInv) * uvPerPatch; const Color color = sampler->SampleLinear(dataHeightmap.Mip0DataPtr->Get(), uv, dataHeightmap.Mip0Size, dataHeightmap.RowPitch); heightmapData[z * heightmapSize + x] = color.R * heightmapScale; } @@ -230,37 +230,30 @@ bool TerrainTools::GenerateTerrain(Terrain* terrain, const Int2& numberOfPatches Texture* splatmaps[2] = { splatmap1, splatmap2 }; Array splatmapData; TextureDataResult data1; - const Vector2 uvPerPatch = Vector2::One / Vector2(numberOfPatches); - const float heightmapSizeInv = 1.0f / (heightmapSize - 1); for (int32 index = 0; index < ARRAY_COUNT(splatmaps); index++) { const auto splatmap = splatmaps[index]; if (!splatmap) continue; - // Prepare data - if (splatmapData.IsEmpty()) - splatmapData.Resize(heightmapSize * heightmapSize); - // Get splatmap data if (GetTextureDataForSampling(splatmap, data1)) return true; const auto sampler = PixelFormatSampler::Get(data1.Format); // Modify heightmap splatmaps with sub-range of the input splatmaps + splatmapData.Resize(heightmapSize * heightmapSize); for (int32 patchIndex = 0; patchIndex < terrain->GetPatchesCount(); patchIndex++) { auto patch = terrain->GetPatch(patchIndex); - - const Vector2 uvStart = Vector2((float)patch->GetX(), (float)patch->GetZ()) * uvPerPatch; + const Float2 uvStart = Float2((float)patch->GetX(), (float)patch->GetZ()) * uvPerPatch; // Sample splatmap pixels with interpolation to get actual splatmap values for (int32 z = 0; z < heightmapSize; z++) { for (int32 x = 0; x < heightmapSize; x++) { - const Vector2 uv = uvStart + Vector2(x * heightmapSizeInv, z * heightmapSizeInv) * uvPerPatch; - + const Float2 uv = uvStart + Float2(x * heightmapSizeInv, z * heightmapSizeInv) * uvPerPatch; const Color color = sampler->SampleLinear(data1.Mip0DataPtr->Get(), uv, data1.Mip0Size, data1.RowPitch); Color32 layers; @@ -374,63 +367,38 @@ Color32* TerrainTools::GetSplatMapData(Terrain* terrain, const Int2& patchCoord, bool TerrainTools::ExportTerrain(Terrain* terrain, String outputFolder) { + PROFILE_CPU_NAMED("Terrain.ExportTerrain"); CHECK_RETURN(terrain && terrain->GetPatchesCount() != 0, true); - const auto firstPatch = terrain->GetPatch(0); - - // Calculate texture size - const int32 patchEdgeVertexCount = terrain->GetChunkSize() * Terrain::ChunksCountEdge + 1; - const int32 patchVertexCount = patchEdgeVertexCount * patchEdgeVertexCount; // Find size of heightmap in patches + const auto firstPatch = terrain->GetPatch(0); Int2 start(firstPatch->GetX(), firstPatch->GetZ()); Int2 end(start); - for (int32 i = 0; i < terrain->GetPatchesCount(); i++) + for (int32 patchIndex = 0; patchIndex < terrain->GetPatchesCount(); patchIndex++) { - const int32 x = terrain->GetPatch(i)->GetX(); - const int32 y = terrain->GetPatch(i)->GetZ(); - - if (x < start.X) - start.X = x; - if (y < start.Y) - start.Y = y; - if (x > end.X) - end.X = x; - if (y > end.Y) - end.Y = y; + const auto patch = terrain->GetPatch(patchIndex); + const Int2 pos(patch->GetX(), patch->GetZ()); + start = Int2::Min(start, pos); + end = Int2::Max(end, pos); } const Int2 size = (end + 1) - start; - // Allocate - with space for non-existent patches + // Allocate heightmap for a whole terrain (NumberOfPatches * 4x4 * ChunkSize + 1) + const Int2 heightmapSize = size * Terrain::ChunksCountEdge * terrain->GetChunkSize() + 1; Array heightmap; - heightmap.Resize(patchVertexCount * size.X * size.Y); - - // Set to any element, where: min < elem < max + heightmap.Resize(heightmapSize.X * heightmapSize.Y); heightmap.SetAll(firstPatch->GetHeightmapData()[0]); - const int32 heightmapWidth = patchEdgeVertexCount * size.X; - - // Fill heightmap with data + // Fill heightmap with data from all patches + const int32 rowSize = terrain->GetChunkSize() * Terrain::ChunksCountEdge + 1; for (int32 patchIndex = 0; patchIndex < terrain->GetPatchesCount(); patchIndex++) { - // Pick a patch const auto patch = terrain->GetPatch(patchIndex); - const float* data = patch->GetHeightmapData(); - - // Beginning of patch - int32 dstIndex = (patch->GetX() - start.X) * patchEdgeVertexCount + - (patch->GetZ() - start.Y) * size.Y * patchVertexCount; - - // Iterate over lines in patch - for (int32 z = 0; z < patchEdgeVertexCount; z++) - { - // Iterate over vertices in line - for (int32 x = 0; x < patchEdgeVertexCount; x++) - { - heightmap[dstIndex + x] = data[z * patchEdgeVertexCount + x]; - } - - dstIndex += heightmapWidth; - } + const Int2 pos(patch->GetX() - start.X, patch->GetZ() - start.Y); + const float* src = patch->GetHeightmapData(); + float* dst = heightmap.Get() + pos.X * (rowSize - 1) + pos.Y * heightmapSize.X * (rowSize - 1); + for (int32 row = 0; row < rowSize; row++) + Platform::MemoryCopy(dst + row * heightmapSize.X, src + row * rowSize, rowSize * sizeof(float)); } // Interpolate to 16-bit int @@ -438,44 +406,42 @@ bool TerrainTools::ExportTerrain(Terrain* terrain, String outputFolder) maxHeight = minHeight = heightmap[0]; for (int32 i = 1; i < heightmap.Count(); i++) { - float h = heightmap[i]; + float h = heightmap.Get()[i]; if (maxHeight < h) maxHeight = h; else if (minHeight > h) minHeight = h; } - - const float maxValue = 65535.0f; - const float alpha = maxValue / (maxHeight - minHeight); + const float alpha = MAX_uint16 / (maxHeight - minHeight); // Storage for pixel data - Array byteHeightmap(heightmap.Capacity()); - - for (auto& elem : heightmap) + Array byteHeightmap; + byteHeightmap.Resize(heightmap.Count()); + for (int32 i = 0; i < heightmap.Count(); i++) { - byteHeightmap.Add(static_cast(alpha * (elem - minHeight))); + float height = heightmap.Get()[i]; + byteHeightmap.Get()[i] = static_cast(alpha * (height - minHeight)); } // Create texture TextureData textureData; - textureData.Height = textureData.Width = heightmapWidth; + textureData.Width = heightmapSize.X; + textureData.Height = heightmapSize.Y; textureData.Depth = 1; textureData.Format = PixelFormat::R16_UNorm; textureData.Items.Resize(1); textureData.Items[0].Mips.Resize(1); - - // Fill mip data TextureMipData* srcMip = textureData.GetData(0, 0); srcMip->Data.Link(byteHeightmap.Get()); srcMip->Lines = textureData.Height; - srcMip->RowPitch = textureData.Width * 2; // 2 bytes per pixel for format R16 + srcMip->RowPitch = textureData.Width * sizeof(uint16); srcMip->DepthPitch = srcMip->Lines * srcMip->RowPitch; // Find next non-existing file heightmap file FileSystem::NormalizePath(outputFolder); const String baseFileName(TEXT("heightmap")); String outputPath; - for (int32 i = 0; i < MAX_int32; i++) + for (int32 i = 0; i < 100; i++) { outputPath = outputFolder / baseFileName + StringUtils::ToString(i) + TEXT(".png"); if (!FileSystem::FileExists(outputPath)) diff --git a/Source/Editor/Utilities/Utils.cs b/Source/Editor/Utilities/Utils.cs index 7594bcd22..ea55edc0b 100644 --- a/Source/Editor/Utilities/Utils.cs +++ b/Source/Editor/Utilities/Utils.cs @@ -212,6 +212,10 @@ namespace FlaxEditor.Utilities if (value is FlaxEngine.Object) return value; + // For custom types use interface + if (value is ICloneable clonable) + return clonable.Clone(); + // For objects (eg. arrays) we need to clone them to prevent editing default/reference value within editor if (value != null && (!value.GetType().IsValueType || !value.GetType().IsClass)) { @@ -548,6 +552,26 @@ namespace FlaxEditor.Utilities return arr; } + internal static void StructureToByteArray(object value, int valueSize, IntPtr tempBuffer, byte[] dataBuffer) + { + var valueType = value.GetType(); + if (valueType.IsEnum) + { + var ptr = FlaxEngine.Interop.NativeInterop.ValueTypeUnboxer.GetPointer(value, valueType); + FlaxEngine.Utils.MemoryCopy(tempBuffer, ptr, (ulong)valueSize); + } + else + Marshal.StructureToPtr(value, tempBuffer, true); + Marshal.Copy(tempBuffer, dataBuffer, 0, valueSize); + } + + internal static object ByteArrayToStructure(IntPtr valuePtr, Type valueType, int valueSize) + { + if (valueType.IsEnum) + return FlaxEngine.Interop.NativeInterop.MarshalToManaged(valuePtr, valueType); + return Marshal.PtrToStructure(valuePtr, valueType); + } + internal static unsafe string ReadStr(this BinaryReader stream, int check) { int length = stream.ReadInt32(); diff --git a/Source/Editor/Utilities/ViewportIconsRenderer.cpp b/Source/Editor/Utilities/ViewportIconsRenderer.cpp index 1f721d289..690c55424 100644 --- a/Source/Editor/Utilities/ViewportIconsRenderer.cpp +++ b/Source/Editor/Utilities/ViewportIconsRenderer.cpp @@ -65,13 +65,14 @@ public: ViewportIconsRendererService ViewportIconsRendererServiceInstance; float ViewportIconsRenderer::Scale = 1.0f; +Real ViewportIconsRenderer::MinSize = 7.0f; +Real ViewportIconsRenderer::MaxSize = 30.0f; +Real ViewportIconsRenderer::MaxSizeDistance = 1000.0f; void ViewportIconsRenderer::GetBounds(const Vector3& position, const Vector3& viewPosition, BoundingSphere& bounds) { - constexpr Real minSize = 7.0; - constexpr Real maxSize = 30.0; - Real scale = Math::Square(Vector3::Distance(position, viewPosition) / 1000.0f); - Real radius = minSize + Math::Min(scale, 1.0f) * (maxSize - minSize); + Real scale = Math::Square(Vector3::Distance(position, viewPosition) / MaxSizeDistance); + Real radius = MinSize + Math::Min(scale, 1.0f) * (MaxSize - MinSize); bounds = BoundingSphere(position, radius * Scale); } diff --git a/Source/Editor/Utilities/ViewportIconsRenderer.h b/Source/Editor/Utilities/ViewportIconsRenderer.h index c7bf7e1c3..a1e1538b8 100644 --- a/Source/Editor/Utilities/ViewportIconsRenderer.h +++ b/Source/Editor/Utilities/ViewportIconsRenderer.h @@ -22,6 +22,21 @@ public: /// API_FIELD() static float Scale; + /// + /// The minimum size of the icons. + /// + API_FIELD() static Real MinSize; + + /// + /// The maximum size of the icons. + /// + API_FIELD() static Real MaxSize; + + /// + /// The distance to the camera at which the icons will be drawn at their maximum size. + /// + API_FIELD() static Real MaxSizeDistance; + /// /// Draws the icons for the actors in the given scene (or actor tree). /// diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index 5956de87d..5fb1c4657 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -541,7 +541,7 @@ namespace FlaxEditor.Viewport // Setup options { - Editor.Instance.Options.OptionsChanged += OnEditorOptionsChanged; + _editor.Options.OptionsChanged += OnEditorOptionsChanged; SetupViewportOptions(); } @@ -587,7 +587,7 @@ namespace FlaxEditor.Viewport // Camera Settings Menu var cameraCM = new ContextMenu(); - _cameraButton = new ViewportWidgetButton(string.Format(MovementSpeedTextFormat, _movementSpeed), Editor.Instance.Icons.Camera64, cameraCM, false, cameraSpeedTextWidth) + _cameraButton = new ViewportWidgetButton(string.Format(MovementSpeedTextFormat, _movementSpeed), _editor.Icons.Camera64, cameraCM, false, cameraSpeedTextWidth) { Tag = this, TooltipText = "Camera Settings", @@ -596,7 +596,7 @@ namespace FlaxEditor.Viewport _cameraWidget.Parent = this; // Orthographic/Perspective Mode Widget - _orthographicModeButton = new ViewportWidgetButton(string.Empty, Editor.Instance.Icons.CamSpeed32, null, true) + _orthographicModeButton = new ViewportWidgetButton(string.Empty, _editor.Icons.CamSpeed32, null, true) { Checked = !_isOrtho, TooltipText = "Toggle Orthographic/Perspective Mode", @@ -869,8 +869,8 @@ namespace FlaxEditor.Viewport { } }); - viewLayers.AddButton("Reset layers", () => Task.ViewLayersMask = LayersMask.Default).Icon = Editor.Instance.Icons.Rotate32; - viewLayers.AddButton("Disable layers", () => Task.ViewLayersMask = new LayersMask(0)).Icon = Editor.Instance.Icons.Rotate32; + viewLayers.AddButton("Reset layers", () => Task.ViewLayersMask = LayersMask.Default).Icon = _editor.Icons.Rotate32; + viewLayers.AddButton("Disable layers", () => Task.ViewLayersMask = new LayersMask(0)); viewLayers.AddSeparator(); var layers = LayersAndTagsSettings.GetCurrentLayers(); if (layers != null && layers.Length > 0) @@ -910,8 +910,8 @@ namespace FlaxEditor.Viewport { } }); - viewFlags.AddButton("Reset flags", () => Task.ViewFlags = ViewFlags.DefaultEditor).Icon = Editor.Instance.Icons.Rotate32; - viewFlags.AddButton("Disable flags", () => Task.ViewFlags = ViewFlags.None).Icon = Editor.Instance.Icons.Rotate32; + viewFlags.AddButton("Reset flags", () => Task.ViewFlags = ViewFlags.DefaultEditor).Icon = _editor.Icons.Rotate32; + viewFlags.AddButton("Disable flags", () => Task.ViewFlags = ViewFlags.None); viewFlags.AddSeparator(); for (int i = 0; i < ViewFlagsValues.Length; i++) { @@ -1091,7 +1091,7 @@ namespace FlaxEditor.Viewport /// private void SetupViewportOptions() { - var options = Editor.Instance.Options.Options; + var options = _editor.Options.Options; _minMovementSpeed = options.Viewport.MinMovementSpeed; MovementSpeed = options.Viewport.MovementSpeed; _maxMovementSpeed = options.Viewport.MaxMovementSpeed; @@ -1298,6 +1298,11 @@ namespace FlaxEditor.Viewport _mouseSensitivity = options.Viewport.MouseSensitivity; _maxSpeedSteps = options.Viewport.TotalCameraSpeedSteps; _cameraEasingDegree = options.Viewport.CameraEasingDegree; + + ViewportIconsRenderer.MinSize = options.Viewport.IconsMinimumSize; + ViewportIconsRenderer.MaxSize = options.Viewport.IconsMaximumSize; + ViewportIconsRenderer.MaxSizeDistance = options.Viewport.MaxSizeDistance; + OnCameraMovementProgressChanged(); } @@ -1711,7 +1716,7 @@ namespace FlaxEditor.Viewport // Check if update mouse var size = Size; - var options = Editor.Instance.Options.Options; + var options = _editor.Options.Options; if (_isControllingMouse) { var rmbWheel = false; @@ -1952,7 +1957,7 @@ namespace FlaxEditor.Viewport return true; // Custom input events - return InputActions.Process(Editor.Instance, this, key); + return InputActions.Process(_editor, this, key); } /// @@ -1969,7 +1974,7 @@ namespace FlaxEditor.Viewport base.Draw(); // Add overlay during debugger breakpoint hang - if (Editor.Instance.Simulation.IsDuringBreakpointHang) + if (_editor.Simulation.IsDuringBreakpointHang) { var bounds = new Rectangle(Float2.Zero, Size); Render2D.FillRectangle(bounds, new Color(0.0f, 0.0f, 0.0f, 0.2f)); @@ -1994,7 +1999,7 @@ namespace FlaxEditor.Viewport /// public override void OnDestroy() { - Editor.Instance.Options.OptionsChanged -= OnEditorOptionsChanged; + _editor.Options.OptionsChanged -= OnEditorOptionsChanged; base.OnDestroy(); } diff --git a/Source/Editor/Viewport/PrefabWindowViewport.cs b/Source/Editor/Viewport/PrefabWindowViewport.cs index a982f6447..c38b36f1e 100644 --- a/Source/Editor/Viewport/PrefabWindowViewport.cs +++ b/Source/Editor/Viewport/PrefabWindowViewport.cs @@ -6,6 +6,8 @@ using System.Linq; using FlaxEditor.Content; using FlaxEditor.Gizmo; using FlaxEditor.GUI.ContextMenu; +using FlaxEditor.GUI.Input; +using FlaxEditor.Modules; using FlaxEditor.SceneGraph; using FlaxEditor.Scripting; using FlaxEditor.Viewport.Cameras; @@ -13,6 +15,7 @@ using FlaxEditor.Viewport.Previews; using FlaxEditor.Windows.Assets; using FlaxEngine; using FlaxEngine.GUI; +using FlaxEngine.Json; using Utils = FlaxEditor.Utilities.Utils; namespace FlaxEditor.Viewport @@ -70,8 +73,11 @@ namespace FlaxEditor.Viewport private PrefabUIEditorRoot _uiRoot; private bool _showUI = false; - + + private int _defaultScaleActiveIndex = 0; + private int _customScaleActiveIndex = -1; private ContextMenuButton _uiModeButton; + private ContextMenuChildMenu _uiViewOptions; /// /// Event fired when the UI Mode is toggled. @@ -137,6 +143,8 @@ namespace FlaxEditor.Viewport UseAutomaticTaskManagement = defaultFeatures; ShowDefaultSceneActors = defaultFeatures; TintColor = defaultFeatures ? Color.White : Color.Transparent; + if (_uiViewOptions != null) + _uiViewOptions.Visible = _showUI; UIModeToggled?.Invoke(_showUI); } } @@ -210,7 +218,7 @@ namespace FlaxEditor.Viewport _uiParentLink = _uiRoot.UIRoot; // UI mode buton - _uiModeButton = ViewWidgetShowMenu.AddButton("UI Mode", (button) => ShowUI = button.Checked); + _uiModeButton = ViewWidgetShowMenu.AddButton("UI Mode", button => ShowUI = button.Checked); _uiModeButton.AutoCheck = true; _uiModeButton.VisibleChanged += control => (control as ContextMenuButton).Checked = ShowUI; @@ -222,6 +230,84 @@ namespace FlaxEditor.Viewport SetUpdate(ref _update, OnUpdate); } + /// + /// Creates the view scaling options. Needs to be called after a Prefab is valid and loaded. + /// + public void CreateViewScalingOptions() + { + if (_uiViewOptions != null) + return; + _uiViewOptions = ViewWidgetButtonMenu.AddChildMenu("UI View Scaling"); + _uiViewOptions.Visible = _showUI; + LoadCustomUIScalingOption(); + Editor.Instance.UI.CreateViewportSizingContextMenu(_uiViewOptions.ContextMenu, _defaultScaleActiveIndex, _customScaleActiveIndex, true, ChangeUIView, (a, b) => + { + _defaultScaleActiveIndex = a; + _customScaleActiveIndex = b; + }); + } + + private void ChangeUIView(UIModule.ViewportScaleOption uiViewScaleOption) + { + _uiRoot.SetViewSize((Float2)uiViewScaleOption.Size); + } + + /// + /// Saves the active ui scaling option. + /// + public void SaveActiveUIScalingOption() + { + var defaultKey = $"{Prefab.ID}:DefaultViewportScalingIndex"; + Editor.Instance.ProjectCache.SetCustomData(defaultKey, _defaultScaleActiveIndex.ToString()); + var customKey = $"{Prefab.ID}:CustomViewportScalingIndex"; + Editor.Instance.ProjectCache.SetCustomData(customKey, _customScaleActiveIndex.ToString()); + } + + private void LoadCustomUIScalingOption() + { + Prefab.WaitForLoaded(); + var defaultKey = $"{Prefab.ID}:DefaultViewportScalingIndex"; + if (Editor.Instance.ProjectCache.TryGetCustomData(defaultKey, out string defaultData)) + { + if (int.TryParse(defaultData, out var index)) + { + var options = Editor.Instance.UI.DefaultViewportScaleOptions; + if (options.Count > index) + { + _defaultScaleActiveIndex = index; + if (index != -1) + ChangeUIView(Editor.Instance.UI.DefaultViewportScaleOptions[index]); + } + // Assume option does not exist anymore so move to default. + else if (index != -1) + { + _defaultScaleActiveIndex = 0; + } + } + } + + var customKey = $"{Prefab.ID}:CustomViewportScalingIndex"; + if (Editor.Instance.ProjectCache.TryGetCustomData(customKey, out string data)) + { + if (int.TryParse(data, out var index)) + { + var options = Editor.Instance.UI.CustomViewportScaleOptions; + if (options.Count > index) + { + _customScaleActiveIndex = index; + if (index != -1) + ChangeUIView(options[index]); + } + // Assume option does not exist anymore so move to default. + else if (index != -1) + { + _defaultScaleActiveIndex = 0; + _customScaleActiveIndex = -1; + } + } + } + } + private void OnUpdate(float deltaTime) { for (int i = 0; i < Gizmos.Count; i++) diff --git a/Source/Editor/Viewport/Previews/PrefabPreview.cs b/Source/Editor/Viewport/Previews/PrefabPreview.cs index 4ee763540..70316e4d9 100644 --- a/Source/Editor/Viewport/Previews/PrefabPreview.cs +++ b/Source/Editor/Viewport/Previews/PrefabPreview.cs @@ -112,8 +112,9 @@ namespace FlaxEditor.Viewport.Previews LinkCanvas(_instance); // Link UI control to the preview + var uiControl = _instance as UIControl; if (_uiControlLinked == null && - _instance is UIControl uiControl && + uiControl != null && uiControl.Control != null && uiControl.Control.Parent == null) { @@ -128,6 +129,12 @@ namespace FlaxEditor.Viewport.Previews _uiControlLinked.Control.Parent = _uiParentLink; _hasUILinked = true; } + + // Use UI mode when root is empty UI Control + if (_uiControlLinked == null && uiControl != null && uiControl.Control == null) + { + _hasUILinked = true; + } } private void LinkCanvas(Actor actor) diff --git a/Source/Editor/Windows/AboutDialog.cs b/Source/Editor/Windows/AboutDialog.cs index 488c6a14d..3971e0ce4 100644 --- a/Source/Editor/Windows/AboutDialog.cs +++ b/Source/Editor/Windows/AboutDialog.cs @@ -97,6 +97,7 @@ namespace FlaxEditor.Windows "Jean-Baptiste Perrier", "Chandler Cox", "Ari Vuollet", + "Vincent Saarmann", }); authors.Sort(); var authorsLabel = new Label(4, topParentControl.Bottom + 20, Width - 8, 70) diff --git a/Source/Editor/Windows/Assets/PrefabWindow.cs b/Source/Editor/Windows/Assets/PrefabWindow.cs index 7f17053ac..1d6d827ab 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.cs @@ -371,6 +371,7 @@ namespace FlaxEditor.Windows.Assets else _viewport.SetInitialUIMode(_viewport._hasUILinked); _viewport.UIModeToggled += OnUIModeToggled; + _viewport.CreateViewScalingOptions(); Graph.MainActor = _viewport.Instance; Selection.Clear(); Select(Graph.Main); @@ -567,6 +568,15 @@ namespace FlaxEditor.Windows.Assets Graph.Dispose(); } + /// + protected override void OnClose() + { + // Save current UI view size state. + _viewport.SaveActiveUIScalingOption(); + + base.OnClose(); + } + /// public EditorViewport PresenterViewport => _viewport; diff --git a/Source/Editor/Windows/EditorWindow.cs b/Source/Editor/Windows/EditorWindow.cs index 6d01432ba..03d435efe 100644 --- a/Source/Editor/Windows/EditorWindow.cs +++ b/Source/Editor/Windows/EditorWindow.cs @@ -116,6 +116,11 @@ namespace FlaxEditor.Windows if (InputOptions.WindowShortcutsAvaliable) Editor.Windows.VisualScriptDebuggerWin.FocusOrShow(); }); + InputActions.Add(options => options.EditorOptionsWindow, () => + { + if (InputOptions.WindowShortcutsAvaliable) + Editor.Windows.EditorOptionsWin.FocusOrShow(); + }); // Register Editor.Windows.OnWindowAdd(this); diff --git a/Source/Editor/Windows/GameWindow.cs b/Source/Editor/Windows/GameWindow.cs index 65202f862..c59214804 100644 --- a/Source/Editor/Windows/GameWindow.cs +++ b/Source/Editor/Windows/GameWindow.cs @@ -6,6 +6,7 @@ using System.Xml; using FlaxEditor.Gizmo; using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.Input; +using FlaxEditor.Modules; using FlaxEditor.Options; using FlaxEngine; using FlaxEngine.GUI; @@ -34,8 +35,8 @@ namespace FlaxEditor.Windows private CursorLockMode _cursorLockMode = CursorLockMode.None; // Viewport scaling variables - private List _defaultViewportScaling = new List(); - private List _customViewportScaling = new List(); + private int _defaultScaleActiveIndex = 0; + private int _customScaleActiveIndex = -1; private float _viewportAspectRatio = 1; private float _windowAspectRatio = 1; private bool _useAspect = false; @@ -246,35 +247,6 @@ namespace FlaxEditor.Windows /// public InterfaceOptions.PlayModeFocus FocusOnPlayOption { get; set; } - private enum ViewportScaleType - { - Resolution = 0, - Aspect = 1, - } - - private class ViewportScaleOptions - { - /// - /// The name. - /// - public string Label; - - /// - /// The Type of scaling to do. - /// - public ViewportScaleType ScaleType; - - /// - /// The width and height to scale by. - /// - public Int2 Size; - - /// - /// If the scaling is active. - /// - public bool Active; - } - private class PlayModeFocusOptions { /// @@ -420,7 +392,7 @@ namespace FlaxEditor.Windows InputActions.Add(options => options.FocusConsoleCommand, () => Editor.Instance.Windows.OutputLogWin.FocusCommand()); } - private void ChangeViewportRatio(ViewportScaleOptions v) + private void ChangeViewportRatio(UIModule.ViewportScaleOption v) { if (v == null) return; @@ -439,11 +411,11 @@ namespace FlaxEditor.Windows { switch (v.ScaleType) { - case ViewportScaleType.Aspect: + case UIModule.ViewportScaleOption.ViewportScaleType.Aspect: _useAspect = true; _freeAspect = false; break; - case ViewportScaleType.Resolution: + case UIModule.ViewportScaleOption.ViewportScaleType.Resolution: _useAspect = false; _freeAspect = false; break; @@ -634,49 +606,12 @@ namespace FlaxEditor.Windows // Viewport aspect ratio { - // Create default scaling options if they dont exist from deserialization. - if (_defaultViewportScaling.Count == 0) - { - _defaultViewportScaling.Add(new ViewportScaleOptions - { - Label = "Free Aspect", - ScaleType = ViewportScaleType.Aspect, - Size = new Int2(1, 1), - Active = true, - }); - _defaultViewportScaling.Add(new ViewportScaleOptions - { - Label = "16:9 Aspect", - ScaleType = ViewportScaleType.Aspect, - Size = new Int2(16, 9), - Active = false, - }); - _defaultViewportScaling.Add(new ViewportScaleOptions - { - Label = "16:10 Aspect", - ScaleType = ViewportScaleType.Aspect, - Size = new Int2(16, 10), - Active = false, - }); - _defaultViewportScaling.Add(new ViewportScaleOptions - { - Label = "1920x1080 Resolution (Full HD)", - ScaleType = ViewportScaleType.Resolution, - Size = new Int2(1920, 1080), - Active = false, - }); - _defaultViewportScaling.Add(new ViewportScaleOptions - { - Label = "2560x1440 Resolution (2K)", - ScaleType = ViewportScaleType.Resolution, - Size = new Int2(2560, 1440), - Active = false, - }); - } - var vsMenu = menu.AddChildMenu("Viewport Size").ContextMenu; - - CreateViewportSizingContextMenu(vsMenu); + Editor.UI.CreateViewportSizingContextMenu(vsMenu, _defaultScaleActiveIndex, _customScaleActiveIndex, false, ChangeViewportRatio, (a, b) => + { + _defaultScaleActiveIndex = a; + _customScaleActiveIndex = b; + }); } // Take Screenshot @@ -774,243 +709,6 @@ namespace FlaxEditor.Windows } } - private void CreateViewportSizingContextMenu(ContextMenu vsMenu) - { - // Add default viewport sizing options - for (int i = 0; i < _defaultViewportScaling.Count; i++) - { - var viewportScale = _defaultViewportScaling[i]; - var button = vsMenu.AddButton(viewportScale.Label); - button.CloseMenuOnClick = false; - button.Icon = viewportScale.Active ? Style.Current.CheckBoxTick : SpriteHandle.Invalid; - button.Tag = viewportScale; - if (viewportScale.Active) - ChangeViewportRatio(viewportScale); - - button.Clicked += () => - { - if (button.Tag == null) - return; - - // Reset selected icon on all buttons - foreach (var child in vsMenu.Items) - { - if (child is ContextMenuButton cmb && cmb.Tag is ViewportScaleOptions v) - { - if (cmb == button) - { - v.Active = true; - button.Icon = Style.Current.CheckBoxTick; - ChangeViewportRatio(v); - } - else if (v.Active) - { - cmb.Icon = SpriteHandle.Invalid; - v.Active = false; - } - } - } - }; - } - if (_defaultViewportScaling.Count != 0) - vsMenu.AddSeparator(); - - // Add custom viewport options - for (int i = 0; i < _customViewportScaling.Count; i++) - { - var viewportScale = _customViewportScaling[i]; - var childCM = vsMenu.AddChildMenu(viewportScale.Label); - childCM.CloseMenuOnClick = false; - childCM.Icon = viewportScale.Active ? Style.Current.CheckBoxTick : SpriteHandle.Invalid; - childCM.Tag = viewportScale; - if (viewportScale.Active) - ChangeViewportRatio(viewportScale); - - var applyButton = childCM.ContextMenu.AddButton("Apply"); - applyButton.Tag = childCM.Tag = viewportScale; - applyButton.CloseMenuOnClick = false; - applyButton.Clicked += () => - { - if (childCM.Tag == null) - return; - - // Reset selected icon on all buttons - foreach (var child in vsMenu.Items) - { - if (child is ContextMenuButton cmb && cmb.Tag is ViewportScaleOptions v) - { - if (child == childCM) - { - v.Active = true; - childCM.Icon = Style.Current.CheckBoxTick; - ChangeViewportRatio(v); - } - else if (v.Active) - { - cmb.Icon = SpriteHandle.Invalid; - v.Active = false; - } - } - } - }; - - var deleteButton = childCM.ContextMenu.AddButton("Delete"); - deleteButton.CloseMenuOnClick = false; - deleteButton.Clicked += () => - { - if (childCM.Tag == null) - return; - - var v = (ViewportScaleOptions)childCM.Tag; - if (v.Active) - { - v.Active = false; - _defaultViewportScaling[0].Active = true; - ChangeViewportRatio(_defaultViewportScaling[0]); - } - _customViewportScaling.Remove(v); - vsMenu.DisposeAllItems(); - CreateViewportSizingContextMenu(vsMenu); - vsMenu.PerformLayout(); - }; - } - if (_customViewportScaling.Count != 0) - vsMenu.AddSeparator(); - - // Add button - var add = vsMenu.AddButton("Add..."); - add.CloseMenuOnClick = false; - add.Clicked += () => - { - var popup = new ContextMenuBase - { - Size = new Float2(230, 125), - ClipChildren = false, - CullChildren = false, - }; - popup.Show(add, new Float2(add.Width, 0)); - - var nameLabel = new Label - { - Parent = popup, - AnchorPreset = AnchorPresets.TopLeft, - Text = "Name", - HorizontalAlignment = TextAlignment.Near, - }; - nameLabel.LocalX += 10; - nameLabel.LocalY += 10; - - var nameTextBox = new TextBox - { - Parent = popup, - AnchorPreset = AnchorPresets.TopLeft, - IsMultiline = false, - }; - nameTextBox.LocalX += 100; - nameTextBox.LocalY += 10; - - var typeLabel = new Label - { - Parent = popup, - AnchorPreset = AnchorPresets.TopLeft, - Text = "Type", - HorizontalAlignment = TextAlignment.Near, - }; - typeLabel.LocalX += 10; - typeLabel.LocalY += 35; - - var typeDropdown = new Dropdown - { - Parent = popup, - AnchorPreset = AnchorPresets.TopLeft, - Items = { "Aspect", "Resolution" }, - SelectedItem = "Aspect", - Width = nameTextBox.Width - }; - typeDropdown.LocalY += 35; - typeDropdown.LocalX += 100; - - var whLabel = new Label - { - Parent = popup, - AnchorPreset = AnchorPresets.TopLeft, - Text = "Width & Height", - HorizontalAlignment = TextAlignment.Near, - }; - whLabel.LocalX += 10; - whLabel.LocalY += 60; - - var wValue = new IntValueBox(16) - { - Parent = popup, - AnchorPreset = AnchorPresets.TopLeft, - MinValue = 1, - Width = 55, - }; - wValue.LocalY += 60; - wValue.LocalX += 100; - - var hValue = new IntValueBox(9) - { - Parent = popup, - AnchorPreset = AnchorPresets.TopLeft, - MinValue = 1, - Width = 55, - }; - hValue.LocalY += 60; - hValue.LocalX += 165; - - var submitButton = new Button - { - Parent = popup, - AnchorPreset = AnchorPresets.TopLeft, - Text = "Submit", - Width = 70, - }; - submitButton.LocalX += 40; - submitButton.LocalY += 90; - - submitButton.Clicked += () => - { - Enum.TryParse(typeDropdown.SelectedItem, out ViewportScaleType type); - - var combineString = type == ViewportScaleType.Aspect ? ":" : "x"; - var name = nameTextBox.Text + " (" + wValue.Value + combineString + hValue.Value + ") " + typeDropdown.SelectedItem; - - var newViewportOption = new ViewportScaleOptions - { - ScaleType = type, - Label = name, - Size = new Int2(wValue.Value, hValue.Value), - }; - - _customViewportScaling.Add(newViewportOption); - vsMenu.DisposeAllItems(); - CreateViewportSizingContextMenu(vsMenu); - vsMenu.PerformLayout(); - }; - - var cancelButton = new Button - { - Parent = popup, - AnchorPreset = AnchorPresets.TopLeft, - Text = "Cancel", - Width = 70, - }; - cancelButton.LocalX += 120; - cancelButton.LocalY += 90; - - cancelButton.Clicked += () => - { - nameTextBox.Clear(); - typeDropdown.SelectedItem = "Aspect"; - hValue.Value = 9; - wValue.Value = 16; - popup.Hide(); - }; - }; - } - /// public override void Draw() { @@ -1231,8 +929,8 @@ namespace FlaxEditor.Windows writer.WriteAttributeString("ShowGUI", ShowGUI.ToString()); writer.WriteAttributeString("EditGUI", EditGUI.ToString()); writer.WriteAttributeString("ShowDebugDraw", ShowDebugDraw.ToString()); - writer.WriteAttributeString("DefaultViewportScaling", JsonSerializer.Serialize(_defaultViewportScaling)); - writer.WriteAttributeString("CustomViewportScaling", JsonSerializer.Serialize(_customViewportScaling)); + writer.WriteAttributeString("DefaultViewportScalingIndex", _defaultScaleActiveIndex.ToString()); + writer.WriteAttributeString("CustomViewportScalingIndex", _customScaleActiveIndex.ToString()); } /// @@ -1244,22 +942,30 @@ namespace FlaxEditor.Windows EditGUI = value1; if (bool.TryParse(node.GetAttribute("ShowDebugDraw"), out value1)) ShowDebugDraw = value1; - if (node.HasAttribute("CustomViewportScaling")) - _customViewportScaling = JsonSerializer.Deserialize>(node.GetAttribute("CustomViewportScaling")); + if (int.TryParse(node.GetAttribute("DefaultViewportScalingIndex"), out int value2)) + _defaultScaleActiveIndex = value2; + if (int.TryParse(node.GetAttribute("CustomViewportScalingIndex"), out value2)) + _customScaleActiveIndex = value2; - for (int i = 0; i < _customViewportScaling.Count; i++) + if (_defaultScaleActiveIndex != -1) { - if (_customViewportScaling[i].Active) - ChangeViewportRatio(_customViewportScaling[i]); + var options = Editor.UI.DefaultViewportScaleOptions; + if (options.Count > _defaultScaleActiveIndex) + ChangeViewportRatio(options[_defaultScaleActiveIndex]); + else + _defaultScaleActiveIndex = 0; } - - if (node.HasAttribute("DefaultViewportScaling")) - _defaultViewportScaling = JsonSerializer.Deserialize>(node.GetAttribute("DefaultViewportScaling")); - - for (int i = 0; i < _defaultViewportScaling.Count; i++) + + if (_customScaleActiveIndex != -1) { - if (_defaultViewportScaling[i].Active) - ChangeViewportRatio(_defaultViewportScaling[i]); + var options = Editor.UI.CustomViewportScaleOptions; + if (options.Count > _customScaleActiveIndex) + ChangeViewportRatio(options[_customScaleActiveIndex]); + else + { + _defaultScaleActiveIndex = 0; + _customScaleActiveIndex = -1; + } } } diff --git a/Source/Editor/Windows/OutputLogWindow.cs b/Source/Editor/Windows/OutputLogWindow.cs index be6e6ff4d..75f8fb887 100644 --- a/Source/Editor/Windows/OutputLogWindow.cs +++ b/Source/Editor/Windows/OutputLogWindow.cs @@ -233,7 +233,10 @@ namespace FlaxEditor.Windows else cm.ClearItems(); + float longestItemWidth = 0.0f; + // Add items + var font = Style.Current.FontMedium; ItemsListContextMenu.Item lastItem = null; foreach (var command in commands) { @@ -244,7 +247,7 @@ namespace FlaxEditor.Windows }); var flags = DebugCommands.GetCommandFlags(command); if (flags.HasFlag(DebugCommands.CommandFlags.Exec)) - lastItem.TintColor = new Color(0.85f, 0.85f, 1.0f, 1.0f); + lastItem.TintColor = new Color(0.75f, 0.75f, 1.0f, 1.0f); else if (flags.HasFlag(DebugCommands.CommandFlags.Read) && !flags.HasFlag(DebugCommands.CommandFlags.Write)) lastItem.TintColor = new Color(0.85f, 0.85f, 0.85f, 1.0f); lastItem.Focused += item => @@ -252,6 +255,10 @@ namespace FlaxEditor.Windows // Set command Set(item.Name); }; + + float width = font.MeasureText(command).X; + if (width > longestItemWidth) + longestItemWidth = width; } cm.ItemClicked += item => { @@ -262,6 +269,10 @@ namespace FlaxEditor.Windows // Setup popup var count = commands.Count(); var totalHeight = count * lastItem.Height + cm.ItemsPanel.Margin.Height + cm.ItemsPanel.Spacing * (count - 1); + + // Account for scroll bars taking up a part of the width + longestItemWidth += 25f; + cm.Width = longestItemWidth; cm.Height = 220; if (cm.Height > totalHeight) cm.Height = totalHeight; // Limit popup height if list is small @@ -314,12 +325,25 @@ namespace FlaxEditor.Windows // Show commands search popup based on current text input var text = Text.Trim(); - if (text.Length != 0) + bool isWhitespaceOnly = string.IsNullOrWhiteSpace(Text) && !string.IsNullOrEmpty(Text); + if (text.Length != 0 || isWhitespaceOnly) { DebugCommands.Search(text, out var matches); - if (matches.Length != 0) + if (matches.Length != 0 || isWhitespaceOnly) { - ShowPopup(ref _searchPopup, matches, text); + string[] commands = []; + if (isWhitespaceOnly) + DebugCommands.GetAllCommands(out commands); + + ShowPopup(ref _searchPopup, isWhitespaceOnly ? commands : matches, text); + + if (isWhitespaceOnly) + { + // Scroll to and select first item for consistent behaviour + var firstItem = _searchPopup.ItemsPanel.Children[0] as Item; + _searchPopup.ScrollToAndHighlightItemByName(firstItem.Name); + } + return; } } diff --git a/Source/Engine/Animations/Graph/AnimGraph.Base.cpp b/Source/Engine/Animations/Graph/AnimGraph.Base.cpp index c49f0e26e..e4d7eda70 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.Base.cpp +++ b/Source/Engine/Animations/Graph/AnimGraph.Base.cpp @@ -93,6 +93,7 @@ void MultiBlendBucketInit(AnimGraphInstanceData::Bucket& bucket) void BlendPoseBucketInit(AnimGraphInstanceData::Bucket& bucket) { bucket.BlendPose.TransitionPosition = 0.0f; + bucket.BlendPose.BlendPoseIndex = -1; bucket.BlendPose.PreviousBlendPoseIndex = -1; } diff --git a/Source/Engine/Animations/Graph/AnimGraph.h b/Source/Engine/Animations/Graph/AnimGraph.h index e03d84fd9..051f6613d 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.h +++ b/Source/Engine/Animations/Graph/AnimGraph.h @@ -239,7 +239,8 @@ public: struct BlendPoseBucket { float TransitionPosition; - int32 PreviousBlendPoseIndex; + int16 BlendPoseIndex; + int16 PreviousBlendPoseIndex; }; struct StateMachineBucket @@ -810,6 +811,7 @@ public: { // Copy the node transformations Platform::MemoryCopy(dstNodes->Nodes.Get(), srcNodes->Nodes.Get(), sizeof(Transform) * _skeletonNodesCount); + dstNodes->RootMotion = srcNodes->RootMotion; // Copy the animation playback state dstNodes->Position = srcNodes->Position; diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp index f75a8abd1..c76bddf3f 100644 --- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp +++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp @@ -676,9 +676,12 @@ Variant AnimGraphExecutor::Blend(AnimGraphNode* node, const Value& poseA, const if (!ANIM_GRAPH_IS_VALID_PTR(poseB)) nodesB = GetEmptyNodes(); + const Transform* srcA = nodesA->Nodes.Get(); + const Transform* srcB = nodesB->Nodes.Get(); + Transform* dst = nodes->Nodes.Get(); for (int32 i = 0; i < nodes->Nodes.Count(); i++) { - Transform::Lerp(nodesA->Nodes[i], nodesB->Nodes[i], alpha, nodes->Nodes[i]); + Transform::Lerp(srcA[i], srcB[i], alpha, dst[i]); } Transform::Lerp(nodesA->RootMotion, nodesB->RootMotion, alpha, nodes->RootMotion); nodes->Position = Math::Lerp(nodesA->Position, nodesB->Position, alpha); @@ -1263,21 +1266,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu { const auto valueA = tryGetValue(node->GetBox(1), Value::Null); const auto valueB = tryGetValue(node->GetBox(2), Value::Null); - const auto nodes = node->GetNodes(this); - - auto nodesA = static_cast(valueA.AsPointer); - auto nodesB = static_cast(valueB.AsPointer); - if (!ANIM_GRAPH_IS_VALID_PTR(valueA)) - nodesA = GetEmptyNodes(); - if (!ANIM_GRAPH_IS_VALID_PTR(valueB)) - nodesB = GetEmptyNodes(); - - for (int32 i = 0; i < nodes->Nodes.Count(); i++) - { - Transform::Lerp(nodesA->Nodes[i], nodesB->Nodes[i], alpha, nodes->Nodes[i]); - } - Transform::Lerp(nodesA->RootMotion, nodesB->RootMotion, alpha, nodes->RootMotion); - value = nodes; + value = Blend(node, valueA, valueB, alpha, AlphaBlendMode::Linear); } break; @@ -1758,35 +1747,38 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu // [2]: int Pose Count // [3]: AlphaBlendMode Mode - // Prepare auto& bucket = context.Data->State[node->BucketIndex].BlendPose; - const int32 poseIndex = (int32)tryGetValue(node->GetBox(1), node->Values[0]); + const int16 poseIndex = (int32)tryGetValue(node->GetBox(1), node->Values[0]); const float blendDuration = (float)tryGetValue(node->GetBox(2), node->Values[1]); const int32 poseCount = Math::Clamp(node->Values[2].AsInt, 0, MaxBlendPoses); const AlphaBlendMode mode = (AlphaBlendMode)node->Values[3].AsInt; - - // Skip if nothing to blend if (poseCount == 0 || poseIndex < 0 || poseIndex >= poseCount) - { break; + + // Check if swap transition end points + if (bucket.PreviousBlendPoseIndex == poseIndex && bucket.BlendPoseIndex != poseIndex && bucket.TransitionPosition >= ANIM_GRAPH_BLEND_THRESHOLD) + { + bucket.TransitionPosition = blendDuration - bucket.TransitionPosition; + Swap(bucket.BlendPoseIndex, bucket.PreviousBlendPoseIndex); } // Check if transition is not active (first update, pose not changing or transition ended) bucket.TransitionPosition += context.DeltaTime; + bucket.BlendPoseIndex = poseIndex; if (bucket.PreviousBlendPoseIndex == -1 || bucket.PreviousBlendPoseIndex == poseIndex || bucket.TransitionPosition >= blendDuration || blendDuration <= ANIM_GRAPH_BLEND_THRESHOLD) { bucket.TransitionPosition = 0.0f; + bucket.BlendPoseIndex = poseIndex; bucket.PreviousBlendPoseIndex = poseIndex; - value = tryGetValue(node->GetBox(FirstBlendPoseBoxIndex + poseIndex), Value::Null); + value = tryGetValue(node->GetBox(FirstBlendPoseBoxIndex + bucket.BlendPoseIndex), Value::Null); break; } - ASSERT(bucket.PreviousBlendPoseIndex >= 0 && bucket.PreviousBlendPoseIndex < poseCount); // Blend two animations { const float alpha = bucket.TransitionPosition / blendDuration; const auto valueA = tryGetValue(node->GetBox(FirstBlendPoseBoxIndex + bucket.PreviousBlendPoseIndex), Value::Null); - const auto valueB = tryGetValue(node->GetBox(FirstBlendPoseBoxIndex + poseIndex), Value::Null); + const auto valueB = tryGetValue(node->GetBox(FirstBlendPoseBoxIndex + bucket.BlendPoseIndex), Value::Null); value = Blend(node, valueA, valueB, alpha, mode); } diff --git a/Source/Engine/Audio/AudioSource.cpp b/Source/Engine/Audio/AudioSource.cpp index cff89e7e1..f16f95423 100644 --- a/Source/Engine/Audio/AudioSource.cpp +++ b/Source/Engine/Audio/AudioSource.cpp @@ -167,8 +167,8 @@ void AudioSource::Play() } else { - // Source was nt properly added to the Audio Backend - LOG(Warning, "Cannot play unitialized audio source."); + // Source was not properly added to the Audio Backend + LOG(Warning, "Cannot play uninitialized audio source."); } } @@ -401,6 +401,9 @@ void AudioSource::Update() _startingToPlay = false; } + if (Math::NearEqual(GetTime(), _startTime) && _isActuallyPlayingSth && _startingToPlay) + ClipStarted(); + if (!UseStreaming() && Math::NearEqual(GetTime(), 0.0f) && _isActuallyPlayingSth && !_startingToPlay) { int32 queuedBuffers; @@ -416,6 +419,7 @@ void AudioSource::Update() { Stop(); } + ClipFinished(); } } @@ -486,6 +490,7 @@ void AudioSource::Update() { Stop(); } + ClipFinished(); } ASSERT(_streamingFirstChunk < clip->Buffers.Count()); diff --git a/Source/Engine/Audio/AudioSource.h b/Source/Engine/Audio/AudioSource.h index b83a2b408..9858d283a 100644 --- a/Source/Engine/Audio/AudioSource.h +++ b/Source/Engine/Audio/AudioSource.h @@ -76,6 +76,16 @@ public: API_FIELD(Attributes="EditorOrder(10), DefaultValue(null), EditorDisplay(\"Audio Source\")") AssetReference Clip; + /// + /// Event fired when the audio clip starts. + /// + API_EVENT() Action ClipStarted; + + /// + /// Event fired when the audio clip finishes. + /// + API_EVENT() Action ClipFinished; + /// /// Gets the velocity of the source. Determines pitch in relation to AudioListener's position. Only relevant for spatial (3D) sources. /// diff --git a/Source/Engine/Content/Assets/MaterialInstance.cpp b/Source/Engine/Content/Assets/MaterialInstance.cpp index 7d6c9d7fc..b3710ebbd 100644 --- a/Source/Engine/Content/Assets/MaterialInstance.cpp +++ b/Source/Engine/Content/Assets/MaterialInstance.cpp @@ -90,9 +90,11 @@ void MaterialInstance::OnBaseParamsChanged() // Get the newest parameters baseParams->Clone(Params); +#if 0 // Override all public parameters by default for (auto& param : Params) param.SetIsOverride(param.IsPublic()); +#endif // Copy previous parameters values for (int32 i = 0; i < oldParams.Count(); i++) diff --git a/Source/Engine/Content/Assets/Texture.cpp b/Source/Engine/Content/Assets/Texture.cpp index e31b40a25..4e3aec94d 100644 --- a/Source/Engine/Content/Assets/Texture.cpp +++ b/Source/Engine/Content/Assets/Texture.cpp @@ -36,7 +36,7 @@ bool Texture::Save(const StringView& path) bool Texture::Save(const StringView& path, const InitData* customData) { - if (OnCheckSave()) + if (OnCheckSave(path)) return true; ScopeLock lock(Locker); diff --git a/Source/Engine/ContentImporters/CreateMaterial.cpp b/Source/Engine/ContentImporters/CreateMaterial.cpp index 3991585a3..a41d09454 100644 --- a/Source/Engine/ContentImporters/CreateMaterial.cpp +++ b/Source/Engine/ContentImporters/CreateMaterial.cpp @@ -93,14 +93,15 @@ namespace }; template - void AddInput(MaterialLayer* layer, Meta11 meta, MaterialGraphBoxes box, const Guid& texture, const T& value, const T& defaultValue, const Float2& pos, ShaderGraphNode<>** outTextureNode = nullptr) + void AddInput(MaterialLayer* layer, Meta11 meta, MaterialGraphBoxes box, const Guid& texture, const T& value, const T& defaultValue, const Float2& pos, ShaderGraphNode<>** outTextureNode = nullptr, uint8 channel = MAX_uint8) { auto textureNode = AddTextureNode(layer, texture); auto valueNode = AddValueNode(layer, value, defaultValue); + auto textureNodeBox = channel == MAX_uint8 ? 1 : channel + 2; // Color or specific channel (RGBA) if (textureNode && valueNode) { auto diffuseMultiply = AddMultiplyNode(layer); - CONNECT(diffuseMultiply->Boxes[0], textureNode->Boxes[1]); + CONNECT(diffuseMultiply->Boxes[0], textureNode->Boxes[textureNodeBox]); CONNECT(diffuseMultiply->Boxes[1], valueNode->Boxes[0]); CONNECT(layer->Root->Boxes[static_cast(box)], diffuseMultiply->Boxes[2]); SET_POS(valueNode, pos + Float2(-467.7404, 91.41332)); @@ -109,7 +110,7 @@ namespace } else if (textureNode) { - CONNECT(layer->Root->Boxes[static_cast(box)], textureNode->Boxes[1]); + CONNECT(layer->Root->Boxes[static_cast(box)], textureNode->Boxes[textureNodeBox]); SET_POS(textureNode, pos + Float2(-293.5272f, -2.926111f)); } else if (valueNode) @@ -178,8 +179,9 @@ CreateAssetResult CreateMaterial::Create(CreateAssetContext& context) // Opacity AddInput(layer, meta, MaterialGraphBoxes::Opacity, options.Opacity.Texture, options.Opacity.Value, 1.0f, Float2(0, 400)); - // Opacity - AddInput(layer, meta, MaterialGraphBoxes::Roughness, options.Roughness.Texture, options.Roughness.Value, 0.5f, Float2(200, 400)); + // Roughness + Metalness + AddInput(layer, meta, MaterialGraphBoxes::Roughness, options.Roughness.Texture, options.Roughness.Value, 0.5f, Float2(200, 400), nullptr, options.Roughness.Channel); + AddInput(layer, meta, MaterialGraphBoxes::Metalness, options.Metalness.Texture, options.Metalness.Value, 0.0f, Float2(200, 600), nullptr, options.Metalness.Channel); // Normal auto normalMap = AddTextureNode(layer, options.Normals.Texture, true); diff --git a/Source/Engine/ContentImporters/CreateMaterial.h b/Source/Engine/ContentImporters/CreateMaterial.h index 6b37067e8..df0680e18 100644 --- a/Source/Engine/ContentImporters/CreateMaterial.h +++ b/Source/Engine/ContentImporters/CreateMaterial.h @@ -40,9 +40,17 @@ public: struct { float Value = 0.5f; + uint8 Channel = 0; Guid Texture = Guid::Empty; } Roughness; + struct + { + float Value = 0.0f; + uint8 Channel = 0; + Guid Texture = Guid::Empty; + } Metalness; + struct { Guid Texture = Guid::Empty; diff --git a/Source/Engine/ContentImporters/ImportModel.cpp b/Source/Engine/ContentImporters/ImportModel.cpp index be71236ab..91547dc8d 100644 --- a/Source/Engine/ContentImporters/ImportModel.cpp +++ b/Source/Engine/ContentImporters/ImportModel.cpp @@ -26,6 +26,7 @@ #include "Engine/Utilities/RectPack.h" #include "Engine/Scripting/Scripting.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Editor/Utilities/EditorUtilities.h" #include "AssetsImportingManager.h" bool ImportModel::TryGetImportOptions(const StringView& path, Options& options) @@ -281,13 +282,19 @@ CreateAssetResult ImportModel::Import(CreateAssetContext& context) // Import all of the objects recursive but use current model data to skip loading file again options.Cached = &cached; - Function splitImport = [&context, &autoImportOutput](Options& splitOptions, const StringView& objectName, String& outputPath, MeshData* meshData) + HashSet objectNames; + Function splitImport = [&context, &autoImportOutput, &objectNames](Options& splitOptions, const StringView& objectName, String& outputPath, MeshData* meshData) { // Recursive importing of the split object String postFix = objectName; const int32 splitPos = postFix.FindLast(TEXT('|')); - if (splitPos != -1) + if (splitPos != -1 && splitPos + 1 < postFix.Length()) postFix = postFix.Substring(splitPos + 1); + EditorUtilities::ValidatePathChars(postFix); // Ensure name is valid path + int32 duplicate = 0; + String postFixPre = postFix; + while (!objectNames.Add(postFix)) // Ensure name is unique + postFix = String::Format(TEXT("{} {}"), postFixPre, duplicate++); // TODO: check for name collisions with material/texture assets outputPath = autoImportOutput / String(StringUtils::GetFileNameWithoutExtension(context.TargetAssetPath)) + TEXT(" ") + postFix + TEXT(".flax"); splitOptions.SubAssetFolder = TEXT(" "); // Use the same folder as asset as they all are imported to the subdir for the prefab (see SubAssetFolder usage above) diff --git a/Source/Engine/Core/Math/BoundingFrustum.cpp b/Source/Engine/Core/Math/BoundingFrustum.cpp index e296fa1a8..e0697ce5e 100644 --- a/Source/Engine/Core/Math/BoundingFrustum.cpp +++ b/Source/Engine/Core/Math/BoundingFrustum.cpp @@ -63,9 +63,16 @@ void BoundingFrustum::SetMatrix(const Matrix& matrix) Plane BoundingFrustum::GetPlane(int32 index) const { - if (index > 5) - return Plane(); - return _planes[index]; + switch (index) + { + case 0: return _pLeft; + case 1: return _pRight; + case 2: return _pTop; + case 3: return _pBottom; + case 4: return _pNear; + case 5: return _pFar; + default: return Plane(); + } } static Vector3 Get3PlanesInterPoint(const Plane& p1, const Plane& p2, const Plane& p3) diff --git a/Source/Engine/Core/Math/BoundingFrustum.cs b/Source/Engine/Core/Math/BoundingFrustum.cs index 6f6e034ce..4f1e27e1e 100644 --- a/Source/Engine/Core/Math/BoundingFrustum.cs +++ b/Source/Engine/Core/Math/BoundingFrustum.cs @@ -182,7 +182,7 @@ namespace FlaxEngine /// /// Returns one of the 6 planes related to this frustum. /// - /// Plane index where 0 fro Left, 1 for Right, 2 for Top, 3 for Bottom, 4 for Near, 5 for Far + /// Plane index where 0 for Left, 1 for Right, 2 for Top, 3 for Bottom, 4 for Near, 5 for Far /// The frustum plane. public Plane GetPlane(int index) { diff --git a/Source/Engine/Core/Math/BoundingFrustum.h b/Source/Engine/Core/Math/BoundingFrustum.h index 30eb70f43..a3a5442e9 100644 --- a/Source/Engine/Core/Math/BoundingFrustum.h +++ b/Source/Engine/Core/Math/BoundingFrustum.h @@ -148,13 +148,13 @@ public: Plane GetPlane(int32 index) const; /// - /// Gets the the 8 corners of the frustum: Near1 (near right down corner), Near2 (near right top corner), Near3 (near Left top corner), Near4 (near Left down corner), Far1 (far right down corner), Far2 (far right top corner), Far3 (far left top corner), Far4 (far left down corner). + /// Gets the 8 corners of the frustum: Near1 (near right down corner), Near2 (near right top corner), Near3 (near Left top corner), Near4 (near Left down corner), Far1 (far right down corner), Far2 (far right top corner), Far3 (far left top corner), Far4 (far left down corner). /// /// The corners. void GetCorners(Float3 corners[8]) const; /// - /// Gets the the 8 corners of the frustum: Near1 (near right down corner), Near2 (near right top corner), Near3 (near Left top corner), Near4 (near Left down corner), Far1 (far right down corner), Far2 (far right top corner), Far3 (far left top corner), Far4 (far left down corner). + /// Gets the 8 corners of the frustum: Near1 (near right down corner), Near2 (near right top corner), Near3 (near Left top corner), Near4 (near Left down corner), Far1 (far right down corner), Far2 (far right top corner), Far3 (far left top corner), Far4 (far left down corner). /// /// The corners. void GetCorners(Double3 corners[8]) const; diff --git a/Source/Engine/Debug/DebugCommands.cpp b/Source/Engine/Debug/DebugCommands.cpp index 5d94cf557..fc568a181 100644 --- a/Source/Engine/Debug/DebugCommands.cpp +++ b/Source/Engine/Debug/DebugCommands.cpp @@ -435,6 +435,14 @@ void DebugCommands::InitAsync() AsyncTask = Task::StartNew(InitCommands); } +void DebugCommands::GetAllCommands(Array& commands) +{ + EnsureInited(); + ScopeLock lock(Locker); + for (const auto& command : Commands) + commands.Add(command.Name); +} + DebugCommands::CommandFlags DebugCommands::GetCommandFlags(StringView command) { CommandFlags result = CommandFlags::None; diff --git a/Source/Engine/Debug/DebugCommands.h b/Source/Engine/Debug/DebugCommands.h index 73b2def69..f25fe0581 100644 --- a/Source/Engine/Debug/DebugCommands.h +++ b/Source/Engine/Debug/DebugCommands.h @@ -46,6 +46,12 @@ public: /// API_FUNCTION() static void InitAsync(); + /// + /// Gets all available commands. + /// + /// The output list of all commands (unsorted). + API_FUNCTION() static void GetAllCommands(API_PARAM(Out) Array& commands); + /// /// Returns flags of the command. /// diff --git a/Source/Engine/Graphics/Materials/IMaterial.h b/Source/Engine/Graphics/Materials/IMaterial.h index 23d06589b..cd2b7a1e8 100644 --- a/Source/Engine/Graphics/Materials/IMaterial.h +++ b/Source/Engine/Graphics/Materials/IMaterial.h @@ -156,8 +156,8 @@ public: /// GPUTextureView* Input = nullptr; - BindParameters(::GPUContext* context, const ::RenderContext& renderContext); - BindParameters(::GPUContext* context, const ::RenderContext& renderContext, const ::DrawCall& drawCall, bool instanced = false); + FLAXENGINE_API BindParameters(::GPUContext* context, const ::RenderContext& renderContext); + FLAXENGINE_API BindParameters(::GPUContext* context, const ::RenderContext& renderContext, const ::DrawCall& drawCall, bool instanced = false); // Per-view shared constant buffer (see ViewData in MaterialCommon.hlsl). static GPUConstantBuffer* PerViewConstants; diff --git a/Source/Engine/Graphics/Materials/MaterialShader.h b/Source/Engine/Graphics/Materials/MaterialShader.h index aedf2e870..117246671 100644 --- a/Source/Engine/Graphics/Materials/MaterialShader.h +++ b/Source/Engine/Graphics/Materials/MaterialShader.h @@ -10,7 +10,7 @@ /// /// Current materials shader version. /// -#define MATERIAL_GRAPH_VERSION 174 +#define MATERIAL_GRAPH_VERSION 175 class Material; class GPUShader; diff --git a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp index 0dae6c93e..a140fb577 100644 --- a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp +++ b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp @@ -2,6 +2,7 @@ #include "MaterialShaderFeatures.h" #include "Engine/Graphics/RenderTask.h" +#include "Engine/Graphics/RenderBuffers.h" #include "Engine/Graphics/Textures/GPUTexture.h" #include "Engine/Renderer/RenderList.h" #include "Engine/Renderer/ShadowsPass.h" @@ -24,18 +25,27 @@ void ForwardShadingFeature::Bind(MaterialShader::BindParameters& params, SpanFog) { cache->Fog->GetExponentialHeightFogData(view, data.ExponentialHeightFog); + VolumetricFogOptions volumetricFog; + cache->Fog->GetVolumetricFogOptions(volumetricFog); + if (volumetricFog.UseVolumetricFog() && params.RenderContext.Buffers->VolumetricFog) + volumetricFogTexture = params.RenderContext.Buffers->VolumetricFog->ViewVolume(); + else + data.ExponentialHeightFog.VolumetricFogMaxDistance = -1.0f; } else { data.ExponentialHeightFog.FogMinOpacity = 1.0f; data.ExponentialHeightFog.ApplyDirectionalInscattering = 0.0f; } + params.GPUContext->BindSR(volumetricFogTextureRegisterIndex, volumetricFogTexture); // Set directional light input if (cache->DirectionalLights.HasItems()) diff --git a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.h b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.h index 25689e765..54b91af23 100644 --- a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.h +++ b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.h @@ -25,7 +25,7 @@ struct ForwardShadingFeature : MaterialShaderFeature { enum { MaxLocalLights = 4 }; - enum { SRVs = 4 }; + enum { SRVs = 5 }; PACK_STRUCT(struct Data { diff --git a/Source/Engine/Graphics/Models/ModelData.cpp b/Source/Engine/Graphics/Models/ModelData.cpp index 877d8c826..69ae5d9a1 100644 --- a/Source/Engine/Graphics/Models/ModelData.cpp +++ b/Source/Engine/Graphics/Models/ModelData.cpp @@ -372,6 +372,8 @@ bool MaterialSlotEntry::UsesProperties() const Opacity.TextureIndex != -1 || Math::NotNearEqual(Roughness.Value, 0.5f) || Roughness.TextureIndex != -1 || + Math::NotNearEqual(Metalness.Value, 0.5f) || + Metalness.TextureIndex != -1 || Normals.TextureIndex != -1; } diff --git a/Source/Engine/Graphics/Models/ModelData.h b/Source/Engine/Graphics/Models/ModelData.h index 8e401b973..d9167c858 100644 --- a/Source/Engine/Graphics/Models/ModelData.h +++ b/Source/Engine/Graphics/Models/ModelData.h @@ -327,14 +327,23 @@ struct FLAXENGINE_API MaterialSlotEntry { float Value = 0.5f; int32 TextureIndex = -1; + uint8 Channel = 0; } Roughness; + struct + { + float Value = 0.0f; + int32 TextureIndex = -1; + uint8 Channel = 0; + } Metalness; + struct { int32 TextureIndex = -1; } Normals; bool TwoSided = false; + bool Wireframe = false; bool UsesProperties() const; static float ShininessToRoughness(float shininess); diff --git a/Source/Engine/Graphics/RenderView.h b/Source/Engine/Graphics/RenderView.h index e7b60491c..a85480e3e 100644 --- a/Source/Engine/Graphics/RenderView.h +++ b/Source/Engine/Graphics/RenderView.h @@ -265,6 +265,14 @@ public: return Projection.M44 >= 1.0f; } + /// + /// Determines whether view Origin has been moved in this frame. Old history buffers/data might be invalid. + /// + FORCE_INLINE bool IsOriginTeleport() const + { + return Origin != PrevOrigin; + } + public: // Ignore deprecation warnings in defaults PRAGMA_DISABLE_DEPRECATION_WARNINGS diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp index 0c0a98e14..b119797e7 100644 --- a/Source/Engine/Level/Actor.cpp +++ b/Source/Engine/Level/Actor.cpp @@ -1124,9 +1124,10 @@ void Actor::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) } else if (!parent && parentId.IsValid()) { + Guid tmpId; if (_prefabObjectID.IsValid()) LOG(Warning, "Missing parent actor {0} for \'{1}\', prefab object {2}", parentId, ToString(), _prefabObjectID); - else + else if (!modifier->IdsMapping.TryGet(parentId, tmpId) || tmpId.IsValid()) // Skip warning if object was mapped to empty id (intentionally ignored) LOG(Warning, "Missing parent actor {0} for \'{1}\'", parentId, ToString()); } } @@ -1694,7 +1695,7 @@ bool Actor::ToBytes(const Array& actors, MemoryWriteStream& output) } // Collect object ids that exist in the serialized data to allow references mapping later - Array ids(Math::RoundUpToPowerOf2(actors.Count() * 2)); + Array ids(actors.Count()); for (int32 i = 0; i < actors.Count(); i++) { // By default we collect actors and scripts (they are ManagedObjects recognized by the id) @@ -1997,19 +1998,27 @@ Actor* Actor::Clone() // Remap object ids into a new ones auto modifier = Cache::ISerializeModifier.Get(); - for (int32 i = 0; i < actors->Count(); i++) + for (const Actor* actor : *actors.Value) { - auto actor = actors->At(i); if (!actor) continue; modifier->IdsMapping.Add(actor->GetID(), Guid::New()); - for (int32 j = 0; j < actor->Scripts.Count(); j++) + for (const Script* script : actor->Scripts) { - const auto script = actor->Scripts[j]; if (script) modifier->IdsMapping.Add(script->GetID(), Guid::New()); } } + if (HasPrefabLink() && HasParent() && !IsPrefabRoot()) + { + // When cloning actor that is part of prefab (but not as whole), ignore the prefab hierarchy + Actor* parent = GetParent(); + do + { + modifier->IdsMapping.Add(parent->GetPrefabObjectID(), Guid::Empty); + parent = parent->GetParent(); + } while (parent && !parent->IsPrefabRoot()); + } // Deserialize objects Array output; diff --git a/Source/Engine/Level/Actors/Light.h b/Source/Engine/Level/Actors/Light.h index 08d311b6d..0e2a441db 100644 --- a/Source/Engine/Level/Actors/Light.h +++ b/Source/Engine/Level/Actors/Light.h @@ -63,7 +63,8 @@ protected: { const float dst2 = (float)Vector3::DistanceSquared(viewPosition, position); const float dst = Math::Sqrt(dst2); - brightness *= Math::Remap(dst, 0.9f * ViewDistance, ViewDistance, 1.0f, 0.0f); + if (dst < ViewDistance && dst > ViewDistance * 0.9f) + brightness *= Math::Remap(dst, 0.9f * ViewDistance, ViewDistance, 1.0f, 0.0f); return dst < ViewDistance; } return true; diff --git a/Source/Engine/Level/Actors/Sky.cpp b/Source/Engine/Level/Actors/Sky.cpp index 4e635489f..6d7d5efe2 100644 --- a/Source/Engine/Level/Actors/Sky.cpp +++ b/Source/Engine/Level/Actors/Sky.cpp @@ -21,7 +21,8 @@ #endif GPU_CB_STRUCT(Data { - Matrix WVP; + Matrix WorldViewProjection; + Matrix InvViewProjection; Float3 ViewOffset; float Padding; ShaderGBufferData GBuffer; @@ -30,9 +31,6 @@ GPU_CB_STRUCT(Data { Sky::Sky(const SpawnParams& params) : Actor(params) - , _shader(nullptr) - , _psSky(nullptr) - , _psFog(nullptr) { _drawNoCulling = 1; _drawCategory = SceneRendering::PreRender; @@ -52,7 +50,6 @@ Sky::Sky(const SpawnParams& params) Sky::~Sky() { SAFE_DELETE_GPU_RESOURCE(_psSky); - SAFE_DELETE_GPU_RESOURCE(_psFog); } void Sky::InitConfig(ShaderAtmosphericFogData& config) const @@ -91,7 +88,7 @@ void Sky::Draw(RenderContext& renderContext) if (HasContentLoaded() && EnumHasAnyFlags(renderContext.View.Flags, ViewFlags::Sky)) { // Ensure to have pipeline state cache created - if (_psSky == nullptr || _psFog == nullptr) + if (_psSky == nullptr) { const auto shader = _shader->GetShader(); @@ -113,21 +110,6 @@ void Sky::Draw(RenderContext& renderContext) LOG(Warning, "Cannot create graphics pipeline state object for '{0}'.", ToString()); } } - if (_psFog == nullptr) - { - _psFog = GPUDevice::Instance->CreatePipelineState(); - - GPUPipelineState::Description psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle; - psDesc.PS = shader->GetPS("PS_Fog"); - psDesc.DepthWriteEnable = false; - psDesc.DepthClipEnable = false; - psDesc.BlendMode = BlendingMode::Additive; - - if (_psFog->Init(psDesc)) - { - LOG(Warning, "Cannot create graphics pipeline state object for '{0}'.", ToString()); - } - } } // Register for the sky and fog pass @@ -139,7 +121,6 @@ void Sky::Draw(RenderContext& renderContext) void Sky::Serialize(SerializeStream& stream, const void* otherObj) { - // Base Actor::Serialize(stream, otherObj); SERIALIZE_GET_OTHER_OBJ(Sky); @@ -152,7 +133,6 @@ void Sky::Serialize(SerializeStream& stream, const void* otherObj) void Sky::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) { - // Base Actor::Deserialize(stream, modifier); DESERIALIZE_MEMBER(Sun, SunLight); @@ -173,39 +153,7 @@ bool Sky::IntersectsItself(const Ray& ray, Real& distance, Vector3& normal) void Sky::DrawFog(GPUContext* context, RenderContext& renderContext, GPUTextureView* output) { - // Get precomputed cache and bind it to the pipeline - AtmosphereCache cache; - if (!AtmospherePreCompute::GetCache(&cache)) - return; - context->BindSR(4, cache.Transmittance); - context->BindSR(5, cache.Irradiance); - context->BindSR(6, cache.Inscatter->ViewVolume()); - - // Bind GBuffer inputs - context->BindSR(0, renderContext.Buffers->GBuffer0); - context->BindSR(1, renderContext.Buffers->GBuffer1); - context->BindSR(2, renderContext.Buffers->GBuffer2); - context->BindSR(3, renderContext.Buffers->DepthBuffer); - - // Setup constants data - Data data; - GBufferPass::SetInputs(renderContext.View, data.GBuffer); - data.ViewOffset = renderContext.View.Origin + GetPosition(); - InitConfig(data.Fog); - data.Fog.AtmosphericFogSunPower *= SunLight ? SunLight->Brightness : 1.0f; - bool useSpecularLight = EnumHasAnyFlags(renderContext.View.Flags, ViewFlags::SpecularLight); - if (!useSpecularLight) - { - data.Fog.AtmosphericFogSunDiscScale = 0; - } - - // Bind pipeline - auto cb = _shader->GetShader()->GetCB(0); - context->UpdateCB(cb, &data); - context->BindCB(0, cb); - context->SetState(_psFog); - context->SetRenderTarget(output); - context->DrawFullscreenTriangle(); + MISSING_CODE("sky fog"); } bool Sky::IsDynamicSky() const @@ -231,14 +179,14 @@ void Sky::ApplySky(GPUContext* context, RenderContext& renderContext, const Matr // Setup constants data Matrix m; Data data; - Matrix::Multiply(world, renderContext.View.Frustum.GetMatrix(), m); - Matrix::Transpose(m, data.WVP); + Matrix::Multiply(world, renderContext.View.ViewProjection(), m); + Matrix::Transpose(m, data.WorldViewProjection); + Matrix::Transpose(renderContext.View.IVP, data.InvViewProjection); GBufferPass::SetInputs(renderContext.View, data.GBuffer); data.ViewOffset = renderContext.View.Origin + GetPosition(); InitConfig(data.Fog); //data.Fog.AtmosphericFogSunPower *= SunLight ? SunLight->Brightness : 1.0f; - bool useSpecularLight = EnumHasAnyFlags(renderContext.View.Flags, ViewFlags::SpecularLight); - if (!useSpecularLight) + if (EnumHasNoneFlags(renderContext.View.Flags, ViewFlags::SpecularLight)) { // Hide sun disc if specular light is disabled data.Fog.AtmosphericFogSunDiscScale = 0; @@ -253,11 +201,8 @@ void Sky::ApplySky(GPUContext* context, RenderContext& renderContext, const Matr void Sky::EndPlay() { - // Cleanup SAFE_DELETE_GPU_RESOURCE(_psSky); - SAFE_DELETE_GPU_RESOURCE(_psFog); - // Base Actor::EndPlay(); } @@ -268,7 +213,6 @@ void Sky::OnEnable() GetSceneRendering()->AddViewportIcon(this); #endif - // Base Actor::OnEnable(); } @@ -279,13 +223,11 @@ void Sky::OnDisable() #endif GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); - // Base Actor::OnDisable(); } void Sky::OnTransformChanged() { - // Base Actor::OnTransformChanged(); _box = BoundingBox(_transform.Translation); diff --git a/Source/Engine/Level/Actors/Sky.h b/Source/Engine/Level/Actors/Sky.h index 65bcc631a..61bb048ef 100644 --- a/Source/Engine/Level/Actors/Sky.h +++ b/Source/Engine/Level/Actors/Sky.h @@ -20,8 +20,7 @@ class FLAXENGINE_API Sky : public Actor, public IAtmosphericFogRenderer, public DECLARE_SCENE_OBJECT(Sky); private: AssetReference _shader; - GPUPipelineState* _psSky; - GPUPipelineState* _psFog; + GPUPipelineState* _psSky = nullptr; int32 _sceneRenderingKey = -1; public: @@ -57,7 +56,6 @@ private: void OnShaderReloading(Asset* obj) { _psSky = nullptr; - _psFog = nullptr; } #endif void InitConfig(ShaderAtmosphericFogData& config) const; diff --git a/Source/Engine/Level/Actors/Skybox.cpp b/Source/Engine/Level/Actors/Skybox.cpp index 6bfc98723..e08d8a61a 100644 --- a/Source/Engine/Level/Actors/Skybox.cpp +++ b/Source/Engine/Level/Actors/Skybox.cpp @@ -129,6 +129,7 @@ void Skybox::ApplySky(GPUContext* context, RenderContext& renderContext, const M material->SetParameterValue(TEXT("PanoramicTexture"), PanoramicTexture.Get(), false); material->SetParameterValue(TEXT("Color"), Color * Math::Exp2(Exposure), false); material->SetParameterValue(TEXT("IsPanoramic"), PanoramicTexture != nullptr, false); + material->SetParameterValue(TEXT("RotationEuler"), _rotationEuler, false); material->Bind(bindParams); } } @@ -163,4 +164,5 @@ void Skybox::OnTransformChanged() _box = BoundingBox(_transform.Translation); _sphere = BoundingSphere(_transform.Translation, 0.0f); + _rotationEuler = GetOrientation().GetEuler() * DegreesToRadians; } diff --git a/Source/Engine/Level/Actors/Skybox.h b/Source/Engine/Level/Actors/Skybox.h index f571a211f..fb831df44 100644 --- a/Source/Engine/Level/Actors/Skybox.h +++ b/Source/Engine/Level/Actors/Skybox.h @@ -18,6 +18,7 @@ class FLAXENGINE_API Skybox : public Actor, public ISkyRenderer private: AssetReference _proxyMaterial; int32 _sceneRenderingKey = -1; + Float3 _rotationEuler; public: /// diff --git a/Source/Engine/Physics/Actors/IPhysicsDebug.h b/Source/Engine/Physics/Actors/IPhysicsDebug.h index cedd3d822..57e9a97e3 100644 --- a/Source/Engine/Physics/Actors/IPhysicsDebug.h +++ b/Source/Engine/Physics/Actors/IPhysicsDebug.h @@ -12,7 +12,7 @@ public: { } }; -#define ImplementPhysicsDebug void DrawPhysicsDebug(RenderView& view) +#define ImplementPhysicsDebug void DrawPhysicsDebug(RenderView& view) override #else #define ImplementPhysicsDebug #endif diff --git a/Source/Engine/Physics/Actors/RigidBody.cpp b/Source/Engine/Physics/Actors/RigidBody.cpp index a58911dfb..062264b58 100644 --- a/Source/Engine/Physics/Actors/RigidBody.cpp +++ b/Source/Engine/Physics/Actors/RigidBody.cpp @@ -159,9 +159,10 @@ void RigidBody::SetCenterOfMassOffset(const Float3& value) { if (value == _centerOfMassOffset) return; + Float3 delta = value - _centerOfMassOffset; _centerOfMassOffset = value; if (_actor) - PhysicsBackend::SetRigidDynamicActorCenterOfMassOffset(_actor, _centerOfMassOffset); + PhysicsBackend::AddRigidDynamicActorCenterOfMassOffset(_actor, delta); } void RigidBody::SetConstraints(const RigidbodyConstraints value) @@ -338,6 +339,32 @@ void RigidBody::AddMovement(const Vector3& translation, const Quaternion& rotati SetTransform(t); } +#if USE_EDITOR + +#include "Engine/Debug/DebugDraw.h" + +void RigidBody::OnDebugDrawSelected() +{ + // Draw center of mass + if (!_centerOfMassOffset.IsZero()) + { + DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(GetPosition() + (GetOrientation() * (GetCenterOfMass() - GetCenterOfMassOffset())), 5.0f), Color::Red, 0, false); + } + DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(GetPosition() + (GetOrientation() * GetCenterOfMass()), 2.5f), Color::Aqua, 0, false); + + // Draw all attached colliders + for (Actor* child : Children) + { + const auto collider = Cast(child); + if (collider && collider->GetAttachedRigidBody() == this) + collider->OnDebugDrawSelf(); + } + + Actor::OnDebugDrawSelected(); +} + +#endif + void RigidBody::OnCollisionEnter(const Collision& c) { CollisionEnter(c); @@ -506,7 +533,7 @@ void RigidBody::BeginPlay(SceneBeginData* data) // Apply the Center Of Mass offset if (!_centerOfMassOffset.IsZero()) - PhysicsBackend::SetRigidDynamicActorCenterOfMassOffset(_actor, _centerOfMassOffset); + PhysicsBackend::AddRigidDynamicActorCenterOfMassOffset(_actor, _centerOfMassOffset); // Register actor PhysicsBackend::AddSceneActor(scene, _actor); diff --git a/Source/Engine/Physics/Actors/RigidBody.h b/Source/Engine/Physics/Actors/RigidBody.h index f9f36edf9..82599ebb8 100644 --- a/Source/Engine/Physics/Actors/RigidBody.h +++ b/Source/Engine/Physics/Actors/RigidBody.h @@ -487,6 +487,9 @@ public: void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; void AddMovement(const Vector3& translation, const Quaternion& rotation) override; +#if USE_EDITOR + void OnDebugDrawSelected() override; +#endif // [IPhysicsActor] void* GetPhysicsActor() const override; diff --git a/Source/Engine/Physics/Actors/WheeledVehicle.cpp b/Source/Engine/Physics/Actors/WheeledVehicle.cpp index 35f78f39f..fadc5fe19 100644 --- a/Source/Engine/Physics/Actors/WheeledVehicle.cpp +++ b/Source/Engine/Physics/Actors/WheeledVehicle.cpp @@ -201,6 +201,11 @@ void WheeledVehicle::SetSteering(float value) _steering = Math::Clamp(value, -1.0f, 1.0f); } +float WheeledVehicle::GetSteering() +{ + return _steering; +} + void WheeledVehicle::SetBrake(float value) { value = Math::Saturate(value); @@ -209,11 +214,21 @@ void WheeledVehicle::SetBrake(float value) _tankRightBrake = value; } +float WheeledVehicle::GetBrake() +{ + return _brake; +} + void WheeledVehicle::SetHandbrake(float value) { _handBrake = Math::Saturate(value); } +float WheeledVehicle::GetHandbrake() +{ + return _handBrake; +} + void WheeledVehicle::SetTankLeftThrottle(float value) { _tankLeftThrottle = Math::Clamp(value, -1.0f, 1.0f); @@ -387,6 +402,7 @@ void WheeledVehicle::DrawPhysicsDebug(RenderView& view) void WheeledVehicle::OnDebugDrawSelected() { // Wheels shapes + int32 wheelIndex = 0; for (const auto& wheel : _wheels) { if (wheel.Collider && wheel.Collider->GetParent() == this && !wheel.Collider->GetIsTrigger()) @@ -423,14 +439,20 @@ void WheeledVehicle::OnDebugDrawSelected() { DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(data.State.TireContactPoint, 5.0f), Color::Green, 0, false); } + if (ShowDebugDrawWheelNames) + { + const String text = String::Format(TEXT("Index: {}\nCollider: {}"), wheelIndex, wheel.Collider->GetName()); + DEBUG_DRAW_TEXT(text, currentPos, Color::Blue, 10, 0); + } } + wheelIndex++; } // Anti roll bars axes const int32 wheelsCount = _wheels.Count(); - for (int32 i = 0; i < GetAntiRollBars().Count(); i++) + for (int32 i = 0; i < _antiRollBars.Count(); i++) { - const int32 axleIndex = GetAntiRollBars()[i].Axle; + const int32 axleIndex = _antiRollBars[i].Axle; const int32 leftWheelIndex = axleIndex * 2; const int32 rightWheelIndex = leftWheelIndex + 1; if (leftWheelIndex >= wheelsCount || rightWheelIndex >= wheelsCount) diff --git a/Source/Engine/Physics/Actors/WheeledVehicle.h b/Source/Engine/Physics/Actors/WheeledVehicle.h index 390c0e24a..4b5e9f058 100644 --- a/Source/Engine/Physics/Actors/WheeledVehicle.h +++ b/Source/Engine/Physics/Actors/WheeledVehicle.h @@ -468,10 +468,18 @@ public: API_FIELD(Attributes="EditorOrder(1), EditorDisplay(\"Vehicle\")") bool UseAnalogSteering = false; +#if USE_EDITOR + /// + /// If checked, will draw wheel names and indices at the position of their colliders. + /// + API_FIELD(Attributes="EditorOrder(2), EditorDisplay(\"Vehicle\")") + bool ShowDebugDrawWheelNames = false; +#endif + /// /// Gets the vehicle driving model type. /// - API_PROPERTY(Attributes="EditorOrder(2), EditorDisplay(\"Vehicle\")") DriveTypes GetDriveType() const; + API_PROPERTY(Attributes="EditorOrder(4), EditorDisplay(\"Vehicle\")") DriveTypes GetDriveType() const; /// /// Sets the vehicle driving model type. @@ -481,12 +489,12 @@ public: /// /// Gets the vehicle wheels settings. /// - API_PROPERTY(Attributes="EditorOrder(4), EditorDisplay(\"Vehicle\")") const Array& GetWheels() const; + API_PROPERTY(Attributes="EditorOrder(5), EditorDisplay(\"Vehicle\")") const Array& GetWheels() const; /// /// Gets the vehicle drive control settings. /// - API_PROPERTY(Attributes="EditorOrder(5), EditorDisplay(\"Vehicle\")") DriveControlSettings GetDriveControl() const; + API_PROPERTY(Attributes="EditorOrder(6), EditorDisplay(\"Vehicle\")") DriveControlSettings GetDriveControl() const; /// /// Sets the vehicle drive control settings. @@ -501,7 +509,7 @@ public: /// /// Gets the vehicle engine settings. /// - API_PROPERTY(Attributes="EditorOrder(6), EditorDisplay(\"Vehicle\")") EngineSettings GetEngine() const; + API_PROPERTY(Attributes="EditorOrder(7), EditorDisplay(\"Vehicle\")") EngineSettings GetEngine() const; /// /// Sets the vehicle engine settings. @@ -511,7 +519,7 @@ public: /// /// Gets the vehicle differential settings. /// - API_PROPERTY(Attributes="EditorOrder(7), EditorDisplay(\"Vehicle\")") DifferentialSettings GetDifferential() const; + API_PROPERTY(Attributes="EditorOrder(8), EditorDisplay(\"Vehicle\")") DifferentialSettings GetDifferential() const; /// /// Sets the vehicle differential settings. @@ -521,7 +529,7 @@ public: /// /// Gets the vehicle gearbox settings. /// - API_PROPERTY(Attributes="EditorOrder(8), EditorDisplay(\"Vehicle\")") GearboxSettings GetGearbox() const; + API_PROPERTY(Attributes="EditorOrder(9), EditorDisplay(\"Vehicle\")") GearboxSettings GetGearbox() const; /// /// Sets the vehicle gearbox settings. @@ -531,7 +539,7 @@ public: // /// Sets axles anti roll bars to increase vehicle stability. /// - API_PROPERTY() void SetAntiRollBars(const Array& value); + API_PROPERTY(Attributes="EditorOrder(10), EditorDisplay(\"Vehicle\")") void SetAntiRollBars(const Array& value); // /// Gets axles anti roll bars. @@ -557,18 +565,36 @@ public: /// The value (-1,1 range). API_FUNCTION() void SetSteering(float value); + /// + /// Gets the vehicle steering. Steer is the analog steer value in range (-1,1) where -1 represents the steering wheel at left lock and +1 represents the steering wheel at right lock. + /// + /// The vehicle steering. + API_FUNCTION() float GetSteering(); + /// /// Sets the input for vehicle brakes. Brake is the analog brake pedal value in range (0,1) where 1 represents the pedal fully pressed and 0 represents the pedal in its rest state. /// /// The value (0,1 range). API_FUNCTION() void SetBrake(float value); + /// + /// Gets the vehicle brakes. Brake is the analog brake pedal value in range (0,1) where 1 represents the pedal fully pressed and 0 represents the pedal in its rest state. + /// + /// The vehicle brake. + API_FUNCTION() float GetBrake(); + /// /// Sets the input for vehicle handbrake. Handbrake is the analog handbrake value in range (0,1) where 1 represents the handbrake fully engaged and 0 represents the handbrake in its rest state. /// /// The value (0,1 range). API_FUNCTION() void SetHandbrake(float value); + /// + /// Gets the vehicle handbrake. Handbrake is the analog handbrake value in range (0,1) where 1 represents the handbrake fully engaged and 0 represents the handbrake in its rest state. + /// + /// The vehicle handbrake. + API_FUNCTION() float GetHandbrake(); + /// /// Sets the input for tank left track throttle. It is the analog accelerator pedal value in range (-1,1) where 1 represents the pedal fully pressed to move to forward, 0 to represents the /// pedal in its rest state and -1 represents the pedal fully pressed to move to backward. The track direction will be inverted if the vehicle current gear is rear. diff --git a/Source/Engine/Physics/Colliders/BoxCollider.cpp b/Source/Engine/Physics/Colliders/BoxCollider.cpp index 160120f87..1e90cb91f 100644 --- a/Source/Engine/Physics/Colliders/BoxCollider.cpp +++ b/Source/Engine/Physics/Colliders/BoxCollider.cpp @@ -86,7 +86,7 @@ void BoxCollider::OnDebugDraw() namespace { - OrientedBoundingBox GetWriteBox(const Vector3& min, const Vector3& max, const float margin) + OrientedBoundingBox GetWireBox(const Vector3& min, const Vector3& max, const float margin) { OrientedBoundingBox box; const Vector3 vec = max - min; @@ -111,11 +111,40 @@ namespace } } -void BoxCollider::OnDebugDrawSelected() +void BoxCollider::OnDebugDrawSelf() { const Color color = Color::GreenYellow; DEBUG_DRAW_WIRE_BOX(_bounds, color * 0.3f, 0, false); + Vector3 corners[8]; + _bounds.GetCorners(corners); + const float margin = Math::Min(1.0f, (float)_bounds.GetSize().MinValue() * 0.01f); + const Color wiresColor = color.AlphaMultiplied(0.6f); + if (margin > 0.05f) + { + DEBUG_DRAW_BOX(GetWireBox(corners[0], corners[1], margin), wiresColor, 0, true); + DEBUG_DRAW_BOX(GetWireBox(corners[0], corners[3], margin), wiresColor, 0, true); + DEBUG_DRAW_BOX(GetWireBox(corners[0], corners[4], margin), wiresColor, 0, true); + DEBUG_DRAW_BOX(GetWireBox(corners[1], corners[2], margin), wiresColor, 0, true); + DEBUG_DRAW_BOX(GetWireBox(corners[1], corners[5], margin), wiresColor, 0, true); + DEBUG_DRAW_BOX(GetWireBox(corners[2], corners[3], margin), wiresColor, 0, true); + DEBUG_DRAW_BOX(GetWireBox(corners[2], corners[6], margin), wiresColor, 0, true); + DEBUG_DRAW_BOX(GetWireBox(corners[3], corners[7], margin), wiresColor, 0, true); + DEBUG_DRAW_BOX(GetWireBox(corners[4], corners[5], margin), wiresColor, 0, true); + DEBUG_DRAW_BOX(GetWireBox(corners[4], corners[7], margin), wiresColor, 0, true); + DEBUG_DRAW_BOX(GetWireBox(corners[5], corners[6], margin), wiresColor, 0, true); + DEBUG_DRAW_BOX(GetWireBox(corners[6], corners[7], margin), wiresColor, 0, true); + } + else + { + DEBUG_DRAW_WIRE_BOX(_bounds, wiresColor, 0, true); + } +} + +void BoxCollider::OnDebugDrawSelected() +{ + OnDebugDrawSelf(); + if (_contactOffset > 0) { OrientedBoundingBox contactBounds = _bounds; @@ -123,24 +152,6 @@ void BoxCollider::OnDebugDrawSelected() DEBUG_DRAW_WIRE_BOX(contactBounds, Color::Blue.AlphaMultiplied(0.2f), 0, false); } - Vector3 corners[8]; - _bounds.GetCorners(corners); - const float margin = 1.0f; - const Color wiresColor = color.AlphaMultiplied(0.6f); - DEBUG_DRAW_BOX(GetWriteBox(corners[0], corners[1], margin), wiresColor, 0, true); - DEBUG_DRAW_BOX(GetWriteBox(corners[0], corners[3], margin), wiresColor, 0, true); - DEBUG_DRAW_BOX(GetWriteBox(corners[0], corners[4], margin), wiresColor, 0, true); - DEBUG_DRAW_BOX(GetWriteBox(corners[1], corners[2], margin), wiresColor, 0, true); - DEBUG_DRAW_BOX(GetWriteBox(corners[1], corners[5], margin), wiresColor, 0, true); - DEBUG_DRAW_BOX(GetWriteBox(corners[2], corners[3], margin), wiresColor, 0, true); - DEBUG_DRAW_BOX(GetWriteBox(corners[2], corners[6], margin), wiresColor, 0, true); - DEBUG_DRAW_BOX(GetWriteBox(corners[3], corners[7], margin), wiresColor, 0, true); - DEBUG_DRAW_BOX(GetWriteBox(corners[4], corners[5], margin), wiresColor, 0, true); - DEBUG_DRAW_BOX(GetWriteBox(corners[4], corners[7], margin), wiresColor, 0, true); - DEBUG_DRAW_BOX(GetWriteBox(corners[5], corners[6], margin), wiresColor, 0, true); - DEBUG_DRAW_BOX(GetWriteBox(corners[6], corners[7], margin), wiresColor, 0, true); - - // Base Collider::OnDebugDrawSelected(); } diff --git a/Source/Engine/Physics/Colliders/BoxCollider.h b/Source/Engine/Physics/Colliders/BoxCollider.h index c0eeaea5c..7dbbc830d 100644 --- a/Source/Engine/Physics/Colliders/BoxCollider.h +++ b/Source/Engine/Physics/Colliders/BoxCollider.h @@ -52,6 +52,7 @@ public: // [Collider] #if USE_EDITOR void OnDebugDraw() override; + void OnDebugDrawSelf() override; void OnDebugDrawSelected() override; #endif bool IntersectsItself(const Ray& ray, Real& distance, Vector3& normal) override; diff --git a/Source/Engine/Physics/Colliders/CapsuleCollider.cpp b/Source/Engine/Physics/Colliders/CapsuleCollider.cpp index 8a9e1d1cf..83ce85115 100644 --- a/Source/Engine/Physics/Colliders/CapsuleCollider.cpp +++ b/Source/Engine/Physics/Colliders/CapsuleCollider.cpp @@ -52,6 +52,17 @@ void CapsuleCollider::DrawPhysicsDebug(RenderView& view) DEBUG_DRAW_WIRE_CAPSULE(_transform.LocalToWorld(_center), rotation, radius, height, Color::GreenYellow * 0.8f, 0, true); } +void CapsuleCollider::OnDebugDrawSelf() +{ + Quaternion rotation; + Quaternion::Multiply(_transform.Orientation, Quaternion::Euler(0, 90, 0), rotation); + const float minSize = 0.001f; + const float radius = Math::Max(Math::Abs(_radius) * _cachedScale, minSize); + const float height = Math::Max(Math::Abs(_height) * _cachedScale, minSize); + const Vector3 position = _transform.LocalToWorld(_center); + DEBUG_DRAW_WIRE_CAPSULE(position, rotation, radius, height, Color::GreenYellow, 0, false); +} + void CapsuleCollider::OnDebugDrawSelected() { Quaternion rotation; diff --git a/Source/Engine/Physics/Colliders/CapsuleCollider.h b/Source/Engine/Physics/Colliders/CapsuleCollider.h index 234916f87..784d346eb 100644 --- a/Source/Engine/Physics/Colliders/CapsuleCollider.h +++ b/Source/Engine/Physics/Colliders/CapsuleCollider.h @@ -56,6 +56,7 @@ public: public: // [Collider] #if USE_EDITOR + void OnDebugDrawSelf() override; void OnDebugDrawSelected() override; #endif bool IntersectsItself(const Ray& ray, Real& distance, Vector3& normal) override; diff --git a/Source/Engine/Physics/Colliders/Collider.cpp b/Source/Engine/Physics/Colliders/Collider.cpp index 8249577c2..d60048ba9 100644 --- a/Source/Engine/Physics/Colliders/Collider.cpp +++ b/Source/Engine/Physics/Colliders/Collider.cpp @@ -248,7 +248,7 @@ void Collider::UpdateGeometry() const auto rigidBody = dynamic_cast(GetParent()); if (_staticActor != nullptr || (rigidBody && CanAttach(rigidBody))) { - PhysicsBackend::AttachShape(_shape, actor); + Attach(rigidBody); } else { diff --git a/Source/Engine/Physics/Colliders/Collider.h b/Source/Engine/Physics/Colliders/Collider.h index 5fd34f187..1aa2ce385 100644 --- a/Source/Engine/Physics/Colliders/Collider.h +++ b/Source/Engine/Physics/Colliders/Collider.h @@ -160,6 +160,10 @@ protected: private: void OnMaterialChanged(); + friend RigidBody; +#if USE_EDITOR + virtual void OnDebugDrawSelf() {} +#endif public: // [PhysicsColliderActor] @@ -169,7 +173,6 @@ public: void ClosestPoint(const Vector3& point, Vector3& result) const final; bool ContainsPoint(const Vector3& point) const final; - protected: // [PhysicsColliderActor] void OnEnable() override; diff --git a/Source/Engine/Physics/Colliders/MeshCollider.cpp b/Source/Engine/Physics/Colliders/MeshCollider.cpp index 0902f1106..2af533ca3 100644 --- a/Source/Engine/Physics/Colliders/MeshCollider.cpp +++ b/Source/Engine/Physics/Colliders/MeshCollider.cpp @@ -85,13 +85,18 @@ void MeshCollider::DrawPhysicsDebug(RenderView& view) } } -void MeshCollider::OnDebugDrawSelected() +void MeshCollider::OnDebugDrawSelf() { if (CollisionData && CollisionData->IsLoaded()) { const auto& debugLines = CollisionData->GetDebugLines(); DEBUG_DRAW_LINES(Span(debugLines.Get(), debugLines.Count()), _transform.GetWorld(), Color::GreenYellow, 0, false); } +} + +void MeshCollider::OnDebugDrawSelected() +{ + OnDebugDrawSelf(); // Base Collider::OnDebugDrawSelected(); diff --git a/Source/Engine/Physics/Colliders/MeshCollider.h b/Source/Engine/Physics/Colliders/MeshCollider.h index 99f2980f6..8d0e77179 100644 --- a/Source/Engine/Physics/Colliders/MeshCollider.h +++ b/Source/Engine/Physics/Colliders/MeshCollider.h @@ -31,6 +31,7 @@ public: bool CanAttach(RigidBody* rigidBody) const override; bool CanBeTrigger() const override; #if USE_EDITOR + void OnDebugDrawSelf() override; void OnDebugDrawSelected() override; #endif bool IntersectsItself(const Ray& ray, Real& distance, Vector3& normal) override; diff --git a/Source/Engine/Physics/Colliders/SphereCollider.cpp b/Source/Engine/Physics/Colliders/SphereCollider.cpp index f40d9e4af..0576ae6cf 100644 --- a/Source/Engine/Physics/Colliders/SphereCollider.cpp +++ b/Source/Engine/Physics/Colliders/SphereCollider.cpp @@ -35,9 +35,14 @@ void SphereCollider::DrawPhysicsDebug(RenderView& view) DEBUG_DRAW_WIRE_SPHERE(_sphere, Color::GreenYellow * 0.8f, 0, true); } -void SphereCollider::OnDebugDrawSelected() +void SphereCollider::OnDebugDrawSelf() { DEBUG_DRAW_WIRE_SPHERE(_sphere, Color::GreenYellow, 0, false); +} + +void SphereCollider::OnDebugDrawSelected() +{ + OnDebugDrawSelf(); if (_contactOffset > 0) { diff --git a/Source/Engine/Physics/Colliders/SphereCollider.h b/Source/Engine/Physics/Colliders/SphereCollider.h index 67addf17b..47459cf34 100644 --- a/Source/Engine/Physics/Colliders/SphereCollider.h +++ b/Source/Engine/Physics/Colliders/SphereCollider.h @@ -36,6 +36,7 @@ public: public: // [Collider] #if USE_EDITOR + void OnDebugDrawSelf() override; void OnDebugDrawSelected() override; #endif bool IntersectsItself(const Ray& ray, Real& distance, Vector3& normal) override; diff --git a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp index b023b49f7..0c23a1881 100644 --- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp +++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp @@ -2482,7 +2482,7 @@ Vector3 PhysicsBackend::GetRigidDynamicActorCenterOfMass(void* actor) return P2C(actorPhysX->getCMassLocalPose().p); } -void PhysicsBackend::SetRigidDynamicActorCenterOfMassOffset(void* actor, const Float3& value) +void PhysicsBackend::AddRigidDynamicActorCenterOfMassOffset(void* actor, const Float3& value) { auto actorPhysX = (PxRigidDynamic*)actor; auto pose = actorPhysX->getCMassLocalPose(); diff --git a/Source/Engine/Physics/PhysicsBackend.h b/Source/Engine/Physics/PhysicsBackend.h index dacd6c7fd..4307d2c6d 100644 --- a/Source/Engine/Physics/PhysicsBackend.h +++ b/Source/Engine/Physics/PhysicsBackend.h @@ -164,7 +164,7 @@ public: static Vector3 GetRigidDynamicActorAngularVelocity(void* actor); static void SetRigidDynamicActorAngularVelocity(void* actor, const Vector3& value, bool wakeUp); static Vector3 GetRigidDynamicActorCenterOfMass(void* actor); - static void SetRigidDynamicActorCenterOfMassOffset(void* actor, const Float3& value); + static void AddRigidDynamicActorCenterOfMassOffset(void* actor, const Float3& value); static bool GetRigidDynamicActorIsSleeping(void* actor); static void RigidDynamicActorSleep(void* actor); static void RigidDynamicActorWakeUp(void* actor); diff --git a/Source/Engine/Render2D/Font.cpp b/Source/Engine/Render2D/Font.cpp index cf478abda..63c59ff70 100644 --- a/Source/Engine/Render2D/Font.cpp +++ b/Source/Engine/Render2D/Font.cpp @@ -388,7 +388,10 @@ int32 Font::HitTestText(const StringView& text, const Float2& location, const Te if (dst < smallestDst) { // Pointer is behind the last character in the line - smallestIndex = line.LastCharIndex + 1; + if (text[line.LastCharIndex] == '\n' && lineIndex != lines.Count() - 1) + smallestIndex = line.LastCharIndex; + else + smallestIndex = line.LastCharIndex + 1; // Fix for last line //if (lineIndex == lines.Count() - 1) diff --git a/Source/Engine/Renderer/GBufferPass.cpp b/Source/Engine/Renderer/GBufferPass.cpp index 9d3684010..7c9abb413 100644 --- a/Source/Engine/Renderer/GBufferPass.cpp +++ b/Source/Engine/Renderer/GBufferPass.cpp @@ -19,6 +19,7 @@ #include "Engine/Content/Content.h" #include "Engine/Content/Assets/Model.h" #include "Engine/Level/Actors/Decal.h" +#include "Engine/Level/Actors/Sky.h" #include "Engine/Engine/Engine.h" GPU_CB_STRUCT(GBufferPassData { @@ -416,8 +417,17 @@ void GBufferPass::DrawSky(RenderContext& renderContext, GPUContext* context) // Calculate sphere model transform to cover far plane Matrix m1, m2; - Matrix::Scaling(renderContext.View.Far / ((float)box.GetSize().Y * 0.5f) * 0.95f, m1); // Scale to fit whole view frustum - Matrix::CreateWorld(renderContext.View.Position, Float3::Up, Float3::Backward, m2); // Rotate sphere model + float size = renderContext.View.Far; + Float3 origin = renderContext.View.Position; + if (dynamic_cast(renderContext.List->Sky)) // TODO: refactor sky rendering (eg. let sky draw with custom projection) + { + BoundingSphere frustumBounds; + renderContext.View.CullingFrustum.GetSphere(frustumBounds); + origin = frustumBounds.Center; + size = frustumBounds.Radius; + } + Matrix::Scaling(size / ((float)box.GetSize().Y * 0.5f) * 0.95f, m1); // Scale to fit whole view frustum + Matrix::CreateWorld(origin, Float3::Up, Float3::Backward, m2); // Rotate sphere model m1 *= m2; // Draw sky diff --git a/Source/Engine/Renderer/Renderer.cpp b/Source/Engine/Renderer/Renderer.cpp index 7fd12ba73..cd2c9ca2b 100644 --- a/Source/Engine/Renderer/Renderer.cpp +++ b/Source/Engine/Renderer/Renderer.cpp @@ -353,8 +353,6 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont const bool isGBufferDebug = GBufferPass::IsDebugView(renderContext.View.Mode); { PROFILE_CPU_NAMED("Setup"); - if (renderContext.View.Origin != renderContext.View.PrevOrigin) - renderContext.Task->CameraCut(); // Cut any temporal effects on rendering origin change const int32 screenWidth = renderContext.Buffers->GetWidth(); const int32 screenHeight = renderContext.Buffers->GetHeight(); setup.UpscaleLocation = renderContext.Task->UpscaleLocation; diff --git a/Source/Engine/Renderer/ShadowsPass.cpp b/Source/Engine/Renderer/ShadowsPass.cpp index 0bf2f70d1..34ff5d3a9 100644 --- a/Source/Engine/Renderer/ShadowsPass.cpp +++ b/Source/Engine/Renderer/ShadowsPass.cpp @@ -946,7 +946,10 @@ void ShadowsPass::SetupLight(ShadowsCustomBuffer& shadows, RenderContext& render Matrix shadowView, shadowProjection, shadowVP, cullingVP; // Create view matrix - Matrix::LookAt(frustumCenter + light.Direction * minExtents.Z, frustumCenter, Float3::Up, shadowView); + Float3 up = Float3::Up; + if (Math::Abs(Float3::Dot(light.Direction, up)) > 0.9f) + up = Float3::Right; + Matrix::LookAt(frustumCenter + light.Direction * minExtents.Z, frustumCenter, up, shadowView); // Create viewport for culling with extended near/far planes due to culling issues (aka pancaking) const float cullRangeExtent = 100000.0f; diff --git a/Source/Engine/Renderer/VolumetricFogPass.cpp b/Source/Engine/Renderer/VolumetricFogPass.cpp index 817f25eef..4cb672fc2 100644 --- a/Source/Engine/Renderer/VolumetricFogPass.cpp +++ b/Source/Engine/Renderer/VolumetricFogPass.cpp @@ -179,6 +179,8 @@ bool VolumetricFogPass::Init(RenderContext& renderContext, GPUContext* context, (float)_cache.GridSizeZ); auto& fogData = renderContext.Buffers->VolumetricFogData; fogData.MaxDistance = options.Distance; + if (renderContext.Task->IsCameraCut || renderContext.View.IsOriginTeleport()) + _cache.HistoryWeight = 0.0f; // Init data (partial, without directional light or sky light data); GBufferPass::SetInputs(renderContext.View, _cache.Data.GBuffer); @@ -311,7 +313,7 @@ void VolumetricFogPass::Render(RenderContext& renderContext) PROFILE_GPU_CPU("Volumetric Fog"); // TODO: test exponential depth distribution (should give better quality near the camera) - // TODO: use tiled light culling and render unshadowed lights in single pass + // TODO: use tiled light culling and render shadowed/unshadowed lights in single pass // Try to get shadows atlas GPUTexture* shadowMap; @@ -528,7 +530,7 @@ void VolumetricFogPass::Render(RenderContext& renderContext) { PROFILE_GPU("Light Scattering"); - const bool temporalHistoryIsValid = renderContext.Buffers->VolumetricFogHistory && !renderContext.Task->IsCameraCut && Float3::NearEqual(renderContext.Buffers->VolumetricFogHistory->Size3(), cache.GridSize); + const bool temporalHistoryIsValid = renderContext.Buffers->VolumetricFogHistory && Float3::NearEqual(renderContext.Buffers->VolumetricFogHistory->Size3(), cache.GridSize); const auto lightScatteringHistory = temporalHistoryIsValid ? renderContext.Buffers->VolumetricFogHistory : nullptr; context->BindUA(0, lightScattering->ViewVolume()); diff --git a/Source/Engine/Scripting/Scripting.cpp b/Source/Engine/Scripting/Scripting.cpp index db6867d34..e042b4633 100644 --- a/Source/Engine/Scripting/Scripting.cpp +++ b/Source/Engine/Scripting/Scripting.cpp @@ -904,6 +904,8 @@ ScriptingObject* Scripting::FindObject(Guid id, const MClass* type) if (idsMapping) { idsMapping->TryGet(id, id); + if (!id.IsValid()) + return nullptr; // Skip warning if object was mapped to empty id (intentionally ignored) } // Try to find it diff --git a/Source/Engine/Terrain/Terrain.cpp b/Source/Engine/Terrain/Terrain.cpp index f13d3bcc4..ace6246b2 100644 --- a/Source/Engine/Terrain/Terrain.cpp +++ b/Source/Engine/Terrain/Terrain.cpp @@ -852,9 +852,7 @@ void Terrain::OnEnable() { auto patch = _patches[i]; if (patch->_physicsActor) - { PhysicsBackend::AddSceneActor(scene, patch->_physicsActor); - } } // Base @@ -873,9 +871,7 @@ void Terrain::OnDisable() { auto patch = _patches[i]; if (patch->_physicsActor) - { PhysicsBackend::RemoveSceneActor(scene, patch->_physicsActor); - } } // Base diff --git a/Source/Engine/Terrain/TerrainPatch.cpp b/Source/Engine/Terrain/TerrainPatch.cpp index a9388224d..f73a7552c 100644 --- a/Source/Engine/Terrain/TerrainPatch.cpp +++ b/Source/Engine/Terrain/TerrainPatch.cpp @@ -2218,7 +2218,8 @@ void TerrainPatch::DestroyCollision() void* scene = _terrain->GetPhysicsScene()->GetPhysicsScene(); PhysicsBackend::RemoveCollider(_terrain); - PhysicsBackend::RemoveSceneActor(scene, _physicsActor); + if (_terrain->IsDuringPlay() && _terrain->IsActiveInHierarchy()) + PhysicsBackend::RemoveSceneActor(scene, _physicsActor); PhysicsBackend::DestroyActor(_physicsActor); PhysicsBackend::DestroyShape(_physicsShape); PhysicsBackend::DestroyObject(_physicsHeightField); diff --git a/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp b/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp index f5b043994..b1340bfa7 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp @@ -528,12 +528,24 @@ bool ImportMaterials(ModelData& result, AssimpImporterData& data, String& errorM aiColor3D aColor; if (aMaterial->Get(AI_MATKEY_COLOR_DIFFUSE, aColor) == AI_SUCCESS) materialSlot.Diffuse.Color = ToColor(aColor); + if (aMaterial->Get(AI_MATKEY_COLOR_EMISSIVE, aColor) == AI_SUCCESS) + materialSlot.Emissive.Color = ToColor(aColor); + if (aMaterial->Get(AI_MATKEY_COLOR_EMISSIVE, aColor) == AI_SUCCESS) + materialSlot.Emissive.Color = ToColor(aColor); bool aBoolean; if (aMaterial->Get(AI_MATKEY_TWOSIDED, aBoolean) == AI_SUCCESS) materialSlot.TwoSided = aBoolean; - bool aFloat; + if (aMaterial->Get(AI_MATKEY_ENABLE_WIREFRAME, aBoolean) == AI_SUCCESS) + materialSlot.Wireframe = aBoolean; + float aFloat; if (aMaterial->Get(AI_MATKEY_OPACITY, aFloat) == AI_SUCCESS) materialSlot.Opacity.Value = aFloat; + if (aMaterial->Get(AI_MATKEY_GLOSSINESS_FACTOR, aFloat) == AI_SUCCESS) + materialSlot.Roughness.Value = 1.0f - aFloat; + else if (aMaterial->Get(AI_MATKEY_SHININESS, aFloat) == AI_SUCCESS) + materialSlot.Roughness.Value = MaterialSlotEntry::ShininessToRoughness(aFloat); + if (aMaterial->Get(AI_MATKEY_EMISSIVE_INTENSITY, aFloat) == AI_SUCCESS) + materialSlot.Emissive.Color *= aFloat; if (EnumHasAnyFlags(data.Options.ImportTypes, ImportDataTypes::Textures)) { @@ -541,6 +553,15 @@ bool ImportMaterials(ModelData& result, AssimpImporterData& data, String& errorM ImportMaterialTexture(result, data, aMaterial, aiTextureType_EMISSIVE, materialSlot.Emissive.TextureIndex, TextureEntry::TypeHint::ColorRGB); ImportMaterialTexture(result, data, aMaterial, aiTextureType_NORMALS, materialSlot.Normals.TextureIndex, TextureEntry::TypeHint::Normals); ImportMaterialTexture(result, data, aMaterial, aiTextureType_OPACITY, materialSlot.Opacity.TextureIndex, TextureEntry::TypeHint::ColorRGBA); + ImportMaterialTexture(result, data, aMaterial, aiTextureType_METALNESS, materialSlot.Metalness.TextureIndex, TextureEntry::TypeHint::ColorRGB); + ImportMaterialTexture(result, data, aMaterial, aiTextureType_DIFFUSE_ROUGHNESS, materialSlot.Roughness.TextureIndex, TextureEntry::TypeHint::ColorRGB); + + if (materialSlot.Roughness.TextureIndex != -1 && (data.Path.EndsWith(TEXT(".gltf")) || data.Path.EndsWith(TEXT(".glb")))) + { + // glTF specification with a single metallicRoughnessTexture (G = roughness, B = metalness) + materialSlot.Roughness.Channel = 1; + materialSlot.Metalness.Channel = 2; + } if (materialSlot.Diffuse.TextureIndex != -1) { diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index fe268c350..bddd56296 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -1520,6 +1520,9 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option const Char* roughnessNames[] = { TEXT("roughness"), TEXT("rough") }; TrySetupMaterialParameter(materialInstance, ToSpan(roughnessNames, ARRAY_COUNT(roughnessNames)), material.Roughness.Value, MaterialParameterType::Float); TRY_SETUP_TEXTURE_PARAM(Roughness, roughnessNames, Texture); + const Char* metalnessNames[] = { TEXT("metalness"), TEXT("metallic") }; + TrySetupMaterialParameter(materialInstance, ToSpan(metalnessNames, ARRAY_COUNT(metalnessNames)), material.Metalness.Value, MaterialParameterType::Float); + TRY_SETUP_TEXTURE_PARAM(Metalness, metalnessNames, Texture); #undef TRY_SETUP_TEXTURE_PARAM materialInstance->Save(); @@ -1545,11 +1548,22 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option materialOptions.Opacity.Texture = data.Textures[material.Opacity.TextureIndex].AssetID; materialOptions.Roughness.Value = material.Roughness.Value; if (material.Roughness.TextureIndex != -1) + { materialOptions.Roughness.Texture = data.Textures[material.Roughness.TextureIndex].AssetID; + materialOptions.Roughness.Channel = material.Roughness.Channel; + } + materialOptions.Metalness.Value = material.Metalness.Value; + if (material.Metalness.TextureIndex != -1) + { + materialOptions.Metalness.Texture = data.Textures[material.Metalness.TextureIndex].AssetID; + materialOptions.Metalness.Channel = material.Metalness.Channel; + } if (material.Normals.TextureIndex != -1) materialOptions.Normals.Texture = data.Textures[material.Normals.TextureIndex].AssetID; if (material.TwoSided || material.Diffuse.HasAlphaMask) materialOptions.Info.CullMode = CullMode::TwoSided; + if (material.Wireframe) + materialOptions.Info.FeaturesFlags |= MaterialFeaturesFlags::Wireframe; if (!Math::IsOne(material.Opacity.Value) || material.Opacity.TextureIndex != -1) materialOptions.Info.BlendMode = MaterialBlendMode::Transparent; AssetsImportingManager::Create(AssetsImportingManager::CreateMaterialTag, assetPath, material.AssetID, &materialOptions); @@ -1611,9 +1625,16 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option // Transform the nodes using the import transformation if (data.LODs.HasItems() && data.LODs[0].Meshes.HasItems()) { + BitArray<> visitedNodes; + visitedNodes.Resize(data.Nodes.Count()); + visitedNodes.SetAll(false); for (int i = 0; i < data.LODs[0].Meshes.Count(); ++i) { auto* meshData = data.LODs[0].Meshes[i]; + int32 nodeIndex = meshData->NodeIndex; + if (visitedNodes[nodeIndex]) + continue; + visitedNodes.Set(nodeIndex, true); Transform transform = importTransform; if (options.UseLocalOrigin) { @@ -1627,8 +1648,6 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option transform.Translation -= center; } - int32 nodeIndex = meshData->NodeIndex; - auto& node = data.Nodes[nodeIndex]; node.LocalTransform = transform.LocalToWorld(node.LocalTransform); } diff --git a/Source/Engine/UI/GUI/Brushes/GPUTextureBrush.cs b/Source/Engine/UI/GUI/Brushes/GPUTextureBrush.cs index fa4b5435d..781435740 100644 --- a/Source/Engine/UI/GUI/Brushes/GPUTextureBrush.cs +++ b/Source/Engine/UI/GUI/Brushes/GPUTextureBrush.cs @@ -1,12 +1,14 @@ // Copyright (c) Wojciech Figat. All rights reserved. +using System; + namespace FlaxEngine.GUI { /// /// Implementation of for . /// /// - public sealed class GPUTextureBrush : IBrush + public sealed class GPUTextureBrush : IBrush, IEquatable { /// /// The GPU texture. @@ -47,5 +49,29 @@ namespace FlaxEngine.GUI else Render2D.DrawTexture(Texture, rect, color); } + + /// + public bool Equals(GPUTextureBrush other) + { + return other != null && Texture == other.Texture && Filter == other.Filter; + } + + /// + public override bool Equals(object obj) + { + return obj is GPUTextureBrush other && Equals(other); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(Texture, (int)Filter); + } + + /// + public int CompareTo(object obj) + { + return Equals(obj) ? 1 : 0; + } } } diff --git a/Source/Engine/UI/GUI/Brushes/IBrush.cs b/Source/Engine/UI/GUI/Brushes/IBrush.cs index f11ff6857..861c0b7df 100644 --- a/Source/Engine/UI/GUI/Brushes/IBrush.cs +++ b/Source/Engine/UI/GUI/Brushes/IBrush.cs @@ -1,5 +1,7 @@ // Copyright (c) Wojciech Figat. All rights reserved. +using System; + namespace FlaxEngine.GUI { /// @@ -23,7 +25,7 @@ namespace FlaxEngine.GUI /// /// Interface that unifies input source textures, sprites, render targets, and any other brushes to be used in a more generic way. /// - public interface IBrush + public interface IBrush : IComparable { /// /// Gets the size of the image brush in pixels (if relevant). diff --git a/Source/Engine/UI/GUI/Brushes/LinearGradientBrush.cs b/Source/Engine/UI/GUI/Brushes/LinearGradientBrush.cs index 1ece73d1c..d2155a31c 100644 --- a/Source/Engine/UI/GUI/Brushes/LinearGradientBrush.cs +++ b/Source/Engine/UI/GUI/Brushes/LinearGradientBrush.cs @@ -1,12 +1,14 @@ // Copyright (c) Wojciech Figat. All rights reserved. +using System; + namespace FlaxEngine.GUI { /// /// Implementation of for linear color gradient (made of 2 color). /// /// - public sealed class LinearGradientBrush : IBrush + public sealed class LinearGradientBrush : IBrush, IEquatable { /// /// The brush start color. @@ -50,5 +52,29 @@ namespace FlaxEngine.GUI var endColor = EndColor * color; Render2D.FillRectangle(rect, startColor, startColor, endColor, endColor); } + + /// + public bool Equals(LinearGradientBrush other) + { + return other != null && StartColor == other.StartColor && EndColor == other.EndColor; + } + + /// + public override bool Equals(object obj) + { + return obj is LinearGradientBrush other && Equals(other); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(StartColor, EndColor); + } + + /// + public int CompareTo(object obj) + { + return Equals(obj) ? 1 : 0; + } } } diff --git a/Source/Engine/UI/GUI/Brushes/MaterialBrush.cs b/Source/Engine/UI/GUI/Brushes/MaterialBrush.cs index 13e9dab24..c610e9910 100644 --- a/Source/Engine/UI/GUI/Brushes/MaterialBrush.cs +++ b/Source/Engine/UI/GUI/Brushes/MaterialBrush.cs @@ -1,12 +1,14 @@ // Copyright (c) Wojciech Figat. All rights reserved. +using System; + namespace FlaxEngine.GUI { /// /// Implementation of for rendering. /// /// - public sealed class MaterialBrush : IBrush + public sealed class MaterialBrush : IBrush, IEquatable { /// /// The material. @@ -38,5 +40,29 @@ namespace FlaxEngine.GUI { Render2D.DrawMaterial(Material, rect, color); } + + /// + public bool Equals(MaterialBrush other) + { + return other != null && Material == other.Material; + } + + /// + public override bool Equals(object obj) + { + return obj is MaterialBrush other && Equals(other); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(Material); + } + + /// + public int CompareTo(object obj) + { + return Equals(obj) ? 1 : 0; + } } } diff --git a/Source/Engine/UI/GUI/Brushes/SolidColorBrush.cs b/Source/Engine/UI/GUI/Brushes/SolidColorBrush.cs index 1e4a20670..d848d9368 100644 --- a/Source/Engine/UI/GUI/Brushes/SolidColorBrush.cs +++ b/Source/Engine/UI/GUI/Brushes/SolidColorBrush.cs @@ -1,12 +1,14 @@ // Copyright (c) Wojciech Figat. All rights reserved. +using System; + namespace FlaxEngine.GUI { /// /// Implementation of for single color fill. /// /// - public sealed class SolidColorBrush : IBrush + public sealed class SolidColorBrush : IBrush, IEquatable { /// /// The brush color. @@ -39,5 +41,29 @@ namespace FlaxEngine.GUI { Render2D.FillRectangle(rect, Color * color); } + + /// + public bool Equals(SolidColorBrush other) + { + return other != null && Color == other.Color; + } + + /// + public override bool Equals(object obj) + { + return obj is SolidColorBrush other && Equals(other); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(Color); + } + + /// + public int CompareTo(object obj) + { + return Equals(obj) ? 1 : 0; + } } } diff --git a/Source/Engine/UI/GUI/Brushes/SpriteBrush.cs b/Source/Engine/UI/GUI/Brushes/SpriteBrush.cs index 205a9645c..24ca10015 100644 --- a/Source/Engine/UI/GUI/Brushes/SpriteBrush.cs +++ b/Source/Engine/UI/GUI/Brushes/SpriteBrush.cs @@ -1,12 +1,14 @@ // Copyright (c) Wojciech Figat. All rights reserved. +using System; + namespace FlaxEngine.GUI { /// /// Implementation of for . /// /// - public sealed class SpriteBrush : IBrush + public sealed class SpriteBrush : IBrush, IEquatable { /// /// The sprite. @@ -47,6 +49,30 @@ namespace FlaxEngine.GUI else Render2D.DrawSprite(Sprite, rect, color); } + + /// + public bool Equals(SpriteBrush other) + { + return other != null && Sprite == other.Sprite && Filter == other.Filter; + } + + /// + public override bool Equals(object obj) + { + return obj is SpriteBrush other && Equals(other); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(Sprite, (int)Filter); + } + + /// + public int CompareTo(object obj) + { + return Equals(obj) ? 1 : 0; + } } /// @@ -121,5 +147,29 @@ namespace FlaxEngine.GUI } #endif } + + /// + public bool Equals(Sprite9SlicingBrush other) + { + return other != null && Sprite == other.Sprite && Filter == other.Filter; + } + + /// + public override bool Equals(object obj) + { + return obj is Sprite9SlicingBrush other && Equals(other); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(Sprite, (int)Filter); + } + + /// + public int CompareTo(object obj) + { + return Equals(obj) ? 1 : 0; + } } } diff --git a/Source/Engine/UI/GUI/Brushes/TextureBrush.cs b/Source/Engine/UI/GUI/Brushes/TextureBrush.cs index 189e222b9..e3922eebb 100644 --- a/Source/Engine/UI/GUI/Brushes/TextureBrush.cs +++ b/Source/Engine/UI/GUI/Brushes/TextureBrush.cs @@ -1,12 +1,14 @@ // Copyright (c) Wojciech Figat. All rights reserved. +using System; + namespace FlaxEngine.GUI { /// /// Implementation of for . /// /// - public sealed class TextureBrush : IBrush + public sealed class TextureBrush : IBrush, IEquatable { /// /// The texture. @@ -47,13 +49,37 @@ namespace FlaxEngine.GUI else Render2D.DrawTexture(Texture, rect, color); } + + /// + public bool Equals(TextureBrush other) + { + return other != null && Texture == other.Texture && Filter == other.Filter; + } + + /// + public override bool Equals(object obj) + { + return obj is TextureBrush other && Equals(other); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(Texture, (int)Filter); + } + + /// + public int CompareTo(object obj) + { + return Equals(obj) ? 1 : 0; + } } /// /// Implementation of for using 9-slicing. /// /// - public sealed class Texture9SlicingBrush : IBrush + public sealed class Texture9SlicingBrush : IBrush, IEquatable { /// /// The texture. @@ -130,5 +156,29 @@ namespace FlaxEngine.GUI } #endif } + + /// + public bool Equals(Texture9SlicingBrush other) + { + return other != null && Texture == other.Texture && Filter == other.Filter && BorderSize == other.BorderSize && Border == other.Border; + } + + /// + public override bool Equals(object obj) + { + return obj is Texture9SlicingBrush other && Equals(other); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(Texture, (int)Filter, BorderSize, Border.GetHashCode()); + } + + /// + public int CompareTo(object obj) + { + return Equals(obj) ? 1 : 0; + } } } diff --git a/Source/Engine/UI/GUI/Brushes/UIBrush.cs b/Source/Engine/UI/GUI/Brushes/UIBrush.cs index 4441899c0..044c95285 100644 --- a/Source/Engine/UI/GUI/Brushes/UIBrush.cs +++ b/Source/Engine/UI/GUI/Brushes/UIBrush.cs @@ -1,5 +1,7 @@ // Copyright (c) Wojciech Figat. All rights reserved. +using System; + namespace FlaxEngine.GUI { /// @@ -20,7 +22,7 @@ namespace FlaxEngine.GUI /// /// /// - public sealed class UIBrush : IBrush + public sealed class UIBrush : IBrush, IEquatable { /// /// The UI Brush asset to use. @@ -71,5 +73,29 @@ namespace FlaxEngine.GUI if (asset != null && asset.Brush != null) asset.Brush.Draw(rect, color); } + + /// + public bool Equals(UIBrush other) + { + return other != null && Asset == other.Asset; + } + + /// + public override bool Equals(object obj) + { + return obj is UIBrush other && Equals(other); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(Asset); + } + + /// + public int CompareTo(object obj) + { + return Equals(obj) ? 1 : 0; + } } } diff --git a/Source/Engine/UI/GUI/Brushes/VideoBrush.cs b/Source/Engine/UI/GUI/Brushes/VideoBrush.cs index b8a54662c..25d942ca6 100644 --- a/Source/Engine/UI/GUI/Brushes/VideoBrush.cs +++ b/Source/Engine/UI/GUI/Brushes/VideoBrush.cs @@ -1,12 +1,14 @@ // Copyright (c) Wojciech Figat. All rights reserved. +using System; + namespace FlaxEngine.GUI { /// /// Implementation of for frame displaying. /// /// - public sealed class VideoBrush : IBrush + public sealed class VideoBrush : IBrush, IEquatable { /// /// The video player to display frame from it. @@ -57,5 +59,29 @@ namespace FlaxEngine.GUI else Render2D.DrawTexture(texture, rect, color); } + + /// + public bool Equals(VideoBrush other) + { + return other != null && Player == other.Player && Filter == other.Filter; + } + + /// + public override bool Equals(object obj) + { + return obj is VideoBrush other && Equals(other); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(Player, (int)Filter); + } + + /// + public int CompareTo(object obj) + { + return Equals(obj) ? 1 : 0; + } } } diff --git a/Source/Engine/UI/GUI/Common/CheckBox.cs b/Source/Engine/UI/GUI/Common/CheckBox.cs index c779d6e83..47073cb27 100644 --- a/Source/Engine/UI/GUI/Common/CheckBox.cs +++ b/Source/Engine/UI/GUI/Common/CheckBox.cs @@ -181,8 +181,8 @@ namespace FlaxEngine.GUI ImageColor = style.BorderSelected * 1.2f; BorderColor = style.BorderNormal; BorderColorHighlighted = style.BorderSelected; - CheckedImage = new SpriteBrush(style.CheckBoxTick); - IntermediateImage = new SpriteBrush(style.CheckBoxIntermediate); + CheckedImage = style.CheckBoxTick.IsValid ? new SpriteBrush(style.CheckBoxTick) : new SolidColorBrush(style.Foreground); + IntermediateImage = style.CheckBoxIntermediate.IsValid ? new SpriteBrush(style.CheckBoxIntermediate) : new SolidColorBrush(style.ForegroundGrey); CacheBox(); } diff --git a/Source/Engine/UI/GUI/Common/RichTextBox.Parsing.cs b/Source/Engine/UI/GUI/Common/RichTextBox.Parsing.cs index 9a10a327c..20ef1c401 100644 --- a/Source/Engine/UI/GUI/Common/RichTextBox.Parsing.cs +++ b/Source/Engine/UI/GUI/Common/RichTextBox.Parsing.cs @@ -240,11 +240,13 @@ namespace FlaxEngine.GUI // Organize text blocks within line var horizontalAlignments = TextBlockStyle.Alignments.Baseline; + var verticalAlignments = TextBlockStyle.Alignments.Baseline; for (int i = context.LineStartTextBlockIndex; i < _textBlocks.Count; i++) { ref TextBlock textBlock = ref textBlocks[i]; var vOffset = lineSize.Y - textBlock.Bounds.Height; horizontalAlignments |= textBlock.Style.Alignment & TextBlockStyle.Alignments.HorizontalMask; + verticalAlignments |= textBlock.Style.Alignment & TextBlockStyle.Alignments.VerticalMask; switch (textBlock.Style.Alignment & TextBlockStyle.Alignments.VerticalMask) { case TextBlockStyle.Alignments.Baseline: @@ -272,14 +274,16 @@ namespace FlaxEngine.GUI } } } - var hOffset = Width - lineSize.X; + + // Organize blocks within whole container + var sizeOffset = Size - lineSize; if ((horizontalAlignments & TextBlockStyle.Alignments.Center) == TextBlockStyle.Alignments.Center) { - hOffset *= 0.5f; + sizeOffset.X *= 0.5f; for (int i = context.LineStartTextBlockIndex; i < _textBlocks.Count; i++) { ref TextBlock textBlock = ref textBlocks[i]; - textBlock.Bounds.Location.X += hOffset; + textBlock.Bounds.Location.X += sizeOffset.X; } } else if ((horizontalAlignments & TextBlockStyle.Alignments.Right) == TextBlockStyle.Alignments.Right) @@ -287,7 +291,24 @@ namespace FlaxEngine.GUI for (int i = context.LineStartTextBlockIndex; i < _textBlocks.Count; i++) { ref TextBlock textBlock = ref textBlocks[i]; - textBlock.Bounds.Location.X += hOffset; + textBlock.Bounds.Location.X += sizeOffset.X; + } + } + if ((verticalAlignments & TextBlockStyle.Alignments.Middle) == TextBlockStyle.Alignments.Middle) + { + sizeOffset.Y *= 0.5f; + for (int i = context.LineStartTextBlockIndex; i < _textBlocks.Count; i++) + { + ref TextBlock textBlock = ref textBlocks[i]; + textBlock.Bounds.Location.Y += sizeOffset.Y; + } + } + else if ((verticalAlignments & TextBlockStyle.Alignments.Bottom) == TextBlockStyle.Alignments.Bottom) + { + for (int i = context.LineStartTextBlockIndex; i < _textBlocks.Count; i++) + { + ref TextBlock textBlock = ref textBlocks[i]; + textBlock.Bounds.Location.Y += sizeOffset.Y; } } diff --git a/Source/Engine/UI/GUI/TextBlockStyle.cs b/Source/Engine/UI/GUI/TextBlockStyle.cs index 399de41a3..fee385dfa 100644 --- a/Source/Engine/UI/GUI/TextBlockStyle.cs +++ b/Source/Engine/UI/GUI/TextBlockStyle.cs @@ -1,5 +1,7 @@ // Copyright (c) Wojciech Figat. All rights reserved. +using System; + namespace FlaxEngine.GUI { /// @@ -10,6 +12,7 @@ namespace FlaxEngine.GUI /// /// Text block alignments modes. /// + [Flags] public enum Alignments { /// diff --git a/Source/Engine/Visject/ShaderGraph.h b/Source/Engine/Visject/ShaderGraph.h index 84c4b64d0..ab0e7d405 100644 --- a/Source/Engine/Visject/ShaderGraph.h +++ b/Source/Engine/Visject/ShaderGraph.h @@ -14,7 +14,7 @@ #include "Engine/Content/AssetsContainer.h" #include "Engine/Animations/Curve.h" -#define SHADER_GRAPH_MAX_CALL_STACK 100 +#define SHADER_GRAPH_MAX_CALL_STACK 50 enum class MaterialSceneTextures; template diff --git a/Source/Shaders/ExponentialHeightFog.hlsl b/Source/Shaders/ExponentialHeightFog.hlsl index f6fb918f5..b5af721d8 100644 --- a/Source/Shaders/ExponentialHeightFog.hlsl +++ b/Source/Shaders/ExponentialHeightFog.hlsl @@ -92,4 +92,9 @@ float4 GetExponentialHeightFog(ExponentialHeightFogData exponentialHeightFog, fl return GetExponentialHeightFog(exponentialHeightFog, posWS, camWS, skipDistance, distance(posWS, camWS)); } +float4 CombineVolumetricFog(float4 fog, float4 volumetricFog) +{ + return float4(volumetricFog.rgb + fog.rgb * volumetricFog.a, volumetricFog.a * fog.a); +} + #endif diff --git a/Source/Shaders/Fog.shader b/Source/Shaders/Fog.shader index dfea921cc..5c4344688 100644 --- a/Source/Shaders/Fog.shader +++ b/Source/Shaders/Fog.shader @@ -63,7 +63,7 @@ float4 PS_Fog(Quad_VS2PS input) : SV_Target0 #if VOLUMETRIC_FOG // Sample volumetric fog and mix it in float4 volumetricFog = IntegratedLightScattering.SampleLevel(SamplerLinearClamp, volumeUV, 0); - fog = float4(volumetricFog.rgb + fog.rgb * volumetricFog.a, volumetricFog.a * fog.a); + fog = CombineVolumetricFog(fog, volumetricFog); #endif return fog; diff --git a/Source/Shaders/Sky.shader b/Source/Shaders/Sky.shader index af5d0053c..de29e5d3f 100644 --- a/Source/Shaders/Sky.shader +++ b/Source/Shaders/Sky.shader @@ -7,7 +7,8 @@ #include "./Flax/AtmosphereFog.hlsl" META_CB_BEGIN(0, Data) -float4x4 WVP; +float4x4 WorldViewProjection; +float4x4 InvViewProjection; float3 ViewOffset; float Padding; GBufferData GBuffer; @@ -18,7 +19,7 @@ DECLARE_GBUFFERDATA_ACCESS(GBuffer) struct MaterialInput { - float4 Position : SV_Position; + float4 Position : SV_Position; float4 ScreenPos : TEXCOORD0; }; @@ -30,12 +31,9 @@ MaterialInput VS(ModelInput_PosOnly input) MaterialInput output; // Compute vertex position - output.Position = mul(float4(input.Position.xyz, 1), WVP); + output.Position = mul(float4(input.Position.xyz, 1), WorldViewProjection); output.ScreenPos = output.Position; - // Place pixels on the far plane - output.Position = output.Position.xyzz; - return output; } @@ -45,15 +43,15 @@ GBufferOutput PS_Sky(MaterialInput input) { GBufferOutput output; - // Obtain UVs corresponding to the current pixel - float2 uv = (input.ScreenPos.xy / input.ScreenPos.w) * float2(0.5, -0.5) + float2(0.5, 0.5); + // Calculate view vector (unproject at the far plane) + GBufferData gBufferData = GetGBufferData(); + float4 clipPos = float4(input.ScreenPos.xy / input.ScreenPos.w, 1.0, 1.0); + clipPos = mul(clipPos, InvViewProjection); + float3 worldPos = clipPos.xyz / clipPos.w; + float3 viewVector = normalize(worldPos - gBufferData.ViewPos); // Sample atmosphere color - GBufferData gBufferData = GetGBufferData(); - float3 vsPos = GetViewPos(gBufferData, uv, LinearZ2DeviceDepth(gBufferData, 1)); - float3 wsPos = mul(float4(vsPos, 1), gBufferData.InvViewMatrix).xyz; - float3 viewVector = wsPos - gBufferData.ViewPos; - float4 color = GetAtmosphericFog(AtmosphericFog, gBufferData.ViewFar, wsPos + ViewOffset, gBufferData.ViewPos + ViewOffset); + float4 color = GetAtmosphericFog(AtmosphericFog, gBufferData.ViewFar, gBufferData.ViewPos + ViewOffset, viewVector, gBufferData.ViewFar, float3(0, 0, 0)); // Pack GBuffer output.Light = color; @@ -64,36 +62,3 @@ GBufferOutput PS_Sky(MaterialInput input) return output; } - -META_PS(true, FEATURE_LEVEL_ES2) -float4 PS_Fog(Quad_VS2PS input) : SV_Target0 -{ - float4 result; - /* - // Sample GBuffer - GBufferSample gBuffer = SampleGBuffer(GBuffer, input.TexCoord); - - // TODO: set valid scene color for better inscatter reflectance - //float3 sceneColor = gBuffer.Color * AtmosphericFogDensityOffset; - float3 sceneColor = float3(0, 0, 0); - - // Sample atmosphere color - float3 viewVector = gBuffer.WorldPos - GBuffer.ViewPos; - float SceneDepth = length(ViewVector); - result = GetAtmosphericFog(AtmosphericFog, GBuffer.ViewFar, GBuffer.ViewPos, viewVector, SceneDepth, sceneColor); - - //result.rgb = normalize(ViewVector); - //result.rgb = ViewVector; - //result.rgb = SceneDepth.xxx / GBuffer.ViewFar * 0.5f; - - //result = float4(input.TexCoord, 0, 1); - //result = AtmosphereTransmittanceTexture.Sample(SamplerLinearClamp, input.TexCoord); - //result = float4(AtmosphereIrradianceTexture.Sample(SamplerLinearClamp, input.TexCoord).rgb*5.0, 1.0); - //result = AtmosphereInscatterTexture.Sample(SamplerLinearClamp, float3(input.TexCoord.xy, (AtmosphericFogSunDirection.x+1.0)/2.0)); - */ - - // TODO: finish fog - result = float4(1, 0, 0, 1); - - return result; -} diff --git a/Source/ThirdParty/rapidjson/prettywriter.h b/Source/ThirdParty/rapidjson/prettywriter.h index fe45df1d1..c66b6d734 100644 --- a/Source/ThirdParty/rapidjson/prettywriter.h +++ b/Source/ThirdParty/rapidjson/prettywriter.h @@ -108,7 +108,6 @@ public: } bool String(const Ch* str, SizeType length, bool copy = false) { - RAPIDJSON_ASSERT(str != 0); (void)copy; PrettyPrefix(kStringType); return Base::EndValue(Base::WriteString(str, length)); diff --git a/Source/Tools/Flax.Build/Build/Builder.Projects.cs b/Source/Tools/Flax.Build/Build/Builder.Projects.cs index 59538364c..46925355c 100644 --- a/Source/Tools/Flax.Build/Build/Builder.Projects.cs +++ b/Source/Tools/Flax.Build/Build/Builder.Projects.cs @@ -192,6 +192,8 @@ namespace Flax.Build { // Pick the project format var projectFormats = new HashSet(); + if (Configuration.ProjectFormatVS2026) + projectFormats.Add(ProjectFormat.VisualStudio2026); if (Configuration.ProjectFormatVS2022) projectFormats.Add(ProjectFormat.VisualStudio2022); if (Configuration.ProjectFormatVS2019) @@ -209,8 +211,13 @@ namespace Flax.Build if (projectFormats.Count == 0) projectFormats.Add(Platform.BuildPlatform.DefaultProjectFormat); - // Always generate VS solution files for project (needed for C# Intellisense support) - projectFormats.Add(ProjectFormat.VisualStudio2022); + // Always generate VS solution files for project (needed for C# Intellisense support in other IDEs) + if (!projectFormats.Contains(ProjectFormat.VisualStudio2026) && + !projectFormats.Contains(ProjectFormat.VisualStudio2022) && + !projectFormats.Contains(ProjectFormat.VisualStudio)) + { + projectFormats.Add(ProjectFormat.VisualStudio2022); + } foreach (ProjectFormat projectFormat in projectFormats) GenerateProject(projectFormat); diff --git a/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs b/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs index 947ac575c..f279e65d6 100644 --- a/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs +++ b/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs @@ -192,11 +192,18 @@ namespace Flax.Build { if (string.IsNullOrEmpty(path)) return string.Empty; - if (path.StartsWith(Globals.EngineRoot)) + path = Utilities.NormalizePath(path); + if (IsMacroPath(path, Globals.EngineRoot)) path = "$(EnginePath)" + path.Substring(Globals.EngineRoot.Length); - else if (path.StartsWith(projectPath)) + else if (IsMacroPath(path, projectPath)) path = "$(ProjectPath)" + path.Substring(projectPath.Length); - return Utilities.NormalizePath(path); + return path; + } + + private static bool IsMacroPath(string path, string root) + { + root = Utilities.NormalizePath(root); + return path == root || path.StartsWith(root + '/'); } } diff --git a/Source/Tools/Flax.Build/Configuration.cs b/Source/Tools/Flax.Build/Configuration.cs index 7579c9f4f..ee4b0fff7 100644 --- a/Source/Tools/Flax.Build/Configuration.cs +++ b/Source/Tools/Flax.Build/Configuration.cs @@ -201,6 +201,12 @@ namespace Flax.Build [CommandLine("vs2022", "Generates Visual Studio 2022 project format files. Valid only with -genproject option.")] public static bool ProjectFormatVS2022 = false; + /// + /// Generates Visual Studio 2026 project format files. Valid only with -genproject option. + /// + [CommandLine("vs2026", "Generates Visual Studio 2026 project format files. Valid only with -genproject option.")] + public static bool ProjectFormatVS2026 = false; + /// /// Generates Visual Studio Code project format files. Valid only with -genproject option. /// diff --git a/Source/Tools/Flax.Build/Platforms/GDK/GDKPlatform.cs b/Source/Tools/Flax.Build/Platforms/GDK/GDKPlatform.cs index 9313c65b0..422299c79 100644 --- a/Source/Tools/Flax.Build/Platforms/GDK/GDKPlatform.cs +++ b/Source/Tools/Flax.Build/Platforms/GDK/GDKPlatform.cs @@ -32,8 +32,11 @@ namespace Flax.Build.Platforms if (!toolsets.ContainsKey(WindowsPlatformToolset.v141) && !toolsets.ContainsKey(WindowsPlatformToolset.v142) && !toolsets.ContainsKey(WindowsPlatformToolset.v143) && - !toolsets.ContainsKey(WindowsPlatformToolset.v144)) + !toolsets.ContainsKey(WindowsPlatformToolset.v144) && + !toolsets.ContainsKey(WindowsPlatformToolset.v145)) + { _hasRequiredSDKsInstalled = false; + } } } } diff --git a/Source/Tools/Flax.Build/Platforms/UWP/UWPPlatform.cs b/Source/Tools/Flax.Build/Platforms/UWP/UWPPlatform.cs index 6d8b9e5b1..529e75eda 100644 --- a/Source/Tools/Flax.Build/Platforms/UWP/UWPPlatform.cs +++ b/Source/Tools/Flax.Build/Platforms/UWP/UWPPlatform.cs @@ -31,7 +31,12 @@ namespace Flax.Build.Platforms } // Visual Studio 2017+ supported only - var visualStudio = VisualStudioInstance.GetInstances().FirstOrDefault(x => x.Version == VisualStudioVersion.VisualStudio2017 || x.Version == VisualStudioVersion.VisualStudio2019 || x.Version == VisualStudioVersion.VisualStudio2022); + var visualStudio = VisualStudioInstance.GetInstances().FirstOrDefault(x => + x.Version == VisualStudioVersion.VisualStudio2017 || + x.Version == VisualStudioVersion.VisualStudio2019 || + x.Version == VisualStudioVersion.VisualStudio2022 || + x.Version == VisualStudioVersion.VisualStudio2026 + ); if (visualStudio == null) _hasRequiredSDKsInstalled = false; @@ -46,7 +51,8 @@ namespace Flax.Build.Platforms if (!toolsets.ContainsKey(WindowsPlatformToolset.v141) && !toolsets.ContainsKey(WindowsPlatformToolset.v142) && !toolsets.ContainsKey(WindowsPlatformToolset.v143) && - !toolsets.ContainsKey(WindowsPlatformToolset.v144)) + !toolsets.ContainsKey(WindowsPlatformToolset.v144) && + !toolsets.ContainsKey(WindowsPlatformToolset.v145)) { _hasRequiredSDKsInstalled = false; } diff --git a/Source/Tools/Flax.Build/Platforms/UWP/UWPToolchain.cs b/Source/Tools/Flax.Build/Platforms/UWP/UWPToolchain.cs index 983ac37d2..5458c5d7d 100644 --- a/Source/Tools/Flax.Build/Platforms/UWP/UWPToolchain.cs +++ b/Source/Tools/Flax.Build/Platforms/UWP/UWPToolchain.cs @@ -29,7 +29,12 @@ namespace Flax.Build.Platforms public UWPToolchain(UWPPlatform platform, TargetArchitecture architecture, WindowsPlatformToolset toolsetVer = WindowsPlatformToolset.Latest, WindowsPlatformSDK sdkVer = WindowsPlatformSDK.Latest) : base(platform, architecture, toolsetVer, sdkVer) { - var visualStudio = VisualStudioInstance.GetInstances().FirstOrDefault(x => x.Version == VisualStudioVersion.VisualStudio2017 || x.Version == VisualStudioVersion.VisualStudio2019 || x.Version == VisualStudioVersion.VisualStudio2022); + var visualStudio = VisualStudioInstance.GetInstances().FirstOrDefault(x => + x.Version == VisualStudioVersion.VisualStudio2017 || + x.Version == VisualStudioVersion.VisualStudio2019 || + x.Version == VisualStudioVersion.VisualStudio2022 || + x.Version == VisualStudioVersion.VisualStudio2026 + ); if (visualStudio == null) throw new Exception("Missing Visual Studio 2017 or newer. It's required to build for UWP."); _usingDirs.Add(Path.Combine(visualStudio.Path, "VC", "vcpackages")); diff --git a/Source/Tools/Flax.Build/Platforms/Windows/WindowsPlatform.cs b/Source/Tools/Flax.Build/Platforms/Windows/WindowsPlatform.cs index 02c5ce8f0..a78f8c24f 100644 --- a/Source/Tools/Flax.Build/Platforms/Windows/WindowsPlatform.cs +++ b/Source/Tools/Flax.Build/Platforms/Windows/WindowsPlatform.cs @@ -40,7 +40,8 @@ namespace Flax.Build.Platforms !toolsets.ContainsKey(WindowsPlatformToolset.v141) && !toolsets.ContainsKey(WindowsPlatformToolset.v142) && !toolsets.ContainsKey(WindowsPlatformToolset.v143) && - !toolsets.ContainsKey(WindowsPlatformToolset.v144)) + !toolsets.ContainsKey(WindowsPlatformToolset.v144) && + !toolsets.ContainsKey(WindowsPlatformToolset.v145)) { Log.Warning("Missing MSVC toolset v140 or later (VS 2015 or later C++ build tools). Cannot build for Windows platform."); _hasRequiredSDKsInstalled = false; diff --git a/Source/Tools/Flax.Build/Platforms/Windows/WindowsPlatformBase.cs b/Source/Tools/Flax.Build/Platforms/Windows/WindowsPlatformBase.cs index 3cdc2f44b..26afcd894 100644 --- a/Source/Tools/Flax.Build/Platforms/Windows/WindowsPlatformBase.cs +++ b/Source/Tools/Flax.Build/Platforms/Windows/WindowsPlatformBase.cs @@ -54,6 +54,11 @@ namespace Flax.Build.Platforms /// Visual Studio 2022 (v17.10 and later) /// v144 = 144, + + /// + /// Visual Studio 2026 + /// + v145 = 145, } /// @@ -252,6 +257,8 @@ namespace Flax.Build.Platforms _toolsets[WindowsPlatformToolset.v143] = toolset; else if (version.Major == 14 && version.Minor / 10 == 4) _toolsets[WindowsPlatformToolset.v144] = toolset; + else if (version.Major == 14 && version.Minor / 10 == 5) + _toolsets[WindowsPlatformToolset.v145] = toolset; else Log.Warning("Found Unsupported MSVC toolset version: " + version); } @@ -287,11 +294,12 @@ namespace Flax.Build.Platforms } } - // Visual Studio 2017-2022 - multiple instances + // Visual Studio 2017 or later - multiple instances foreach (var vs in vsInstances.Where(x => x.Version == VisualStudioVersion.VisualStudio2017 || x.Version == VisualStudioVersion.VisualStudio2019 || - x.Version == VisualStudioVersion.VisualStudio2022 + x.Version == VisualStudioVersion.VisualStudio2022 || + x.Version == VisualStudioVersion.VisualStudio2026 )) { FindMsvcToolsets(Path.Combine(vs.Path, "VC", "Tools", "MSVC")); @@ -469,6 +477,7 @@ namespace Flax.Build.Platforms case WindowsPlatformToolset.v142: case WindowsPlatformToolset.v143: case WindowsPlatformToolset.v144: + case WindowsPlatformToolset.v145: { string hostFolder = hostArchitecture == TargetArchitecture.x86 ? "HostX86" : $"Host{hostArchitecture.ToString().ToLower()}"; string nativeCompilerPath = Path.Combine(vcToolChainDir, "bin", hostFolder, architecture.ToString().ToLower(), "cl.exe"); diff --git a/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs b/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs index bd53d0c65..0ce3c2d79 100644 --- a/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs +++ b/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs @@ -89,7 +89,11 @@ namespace Flax.Build.Platforms // Pick the newest installed Visual Studio version if using the default toolset if (toolsetVer == WindowsPlatformToolset.Default) { - if (VisualStudioInstance.HasIDE(VisualStudioVersion.VisualStudio2022)) + if (VisualStudioInstance.HasIDE(VisualStudioVersion.VisualStudio2026)) + { + toolsetVer = WindowsPlatformToolset.v145; + } + else if (VisualStudioInstance.HasIDE(VisualStudioVersion.VisualStudio2022)) { if (toolsets.Keys.Contains(WindowsPlatformToolset.v144)) { @@ -206,6 +210,7 @@ namespace Flax.Build.Platforms case WindowsPlatformToolset.v142: case WindowsPlatformToolset.v143: case WindowsPlatformToolset.v144: + case WindowsPlatformToolset.v145: { switch (Architecture) { @@ -392,6 +397,7 @@ namespace Flax.Build.Platforms var vcToolChainDir = toolsets[Toolset]; switch (Toolset) { + case WindowsPlatformToolset.v145: case WindowsPlatformToolset.v144: case WindowsPlatformToolset.v143: case WindowsPlatformToolset.v142: diff --git a/Source/Tools/Flax.Build/Projects/ProjectFormat.cs b/Source/Tools/Flax.Build/Projects/ProjectFormat.cs index 5f5017ced..3e5fd3a70 100644 --- a/Source/Tools/Flax.Build/Projects/ProjectFormat.cs +++ b/Source/Tools/Flax.Build/Projects/ProjectFormat.cs @@ -37,6 +37,11 @@ namespace Flax.Build.Projects /// VisualStudio2022, + /// + /// Visual Studio 2026. + /// + VisualStudio2026, + /// /// Visual Studio Code. /// diff --git a/Source/Tools/Flax.Build/Projects/ProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/ProjectGenerator.cs index fbbeca14d..a6e5b79ad 100644 --- a/Source/Tools/Flax.Build/Projects/ProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/ProjectGenerator.cs @@ -84,7 +84,11 @@ namespace Flax.Build.Projects // Pick the newest installed Visual Studio version if (format == ProjectFormat.VisualStudio) { - if (VisualStudioInstance.HasIDE(VisualStudioVersion.VisualStudio2022)) + if (VisualStudioInstance.HasIDE(VisualStudioVersion.VisualStudio2026)) + { + format = ProjectFormat.VisualStudio2026; + } + else if (VisualStudioInstance.HasIDE(VisualStudioVersion.VisualStudio2022)) { format = ProjectFormat.VisualStudio2022; } @@ -113,6 +117,7 @@ namespace Flax.Build.Projects case ProjectFormat.VisualStudio2017: case ProjectFormat.VisualStudio2019: case ProjectFormat.VisualStudio2022: + case ProjectFormat.VisualStudio2026: { VisualStudioVersion vsVersion; switch (format) @@ -129,6 +134,9 @@ namespace Flax.Build.Projects case ProjectFormat.VisualStudio2022: vsVersion = VisualStudioVersion.VisualStudio2022; break; + case ProjectFormat.VisualStudio2026: + vsVersion = VisualStudioVersion.VisualStudio2026; + break; default: throw new ArgumentOutOfRangeException(nameof(format), format, null); } switch (type) diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/CSProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/CSProjectGenerator.cs index 35bc7b0ac..99e3b083b 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/CSProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/CSProjectGenerator.cs @@ -50,7 +50,9 @@ namespace Flax.Build.Projects.VisualStudio projectTypes = ProjectTypeGuids.ToOption(ProjectTypeGuids.FlaxVS) + ';' + projectTypes; // Try to reuse the existing project guid from solution file - vsProject.ProjectGuid = GetProjectGuid(solutionPath, vsProject.Name); + vsProject.ProjectGuid = GetProjectGuid(vsProject.Path, vsProject.Name); + if (vsProject.ProjectGuid == Guid.Empty) + vsProject.ProjectGuid = GetProjectGuid(solutionPath, vsProject.Name); if (vsProject.ProjectGuid == Guid.Empty) vsProject.ProjectGuid = Guid.NewGuid(); diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/CSSDKProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/CSSDKProjectGenerator.cs index f126bd3ef..671cfb5e8 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/CSSDKProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/CSSDKProjectGenerator.cs @@ -55,7 +55,9 @@ namespace Flax.Build.Projects.VisualStudio } // Try to reuse the existing project guid from solution file - vsProject.ProjectGuid = GetProjectGuid(solutionPath, vsProject.Name); + vsProject.ProjectGuid = GetProjectGuid(vsProject.Path, vsProject.Name); + if (vsProject.ProjectGuid == Guid.Empty) + vsProject.ProjectGuid = GetProjectGuid(solutionPath, vsProject.Name); if (vsProject.ProjectGuid == Guid.Empty) vsProject.ProjectGuid = Guid.NewGuid(); diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/VCProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/VCProjectGenerator.cs index 2d7b23ce5..3791dfa21 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/VCProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/VCProjectGenerator.cs @@ -28,6 +28,7 @@ namespace Flax.Build.Projects.VisualStudio case VisualStudioVersion.VisualStudio2017: return "v141"; case VisualStudioVersion.VisualStudio2019: return "v142"; case VisualStudioVersion.VisualStudio2022: return "v143"; + case VisualStudioVersion.VisualStudio2026: return "v145"; } return string.Empty; } diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioInstance.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioInstance.cs index b3b67482f..16e5fee80 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioInstance.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioInstance.cs @@ -128,6 +128,8 @@ namespace Flax.Build.Projects.VisualStudio version = VisualStudioVersion.VisualStudio2019; else if (displayName.Contains("2022")) version = VisualStudioVersion.VisualStudio2022; + else if (displayName.Contains("2026")) + version = VisualStudioVersion.VisualStudio2026; else { Log.Warning(string.Format("Unknown Visual Studio installation. Display name: {0}", displayName)); diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs index 337f4497d..d657f6247 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs @@ -128,6 +128,7 @@ namespace Flax.Build.Projects.VisualStudio case VisualStudioVersion.VisualStudio2017: return "15.0"; case VisualStudioVersion.VisualStudio2019: return "16.0"; case VisualStudioVersion.VisualStudio2022: return "17.0"; + case VisualStudioVersion.VisualStudio2026: return "18.0"; } return string.Empty; @@ -193,7 +194,7 @@ namespace Flax.Build.Projects.VisualStudio } /// - public override string SolutionFileExtension => "sln"; + public override string SolutionFileExtension => /*Version >= VisualStudioVersion.VisualStudio2026 ? "slnx" :*/ "sln"; /// public override Project CreateProject() @@ -277,6 +278,20 @@ namespace Flax.Build.Projects.VisualStudio } } + if (Version >= VisualStudioVersion.VisualStudio2026) + GenerateXmlSolution(solution); + else + GenerateAsciiSolution(solution); + } + + private void GenerateXmlSolution(Solution solution) + { + // TODO: Generate the solution file in new format + GenerateAsciiSolution(solution); + } + + private void GenerateAsciiSolution(Solution solution) + { // Try to extract solution folder info from the existing solution file to make random IDs stable var solutionId = Guid.NewGuid(); var folderIds = new Dictionary(); @@ -313,7 +328,7 @@ namespace Flax.Build.Projects.VisualStudio var projects = solution.Projects.Cast().ToArray(); // Header - if (Version == VisualStudioVersion.VisualStudio2022) + if (Version >= VisualStudioVersion.VisualStudio2022) { vcSolutionFileContent.AppendLine("Microsoft Visual Studio Solution File, Format Version 12.00"); vcSolutionFileContent.AppendLine("# Visual Studio Version 17"); diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioVersion.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioVersion.cs index 236aa5d7e..e7fc03c71 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioVersion.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioVersion.cs @@ -26,5 +26,10 @@ namespace Flax.Build.Projects.VisualStudio /// The Visual Studio 2022. /// VisualStudio2022, + + /// + /// The Visual Studio 2026. + /// + VisualStudio2026, } } diff --git a/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs index c92ae6986..12b9d1e4e 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs @@ -648,6 +648,7 @@ namespace Flax.Build.Projects.VisualStudioCode json.AddField("**/Screenshots", true); json.AddField("**/Output", true); json.AddField("**/*.flax", true); + json.AddField("**/Plugins", true); json.EndObject(); // Extension settings @@ -683,9 +684,15 @@ namespace Flax.Build.Projects.VisualStudioCode json.EndObject(); // Referenced projects outside the current project (including engine too) - foreach (var project in Globals.Project.GetAllProjects()) + var projects = Globals.Project.GetAllProjects(); + // Move Engine to last for organizational purposes. + var engineProject = projects.First(x => x.Name == "Flax"); + var projectsWithoutEngine = projects.Where(x => x.Name != "Flax"); + projectsWithoutEngine = projectsWithoutEngine.OrderBy(x => x.Name); + var sortedProjects = projectsWithoutEngine.Concat([engineProject]); + foreach (var project in sortedProjects) { - if (!project.ProjectFolderPath.Contains(Globals.Project.ProjectFolderPath)) + if (!project.ProjectFolderPath.Equals(Globals.Project.ProjectFolderPath)) { json.BeginObject(); {