UInstancedStaticMeshComponent::AddInstances does not appear to be adding per-instance previous transforms when enabled

Hi, I’d like to report a bug in the instanced static mesh component in regards to motion vectors:

When per-instance previous transforms are enabled (SetHasPerInstancePrevTransforms(true)), the ISM expects as many transforms in PerInstancePrevTransform as there are instances as can be seen in UInstancedStaticMeshComponent::MakeInstanceDataFlags:

Flags.bHasPerInstanceDynamicData = PerInstancePrevTransform.Num() > 0 && PerInstancePrevTransform.Num() == GetInstanceCount();

When you add a single instance via AddInstance, the ISM will also add an element to PerInstancePrevTransform.

int32 UInstancedStaticMeshComponent::AddInstanceInternal(int32 InstanceIndex, FInstancedStaticMeshInstanceData* InNewInstanceData, const FTransform& InstanceTransform, bool bWorldSpace)
{
...
	if (bHasPreviousTransforms)
	{
		PerInstancePrevTransform.Add(NewInstanceData->Transform);
	}

However, when you add multiple instances via AddInstances, the ISM will not adjust PerInstancePrevTransform accordingly.

As a workaround, you can enable SetHasPerInstancePrevTransforms after the AddInstances call, but this is not a good solution when you need to add instances more than once. You can disable SetHasPerInstancePrevTransforms before the call and re-enable it after the call, but this will get expensive if there are a lot of existing instances. It will also likely break motion vectors for one frame.

This is how we fixed it in our project:

TArray<int32> UInstancedStaticMeshComponent::AddInstancesInternal(TConstArrayView<FTransform> InstanceTransforms, bool bShouldReturnIndices, bool bWorldSpace, bool bUpdateNavigation)
{
....

	// CUSTOM CODE BEGIN
	if (bHasPreviousTransforms && PerInstancePrevTransform.Num() < NumInstances) // the second condition is probably unnecessary
	{
		PerInstancePrevTransform.Reserve(NumInstances);

		for (InstanceIndex = NumInstances - Count; InstanceIndex < NumInstances; ++InstanceIndex)
		{
			PerInstancePrevTransform.Add(PerInstanceSMData[InstanceIndex].Transform);
		}
	}
	// CUSTOM CODE END

	InvalidateCachedBounds();
	return NewInstanceIndices;
}

Steps to Reproduce
I don’t remember if it crashed, asserted or simply didn’t work but these are the repro steps (from memory):

UInstancedStaticMeshComponent *ISM = NewObject<UInstancedStaticMeshComponent>(this);

ISM->SetStaticMesh(MyStaticMesh);

ISM->SetHasPerInstancePrevTransforms(true);

ISM->SetMobility(EComponentMobility::Movable);

ISM->RegisterComponentWithWorld(GetWorld());

TArray<FTransform> Transforms;

Transforms.SetNum(10);

ISM->AddInstances(Transforms, false);

Hi there,

Thank you for reporting this. Using your information I managed to reproduce this on my end quite easily. It did not crash but I was able to see it didn’t add the previous transforms instances to the PerInstancePrevTransform array. Additionally it’s worth noting that updating the transform of any instances will not update the array or effect the transform of the instance due to having an invalid index.

I don’t see this bug currently tracked, so have submitted a bug report. When it is reflected publicly you will be able to view it here.

Cheers,

Louis

Excellent! Thank you Louis!