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).
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 indexvertexStart + 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."
The Astroboy model is made up of 341 vertices, that are part of 552 triangles. Imagine, if you will, a polygon:
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.
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.
Ah, here's where the rubber hits the road. The first part of the problem was loading the Quake 2 Model and getting the information about the model.
md2_header(
ident: 844121161,
version: 8,
skinwidth: 296,
skinheight: 164,
framesize: 1404,
num_skins: 0,
num_vertices: 341,
num_st: 465,
num_tris: 552,
num_glcmds: 2861,
num_frames: 198,
offset_skins: 68,
offset_st: 68,
offset_tris: 1928,
offset_frames: 8552,
offset_glcmds: 286544,
offset_end: 297988
)
That's the information/metadata provided by the Astroboy model. The ident is the magic number used to identify the file.
A "magic number" used to identify the file. The magic number is 844121161 in decimal (0x32504449 in hexadecimal). The magic number is equal to the int "IDP2" (id polygon 2), which is formed by ('I' + ('D' << 8) + ('P' << 16) + ('2' << 24)).
The GL commands, are an optimized way of sending in the model geometry into an OpenGL renderer. This was not something that was part of the Quake 1 Model format.
The next interesting bits are the vertices, the texture coordinates, the triangles, and the frames. The offsets tell you how many bytes into the file you need to read to access them.
With that information, you can begin to construct a version of the model, which is what I did:
That's each vertex being drawn, potentially all 341 of them. From those 341 points you can create 552 triangles:
My original plan was to create a simple 3D game in the style of Wolfenstein 3D. Alternatively, if you grew up with a Macintosh you would presumably aim to create something in the style of Pathways into Darkness.
The idea being that if you keep your ambitions to just two-dimensions you get to avoid a lot of hard to solve problems. Generally this is because you don't have to deal with moving up or down, vertically. This prevents "rooms" from being stacked on top of each other, and having to implement either elevators, or elaborate ramps like we're building a ziggurat to the heavens. None of that, please.
I also thought about the option of storing maps as text:
################
#...#.....#....#
#@..D.....D...>#
#...#.....#....#
################
World's crummiest rogue-like.
That's roughly what that would look like, helpfully represented in Wolfenstein 3D, a long hallway with a door at either end.
This is what you start with in Xcode, a spinning textured cube. I had already solved the problem of rendering a textured box, so I felt confident what I needed next was a model format.
Originally I started with a delightful Astroboy model by Jonny Gorden. Originally based off the work of Mighty Atom written and illustrated by Osamu Tezuka.
That's an animated gif created by the model's author.
I found the Quake 2 Model format a lot easier to work with compared to its predecessor. That makes sense, I'd imagine iD figured out what works and what does not.
When I was working on my Quake model viewer, I noted that Quake 2 models contained an optimized implementation of essentially all the commands you'd need to pass to an OpenGL context. At the time I envisioned that's how I'd make it work.
That was quite some time ago, and the landscape has changed a bit.
I was trying to get more familiar with Swift and Metal which is Apple's modern "Graphics Framework" in the vein of OpenGL 2.0+, Vulkan, DirectX.
I can't really speak to the others, other than to say Vulkan seems neat.
I picked up a book from the folks over at Kodeco formerly raywenderlich.com.
This book will introduce you to graphics programming in Metal — Apple’s framework for programming on the GPU. Build a complete game engine in Metal! By Caroline Begbie & Marius Horga.
It's a very interesting book and I would highly recommend it. It gave me the information I needed to start work on this. At the point where I decided to start working on my own game engine, I had roughly gotten to the part where I could display a model on a plane, with lighting and shadows.
I should start by explaining what a Quake 2 model is.
In Quake 2 there are two sources of "geometries" that the game engine cares about.
There is the level itself which is a different topic entirely.
Then there are the occupants of the level:
The enemies and other players (in multiplayer) are represented with a 3D Model.
Polygonal modelling – Points in 3D space, called vertices, are connected by line segments to form a polygon mesh. The vast majority of 3D models today are built as textured polygonal models, because they are flexible, because computers can render them so quickly. However, polygons are planar and can only approximate curved surfaces using many polygons. 1
Everything the game requires is contained within a couple of files.
The above links go into far more detail than I will. The MD2 file contains everything required to generate an un-textured 3D Model. This is done by storing the model geometry (triangles), animations, and information about the texture mapping, and vertex normals.
The basic object used in mesh modelling is a vertex, a point in three-dimensional space. Two vertices connected by a straight line become an edge. Three vertices, connected to each other by three edges, define a triangle, which is the simplest polygon in Euclidean space. More complex polygons can be created out of multiple triangles, or as a single object with more than 3 vertices. Four sided polygons (generally referred to as quads) and triangles are the most common shapes used in polygonal modelling. 2
The important take-away here is that it's triangles, and how the graphics system draws a triangle matters greatly. The triangles are stored as 3 points in a 3-dimensional space (x,y,z coordinates).
The direction in which you unwind the points matters. This is used to determine which side of the triangle is "inside" or "outside". This applies to more complicated polygons as well, but we're going to be dealing with triangles for the most part.
Additionally there's a "header" at the start of the file that tells you information required to process it.
The other file required to actually display the model is the texture.
Imagine, if you will, a cube:
If you flattened that cube out, you might end up with something like this:
That is a crude version of how you can imagine a 2D image being applied to a 3D model.
UV mapping is the 3D modelling process of projecting a 3D model's surface to a 2D image for texture mapping. The letters "U" and "V" denote the axes of the 2D texture because "X", "Y", and "Z" are already used to denote the axes of the 3D object in model space, while "W" (in addition to XYZ) is used in calculating quaternion rotations, a common operation in computer graphics. 3
Great!
Each model will typically include several textures or variations on textures.
All of the textures for a Quake 2 model are stored in a format called PCX. It's a format originally from 1985.
That should be everything you need to know going into this.
I wanted to start a blog to try and build a feedback cycle.
I'm hoping to document the various adventures I have in game development.
Where to start?
A beginning is a very delicate time. Know then, that is is the year 10191. The known universe is ruled by the Padishah Emperor Shaddam the Fourth, my father. In this time, the most precious substance in the universe is the spice Melange. The spice extends life. The spice expands consciousness. The spice is vital to space travel.
Originally my plan was to start with a simple Wolfenstein 3D-esque game. Perhaps have the levels be a plain text file. That led me to learn quite a bit about Metal) which is where this will start.
I also required a name to start things with. Space is pretty vast, so let's go there.
Beta Lupi (Latinized from β Lupi) or Kekouan (/ˈkɛkwɑːn/), is a star in the southern constellation of Lupus.1
Lupus being the latin for Wolf.
A long time ago, but sadly lost to both time and space, I wrote a Quake Model Viewer in Objective-C and OpenGL.
So, I should probably start with Quake 2 this time. I thankfully was able to find things that are still accessible (but for how long!?).