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”:
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:
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:
- Get the inverse world transform matrix of the “entry” portal (which in the case of
GetPortalOutVector
the “entry” portal corresponds tothis
). (This gives a matrix that can transform from world space into the object space of the “entry” portal) - 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) - Create a Z rotation matrix rotating by
Math::Pi
- 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) - 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) - 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:
- If the
exitPortal
is nullptr, you should simply set theportalData.mView
member toMatrix4::CreateScale(0.0f)
which is an invalid view matrix (and will just cause you to see nothing through the portal) and return - Otherwise, you need to calculate the correct look at matrix for the portal view:
- The portal view camera position is the player’s position transformed by
GetPortalOutVector
. You want to use a w of1.0f
for positions, so that translations affect it - The portal view camera forward is the player’s camera forward (which you can get from
CameraComponent
) transformed byGetPortalOutVector
. You want to use a w of0.0f
for vectors - The portal view camera up should be the Z axis of the exit portal’s world transform matrix
- 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 - You then need to set these for members of
portalData
:portalData.mView
to the portal view matrix matrixportalData.mCameraPos
to the portal view camera positionportalData.mCameraForward
to the portal view camera forward fromportalData.mCameraUp
to the portal view camera up
- The portal view camera position is the player’s position transformed by
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 theportalData
GetGame()->GetOrangePortal()
for theexitPortal
- Pass in the opposite parameters for the orange portal (i.e. the orange for the
portalData
and the blue for theexitPortal
)
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
usingGetPortalOutVector
and saving the result back inportalData.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 useportalData.mCameraUp
as the up vector. (This will look similar to the call inCalcViewMatrix
)
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 afloat deltaTime
and returns abool
(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
- If the exit portal faces along +/- z, you DO NOT want to change the player’s rotation at all (as it confusing for players)
- Otherwise, you need to figure out the desired new facing of the player:
- If the entry is along +/- z, just use the exit portal
GetQuatForward()
- Otherwise, take the original player
GetForward()
and transform withGetPortalOutVector
(with a w of 0, since it’s a vector) - 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.
- If the entry is along +/- z, just use the exit portal
New Player Velocity
- The new length of the player velocity should be the higher value between 1.5 * the current velocity length and 350
- For the new direction of the player velocity:
- If entry or exit is along +/- z, just use the exit portal
GetQuatForward()
- Otherwise, take the original velocity direction and transform with
GetPortalOutVector
(with a w of 0, since it’s a vector)
- If entry or exit is along +/- z, just use the exit portal
New Player Position
- First, figure out the base teleport point:
- If entry or exit is along +/- z, just use the exit portal position
- Otherwise, take the current player position and transform with
GetPortalOutVector
(with a w of 1, since it’s a position)
- 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 whenUpdateFalling
changes states toOnGround
.
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.