Skip to main content Link Search Menu Expand Document (external link)

Theme: auto

Portal Views and Teleporting

Before you implement portal travel, there are a few minor things to change and add to prepare for portal travel.

Odds and Ends

New Level

First, in Game::LoadData change it so the current level is "Assets/Lab10.json".

You should now load into a new level with a sign that says “02”: Lab10.json

Starting With the Portal Gun

In some levels, we want it so that the player starts out with a portal gun, rather than needing to pick it up first.

In LoadActor, add support for the Player to check for a bool key called "gun". If it’s true, call the “give gun” function immediately after creating the Player so that they start out with a gun. However, if “gun” is false or unset, you should not give the player a gun.

Confirm that the player now spawns in with the portal gun: Spawning in with portal gun

Terminal Z Velocity for Player

Since we’ll be able to fall with portals, we want to set a maximum z-velocity thfor the player. To do this, in PlayerMove::PhysicsUpdate, immediately after the call to FixXYVelocity, make sure that the mVelocity.z cannot go lower than -1000.0f.

Portal Views

Make sure you consult the lecture notes for this part as there is quite a bit of math, and if you do not implement the key functions properly, your portals both will not look right and the teleporting will not work correctly.

GetPortalOutVector

In Portal, add a public member function called GetPortalOutVector. This function will allow you to transform any arbitrary world spaceVector3 (whether it’s a position or a vector) through the portal.

GetPortalOutVector should return a Vector3 and take in:

  • A const Vector3& for the initial vector
  • A Portal* for the portal you are exiting out of (the “exit” portal)
  • A float for the w component

The implementation of this requires the math we discussed in lecture where you’re going to take the initial vector (which is in world space), transform it into the “entry portal” object space, yaw it by Math::Pi radians and into the “exit portal” object space, before transforming back into world space.

The detailed steps are:

  1. Get the inverse world transform matrix of the “entry” portal (which in the case of GetPortalOutVector the “entry” portal corresponds to this). (This gives a matrix that can transform from world space into the object space of the “entry” portal)
  2. Use Vector3::Transform to transform the initial vector by (1), making sure to pass in the w component for the third parameter. (The initial vector is now in object space of the “entry” portal)
  3. Create a Z rotation matrix rotating by Math::Pi
  4. Use Vector3::Transform to transform (2) by (3), making sure to pass in the w component as the third parameter. (This rotates by Pi about the Z so it turns around to account for the flip that will happen when going through a portal)
  5. Use Vector3::Transform to transform (4) by the “exit” portal’s world transform matrix, passing in w for the third parameter. (This will transform the vector back into world space, but now accounting for the transform of the “exit” portal)
  6. Return the vector from (5)

CalcViewMatrix

With GetPortalOutVector implemented, you can now calculate the correct view matrix for the portal. Add a private member function in Portal with the following declaration:

void CalcViewMatrix(struct PortalData& portalData, Portal* exitPortal);

In the implementation:

  1. If the exitPortal is nullptr, you should simply set the portalData.mView member to Matrix4::CreateScale(0.0f) which is an invalid view matrix (and will just cause you to see nothing through the portal) and return
  2. Otherwise, you need to calculate the correct look at matrix for the portal view:
    1. The portal view camera position is the player’s position transformed by GetPortalOutVector. You want to use a w of 1.0f for positions, so that translations affect it
    2. The portal view camera forward is the player’s camera forward (which you can get from CameraComponent) transformed by GetPortalOutVector. You want to use a w of 0.0f for vectors
    3. The portal view camera up should be the Z axis of the exit portal’s world transform matrix
    4. Use Matrix4::CreateLookAt to make the portal view matrix, passing in the appropriate parameters. For the target position, you want the point 50 units “in front of” the portal view camera position
    5. You then need to set these for members of portalData:
      • portalData.mView to the portal view matrix matrix
      • portalData.mCameraPos to the portal view camera position
      • portalData.mCameraForward to the portal view camera forward from
      • portalData.mCameraUp to the portal view camera up

Calling CalcViewMatrix

Finally, override the OnUpdate function in Portal. In here, you need to call CalcViewMatrix on every frame:

  • If this portal is the blue portal, pass in these parameters:
    • GetGame()->GetRenderer()->GetBluePortal() for the portalData
    • GetGame()->GetOrangePortal() for the exitPortal
  • Pass in the opposite parameters for the orange portal (i.e. the orange for the portalData and the blue for the exitPortal)

Updating Portals on the First Frame

To ensure the portal view is correct on the first frame the portal is visible, you should manually call Update(0.0f) on a portal after you create it and set its position/quaternion. This calls the Actor::Update function, which in turn will both call OnUpdate and calculate the new world transform.

Test out your portals on X/Y walls. You should be able to see into the portal, though the portals will not recurse yet. You should get results like in this video:

