Kekouan

Q2Model, Part 5

2023-02-26

Initially, before writing some Swift code to read in a PCX file, I took the option of just converting the texture to a modern file format (PNG).

Astroboy Texture in PNG format

A textured Astroboy being rendered

That's the result of swapping out the texture that formerly covered a rotating cube with the Astroboy texture. It was surprisingly easy to get this up and going, which I guess is partially the point of having a graphics API.

This is still somewhat un-optimized as I am just drawing "primitives":

renderEncoder.setFragmentTexture(texture, index: TextureIndex.color.rawValue)

for (i, vertexBuffer) in q2mesh.vertexBuffers.enumerated() {
    renderEncoder.setVertexBuffer(vertexBuffer,
                                  offset: 0,
                                  index: i)
}

renderEncoder.drawPrimitives(type: q2mesh.primitiveType,
                             vertexStart: 0,
                             vertexCount: q2mesh.vertexCount)

drawPrimitives(type:vertexStart:vertexCount:)

Encodes a command to render one instance of primitives using vertex data in contiguous array elements.

Drawing starts with the first vertex at the array element with index vertexStart and ends at the array element with index vertexStart + vertexCount - 1.

When a draw command is encoded, any necessary references to rendering state or resources previously set on the encoder are recorded as part of the command. After encoding a command, you can safely change the encoding state to set up parameters needed to encode other commands.

https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1516326-drawprimitives

At this point I wanted to do two things, read in the PCX file directly (this would let this work on any Q2 Model) and switch to "indexed primitives."

Indexed Primitives

The Astroboy model is made up of 341 vertices, that are part of 552 triangles. Imagine, if you will, a polygon:

A pentagon inscribed inside of a circle

There are 5 triangles, but they all connect to the center of the circle, and each triangle shares a point with its nearest neighbour. So there's a total of 6 unique points, but if you treat each triangle as it's own entity you'd have to specify 15 points in total.

A decagon inscribed inside of a circle

Same idea here, 11 unique points, but if you treat each vertex or triangle separately that's 30 points. Quake 2 Models can only have a maximum of 2048 vertices, but that's very low compared to a model you'd be dealing with today. So it's a good idea to optimize this by providing a list of all the vertices, and then just referencing those vertices by index number when constructing triangles.