Randomize Array of FTransform and compare if the elements are duplicated C++?

I am having trouble translating this bp functions to C++, I need some help to correct my mistakes.

1

MyGM.h

UFUNCTION(BlueprintCallable, meta = (Category = ItemGeneration, OverrideNativeName =   RandomLocation))
void RandomLocation(TArray<FTransform>& Available, FTransform& Location);|

MyGM.cpp

void AMyGM::RandomLocation( TArray<FTransform>& Available,  FTransform& Location)
{
	Location = Available[FMath::RandRange(0, Available.Num() - 1)];
}

2 this is called in beginplay

in beginplay

ItemGroupRef->GetAllTransforms(TransformsOut);
RandomLocation(TransformsOut, RandomLocationOut); 
if (UsedLocations.Find(RandomLocationOut) == -1)
	{
		SpawnLocation = RandomLocationOut;
		UsedLocations.Add(SpawnLocation);
	}
else{
		RandomLocation(TransformsOut, RandomLocationOut);
	}

Can you tell us what’s going wrong? What behaviour do you intend to achieve?

Your function looks OK.
However I have some suggestions to make it slightly better:
Make your input parameter const, as well as your function (you’re not affecting it’s values or members). Then you could also make it BlueprintPure, so that it doesn’t require an execution pin.
Like this:

UFUNCTION(BlueprintCallable, BlueprintPure, meta = (Category = ItemGeneration, OverrideNativeName =   RandomLocation))
void RandomLocation(const TArray<FTransform>& Available, FTransform& Location) const;
1 Like

Well the way it works in your BP, the function becomes a loop, which is not the case for your C++ code. In C++ you could do what you want to do using a do while loop. You can also change your RandomLocation function to return the vector instead of returning void, and you can also use UsedLocations.Contains instead of Find, just for easier to follow code

1 Like

AndreasV Time 2h
Can you tell us what’s going wrong? What behaviour do you intend to achieve?

Error:
binary ‘==’: no operator found which takes a left-hand operand of type ‘const FTransform’ (or there is no acceptable Conversion]

Goal:
1: doing -1 and randomize range the locations array to avoid any duplicate locations return
2: rechecking if there is not duplicated in the if statement but it fails with the error type C2678

Making the function pure and using it the way it’s used right now in the BP would not provide the same result. Pure functions are executed once for every node connected to them, and since this function returns a random element (which by the way can be done using the random element node in BP), then the first result plugged in to Find would be different than the one plugged in to Set Spawn Location, therefore producing wrong results

Use UsedLocations.Contains(RandomLocationOut) instead

I think because of the loop the Location would actually change each time no?

Anyway, it all depends what OP intends to achieve.
I suppose it’s just adding an entry from Available Transforms to UsedLocations that was not already in there. In that case the RandomLocation node should re-evaluate each time.

OP what are you trying to achieve?

It will be reevaluated correctly if it’s not pure.

If it is pure, then the value that you get on 1 will be different than 2. And since we are still technically in the same loop iteration, the value needs to be the same

1 Like

My mistake, you’re correct, didn’t pay attention to the second one.

1: GetAllTransforms is returning the transforms of a group of 5 static meshes connected to scene.
2: RandomLocations randomize the range of transforms as available locations
3: get these available randomized locations from RandomLocations and check if the randomization is correct , if not repeat.
4: if correct randomization its mean the location is not duplicated and it can be used as a spaen location, so add the used locations to spawn locations.

If you want to do that in C++ it gets a bit more complex, but in BP it’s quite easy:

2 Likes

You exactly get my point what I want to achieve, look at to this post here is the full thread on this issue , Link

The issue is, like others have mentioned that transform in UE doesn’t have a binary operator, so the array internal functions Find or Contain cannot compare each element to the one you want to search for.

If you really want to do it that way, then you can use something like this:

if (UsedLocations.ContainsByPredicate([RandomLocationOut] (const FTransform Transform)
	{
		return Transform.GetLocation() == RandomLocationOut.GetLocation()
			&& Transform.GetRotation() == RandomLocationOut.GetRotation()
			&& Transform.GetScale3D() == RandomLocationOut.GetScale3D();
	}))
{
     // Your then code here
}
else
{
    // Your else code here
}
1 Like

so my function will look like this?

for (auto& SpawnArrayElements : ItemsIDArr)
{
  ItemGroupRef->GetAllTransforms(Available);
  RandomLocation(Available, Location);

	if (UsedLocations.ContainsByPredicate([Location](const FTransform Transform)
	{
		return Transform.GetLocation() == Location.GetLocation()
			&& Transform.GetRotation() == Location.GetRotation()
			&& Transform.GetScale3D() == Location.GetScale3D();
	}))
	{
		SpawnLocation = Location;
		UsedLocations.Add(SpawnLocation);
	}
	else
	{
		RandomLocation(Available, Location);
	}
}
//Debugging printing the output values
FString SpawnLoc = SpawnLocation.ToString();//converting to string
printf100s("Spawn Location: %s", *SpawnLoc);//printing the string on screen
ItemsIDArr.Empty();//empty array each loop ends
print100s("---------------------------------------");//add marker to sort things readable

updated the variable names, it was my mistake :smiley:

now I have crashed the editor at this point

Well like I said earlier, after you call RandomLocation, you need to have a do while, otherwise it will just stop if the random transform returned is not one that hasn’t been used before. So you need to do something like:

do
{
	RandomLocation(Available, Location);
}
while (UsedLocations.ContainsByPredicate([Location](const FTransform Transform)
	{
		return Transform.GetLocation() == Location.GetLocation()
			&& Transform.GetRotation() == Location.GetRotation()
			&& Transform.GetScale3D() == Location.GetScale3D();
	}));

SpawnLocation = Location;
UsedLocations.Add(SpawnLocation);

But I don’t recommend this, this can potentially cause freezes

This will keep getting a random element until it finds one that is not in the UsedLocations array

1 Like

I updated the code and still getting the crashed breakpoint in Actor.cpp

but if I check for a nullptr against ItemGroupRef which is calling the GetTransformss function form AGroupActor class , the cast failed.

I am casting like this AItemGroup* ItemGroupRef{};

You think this is not the reason for the crash?

Updated code:

if (ItemGroupRef)
{
for (auto& SpawnArrayElements : ItemsIDArr)
{
	SpawnType = SpawnArrayElements.Type;
	SpawnID = SpawnArrayElements.ID; 
	ItemGroupRef->GetAllTransforms(Available);
	do
	{
		RandomLocation(Available, Location);
	} while (UsedLocations.ContainsByPredicate([Location](const FTransform Transform)
	{
		return Transform.GetLocation() == Location.GetLocation()
			&& Transform.GetRotation() == Location.GetRotation()
			&& Transform.GetScale3D() == Location.GetScale3D();
	}));
	SpawnLocation = Location;
	UsedLocations.Add(SpawnLocation);
}
FString SpawnLoc = SpawnLocation.ToString();
printf100s("Spawn Location: %s", *SpawnLoc);
ItemGroupRef->GetAllTransforms(Available);
ItemsIDArr.Empty();
print100s("---------------------------------------");
}
else
{
print100s("----------------ITEMGROUPREF CAST FAILED ---------------");
return;
}

checking for nullptr avoid editor to crash

Not sure what the issue is now, but you are printing the spawn location outside of the for, so that will only print the last spawn location, once

1 Like