Quake Lenses - Manual

## Manual

This is a reference manual that contains technical details and everything you need to know to create a lens.

### Technical Lens Details

To draw the player's view, an intermediate lookup table is used to map a pixel on the screen to a pixel on the cubemap. The lens is responsible for the structure of the lookup table. In other words, the lens is responsible for directing a ray of light from the environment to a pixel on the screen, or vice versa. This is called a forward mapping and an inverse mapping, respectively.

- Forward: environment to screen coordinates
- Inverse: screen to environment coordinates

When defining these mappings, you can choose from the following coordinate systems described in the next sections:

- Radial: (radius ↔ optical angle)
- Spherical: (x,y ↔ latitude,longitude)
- Cartesian: (x,y ↔ 3D vector)
- Cubic: (x,y ↔ cubeface,u,v)

#### Radial Coordinates

This is the simplest coordinate system commonly used to define physical camera lenses. It describes the relationship between the radial position R on the film and the angle θ from the optical axis of the lens.

#### Screen Coordinates

The rest of the modes assume the same coordinate systems for the screen, but different coordinate systems for the environment. The screen uses 2 coordinates (x,y). The origin (0,0) is at the center of the screen. Positive x to the right. Positive y is up.

#### Spherical Coordinates

You can use two angles to define a 3D direction. You may know them by any of the following standard names:

- (yaw, pitch)
- (heading, elevation)
- (azimuth φ, elevation θ)
- (longitude λ, latitude φ)

Latitude and Longitude are used in Quake Lenses because they have become standard for defining map projections used in cartography. The open-source project called proj.4 provides many equations that you can directly use in Quake Lenses. Latitude and Longitude are zero when looking straight ahead along the optical axis. Positive longitude to right. Positive latitude is up.

#### Cartesian Coordinates

You can also define a 3D direction using a unit vector (x,y,z). Quake Lenses currently uses a left-handed coordinate system, as is standard in Quake. Positive z is straight forward. Positive x is right. Positive y is up.

#### Cubic Coordinates

This is a non-standard coordinate system that allows you to directly refer to a point on a given cubemap face using (side,u,v). u and v are between 0 and 1. Positive u is right. Positive v is down. The side is an integer with the following enumeration:

- 0: FRONT
- 1: RIGHT
- 2: BEHIND
- 3: LEFT
- 4: TOP
- 5: BOTTOM

### Creating a Lua script for the Lens

To create a lens, you must write a Lua script. The game will look for the following variables and functions in order to build the lookup table for the player's view.

Variables

- hsym = boolean : flag horizontal symmetry
- vsym = boolean : flag vertical symmetry
- max_hfov = number : maximum horizontal FOV
- max_vfov = number : maximum vertical FOV
- hfit_size = number: width of the image used for horizontal fitting
- vfit_size = number : height of the screen used for vertical fitting

Functions

- theta_to_r : forward radial projection
- r_to_theta : inverse radial projection
- latlon_to_xy : forward spherical projection
- xy_to_latlon : inverse spherical projection
- ray_to_xy : forward cartesian projection
- xy_to_ray : inverse cartesian projection
- cubemap_to_xy : forward cubic projection
- xy_to_cubemap : inverse cubic projection
- xy_isvalid : inverse check
- r_isvalid : inverse check

One way to learn how to use these is to look at the examples in the /lenses folder. The basic idea is that a function takes in a set of coordinates, and returns a new set of coordinates. (The coordinate systems are described in the previous sections.) For example, latlon_to_xy takes two arguments (lat,lon) and returns two arguments (x,y). xy_to_ray takes two arguments (x,y) and returns three arguments for a ray (rx,ry,rz). The following sections describe how to structure your lens in more detail.

#### Scaling (Zooming)

A lens creates an image of varying size. It can be anything from 1x1 units to 1000x1000 units, or even infinitely large. Due to the wide range of sizes, the game needs to know how to scale it to the screen. Different scales result in different zoom levels. There are currently two ways of scaling: FOV or fitting.

FOV

When the user specifies an FOV, the game must use a forward map to determine the scale of the image. It uses the theta_to_r, latlon_to_xy, or ray_to_xy to determine where the edges of the screen should be on the image. For example, if you have an hfov of 180, you can call latlon_to_xy(0,pi/2) to get the x position of the furthest right position. For simplicity, the game assumes a centered image, so it just multiplies this by 2 to get the width of the image. The game then scales the image so that it fits the width of the screen.

Fitting

As an alternative to the FOV, you can explicity specify a maximum width and height of the image created by your lens, using hfit_size and vfit_size, respectively. The game will then use this instead of the forward map to scale it to the screen. The user can then type "fit" to scale the image as large as possible to fit the screen. The user can also type "hfit" or "vfit" to only fit the image horizontally or vertically regardless of cropping. (The "fit" command automatically determines which one to use to avoid cropping.)

#### Lookup Table construction

The game constructs the lookup table in one of two ways. It will first try to find an inverse function in the lens script. The game will then convert the position in the lookup table to a screen coordinate (r or xy) and check if it is inside the image by calling r_isvalid or xy_isvalid. If it is not valid it will leave the pixel blank. If it is valid, it will pass the screen coordinate to the inverse map function. It will then use the returned value to select a pixel from the cubemap.

If the game does not find an inverse function, it will look for the forward function instead. The game will then iterate through the cubemap, converting the position to an environment coordinate (radial, spherical, cartesian, or cubic) and passing it to the forward function. It will then convert the returned screen coordinate to a position in the lensmap for storing the cubemap pixel. This does not guarantee that all the positions in the lensmap will be filled, and will often result in noticeable holes. (Potential improvement: The holes can be filled by drawing a filled quad determined by the corners of the pixel)

#### Lens Symmetry

The lookup table takes some time to calculate, and the performance is slowed by using the Lua interpreter instead of C code for the lens mappings. We can exploit the potential symmetry of the lens to make the calculation up to four times faster. If the lens is horizontally or vertically symmetric, you can set the hsym or vsym flag to true, respectively. That way, the game can deduce the rest of the lookup table after calculating a single half or quadrant.