This will be my development log as I attempt to create a (hopefully!) useful toolkit for PSX programming in C++ using the original PSYQ sdk.
I tried to do something similar a few years ago when I was new to programming - PSX_App on my github - but now that I've had more experience with both C++ and how the PS1 hardware/system works, I'm hoping to make something of higher quality and that can do more than just draw a 3D plane, lol.
The source code will be stored here on bitbucket for now but I think I'll end up using it to replace the repo on github once I feel its a bit more worth sharing.
I've actually been working on this for a couple of days, but didn't think of keeping a log until today. That being said, I've had some decent progress so far, and have the following classes implemented:
What I spent most of today working on, however, was the Pad class (as in joy/directional pad). This class will be used to access the controllers on the playstation. It turns out that getting the analog sticks to work is much tricker than I would've expected!
So PSYQ provides you two ways to access the controllers. The first is much simpler and uses these basic functions:
Pretty straightforward - you initialize the controller system with PadInit() and get the buttons status with PadRead(). This would be great, but there's a slight issue: you can't access the analog sticks or rumble motors this way :(
So instead you need to use this set of functions:
ResetCallback() PadInitDirect() PadStartCom() ...
Among other functions. Instead of calling PadRead() each time you want to check controller values, instead the PSX BIOs auto read and store controller data into the buffers you send to PadInitDirect(). The PadStartCom() function tells the BIOs to do this. Since this is done automatically every vertical sync (I think) in the background, concurrency issues are introduced (reading from your data buffers while the BIOs are trying to write to them at the same time) and I kept freezing and crashing the emulator/playstation! Thus I spent the afternoon looking at the PSYQ documentation and code samples. I ended up finding this thread in the PSX dev forums which contained an analog controller demo/example. Compiling and running this example worked fine on its own, but when I tried to integrate it into my project I was still crashing #!%$^&
After some more tinkering, I figured out that the issue was the order I was initializing things with the System and Pad. Basically, the order of important initialization functions I was using was:
ResetGraph() InitGeom() ResetCallback() PadInitDirect() ...
but what I needed was:
ResetCallback() PadInitDirect() ... ResetGraph() InitGeom()
So I was initializing graphics first, when instead I needed to initialize BIOs system callbacks (ResetCallback) and do controller stuff first. The only issue I have now is that the vibration is set on immediately once you start the program and can't stop it, lol. But, I think this should be a pretty easy fix and I'll work on it later.
I fixed the controller vibration issue mentioned previously. The issue was that the buffer which stores each actuator/motor speed wasn't being initialized, so they had garbage starting values when the program starts which had non-zero values, so the controller library interpreted that as the motor being 'on'.
Motor = 0; // the first actuator, which only has an on/off state (0 or 1) Motor = 0; // the second actuator, which has a variable speed from 0 to 255
Once that was fixed, I cleaned up the class a little and added accessor functions to get button values and analog stick values. For the analog sticks, the values for each x/y axis is a value from 0 (pushed far left) to 255 (pushed far right). I had the accessor functions offset these values by 128 so instead the client will read analog values in a range from -128 to 127, with 0 being the default middle value. This makes it easier to distinguish between left/right or up/down.
The final steps for the Pad class will be to add vibration accessors for the client and add controller 2 support. After that will be either texture loading or font loading (although I guess font loading would involve loading a font texture anyways, so probably that)
Added second controller options and vibration function options. Ez-peasy. and also cleaned up the Pad code.
An interesting note from adding controller 2 support is that all of the PSYQ library controller functions use 0 and 16 (0x00 and 0x10) instead of 0 and 1 for specifying which controller the function is using. My guess as to why this is is that the controller number argument is used to represent the 16th bit of the instruction sent to the controller. Meaning, instructions with the 16th bit set to 0/1 are for the first/second controller. Just a theory.
0b1000000000000000 // 16th bit == 1 --> command for controller 2 0b0000000000000000 // 16th bit == 0 --> command for controller 1
Added basic texture uploading (texture class) and modified Sprite class to use it. Also added bmp2array utility to read 16-bit bmps and print out a C-array with its texture data. Other bit depths like 8bit and 24bit are TODO still. Probably more important is 8 bit in order to preserve VRAM. The 16-bit bmps can be created by exporting images from GIMP as bmp, then under 'Advanced Options', select X1 R5 G5 B5 format.
Another TODO is to have an option to enable/disable transparency. The PSX treats any exact black color in a texture (all zeros) as transparent. I think it'd be nice to have an option that automatically slightly increases the brightness of any black colors in the texture that way they aren't drawn as transparent by the psx unless desired.
I made a simple crate texture to test, and can move it around the screen with the directional pads, which can be seen in the screenshots below :)
Added texture to models (Model_textured, Cube_textured) classes. Moved some of the primitive setting stuff internally to the texture class; as the functionality was more related to textures than models. Finally, sprites and models can now call set_texture to share the same texture, which prevents unnecessary multiple loadings of the same data.
I noticed the test program somtimes is freezing on the PS1 hardware; I'm assuming it's related to the Pad class again. Oh well for now; it happens seemingly infrequently so I'll ignore it.
Added a Font class, to load bitmaps fonts (images) and render text with them. This is done by calculating where in the image the appropriate character is based on its input ascii value.
The fonts I used to test are by ZiNGOT on opengameart.org which I reorganized as 8x4 tables (they came entirely horizontal/vertical, which wouldn't fit well in PSX Vram).
Have a 3D model of a car partially loaded. Input was a simple car model from here, loaded into Blender and exported as a .PLY file, then I wrote a program to parse the .PLY and print out vertex, normal, and face index arrays to store in a header file. The arrays are set as options to the Model class in the main program. The PSX does have a specific 3d model format (.TMD) so I suppose that might be worth looking into.
I think something might be off with the normals as the result looks kind of funny:
But, at least progress was made :)
Fixed the orientation of loaded car model from ply2array. The issue was the combination of two factors: first, the car had the wrong orientation to begin with in Blender (it was pointing towards the Y axis, not the Z axis), and second, the Y and Z coordinates needed to be negated in ply2array.c. Now the car points forward and is flat like its supposed to.
Added lookat camera to hopefully use as a third person perspective. The camera currently follows/targets the car model I've loaded. The camera code was basically just copied from the PSn00bSDK examples. I think I need to convert the two spinning cube coordinates to fixed point format as they both seem to be stuck together now (need to be scaled by ONE aka 4096).
Added third person rotation to the camera. Technically it needs a bit more tweaking (rotation around the x axis is a bit wonky, but feels generally smooth so its good enough for now). Took me a little while to figure out how the angles work to move the camera around the target like a sphere. These two links were pretty helpful:
Additionally, I had to experiment a bit with the PSX 'rcos' and 'rsin' functions in combination with fixed point Q4.12 format to get the right combination of divisors/conversions between regular and fixed point formats. What I found is that basically for each multiplication between two fixed point numbers, you have to divide the result by 2^n (shift right by n). So for two multiplications you have to shift right by n twice. So my final current camera position offsets are calculated as follows:
offsetX = (rsin( yaw ) * radius ) >> 12; offsetY = (rcos( yaw ) * radius ) >> 12; offsetZ = (rsin( pitch ) * radius ) >> 12;
where the shifts divide out the n in Qm.n format (12 in this case). The result looks like so: