Mechwarrior-inspired game (code critique requested)

Ive been struggling with this for a couple weeks now and still cant figure it out… i managed to switch over to using brads, but when i try to implement fixed points instead of floats i have to replace all my floats with SQ7X8, and since the index expects an uint8_t i cant seem to get it working no matter what I try. At this point ive had the most luck just trying to implement my own lookup tables… at least then itll compile and upload, but i cant seem to figure out how to correctly map the angles so the functionality gets all fucked up there too.

Trying to adapt the code above (from your Trig.h file) causes numerous issues due to the difference in types between how my rotation is currently set up (even after changing from radians to brads using SQ7X8) since those Sin and Cos functions return an SQ1x14 and im really not sure how to convert between the two since static casting it would result in a conversion error due to narrowing

I’ll try to provide more information when I have a moment to do so.

I don’t have much spare time at the moment because one of my elderly relatives has been in hospital and consequently I’ve been needed elsewhere.

No worries! Sorry to hear though, hope everything is alright

I haven’t covered everything, but this was what I had time to cover:

All units measuring the same thing, be it angles, lengths, weight, et cetera, can be converted between each other using nothing more than multiplication by a constant.

The reason they can be converted through multiplication by a constant is because ultimately all units that measure the same thing are measuring the same thing, but in different quantities, thus the only difference between them is a difference of scale.

If you have a teaspoon of water and a tablespoon of water, either way you still have water, and the difference is only one of quantity: how many times larger a tablespoon is than a teaspoon, and how many times smaller a teaspoon is than a tablespoon. That same idea applies to all units, even when those units are abstract rather than being represented by or derived from real-world objects.

That constant may be represented as some awkward decimal number with a long list of seemingly abritrary digits after the decimal point, or it may be represented in a much more sensible way with a fraction that uses meaningful numbers…

To convert from millimetres to metres, you’d multiply by \frac{1000}{1}
To convert from metres to millimetres you’d multiply by \frac{1}{1000}

To convert from radians to degrees you’d multiply by \frac{180}{\pi}.
To convert from degrees to radians you’d multiply by \frac{\pi}{180}

Hopefully you’re starting to see a pattern…

To convert from radians to brads you’d multiply by \frac{128}{\pi}.
To convert from brads to radians you’d multiply by \frac{\pi}{128}.

Hopefully you see the relationship between converting between radians and brads and convering between radians and degrees, but just to spell it out…

To derive the conversion constant, you:

  • Take two quantities that are known to be equivalent
    • E.g. π radians, 180 degrees, and 128 brads all represent the same thing - a half turn
  • You take the quantity for the unit you are converting to and make that the numerator
  • You take the quantity for the unit you are converting from and make that the denominator

There are of course ways to make all of these conversions easier for humans to deal with, or to make them cheaper for computers to compute.

In your case though, you only need to convert your constants, so you only need to do it once and you only need to do it outside of your program, you don’t need to write any code to do it, so you’re free to do it as expensively or slowly as you like.

They’re not really designed with humans in mind, but their actual definition is a relatively sensible one.

1 radian is defined as the angle of a segment of the unit circle (i.e. a circle with a radius of 1) that has an arc length of 1.

Or, to put it more simply, you take a circle with a radius of 1, measure 1 along its edge, and the angle between those is 1 radian.

As a result of that definition, 2\pi is the number of radians in a whole circle/full turn, and \pi is the number of radians in a semicircle/half turn.

For more info and diagrams of that definition in action, see Maths is Fun’s Radians article. It’s a very good website that explains a lot of mathematical topics in very simple and approachable ways.

Not necessarily. It depends whether you have to store those rotations or not.

If you’re only rotating in response to some event such as button presses or as part of enemy AI then you can simply use addition and subtraction as required.

If you’re doing something like storing angular velocity on the other hand, then you’d need to deal with signed quantities.

Any of the three can be signed or unsigned, as can turns and gradians.

A sign (or equivalent) is necessary for indicating direction, but not merely for measuring position.

For comparion, think of how you can have world/screen coordinates be unsigned, but to represent velocity you need the sign to indicate which direction the object is travelling towards. It’s the same principle.

I’m taking a memory address because the lookup table is stored in progmem and the progmem-reading functions require a memory address to know which area of progmem to read from.

Theoretically I could have used / 64 and % 64 instead of ((brads & 0xC0) >> 6) and ((brads & 0x3F) >> 0) and arrived at the same results, but when I wrote the function I was thinking of things in terms of bits. Specifically, how the upper two bits of any unsigned integer representing a number of brads uniquely identify which quadrant of a circle the angle is in.

The whole crux of the optimisation being used here is that the sine and cosine functions produce effectively the same set of values for each quadrant of a circle with only minor differences in sign and ‘monotony’. Thus, by accounting for those differences by other means, one can reduce the number of entries that need to be stored in the lookup table.

