Mirror Mechanic

A mirror lays in the center of a room.
Look inside, everything is reflected, except you.

There were a few ways I considered accomplishing this:

  • Build both sides of the mirror. Mirror Actor handles mirroring effect on each actor.
  • Build one side of the mirror. Mirror Actor uses a second camera to draw the same room from projected location.
  • Build one side of the mirror. Mirror Actor would create a duplicate room, the mirroring effect is handled by an actor. The camera for the duplicate room would be flipped on the x axis, and projected onto the mirror location in the room.

Choosing the method of the mirroring also depends on the end function of the mirror. Does the mirror just reflect beams and projectiles that hit it? Are there differences between the mirror worlds? Can the player pass through the mirror? Ultimately I chose the first option. For its more simple implementation in Unreal Engine 4, and alignment with target gameplay.

Now to the task of  mirroring actors in the scene. Example explanation using the Cube:

  • Spawn a Cube on each side of the mirror.
  • Add the Cubes to a list of pairs managed by the mirror actor.
  • Multiply the scale of one of the mirrored Cube by (-1,1,1) to flip the mesh.
  • Make sure to track which Cube should be the leader. In Daakorun, this is chosen by most recent time the Cube was touched by the player or player effect.
  • On Tick, update the follower Cube’s position and rotation in the world. Mirroring positions and rotations was very simple to accomplish on a fixed axis, however was more complicated on a mirror set at an arbitrary angle.
  • On Tick, update the lead Cube’s gravity vector based on which side of the mirror it is on.

Below is an early test of the mechanic. Though not apparent, this video was taken before the mirror supported arbitrary rotations and angles that altered gravity for a single side.

I was pleased to find the FVector::MirrorByPlane function to calculate the mirrored position. To get a mirrored FRotator, I found this documentation extremely helpful, Reflection using Quaternions. To get a mirrored gravity vector, the above mentioned methods can be used. The culmination of these resources can be seen below in an excerpt of the c++ code I use to calculate positions and rotations for mirrored objects in Daakorun. There may be some shortcuts to take later in optimization, but for now the code falls back on being readable.

 

Code:

void UDaakorunFunctionLibrary::CalculateReflectionTransform(
  const FTransform MirrorTransform, const FTransform ObjectTransform,
  FTransform & NewObjectTransform, bool & NormalSide)
{
  // Mirror details
  FRotator mirrorRot = MirrorTransform.Rotator();
  FVector  mirrorPos = MirrorTransform.GetLocation();
  FVector mirrorRotForward = UKismetMathLibrary::GetForwardVector(mirrorRot);
  FPlane mirrorPlane = FPlane(mirrorPos, mirrorRotForward);
  
  // Object details
  FRotator objectRot = ObjectTransform.Rotator();
  FVector  objectPos = ObjectTransform.GetLocation();
  FVector  objectScale = ObjectTransform.GetScale3D();
  
  // Reflect position
  FVector reflectedPosition = objectPos.MirrorByPlane(mirrorPlane);
  
  // Reflect rotator
  // Transform object rotator to mirror space
  FRotator objectRot_local = UKismetMathLibrary::InverseTransformRotation(
    MirrorTransform, objectRot);
  
  // Create local Plane to reflect accross
  FPlane mirrorPlane_local = FPlane(FVector(0,0,0), UKismetMathLibrary::
    GetForwardVector(FRotator(0,0,0)));
  
  // Do Mirroring
  FQuat Pin = objectRot_local.Quaternion();
  FQuat q = FQuat(mirrorPlane_local.X, mirrorPlane_local.Y, mirrorPlane_local.Z, 0);
  FRotator reflectedRotator_local = (q * Pin * q).Rotator();
  
  // Transform new rotator back into world space
  FRotator reflectedRotator = UKismetMathLibrary::TransformRotation(
    MirrorTransform, reflectedRotator_local);
          
  // Reflection Scale
  FVector reflectedScale = objectScale * FVector(-1, 1, 1);
  
  // Create final transform
  NewObjectTransform = FTransform(reflectedRotator, reflectedPosition,
    reflectedScale);
  
  // Calculate which side of the mirror the asset is on
  float distanceFromPlane = UKismetMathLibrary::Dot_VectorVector((objectPos -
    mirrorPos), mirrorRotForward);
  
  // Which side of the mirror are we on?
  NormalSide = distanceFromPlane >= 0;
}