If your portal views seem way off from the above video, you will need to debug why the view is not working properly before you can proceed. Without a working GetPortalOutVector, none of the teleporting will work as expected, so you need to fix your view first before moving on.

Portal View Recursion

Once your initial portal views are working, you only need to make a few changes to add recursion.

First, in Renderer.h, change the MAX_PORTAL_RECURSIONS constant to 6.

Then in Renderer.cpp, you need to implement the PortalViewRecurse function which takes in the portal data, the entry portal, and the exit portal. All you need to do is:

  • Transform portalData.mCameraPos using GetPortalOutVector and saving the result back in portalData.mCameraPos (think about what w you should use here)
  • Do the same for portalData.mCameraForward (again, think about what w you should use)
  • Update portalData.mView to be a new look-at matrix calculated accounting for the updated position and forward. You can just use portalData.mCameraUp as the up vector. (This will look similar to the call in CalcViewMatrix)

Your portals should now recurse, as in this video:

Basic Portal Teleporting

The portals need a collision component so the player can check for collision against them. However, the size of the portal’s collision component varies depending on the surface normal it’s created on (keep in mind that since the collision is an AABB, the surface normals are always parallel to one of the coordinate axes):

  • If it’s +/- unit x, the size is (110.0f, 125.0f, 10.0f)
  • If it’s +/- unit y, the size is (10.0f, 125.0f, 110.0f)
  • Otherwise, the size is (110.0f, 10.0f, 125.0f)

Set it up so when you create the portals, you also create a collision component with the correct size.

In PlayerMove, add two functions:

  • UpdatePortalTeleport that takes in a float deltaTime and returns a bool (it will return true if the player teleports through a portal, and false otherwise).
  • A function that will do the actual teleport calculations and takes in an entry and exit Portal*

Then, call UpdatePortalTeleport in UpdateOnGround, UpdateJump, and UpdateFalling. You want to call it immediately after the PhysicsUpdate calls but before any collision checks. If UpdatePortalTeleport returns true, you should immediately return from the UpdateXXX function because you don’t want to process the rest of the state on that frame.

For UpdatePortalTeleport, you can consult the slides for additional tips. Here are the requirements:

  • You can only teleport if there’s both a blue and orange portal
  • You can’t teleport if it’s been less than 0.2 seconds since a previous portal teleport. This is to prevent you from instantly teleporting back through the portal you just exited. (Track/check for this as you see appropriate)
  • You only initiate the teleport when you intersect with one of the portals (that portal is the entry portal, and the opposite is the exit portal)
  • When you decide to teleport, you should call your function to do the teleport
  • If you teleport, return true, otherwise return false

As for the function that does the teleport, there are three main things to calculate: the new rotation of the player, the new velocity of the player, and the new position of the player.

New Player Rotation

  1. If the exit portal faces along +/- z, you DO NOT want to change the player’s rotation at all (as it confusing for players)
  2. Otherwise, you need to figure out the desired new facing of the player:
    1. If the entry is along +/- z, just use the exit portal GetQuatForward()
    2. Otherwise, take the original player GetForward() and transform with GetPortalOutVector (with a w of 0, since it’s a vector)
    3. Once you have the desired new facing vector, you need the angle between unit x and the desired new facing, and set the player’s rotation to that. Remember that you can use the dot product and cross product to calculate this.

New Player Velocity

  1. The new length of the player velocity should be the higher value between 1.5 * the current velocity length and 350
  2. For the new direction of the player velocity:
    1. If entry or exit is along +/- z, just use the exit portal GetQuatForward()
    2. Otherwise, take the original velocity direction and transform with GetPortalOutVector (with a w of 0, since it’s a vector)

New Player Position

  1. First, figure out the base teleport point:
    1. If entry or exit is along +/- z, just use the exit portal position
    2. Otherwise, take the current player position and transform with GetPortalOutVector (with a w of 1, since it’s a position)
  2. Once you have the base teleport point, the new position of the player is the base teleport point plus 50 units in the direction of the exit portal’s GetQuatForward() (this is so you don’t clip into the wall)

Additional Teleport Logic

When you perform the teleport, you also need to:

  • Change the state to Falling
  • Set a bool that says you’re falling due to a portal teleport. If this bool is true, don’t run the velocity limiting code in FixXYVelocity(). You should need to set this bool back to false when UpdateFalling changes states to OnGround.

Confirm that your portal teleporting works. Try teleporting in some different combinations, and make sure you can make the jump across the final gap using portals. This video shows some examples to try:

Confirm that you can make the jump across the final gap with portals. If you cannot, there is something wrong with your implementation and you will NOT get credit for this spec (as the game is impossible to play without this feature working).

Once you’ve pushed this code, you’re ready to move on to part 2.