diff --git a/.github/ISSUE_TEMPLATE/1-bug.yaml b/.github/ISSUE_TEMPLATE/1-bug.yaml new file mode 100644 index 000000000..acae26c00 --- /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 repoduction projects! + - type: textarea + id: description-area + attributes: + label: Description + description: Please provide a description and what you expected to happen. + validations: + required: true + - type: textarea + id: steps-area + attributes: + label: Steps to reproduce + description: Please provide an repoduction steps. + 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..235c863e3 --- /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: benifits-area + attributes: + label: Benifits + description: Please provide what benifits 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/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 657d990e4..92f3bfd30 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 // Public accessors for lighting data, use them as data binding might change but those methods will remain. LightData GetDirectionalLight() { return DirectionalLight; } @@ -153,6 +154,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/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..954059a28 --- /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) + 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/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 19a0c1142..fe89026c5 100644 --- a/Source/Editor/Modules/UIModule.cs +++ b/Source/Editor/Modules/UIModule.cs @@ -37,6 +37,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; @@ -371,6 +418,8 @@ namespace FlaxEditor.Modules // Update window background mainWindow.BackgroundColor = Style.Current.Background; + + InitViewportScaleOptions(); InitSharedMenus(); InitMainMenu(mainWindow); @@ -392,6 +441,57 @@ namespace FlaxEditor.Modules } } + 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() { @@ -538,6 +638,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)); @@ -829,6 +930,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 @@ -1085,5 +1197,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/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..7fdeb6657 100644 --- a/Source/Editor/Surface/SurfaceNode.cs +++ b/Source/Editor/Surface/SurfaceNode.cs @@ -917,7 +917,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 +1075,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 +1083,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); } @@ -1133,7 +1133,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); 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.Input.cs b/Source/Editor/Surface/VisjectSurface.Input.cs index 891750fd7..51fd96ad6 100644 --- a/Source/Editor/Surface/VisjectSurface.Input.cs +++ b/Source/Editor/Surface/VisjectSurface.Input.cs @@ -292,7 +292,7 @@ namespace FlaxEditor.Surface if (_leftMouseDown) { // Connecting - if (_connectionInstigator != null) + if (_connectionInstigators.Count > 0) { } // Moving @@ -462,7 +462,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 @@ -608,7 +608,7 @@ namespace FlaxEditor.Surface _movingNodesDelta = Float2.Zero; } // Connecting - else if (_connectionInstigator != null) + else if (_connectionInstigators.Count > 0) { } // Selecting @@ -680,7 +680,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/Utilities/Utils.cs b/Source/Editor/Utilities/Utils.cs index 949479783..688cec470 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 59c980156..44b3b4ea0 100644 --- a/Source/Editor/Utilities/ViewportIconsRenderer.cpp +++ b/Source/Editor/Utilities/ViewportIconsRenderer.cpp @@ -66,13 +66,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 b4af43281..0f9fce7c0 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -1292,6 +1292,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(); } diff --git a/Source/Editor/Viewport/PrefabWindowViewport.cs b/Source/Editor/Viewport/PrefabWindowViewport.cs index b1b86008d..39c126c37 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/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/ContentWindow.Search.cs b/Source/Editor/Windows/ContentWindow.Search.cs index f28dc4834..5a0ed63aa 100644 --- a/Source/Editor/Windows/ContentWindow.Search.cs +++ b/Source/Editor/Windows/ContentWindow.Search.cs @@ -115,6 +115,7 @@ namespace FlaxEditor.Windows var root = _root; root.LockChildrenRecursive(); + PerformLayout(); // Update tree var query = _foldersSearchBox.Text; diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs index 3d2ec4a66..d0cf84251 100644 --- a/Source/Editor/Windows/ContentWindow.cs +++ b/Source/Editor/Windows/ContentWindow.cs @@ -1085,6 +1085,8 @@ namespace FlaxEditor.Windows if (Editor.ContentDatabase.Find(_lastViewedFolderBeforeReload) is ContentFolder folder) _tree.Select(folder.Node); } + + OnFoldersSearchBoxTextChanged(); } private void Refresh() diff --git a/Source/Editor/Windows/GameWindow.cs b/Source/Editor/Windows/GameWindow.cs index 080744c2f..967859d66 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() { @@ -1233,8 +931,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()); } /// @@ -1246,22 +944,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 d74c52623..52555f564 100644 --- a/Source/Editor/Windows/OutputLogWindow.cs +++ b/Source/Editor/Windows/OutputLogWindow.cs @@ -246,7 +246,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 => @@ -320,12 +320,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/Editor/Windows/SceneTreeWindow.cs b/Source/Editor/Windows/SceneTreeWindow.cs index 75a3723cf..ab6d2e262 100644 --- a/Source/Editor/Windows/SceneTreeWindow.cs +++ b/Source/Editor/Windows/SceneTreeWindow.cs @@ -68,6 +68,7 @@ namespace FlaxEditor.Windows TooltipText = "Search the scene tree.\n\nYou can prefix your search with different search operators:\ns: -> Actor with script of type\na: -> Actor type\nc: -> Control type", }; _searchBox.TextChanged += OnSearchBoxTextChanged; + ScriptsBuilder.ScriptsReloadEnd += OnSearchBoxTextChanged; // Scene tree panel _sceneTreePanel = new Panel @@ -112,7 +113,7 @@ namespace FlaxEditor.Windows InputActions.Add(options => options.LockFocusSelection, () => Editor.Windows.EditWin.Viewport.LockFocusSelection()); InputActions.Add(options => options.Rename, RenameSelection); } - + /// public override void OnPlayBeginning() { @@ -125,6 +126,7 @@ namespace FlaxEditor.Windows { base.OnPlayBegin(); _blockSceneTreeScroll = false; + OnSearchBoxTextChanged(); } /// @@ -139,6 +141,7 @@ namespace FlaxEditor.Windows { base.OnPlayEnd(); _blockSceneTreeScroll = true; + OnSearchBoxTextChanged(); } /// @@ -174,6 +177,7 @@ namespace FlaxEditor.Windows return; _tree.LockChildrenRecursive(); + PerformLayout(); // Update tree var query = _searchBox.Text; @@ -599,6 +603,7 @@ namespace FlaxEditor.Windows _dragHandlers = null; _tree = null; _searchBox = null; + ScriptsBuilder.ScriptsReloadEnd -= OnSearchBoxTextChanged; base.OnDestroy(); } diff --git a/Source/Engine/Audio/AudioSource.cpp b/Source/Engine/Audio/AudioSource.cpp index 98ee0f9c0..c8b0f3bab 100644 --- a/Source/Engine/Audio/AudioSource.cpp +++ b/Source/Engine/Audio/AudioSource.cpp @@ -168,8 +168,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."); } } @@ -408,6 +408,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; @@ -423,6 +426,7 @@ void AudioSource::Update() { Stop(); } + ClipFinished(); } } @@ -493,6 +497,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 9d6d28ab4..58903912c 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/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/Debug/DebugCommands.cpp b/Source/Engine/Debug/DebugCommands.cpp index d36c8b489..fa171d5dd 100644 --- a/Source/Engine/Debug/DebugCommands.cpp +++ b/Source/Engine/Debug/DebugCommands.cpp @@ -460,6 +460,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/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 27943942b..26b473e09 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/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp index 551f6fba9..7d07cd0e7 100644 --- a/Source/Engine/Level/Actor.cpp +++ b/Source/Engine/Level/Actor.cpp @@ -1185,14 +1185,11 @@ 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()); - } } } } @@ -1757,7 +1754,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) @@ -2060,19 +2057,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/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/WheeledVehicle.cpp b/Source/Engine/Physics/Actors/WheeledVehicle.cpp index a4259a8c8..42da0c7c1 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 916a9f9ee..3e857ba23 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/Collider.cpp b/Source/Engine/Physics/Colliders/Collider.cpp index 7a5918c49..f93a9bd88 100644 --- a/Source/Engine/Physics/Colliders/Collider.cpp +++ b/Source/Engine/Physics/Colliders/Collider.cpp @@ -246,7 +246,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/Renderer/ShadowsPass.cpp b/Source/Engine/Renderer/ShadowsPass.cpp index ea26eb043..e391ef4a9 100644 --- a/Source/Engine/Renderer/ShadowsPass.cpp +++ b/Source/Engine/Renderer/ShadowsPass.cpp @@ -944,7 +944,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 290c803d2..1328e8a4a 100644 --- a/Source/Engine/Renderer/VolumetricFogPass.cpp +++ b/Source/Engine/Renderer/VolumetricFogPass.cpp @@ -167,6 +167,8 @@ bool VolumetricFogPass::Init(RenderContext& renderContext, GPUContext* context, (float)_cache.GridSizeZ); auto& fogData = renderContext.Buffers->VolumetricFogData; fogData.MaxDistance = options.Distance; + if (renderContext.Task->IsCameraCut) + _cache.HistoryWeight = 0.0f; // Init data (partial, without directional light or sky light data); GBufferPass::SetInputs(renderContext.View, _cache.Data.GBuffer); @@ -516,7 +518,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 cc77f2337..2f7b9f9d6 100644 --- a/Source/Engine/Scripting/Scripting.cpp +++ b/Source/Engine/Scripting/Scripting.cpp @@ -916,6 +916,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/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 cdcd799b3..0ae1e0639 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -1521,6 +1521,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(); @@ -1546,11 +1549,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); @@ -1612,9 +1626,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) { @@ -1628,8 +1649,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/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/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/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 18459ddb5..4c9d521b4 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 3bb84760e..970d9697a 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 1979c4748..fe5abe4a3 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 4fc77e760..1a52274a7 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(); {