Hi,
I wanted to report some bugs we found in the Water plugin code and pass along some fixes we made:
- In AWaterZone::GetZoneBounds() the return value is world space but the Z location of the actor isn’t added to the FBox it returns. This causes the renderer culling bounds of the water zone to be incorrect as well as the WP streaming bounds. The fixed code would be like this
FBox AWaterZone::GetZoneBounds() const
{
const FBox2D ZoneBounds2D = GetZoneBounds2D();
// #todo_water [roey]: Water zone doesn't have an explicit z bounds yet. For now just use the x or y.
const double StreamingBoundsZ = FMath::Max(ZoneExtent.X, ZoneExtent.Y);
const FVector ActorLocation = GetActorLocation();
return FBox(FVector3d(ZoneBounds2D.Min, ActorLocation.Z - StreamingBoundsZ / 2.), FVector3d(ZoneBounds2D.Max, ActorLocation.Z + StreamingBoundsZ / 2.0));
}
- FWaterQuadTree::Unlock() creates too short a FQueue which causes a crash in some situations with deep, but narrow quadtrees. It seems it needs to actually be:
FQueue Queue(GetMaxLeafCount()+TreeDepth);* UWaterSubsystem::FindWaterZone finds the wrong WaterZones when doing a LevelInstance Edit-In-Place. It incorrectly chooses actors from the parent umap which results in undesired map to map references. There are probably multiple ways to fix this, but we opted to filter the actors by the LevelInstance to only find sibling actors
TSoftObjectPtr<AWaterZone> UWaterSubsystem::FindWaterZone(const UWorld* World, const FBox2D& Bounds, const TSoftObjectPtr<const ULevel> PreferredLevel)
{
if (!World)
{
return {};
}
//Only find zones which are in the same LI as the water body.
//PreferredLevel we are passed in is the result of GetLevel() on the AWaterBody so we can get the LI from that (if there is one)
auto LevelInstanceSubsystem = World->GetSubsystem<ULevelInstanceSubsystem>();
check(LevelInstanceSubsystem);
ILevelInstanceInterface* LevelInstance = LevelInstanceSubsystem->GetOwningLevelInstance(PreferredLevel.Get());
// Score each overlapping water zone and then pick the best.
TMap<TSoftObjectPtr<AWaterZone>, int32> ViableZones;
#if WITH_EDITOR
// Within the editor, we also want to check unloaded actors to ensure that the water body has serialized the best possible water zone, rather than just looking through what might be loaded now.
if (GEditor && !World->IsGameWorld())
{
FActorContainerID ContainerID = LevelInstance ? LevelInstance->GetLevelInstanceID().GetContainerID() : FActorContainerID::GetMainContainerID();
if (UWorldPartition* WorldPartition = World->GetWorldPartition())
{
const FBox Bounds3D(FVector(Bounds.Min.X, Bounds.Min.Y, -HALF_WORLD_MAX), FVector(Bounds.Max.X, Bounds.Max.Y, HALF_WORLD_MAX));
FWorldPartitionHelpers::ForEachIntersectingActorDescInstance<AWaterZone>(WorldPartition, Bounds3D, [&Bounds, &ViableZones, ContainerID](const FWorldPartitionActorDescInstance* ActorDescInstance)
{
if (ActorDescInstance->GetContainerInstance()->GetContainerID() == ContainerID)
{
FWaterZoneActorDesc* WaterZoneActorDesc = (FWaterZoneActorDesc*)ActorDescInstance->GetActorDesc();
ViableZones.Emplace(ActorDescInstance->GetActorSoftPath(), WaterZoneActorDesc->GetOverlapPriority());
}
return true;
});
}
}
#endif // WITH_EDITOR
for (AWaterZone* WaterZone : TActorRange<AWaterZone>(World, AWaterZone::StaticClass(), EActorIteratorFlags::SkipPendingKill))
{
const FBox2D WaterZoneBounds = WaterZone->GetZoneBounds2D();
if (Bounds.Intersect(WaterZoneBounds) && LevelInstanceSubsystem->GetOwningLevelInstance(WaterZone->GetLevel()) == LevelInstance)
{
ViableZones.Emplace(WaterZone, WaterZone->GetOverlapPriority());
}
}
//The rest is the same as the original code...
<Continued in a reply since I hit the max character limit>
[Attachment Removed]