Specifically for 8-bit brads, one can have a 64 element lookup table instead of a 256 element lookup table, and by reusing that same table for both sine and cosine, one only needs one lookup table instead of two.

(If this had been for a system with more memory, or at least where the memory was less of a concern than processor speed, I’d have just used two 256-element lookup tables and laughed at how two notoriously expensive operations had suddenly been reduced to an addition and a memory read.)

I’m not sure you’ll actually need to be giving signed brads to sin and cos since like I say you only need a sign if you’re dealing with direction (i.e. relative angles), and I expect in most cases you’ll be feeding absolute angles to sin and cos, but even if you do need to it’s pretty straightforward.

sin(-x) = -sin(x), e.g. for brads sin(-128) == -sin(128), so really all you’d have to do for a version that accepts int8_t is (angle < 0) ? -sin(static_cast<uint8_t>(-angle)) : sin(static_cast<uint8_t>(angle))

I’m confused about why you’re factoring collisions into the size of your objects.
Unless you mean the dimensions of your objects? (Width and height.)

If you’re only using sine and cosine, you won’t need another lookup table, you can just reuse the one from that example code.

It expects a uint8_t because the brads are supposed to go from 0 to 255.

If you’re trying to have fixed point brads then you’ll need to compensate.

For UQ8x8 that’s easy, you just take the integer part and ignore the fraction part.
For SQ7x8 you ought to be able to get away with the same thing.

You can change that.

It uses SQ1x14 because that’s the type that best fits the range of values sine and cosine produce (from -1 to +1, with all the values in between being fractional).

If you’re only dealing with SQ7x8 then all you have to do is replace the use of SQ1x14 with SQ7x8. Just be aware that you’ll be discarding a lot of fractional precision by doing so.

You could instead try using SQ15x16, which is the next scale up from SQ7x8 and will thus have better precision (certainly enough to encompass all the same values that SQ1x14 can represent), but there will be a cost (in terms of progmem and possibly CPU cycles) for doing so.

You would only need the SQ15x16 for the duration of the calculation, after which you can convert back to SQ7x8 when you need to store the result.

You would of course be discarding some precision, but it’s better to have the precision during the calculation and then discard it than to discard it prior to the calculation or to attempt to retain the precision when you store the result (because that will result in wasted RAM).

Sorry, that is what i meant- either way, i was just anticipating a little more overhead due to that.

And the rest of that makes much more sense, thank you! I’ll have to step things back a bit and try again with all of that in mind… i may still have to redo everything from the beginning but if thats the case at least i can plan it out a little better with all that stuff in mind, thanks!

Ok, so i couldn’t figure out how to convert over what I had (probably since i was trying to change 3 things at once- my data types, units of rotation, and trig functions) so I had to just start over again from scratch and implement everything from the beginning… now i’ve managed to get the minimap (and player rotation within it) set up again and working correctly, but i can’t get the 3D perspective projection to work… luckily I was able to salvage that part from my previous version at least, and it almost works, except it not only renders the 3D grid but also has a whole bunch of extra “noise” pixels and I’m not sure why… it may be due to overflow since I’m using static_cast, but im really not sure since ive tried to change my data types around a little bit to see if it helped (using larger types, signed vs unsigned, etc) and still keep getting the same results. So at this point i could definitely use a second (more experienced) set of eyes to maybe spot the issue.

The code can be found here

Also please excuse my use of ‘int’ in a few places (instead of explicitly using signed/unsigned 8 or 16 bit), i just did this due to the issue ive been having in case it was due to overflow and the compiler might set it to the correct type, but i know this isn’t necessarily best practice. Thanks!

Oh whoops, nevermind- i actually just got it working. In my previous version i was able to just skip over any points that were behind the player, but for some reason this time i also had to skip over any points that were too close and skip over any points outside of the player’s field of view… not really sure why considering i just reused the same function in both versions (modified to use fixed points) but whatever, at least i finally got it :man_shrugging: lol. Now i just need to tidy up/orgranize the code a bit and I can compare the difference in memory between both versions (which im sure will be a decent amount, just curious to see an actual comparison)… also i can already see an immediate improvement in speed between the two and can confirm using fixed points is much faster (which may be due to the fact that it isnt quite as accurate, but still definitely an improvement)

So now that I’ve directly compared the two, my version with fixed points frees up 13 bytes of RAM… which doesnt sound like much at face value, but bearing in mind that all i have is the 2D minimap and 3D grid at the moment then im sure this will add up to a pretty large amount of memory savings once i put back in all the other parts i had as well. Anyway, thanks for the help/guidance, much appreciated!

Now I just need to change the repo to the correct licensing (and make sure I’m following the one specified in Pharap’s trig library since im using it) then i might fork this to its own branch (so anyone can use it as a template for other types of games if they want to) or might just wait and put the code up with the final version