From 861657d7ce6be2cdf2920e364025cdbeb01c1643 Mon Sep 17 00:00:00 2001 From: "adnan.mujkic" Date: Sat, 12 Sep 2026 15:25:34 +0200 Subject: [PATCH] Fixes for removal mode --- Content/Prefabs/Bush1.uasset | 4 +- Content/Prefabs/Bush1_FlowerRed.uasset | 4 +- Content/Prefabs/Bush1_FlowerWhite.uasset | 4 +- Content/Prefabs/Bush1_FlowerYellow.uasset | 4 +- Content/Prefabs/Bush2.uasset | 4 +- Content/Prefabs/Bush2_FlowerRed.uasset | 4 +- Content/Prefabs/Bush2_FlowerWhite.uasset | 4 +- Content/Prefabs/Bush2_FlowerYellow.uasset | 4 +- Content/Prefabs/Bush3.uasset | 4 +- Content/Prefabs/Bush3_FlowerRed.uasset | 4 +- Content/Prefabs/Bush3_FlowerWhite.uasset | 4 +- Content/Prefabs/Bush3_FlowerYellow.uasset | 4 +- Content/Prefabs/Bush4.uasset | 4 +- Content/Prefabs/Bush4_FlowerRed.uasset | 4 +- Content/Prefabs/Bush4_FlowerWhite.uasset | 4 +- Content/Prefabs/Bush4_FlowerYellow.uasset | 4 +- Content/Prefabs/FencePlaceable.uasset | 4 +- .../Prefabs/Flowers/BP_FlowersYellow.uasset | 4 +- .../Flowers/FoliageActor2Normal.uasset | 4 +- .../Prefabs/Flowers/FoliageActor2RED.uasset | 4 +- .../Prefabs/Flowers/FoliageActor2WHITE.uasset | 4 +- .../Prefabs/Rocks/BP_RemovableRock1.uasset | 4 +- .../Prefabs/Rocks/BP_RemovableRock2.uasset | 4 +- .../Prefabs/Rocks/BP_RemovableRock3.uasset | 4 +- .../Prefabs/Rocks/BP_RemovableRock4.uasset | 4 +- Content/Prefabs/Tree2.uasset | 4 +- Content/Prefabs/Tree3.uasset | 4 +- Content/Prefabs/Tree4.uasset | 4 +- .../C/G7/KMNTOSL8H9XWZ8XFJ0YGJ8.uasset | 2 +- .../Components/MoveActorsComponent.cpp | 158 +++++++++++++++++- .../Components/MoveActorsComponent.h | 71 +++++++- .../GameObjects/RemovableStaticMeshActor.cpp | 36 +++- .../GameObjects/RemovableStaticMeshActor.h | 21 ++- 33 files changed, 330 insertions(+), 70 deletions(-) diff --git a/Content/Prefabs/Bush1.uasset b/Content/Prefabs/Bush1.uasset index 5c33ae46..a27a1ab2 100644 --- a/Content/Prefabs/Bush1.uasset +++ b/Content/Prefabs/Bush1.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7d8178a62956b474aa750f2fbf0998eb50f998124ef4bc5e62aad2062c40b4cf -size 30244 +oid sha256:3d52e7144e50f9fd413c2125e2a3c691eddace2e872a051f14bf81d41286a699 +size 33609 diff --git a/Content/Prefabs/Bush1_FlowerRed.uasset b/Content/Prefabs/Bush1_FlowerRed.uasset index 05afc67e..8ae6d981 100644 --- a/Content/Prefabs/Bush1_FlowerRed.uasset +++ b/Content/Prefabs/Bush1_FlowerRed.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8f0e0c8a42b9cdee488fdb86ac474627baa1e805d493ec9b3e1553c22e405ebf -size 33446 +oid sha256:83016f362f2c2e43ec1b115d7e91a16a5d5accc9f9f14390d85e897dc113beee +size 37057 diff --git a/Content/Prefabs/Bush1_FlowerWhite.uasset b/Content/Prefabs/Bush1_FlowerWhite.uasset index 52b6eaed..77ab6ce6 100644 --- a/Content/Prefabs/Bush1_FlowerWhite.uasset +++ b/Content/Prefabs/Bush1_FlowerWhite.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9fa49115a2d3173310f947108ba56535ebe68a27cf3c1f847bda1f58ec27ad02 -size 33699 +oid sha256:c45ad76500b4996f04ffd4575843ab294287b521996df9f78254913e58b36bae +size 37544 diff --git a/Content/Prefabs/Bush1_FlowerYellow.uasset b/Content/Prefabs/Bush1_FlowerYellow.uasset index 1f3f546e..be0e1858 100644 --- a/Content/Prefabs/Bush1_FlowerYellow.uasset +++ b/Content/Prefabs/Bush1_FlowerYellow.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bdc2847766d43b607f7ee32d9a65fa1cdff9b8c4f3beab391d7fbaff5c5c7f16 -size 33693 +oid sha256:11b2d55a4e7bc19899f8ea6fe113385810f0aa1a5c0b7bd4a3263814d42d2ad7 +size 37459 diff --git a/Content/Prefabs/Bush2.uasset b/Content/Prefabs/Bush2.uasset index 29cecc16..3b994ace 100644 --- a/Content/Prefabs/Bush2.uasset +++ b/Content/Prefabs/Bush2.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:01841d66a89344651dfbc4f8f29a7451ed8bd694388c69b91974295b3afca281 -size 33207 +oid sha256:145457e63bb81f1ec93ca45e51dec9e83ca4fe359b3420b583d6d22f31b783fd +size 37994 diff --git a/Content/Prefabs/Bush2_FlowerRed.uasset b/Content/Prefabs/Bush2_FlowerRed.uasset index e37a5211..682d13a1 100644 --- a/Content/Prefabs/Bush2_FlowerRed.uasset +++ b/Content/Prefabs/Bush2_FlowerRed.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:444ee2a32974811b35cd85a125aa36eefd92ea6b6ad7c7fb1aac07b2716f02bd -size 35835 +oid sha256:b2c098fe9d67c2543ea4302e074d737c8d904c78ff265a097deecd5a4135862b +size 40499 diff --git a/Content/Prefabs/Bush2_FlowerWhite.uasset b/Content/Prefabs/Bush2_FlowerWhite.uasset index d440db41..8d223a15 100644 --- a/Content/Prefabs/Bush2_FlowerWhite.uasset +++ b/Content/Prefabs/Bush2_FlowerWhite.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5dfc607227234354f0413e17bd0f22df340154c9998cefb5a6a279e0644f5d65 -size 36105 +oid sha256:4c62c508ea96af6a9f65a8fab5b926b884563722332fd9b35e931d0095acaeae +size 41004 diff --git a/Content/Prefabs/Bush2_FlowerYellow.uasset b/Content/Prefabs/Bush2_FlowerYellow.uasset index b5b6d316..6b78e9f6 100644 --- a/Content/Prefabs/Bush2_FlowerYellow.uasset +++ b/Content/Prefabs/Bush2_FlowerYellow.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0a14036773b81218b6a6003fe31058d7311b08f4918f58eb4bd427ea7840078a -size 36117 +oid sha256:b26a67f6f6f174816a1661fc91baddcccde3b4340a9fe958199e92bf5c50f5ba +size 40961 diff --git a/Content/Prefabs/Bush3.uasset b/Content/Prefabs/Bush3.uasset index 00c17237..59202e0b 100644 --- a/Content/Prefabs/Bush3.uasset +++ b/Content/Prefabs/Bush3.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c40960d20b2abbcd1bc8d7774ee51ffacfc553cac8d18f8a2bcb5e9018989752 -size 29861 +oid sha256:de81fb850bb3fe4710161a2de9dc7ccbff6129debc444da91042b3d7eac552e1 +size 32249 diff --git a/Content/Prefabs/Bush3_FlowerRed.uasset b/Content/Prefabs/Bush3_FlowerRed.uasset index 5992b057..5c310d6e 100644 --- a/Content/Prefabs/Bush3_FlowerRed.uasset +++ b/Content/Prefabs/Bush3_FlowerRed.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4f4d11dade38cc59aba5da963cf28a2855e829420d0427b90a7aa04e4531aff8 -size 31943 +oid sha256:80e6bd68e4f5c1faf2d387fee47ac0cc8f478c219e5da3f17d647856c65c9502 +size 34535 diff --git a/Content/Prefabs/Bush3_FlowerWhite.uasset b/Content/Prefabs/Bush3_FlowerWhite.uasset index aacd6f36..8c15e42f 100644 --- a/Content/Prefabs/Bush3_FlowerWhite.uasset +++ b/Content/Prefabs/Bush3_FlowerWhite.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cb8cbb9c38f71d44d76db16d98a7753d09b957a700f8b43adebc31d43336096e -size 32013 +oid sha256:1d8ce627d252b61977f537605938d8386bb4652917ef56a5c6a658ffb95f1ab7 +size 34780 diff --git a/Content/Prefabs/Bush3_FlowerYellow.uasset b/Content/Prefabs/Bush3_FlowerYellow.uasset index 5178eeb8..dfaec920 100644 --- a/Content/Prefabs/Bush3_FlowerYellow.uasset +++ b/Content/Prefabs/Bush3_FlowerYellow.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:81c936429eb96a26f1f2d0a30cb255725ca725976381d556904ea9cb8c0184e7 -size 32228 +oid sha256:8d0022855c7f905b78af1f5e1d859f54c4cf61c08fb07ec0104773f02d8843dc +size 34961 diff --git a/Content/Prefabs/Bush4.uasset b/Content/Prefabs/Bush4.uasset index 9a9c6e0d..6176f660 100644 --- a/Content/Prefabs/Bush4.uasset +++ b/Content/Prefabs/Bush4.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3227a54a5387ab20dba9d9e8c6f772b0766565145e1b0b2c05f78842e03f1a7d -size 32700 +oid sha256:b05ad9d43df619a1847d45d7fb6924e1dfaaccb9f6dfcf647f63073867948af4 +size 37204 diff --git a/Content/Prefabs/Bush4_FlowerRed.uasset b/Content/Prefabs/Bush4_FlowerRed.uasset index 0a85d5f0..23ec60ef 100644 --- a/Content/Prefabs/Bush4_FlowerRed.uasset +++ b/Content/Prefabs/Bush4_FlowerRed.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:13f839c8c2154df08d8de0e46c7eaf5808f9452e0513686293f2ebf839532033 -size 34394 +oid sha256:2fd4d178fa7c71230ecd9dea97aece48c4863a8c6b51349cb6003373a7f36fe6 +size 38371 diff --git a/Content/Prefabs/Bush4_FlowerWhite.uasset b/Content/Prefabs/Bush4_FlowerWhite.uasset index ac7f34cc..1d201243 100644 --- a/Content/Prefabs/Bush4_FlowerWhite.uasset +++ b/Content/Prefabs/Bush4_FlowerWhite.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:221f8890e955314be21ead7cd1d0eecdbadd2834e0e5387e68d3c7143ba75df0 -size 34688 +oid sha256:c915628763ee97838d13d472e6234d4059a134a4c8683a49be64ebd07af40421 +size 38953 diff --git a/Content/Prefabs/Bush4_FlowerYellow.uasset b/Content/Prefabs/Bush4_FlowerYellow.uasset index df7556f4..dc7582dd 100644 --- a/Content/Prefabs/Bush4_FlowerYellow.uasset +++ b/Content/Prefabs/Bush4_FlowerYellow.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ae9ee2e4d21eb782a664f0639ccc473fa69951e3c3c2ad83ea898f32f0c808f3 -size 34213 +oid sha256:ac2dd309e7044cb1300c077aaac3abd3f572ce62e3c6a2206bb201b9ede518e3 +size 38421 diff --git a/Content/Prefabs/FencePlaceable.uasset b/Content/Prefabs/FencePlaceable.uasset index a67b99f3..fe1399c6 100644 --- a/Content/Prefabs/FencePlaceable.uasset +++ b/Content/Prefabs/FencePlaceable.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:338d6b4322b4b7398b431e8a40e4c88f0e8f09953c766255de8980af027062de -size 27891 +oid sha256:0e0c93e343f7e45f12797ba50e7085ddc2494d69258fcb61b509ff8d924d4662 +size 27882 diff --git a/Content/Prefabs/Flowers/BP_FlowersYellow.uasset b/Content/Prefabs/Flowers/BP_FlowersYellow.uasset index 1b95b015..df909d7e 100644 --- a/Content/Prefabs/Flowers/BP_FlowersYellow.uasset +++ b/Content/Prefabs/Flowers/BP_FlowersYellow.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:72f1bd7360b36ef3132c8fece23054d5ccba2fdbacfd3874eced5440d9d76fd9 -size 41757 +oid sha256:c0f3ea78f6ab878b8f1224142f1d3eb52a4a64327e6e2f5f6a410215d6652b21 +size 42624 diff --git a/Content/Prefabs/Flowers/FoliageActor2Normal.uasset b/Content/Prefabs/Flowers/FoliageActor2Normal.uasset index c50dc507..33c27139 100644 --- a/Content/Prefabs/Flowers/FoliageActor2Normal.uasset +++ b/Content/Prefabs/Flowers/FoliageActor2Normal.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3f13f471fde06ec501897a79e737d11a4c32ea0a5a1bca8728ac12ff94521b17 -size 68243 +oid sha256:51264f827d3618a3f3f1b5418f5da0ebaad6fa224f1b25297a6c1dc625c2e832 +size 69758 diff --git a/Content/Prefabs/Flowers/FoliageActor2RED.uasset b/Content/Prefabs/Flowers/FoliageActor2RED.uasset index 8b16f0d3..c0905270 100644 --- a/Content/Prefabs/Flowers/FoliageActor2RED.uasset +++ b/Content/Prefabs/Flowers/FoliageActor2RED.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:52fe813f18d6088ee0bd301aa3ae8de5b06ba53d4068e8a74e3b0cf716c6464c -size 68944 +oid sha256:88417ba3da8b4f530c204cb8a2204876cb86e88ae140f29970c4193942cd6ddf +size 70625 diff --git a/Content/Prefabs/Flowers/FoliageActor2WHITE.uasset b/Content/Prefabs/Flowers/FoliageActor2WHITE.uasset index 31aec2f3..b1086cd2 100644 --- a/Content/Prefabs/Flowers/FoliageActor2WHITE.uasset +++ b/Content/Prefabs/Flowers/FoliageActor2WHITE.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5eee720821bbe8e4814b1565cf524825242c5952690058e1854da82ffd3d06d7 -size 72982 +oid sha256:5e817d985221e008950a643c7c7a40e58846e32227020bc5aff7738e659e31a8 +size 78127 diff --git a/Content/Prefabs/Rocks/BP_RemovableRock1.uasset b/Content/Prefabs/Rocks/BP_RemovableRock1.uasset index b1b8ed48..f04f7d3a 100644 --- a/Content/Prefabs/Rocks/BP_RemovableRock1.uasset +++ b/Content/Prefabs/Rocks/BP_RemovableRock1.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a3b8f9d48a5e034432617087b9e6f71ed16107d5ec414e98af73f607d2be9d30 -size 34378 +oid sha256:ea45382c4b22ec7b8a4b64139165dfe79e47d1d460e9ed82e1b1e60ae1fe8d75 +size 37480 diff --git a/Content/Prefabs/Rocks/BP_RemovableRock2.uasset b/Content/Prefabs/Rocks/BP_RemovableRock2.uasset index e7b93584..43b866cb 100644 --- a/Content/Prefabs/Rocks/BP_RemovableRock2.uasset +++ b/Content/Prefabs/Rocks/BP_RemovableRock2.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:724fc068a66816174ac733b50cab908d9a5482e026c9d42d4fd566282d63e0c5 -size 34599 +oid sha256:c83e19e7012c0687d0eb1288c28dc8b7e25326c863f27550f9cc6e2abfa61609 +size 37757 diff --git a/Content/Prefabs/Rocks/BP_RemovableRock3.uasset b/Content/Prefabs/Rocks/BP_RemovableRock3.uasset index 2b9e9fed..71e7a760 100644 --- a/Content/Prefabs/Rocks/BP_RemovableRock3.uasset +++ b/Content/Prefabs/Rocks/BP_RemovableRock3.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5764fcf3ae72cf5ea52793e6c8dba319424fa1043d6049069b20229548df0f8d -size 34646 +oid sha256:3409c05191048d749f35157d2962a4fc64677fc48816272fd3eec70b2914925f +size 37851 diff --git a/Content/Prefabs/Rocks/BP_RemovableRock4.uasset b/Content/Prefabs/Rocks/BP_RemovableRock4.uasset index 478c27c4..616d8376 100644 --- a/Content/Prefabs/Rocks/BP_RemovableRock4.uasset +++ b/Content/Prefabs/Rocks/BP_RemovableRock4.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d9224a44d43491f58aa4b83295e40e3d299d30306da02ac92af2123970815beb -size 37896 +oid sha256:d6ec4f4ce06894bc61e0085f2da9f053ed4fc30bb8b061b0b496a988565fd57e +size 40271 diff --git a/Content/Prefabs/Tree2.uasset b/Content/Prefabs/Tree2.uasset index 18c4642a..d02db181 100644 --- a/Content/Prefabs/Tree2.uasset +++ b/Content/Prefabs/Tree2.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f7c1a8c45949edf4b7e178a2d7dee64279bb0e0c19c86f7ce14f0eed27262b7d -size 94139 +oid sha256:1fd06752fa9f82ccbc911e5a0652c22d9e7de385bca2acecce8974b8cbbc10b0 +size 98042 diff --git a/Content/Prefabs/Tree3.uasset b/Content/Prefabs/Tree3.uasset index f3de63a9..ea7eb342 100644 --- a/Content/Prefabs/Tree3.uasset +++ b/Content/Prefabs/Tree3.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:85ce03522066a15e7a00527999b6e6d1a88b4d0ae2a9379a30e8c00a65f134e5 -size 90699 +oid sha256:14d6028b383a405d72325b8c4dac3bdb1f0cd231e98e1809f560f9815deb45dc +size 89530 diff --git a/Content/Prefabs/Tree4.uasset b/Content/Prefabs/Tree4.uasset index a81e0eb1..698351b0 100644 --- a/Content/Prefabs/Tree4.uasset +++ b/Content/Prefabs/Tree4.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a48daef722a1c7273b1fe4ba8a23c43592ab52ba8d62aa73b4b795b9f6b2e35f -size 38584 +oid sha256:ee28d85e2219a928ca089757e236bf895d6e7ba18ec821b0c4de08903a94db34 +size 41902 diff --git a/Content/__ExternalActors__/Maps/Persistent_GameWorld/C/G7/KMNTOSL8H9XWZ8XFJ0YGJ8.uasset b/Content/__ExternalActors__/Maps/Persistent_GameWorld/C/G7/KMNTOSL8H9XWZ8XFJ0YGJ8.uasset index 4c206f17..b3aa0080 100644 --- a/Content/__ExternalActors__/Maps/Persistent_GameWorld/C/G7/KMNTOSL8H9XWZ8XFJ0YGJ8.uasset +++ b/Content/__ExternalActors__/Maps/Persistent_GameWorld/C/G7/KMNTOSL8H9XWZ8XFJ0YGJ8.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:836b837f7a72fba802585d957402fb9add94a967807e075e1e0eef61cf80afce +oid sha256:a5a9a8efe49279e488d133c41821714d083941cfc166ce047df49fc7d96e3027 size 7675 diff --git a/Source/ProjectEleri/Components/MoveActorsComponent.cpp b/Source/ProjectEleri/Components/MoveActorsComponent.cpp index 5698148a..efcd9455 100644 --- a/Source/ProjectEleri/Components/MoveActorsComponent.cpp +++ b/Source/ProjectEleri/Components/MoveActorsComponent.cpp @@ -4,7 +4,6 @@ #include "MoveActorsComponent.h" #include "../System/Subsystem/ObjectPersistenceSubsystem.h" #include "../System/MainBlueprintFunctionLibrary.h" -#include "Kismet/KismetMathLibrary.h" #include "Kismet/GameplayStatics.h" #include "EnhancedInputSubsystems.h" #include "../GameObjects/RemovableStaticMeshActor.h" @@ -14,6 +13,17 @@ #include "Engine/StaticMeshActor.h" #include "LandscapeProxy.h" +namespace +{ + /** Snaps a single coordinate onto the grid lattice. The grid lines run through the world origin, + * exactly like UKismetMathLibrary::Vector_SnappedToGrid, but on one axis at a time and in double + * precision so that the result does not depend on the size of the world. */ + double SnapCoordinateToGrid(double Coordinate, double GridSnap) + { + return GridSnap <= 0.0 ? Coordinate : FMath::RoundToDouble(Coordinate / GridSnap) * GridSnap; + } +} + // Sets default values for this component's properties UMoveActorsComponent::UMoveActorsComponent() { @@ -66,17 +76,27 @@ void UMoveActorsComponent::TickComponent(float DeltaTime, ELevelTick TickType, F // Prefer the per-actor tuning values, falling back to the component defaults. const float Distance = MovingActor->DistanceFromActor > 0.f ? MovingActor->DistanceFromActor : PlacementDistance; - const float GridSnap = MovingActor->GridScale > 0.f ? MovingActor->GridScale : GridSnapSize; - FVector PosVector = PlayerCharacter->GetActorLocation() + (ForwardVec * Distance) + FVector(0.f, 0.f, PlacementHeightOffset); - - if (bGridMovementActive) - { - PosVector = UKismetMathLibrary::Vector_SnappedToGrid(PosVector, GridSnap); - } + // The grid pitch is the base cell size of this component grown by the whole number multiplier of the + // object that is being moved, so an object that reserves more room still lands on the same lattice. + const double GridSnap = GetGridPitchForActor(MovingActor); const FVector TargetScale = (MovingActor->ScaleOverride != FVector::ZeroVector) ? MovingActor->ScaleOverride : MovingActor->GetActorScale(); - MovingActor->SetActorTransform(FTransform(MovingActor->GetActorRotation(), PosVector, TargetScale)); + + // The object is only snapped in the grid plane. Its height is decided by the surface probe below, so + // snapping the hover position on Z as well would make the object jump in grid sized steps while + // aiming, long before it ever touches a surface. + FVector PosVector = PlayerCharacter->GetActorLocation() + (ForwardVec * Distance) + FVector(0.f, 0.f, PlacementHeightOffset); + + FRotator TargetRotation = MovingActor->GetActorRotation(); + if (bGridMovementActive && GridRotationSnapDegrees > 0.f) + { + // A rotated object does not cover whole cells, which is what pulls it out of sync with the + // objects that are placed around it, so its yaw is quantized while the grid is active. + TargetRotation.Yaw = SnapCoordinateToGrid(TargetRotation.Yaw, GridRotationSnapDegrees); + } + + MovingActor->SetActorLocation(SnapFootprintToGrid(PosVector, TargetScale, TargetRotation, GridSnap)); // Look for a surface to rest the object on. The probe starts above the object so it can // also land on top of already placed actors (allowing objects to be stacked on each other) @@ -262,6 +282,126 @@ FBoxSphereBounds UMoveActorsComponent::GetMovingActorMeshBounds() const return FBoxSphereBounds(MovingActor->GetActorLocation(), FVector::ZeroVector, 0.0); } +FVector UMoveActorsComponent::SnapFootprintToGrid(const FVector& InLocation, const FVector& TargetScale, const FRotator& TargetRotation, double GridSnap) +{ + if (!bGridMovementActive || GridSnap <= 0.0 || !IsValid(MovingActor)) + { + return InLocation; + } + + // The rotation and the scale are applied before the footprint is measured: both of them change the + // bounds that the footprint is derived from. + MovingActor->SetActorTransform(FTransform(TargetRotation, InLocation, TargetScale)); + + if (GridFitMode == EPlaceableGridFitMode::Pivot) + { + return FVector( + SnapCoordinateToGrid(InLocation.X, GridSnap), + SnapCoordinateToGrid(InLocation.Y, GridSnap), + InLocation.Z); + } + + FBoxSphereBounds MeshBounds = GetMovingActorMeshBounds(); + const FVector Size = MeshBounds.BoxExtent * 2.0; + + // An object without a mesh has no footprint to align, so it keeps the plain pivot snap. + if (Size.IsNearlyZero()) + { + return FVector( + SnapCoordinateToGrid(InLocation.X, GridSnap), + SnapCoordinateToGrid(InLocation.Y, GridSnap), + InLocation.Z); + } + + // A mesh that is not an exact multiple of the grid still owns whole cells: 175cm on a 50cm grid is + // four cells. Whole cells keep every object on the same lattice, so neighbours are always a whole + // number of cells apart and can never drift. Rounding up (instead of to the nearest cell) is what + // guarantees that an object fits inside the cells it claims and can never overlap the object next to + // it - the price is a symmetric margin inside the cell instead of a different gap for every object. + // This is what makes it possible to use meshes that were not modelled to the grid. + const double CellsX = GetGridCellCount(Size.X, GridSnap); + const double CellsY = GetGridCellCount(Size.Y, GridSnap); + + if (GridFitMode == EPlaceableGridFitMode::FootprintFitted) + { + // The smaller of the two axis factors is used so that the mesh never spills over a cell and its + // proportions are kept. A mesh that would need a bigger change than GridFitMaxScaleDelta keeps + // its margin instead of being distorted. + const double MaxDelta = GridFitMaxScaleDelta; + const double FitScale = FMath::Min( + (CellsX * GridSnap) / FMath::Max(Size.X, 1.0), + (CellsY * GridSnap) / FMath::Max(Size.Y, 1.0)); + + const double ClampedFitScale = FMath::Clamp(FitScale, 1.0 - MaxDelta, 1.0 + MaxDelta); + if (!FMath::IsNearlyEqual(ClampedFitScale, 1.0, 0.001)) + { + MovingActor->SetActorScale3D(TargetScale * ClampedFitScale); + MeshBounds = GetMovingActorMeshBounds(); + } + } + + // The bounding box is centered in the cells it occupies instead of the pivot being put on a grid + // line, so meshes whose pivot sits in a corner line up with the rest of the world. Z is untouched. + const double BlockMinX = SnapCoordinateToGrid(MeshBounds.Origin.X - MeshBounds.BoxExtent.X, GridSnap); + const double BlockMinY = SnapCoordinateToGrid(MeshBounds.Origin.Y - MeshBounds.BoxExtent.Y, GridSnap); + + const FVector DesiredCenter( + BlockMinX + (CellsX * GridSnap) * 0.5, + BlockMinY + (CellsY * GridSnap) * 0.5, + MeshBounds.Origin.Z); + + return InLocation + (DesiredCenter - MeshBounds.Origin); +} + +FVector2D UMoveActorsComponent::GetMovingActorGridFootprint() const +{ + const double GridSnap = GetGridPitchForActor(MovingActor); + if (GridSnap <= 0.0) + { + return FVector2D::ZeroVector; + } + + const FVector Size = GetMovingActorMeshBounds().BoxExtent * 2.0; + if (Size.IsNearlyZero()) + { + return FVector2D::ZeroVector; + } + + return FVector2D( + GetGridCellCount(Size.X, GridSnap), + GetGridCellCount(Size.Y, GridSnap)); +} + +double UMoveActorsComponent::GetGridPitchForActor(const ARemovableStaticMeshActor* Actor) const +{ + return GetGridPitch(Actor, GridSnapSize); +} + +double UMoveActorsComponent::GetGridPitch(const ARemovableStaticMeshActor* Actor, double BaseGridSnapSize) +{ + // The multiplier is clamped here instead of being trusted from the property: it is writable from + // Blueprint, where the editor-only ClampMin does not apply, and a zero multiplier would turn the cell + // size into zero, which reads as "no grid" in every bit of maths below. + const double Multiplier = Actor ? static_cast(Actor->GetGridSizeMultiplier()) : 1.0; + + return BaseGridSnapSize * Multiplier; +} + +double UMoveActorsComponent::GetGridCellCount(double Size, double GridSnap) +{ + if (GridSnap <= 0.0) + { + return 1.0; + } + + // The slack keeps a mesh that is exactly as wide as a whole number of cells (or a hair wider, since + // mesh bounds are never exact) from claiming an extra cell, which would leave a gap the size of a + // whole cell next to it. + constexpr double CellCountTolerance = 0.001; + + return FMath::Max(1.0, FMath::CeilToDouble(Size / GridSnap - CellCountTolerance)); +} + void UMoveActorsComponent::ToggleGridMovement() { bGridMovementActive = !bGridMovementActive; diff --git a/Source/ProjectEleri/Components/MoveActorsComponent.h b/Source/ProjectEleri/Components/MoveActorsComponent.h index 786080dd..827a161a 100644 --- a/Source/ProjectEleri/Components/MoveActorsComponent.h +++ b/Source/ProjectEleri/Components/MoveActorsComponent.h @@ -22,6 +22,22 @@ enum class ERemovalAction : uint8 MAX }; +/** How the object being moved is aligned with the placement grid. Meshes are rarely exact multiples of + * the grid pitch, which is what makes placed objects drift out of sync with the objects around them. */ +UENUM(BlueprintType) +enum class EPlaceableGridFitMode : uint8 +{ + /** Snap the actor pivot to the grid. Only correct when every pivot sits at the center of its mesh. */ + Pivot, + /** Quantize the mesh' footprint to whole grid cells (rounded up, so the mesh always fits inside the + * cells it claims) and center it in them. A 175cm mesh on a 50cm grid claims four cells (200cm) and + * keeps a 12.5cm margin on either side, so neighbours can never drift apart. */ + FootprintCentered, + /** As FootprintCentered, but the mesh is also grown (within GridFitMaxScaleDelta) so that it fills + * the cells it occupies exactly, which removes the margin. */ + FootprintFitted +}; + class ARemovableStaticMeshActor; class AEleriPlayerController; class AMyCharacter; @@ -53,8 +69,33 @@ public: UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Placement") float PlacementHeightOffset = 100.f; + /** Default size of one grid cell, in Unreal units: 50 units is half a metre. The placed actors fall + * back on this value when they report their footprint while this component is not around yet (in the + * editor), so both use the same base size. */ + static constexpr float DefaultGridSnapSize = 50.f; + + /** Size of one grid cell, in Unreal units. Every object is placed on the lattice this defines, grown + * by the whole number multiplier that the placed actor carries (see ARemovableStaticMeshActor:: + * GridScale). Keeping the base size in one place is what keeps objects of every size on a single + * lattice instead of letting each of them drift on a lattice of its own. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Placement", meta = (ClampMin = "1.0", UIMin = "1.0")) + float GridSnapSize = DefaultGridSnapSize; + + /** How the object being moved is aligned with the grid, see EPlaceableGridFitMode. The pivot based + * mode is only correct when every mesh' pivot sits at the center of the mesh. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Placement") - float GridSnapSize = 250.f; + EPlaceableGridFitMode GridFitMode = EPlaceableGridFitMode::FootprintCentered; + + /** Yaw steps the object is quantized to while the grid is active. An object that is rotated freely + * does not cover whole cells, which pushes it out of sync with the objects placed around it. + * Use 0 to keep free rotation. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Placement", meta = (ClampMin = "0.0")) + float GridRotationSnapDegrees = 90.f; + + /** Largest relative size change FootprintFitted is allowed to apply so that a mesh fills its cells + * exactly. 0.15 = up to 15%. A mesh that would need more than this keeps its cell margin. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Placement", meta = (ClampMin = "0.0")) + float GridFitMaxScaleDelta = 0.15f; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Placement") float GroundTraceLength = 1000.f; @@ -106,6 +147,12 @@ protected: * the detection box and the interaction widget, which do not match the mesh that is placed. */ FBoxSphereBounds GetMovingActorMeshBounds() const; + /** Aligns the footprint of the object being moved with the placement grid and applies the rotation + * and the scale it is going to be placed with. Returns the location the actor has to be moved to. + * The pivot is not used for the alignment, because it is not guaranteed to sit at the center (or + * at the edge) of the mesh. */ + FVector SnapFootprintToGrid(const FVector& InLocation, const FVector& TargetScale, const FRotator& TargetRotation, double GridSnap); + public: // Called every frame @@ -115,6 +162,28 @@ public: UFUNCTION(BlueprintCallable, BlueprintPure) FORCEINLINE bool IsGridMovementActive() const { return bGridMovementActive; } + /** Footprint of the object that is currently being moved, in whole grid cells (X = cells along X, + * Y = cells along Y). Handy for showing the size of an object in a build UI. Zero when nothing is + * being moved. */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Placement") + FVector2D GetMovingActorGridFootprint() const; + + /** Amount of whole grid cells a size spans. The footprint is rounded up, so that a mesh always fits + * inside the cells it claims, with a small tolerance against the float noise that mesh bounds carry. + * The placement and the footprint report both use this, so the two can never disagree. */ + static double GetGridCellCount(double Size, double GridSnap); + + /** Size of one grid cell for an actor: the base cell size of this component scaled by the whole + * number GridScale multiplier of the actor, so 1 keeps the base grid and 3 gives 150 units cells on + * a 50 units grid. The multiplier is applied here instead of being trusted from the property, so a + * stray zero (the property is writable from Blueprint as well) can not shrink the grid to nothing. */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Placement") + double GetGridPitchForActor(const ARemovableStaticMeshActor* Actor) const; + + /** As GetGridPitchForActor, but against an explicit base cell size. The actors use this to report + * their footprint while the component that owns the base size is not available (in the editor). */ + static double GetGridPitch(const ARemovableStaticMeshActor* Actor, double BaseGridSnapSize); + UFUNCTION(BlueprintCallable, BlueprintPure) bool IsRemovingModePossible() const { return bRemovalModePossible; } UFUNCTION(BlueprintCallable, BlueprintPure) diff --git a/Source/ProjectEleri/GameObjects/RemovableStaticMeshActor.cpp b/Source/ProjectEleri/GameObjects/RemovableStaticMeshActor.cpp index f0800941..f4383d1d 100644 --- a/Source/ProjectEleri/GameObjects/RemovableStaticMeshActor.cpp +++ b/Source/ProjectEleri/GameObjects/RemovableStaticMeshActor.cpp @@ -154,10 +154,44 @@ void ARemovableStaticMeshActor::Place(bool ReturnToOldLocation, bool bForce) void ARemovableStaticMeshActor::Rotate(bool bPositive) { FRotator CurrentRotation = this->GetActorRotation(); - CurrentRotation.Yaw += bPositive ? 45.f : -45.f; + CurrentRotation.Yaw += bPositive ? 90.f : -90.f; this->SetActorRotation(CurrentRotation); } +void ARemovableStaticMeshActor::ReportGridFootprint() +{ + // The base cell size is a property of the Move Actors component, which only exists at runtime. While + // editing a placed actor the component is missing, so the class default is used, which is the value + // the placement uses unless the level overrides it. + const double BaseGridSnapSize = MoveableComponent ? MoveableComponent->GridSnapSize : UMoveActorsComponent::DefaultGridSnapSize; + const double Pitch = UMoveActorsComponent::GetGridPitch(this, BaseGridSnapSize); + + if (Pitch <= 0.0) + { + UE_LOG(LogTemp, Warning, TEXT("%s: no grid pitch to report against. Set GridSnapSize on the Move Actors component."), *GetName()); + return; + } + + const UStaticMeshComponent* MeshComponent = GetStaticMeshComponent(); + if (!MeshComponent || !MeshComponent->GetStaticMesh()) + { + UE_LOG(LogTemp, Warning, TEXT("%s: no static mesh assigned."), *GetName()); + return; + } + + const FBoxSphereBounds MeshBounds = MeshComponent->CalcBounds(MeshComponent->GetComponentTransform()); + const FVector Size = MeshBounds.BoxExtent * 2.0; + // The same cell rule that the placement uses, so the reported margin is the margin that the object + // will actually keep on the grid. + const double CellsX = UMoveActorsComponent::GetGridCellCount(Size.X, Pitch); + const double CellsY = UMoveActorsComponent::GetGridCellCount(Size.Y, Pitch); + const double FillScale = FMath::Max((CellsX * Pitch) / FMath::Max(Size.X, 1.0), (CellsY * Pitch) / FMath::Max(Size.Y, 1.0)); + + UE_LOG(LogTemp, Display, TEXT("%s: mesh %.1f x %.1f on a %.1f grid (%d x %.1f base) -> %.0f x %.0f cells (margin %.1f x %.1f, fill scale %.2f)"), + *GetName(), Size.X, Size.Y, Pitch, GetGridSizeMultiplier(), BaseGridSnapSize, CellsX, CellsY, + CellsX * Pitch - Size.X, CellsY * Pitch - Size.Y, FillScale); +} + void ARemovableStaticMeshActor::ReturnToInventory() { if (InventoryRef) diff --git a/Source/ProjectEleri/GameObjects/RemovableStaticMeshActor.h b/Source/ProjectEleri/GameObjects/RemovableStaticMeshActor.h index c3f63cbe..707688ff 100644 --- a/Source/ProjectEleri/GameObjects/RemovableStaticMeshActor.h +++ b/Source/ProjectEleri/GameObjects/RemovableStaticMeshActor.h @@ -68,8 +68,19 @@ public: UPROPERTY(EditAnywhere, BlueprintReadWrite) float DistanceFromActor; - UPROPERTY(EditAnywhere, BlueprintReadWrite) - float GridScale; + /** Whole number multiplier on the placement grid: the object is placed on cells that are + * GridSnapSize * GridScale units, so 1 (the default) keeps the grid the Move Actors component + * defines and 3 reserves 150 units blocks on a 50 units grid. Keep it a whole number: a fractional + * multiplier puts the object on a lattice of its own that only lines up with everything around it + * every now and then, which is exactly what makes placed objects drift. The value is clamped to at + * least 1 where it is used, a zero sized cell would mean "no grid at all". */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (ClampMin = "1", UIMin = "1")) + int32 GridScale = 1; + + /** GridScale with its lower bound applied. The property is writable from Blueprint, where ClampMin + * does not apply, so the value is sanitised at the point of use instead of being trusted. */ + UFUNCTION(BlueprintCallable, BlueprintPure) + FORCEINLINE int32 GetGridSizeMultiplier() const { return FMath::Max(1, GridScale); } UPROPERTY(EditAnywhere, BlueprintReadWrite) UMaterialInterface* CanPlaceMaterial; @@ -92,6 +103,12 @@ public: UFUNCTION(BlueprintCallable, Category = "Removable Object") void UpdateOverlapBoxExtent(); + /** Logs how the mesh of this actor relates to the placement grid: its size, the amount of cells it + * occupies and the scale that would make it fill those cells. Meshes do not have to be modelled to + * the grid, this only makes the margin they keep visible so that near misses stand out. */ + UFUNCTION(CallInEditor, BlueprintCallable, Category = "Removable Object") + void ReportGridFootprint(); + //Used for movable actors to override their box extent for checking collision UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Removable Object") FVector BoxExtentOverride = FVector::ZeroVector;