From ZDoom Wiki
Jump to navigation Jump to search
Note: This feature is for ZScript only.

Shape2D is a class that can be used to create arbitrary flat shapes to be rendered to the screen (see: DrawShape). This class can be more difficult to use than most since it requires knowledge of texture coordinate mapping and how shapes are created via triangles. When used correctly, however, it can be very powerful for manipulating how textures are drawn on the screen. A common use case would be to turn a square camera texture into a circular one to mimic a weapon scope. The nice part about shapes are that they entirely remove the need to use additional textures to mask unwanted parts on the main texture and can be freely rotated (which, in turn, rotates the texture in it). They can also be reused meaning they can be cached to help save on performance. The largest downside is that they must be scaled manually since no position or size handling is done on it when drawn.


  • Vertex - A 2D coordinate (x and y pair) that corresponds to a pixel on the screen. This does not take any sort of scaling from a virtual resolution or the view port into account meaning the position must be scaled manually. This is best done via a Shape2DTransform object (see documentation below). The origin of the screen is considered to be at the top left corner. To make scaling simple, the center of the shape should be at the origin of the screen. The shape should also be a unit version of that specific shape e.g. for squares the length of each side should be 1, for circles the radius should be 1, etc. This will make applying transformations to the shape easy. When a vertex is pushed to a shape, it is given an index. This starts at 0 with the first one pushed and goes in order (e.g. 0, 1, 2, 3 ...). Indices cannot be changed after the vertex is pushed nor can individual vertices be removed.
  • UV Coordinate - A 2D coordinate (u and v pair) that corresponds to a location on the texture being drawn. Unlike vertices which use real positions, UV coordinates use a more abstract coordinate system. Each texture has a bounding box (a box that contains the entire texture) surrounding it, with a UV coordinate of (0,0) being the top left of the box and a UV coordinate of (1,1) being the bottom right of the box. Since textures can be different sizes, you can view this coordinate as a percentage of the texture's width and height. For instance, let's say we have a UV coordinate in our shape at (0.5,0.25). For a texture with a size of 128x128, that would correspond to the pixel at (64,32). For a texture with a size of 64x32, that would correspond to the pixel at (32,8). This coordinate basically says "place a coordinate at half the texture's width and a quarter of its height." When a UV coordinate is pushed to a shape it is also give an index with the same rules as vertices. This is important because each UV coordinate will match the vertex with the same index. Going back to our example, if the coordinate (0.5,0.25) had an index of 0, then that pixel on the texture would show up at the location of vertex 0. Generally the shape created by the UV coordinates matches the shape of the vertices (otherwise you get odd texture warping). Anything outside of the shape you define by the UV coordinates will not be drawn allowing you to customize which parts of the texture are visible and which aren't. Indices cannot be changed after the UV coordinate is pushed nor can individual coordinates be removed.

Warning: The number of vertices and UV coordinates in a shape must be the same. Attempts to draw shapes without them matching will cause a VM abort.

  • Triangle - In computer graphics surfaces are often made up of a series of interconnected triangles. Triangles are used since they are the simplest shape that can be created making them a great foundation for building other shapes (even shapes like circles). GZDoom's Shapes are no different and must have triangles defining their surface for the texture to draw on. For example, let's assume we're working with a square. Its vertex positions are at (0,0), (1,0), (1,1), and (0,1) in that order with indices of 0, 1, 2, and 3 respectively. If you slice a square in half diagonally from one corner to the other, two triangles are created. One can be created with vertex indices 0, 1, 2; and the other with vertex indices 0, 3, 2. These two triangles cover the entire surface of the square, and those are the values we would push for our triangles. Generally all of your triangles will start from a single point, often vertex 0, but this is not required. If an area of the shape's surface doesn't have a triangle then nothing will be drawn there. Individual triangles cannot be removed after being pushed.



  • void SetTransform(Shape2DTransform transform)
  • Applies the transformation defined by transform to the shape
  • void Clear(int which = C_Verts|C_Coords|C_Indices)
  • Clears information about the shape. Can be the following:
  • C_Verts - Clear the shape's vertices
  • C_Coords - Clear the shape's uv coordinates
  • C_Indices - Clear the triangles defining the surface
  • void PushVertex(Vector2 v)
  • Pushes a vertex at the absolute xy position on the screen
  • void PushCoord(Vector2 c)
  • Pushes a uv coordinate on the texture. Ideally this would use values with a range between [0,1]
  • void PushTriangle(int a, int b, int c)
  • Pushes a triangle whose three points are at the indices for vertex a, vertex b, and vertex c


This class serves as a helper class for manipulating Shapes. It includes extended functionality for helping out with scaling, rotating, and translating the shape on the screen. This class should be used to handle modifying the shape since it provides a decent performance boost compared to writing functions to handle it manually. To set a shape's transform, create an instance of this class, set up the transformations via the methods below, and then call SetTransform() on the shape, passing it the created object. The order you apply the transformations is important and should generally be applied in the order of scaling first, rotating second, and translating third.



  • void Clear()
  • Remove any applied transformations
  • void Rotate(double angle)
  • Rotate the vertex coordinates (and thus the texture in the shape) by angle degrees about the top left of the screen. Positive values rotate clockwise while negative values rotate counterclockwise
  • void Scale(Vector2 scaleVec)
  • Scale the x and y vertex offsets by scaleVec.x and scaleVec.y respectively
  • void Translate(Vector2 translateVec)
  • Shift the x and y vertex coordinates by translateVec.x and traslateVec.y amount respectively


// Create our square
let square = new("Shape2D");

// Set the vertices of the square (corresponds to a location on the screen)
// This square is centered at the origin of the screen and each side has a length of 1, making it great for scaling

// Set the uv coordinates of the texture (defines which point of the texture maps to which vertex)

// Set the triangles of the square using the vertex indices (creates a surface to draw the texture on)

// Now the we have our square set up, let's scale it and draw it somewhere else on the screen

// Create the transformer
let transformation = new("Shape2DTransform");

// Note: order is important here! You should always scale first, rotate second, and translate last to ensure your shape changes how you expect it to
transformation.Scale((300, 300)); // Scale the square so each side has a length of 300
transformation.Rotate(90); // Rotate the square by 90 degrees clockwise
transformation.Translate((Screen.GetWidth() / 2, Screen.GetHeight() / 2)); // Move the shape to the center of the screen

// Apply the transformation to our square

// Draws the rotated square with 300x300 dimensions in the center of the screen
// Note: This must be done from a function designed for drawing such as RenderOverlay/Underlay() or BaseStatusBar's Draw()
Screen.DrawShape(myTexture, false, square);