Small3dlib questions

I have been working with @drummyfish’s excellent small3dlib library to try to make a game. I asked some questions in Discord and was encouraged to post my questions here. My questions are a bit scattered, but I’ll try to compile them in one post.

I wanted to move the camera perpendicular to its facing direction on the XZ axis (basically strafing), and I didn’t know the “canonical” way to do it. I figured out a way that worked, but then found a demo example that I expect is the better way.

My original approach was to try to convert the camera’s angle of rotation into a vector and calculated its individual components on the x and z axes. The library header says that a full 360 degree rotation is S3L_FRACTIONS_PER_UNIT, so I thought a 90 degree rotation would be S3L_FRACTIONS_PER_UNIT/4, but that moved along the direction the camera was facing. Instead, S3L_FRACTIONS_PER_UNIT/2 worked. Why is that? Here is the code I was using to strafe right (the hal3d functions are just wrapper functions I wrote):

*camera.x -= hal3d_cos(*camera.ry + S3L_FRACTIONS_PER_UNIT/2);
*camera.z -= hal3d_sin(*camera.ry + S3L_FRACTIONS_PER_UNIT/2);

After going through the code of some of the included examples, I found one already did strafing and used vector math, which looks like it’s how it should be done (code from level.c example showing strafing right):

S3L_Vec4 camF, camR;

S3L_rotationToDirections(scene.camera.transform.rotation,20,&camF,&camR,0);

...

else if (state[SDL_SCANCODE_RIGHT])
  S3L_vec3Add(&scene.camera.transform.translation,camR);

However, I have been having trouble getting this to work because I think only one file can include small3dlib.h at a time without problems. I’m trying to divide my project where each game entity is in its own file, but I can’t refer to any of the S3L_* functions without creating a wrapper function in the file that can include the header library. I have encountered some header libraries that allow for setting a preprocessor variable to include only function and type declarations so a header library can be included in multiple files. Is there a way to do this with this library?

Right now I have been using a single file as an interface with small3dlib that I’m calling hal3d, and all other components call that as an abstraction layer. However, I also have a types.h header that defines some of the types from small3dlib.h such as S3L_Unit and S3L_Index so I can use them in other files. This worked until I needed the definition of S3L_Vec4, and now I’m getting errors like

../small3dlib/small3dlib.h:1595:6: note: expected ‘S3L_Vec4 {aka struct <anonymous>}’ but argument is of type ‘S3L_Vec4 {aka struct <anonymous>}’

probably because it doesn’t encounter the two definitions identical.

My main questions are:

  • Why does subtracting S3L_FRACTIONS_PER_UNIT/2 from an angle correspond to a 90 degree rotation?
  • Is the method of strafing implemented in the examples a better/more efficient approach?
  • Is there a way I can include the library in multiple files without conflicting definitions? If not, @drummyfish, is that a feature that you would be interested in adding?
3 Likes

Probably the only question I can answer, though it’s mostly a guess.
I would presume it’s because 90 degrees is equivalent to pi/2 radians.


And for the record, I think pi/2 radians = 90 degrees is incredibly stupid,
which is why I’m a big supporter of tau, the superior circle constant.

1 Like

The documentation in the library header says

Angles are in S3L_Units, a full angle (2 pi) is S3L_FRACTIONS_PER_UNITs.

This is what made me wonder why 90 degrees is not S3L_FRACTIONS_PER_UNIT/4, because the documentation appears to imply S3L_FRACTIONS_PER_UNIT == 2 pi == tau. Maybe 360 degrees of rotation is actually 2*S3L_FRACTIONS_PER_UNIT?

1 Like

For me it works with S3L_FRACTION_PER_UNIT / 4 as it should, but as I’m looking at it the signs have to differ, i.e.:

camera.transform.translation.x -= S3L_sin(scene.camera.transform.rotation.y + S3L_FRACTIONS_PER_UNIT / 4) / 128;
camera.transform.translation.z += S3L_cos(scene.camera.transform.rotation.y + S3L_FRACTIONS_PER_UNIT / 4) / 128;

This is maybe a bit unfortunate but it’s because of how I’ve defined rotation, it’s also written in the doc comment:

Positive rotation about an axis rotates CW (clock-wise) when looking in the direction of the axis.

Which means that camera rotating around Y goes turning “left”, while creating a direction vector with sin and cos will yield a vector turning “right”, so you have to invert one sign.

I’ve tried the above in the demo and it has worked.

If you only plan to strafe in XZ plane, you can do it this way (it’s likely faster, but you won’t possibly notice it), otherwise use S3L_rotationToDirections because determining direction vectors for arbitrary rotation is hard.

You can include the library from multiple files, there will only be problems if you use multiple compile units, i.e. multiple *.cpp (or *.c) files (which include the library) that you compile separately and then link – which only makes sense with big programs that take ages to recompile as a whole. I haven’t intended for the library to be used with this kind of compilation so it would need to be manually edited for this purpose (which would be easy – just split it into .h and .c).

So if you use single compile unit – as I think should be the case – it should work, you just include the file anywhere you need – there’s an include guard, so it will actually only get included once, but the other files should see the first included version. What I do if I want to split the program into multiple files is simply put each module in a separate .h file (containing implementations) and then include them all in main.c – it is possible to write any program in a single file, but for the sake of readability and better organization it can be better to split it into multiple parts. E.g.:

#include "small3dlib.h"

#include "player.h"     // sees the library 
#include "enemies.h"    // sees the library
#include "levels.h"     // sees the library

int main()
{
  ...
}

Yes, I’ve seen that too – again, it’s for multiple compilation units. I don’t do this with my library, but it can easily be achieved by splitting the file to a header (declarations, top of the file) and the implementation (the rest of the file), here.

2 Likes

Thanks! That answers most of my questions. Is there any significance to dividing by 128, or was that an amount that gave the right movement speed?

I understand if you don’t want to design the library for multiple compilation units. Would you be uninterested in a pull request adding support for that? I’m just asking in case I decide to make that change for my project, which I did split into multiple .c files.

2 Likes

The moral of this story: don’t guess. :P

Sometimes using a .cpp file is the cleanest way to account for a circular dependence.

For example in my recent entry into an Arduboy non-game jam there’s a circular dependence between Game and all the game states because Game contains an object of every state and all the states need to access Game's member functions.

Trying to solve this using only headers would get messy very quickly.
You’d have to make sure that all the states were defined first, then Game, and then the functions for the states,
which could only be done by either putting the function definitions in a separate header or putting everything into the same file, both of which are quite messy/brittle options.

Besides which, some people just prefer having separate .h and .cpp files.

Try to avoid using this as a fix, it’s a clunky way of doing things.

(And just being pedantic: there’s technically no such thing as a ‘preprocessor variable’.)

Do you prefer preprocessor identifier, or preprocessor definition? Also, what would you recommend as a “correct” method of doing this for a header-only library?

Yes, it was just for the speed.

I definitely want to keep the library in a single file, but am now considering adding the macro way of including only the declarations since you showed interest in that. It shouldn’t be difficult, so I’ll try to do it when I have time.

1 Like

‘Macro’ is both the technical term (as used by the standard) and the term most people recognise.

Normally I wouldn’t call out a minor mislabelling,
but calling it a ‘preprocessor variable’ could cause confusion with actual variables.

Preprocessor identifier is somewhat correct as the ‘names’ that identify macros are technically identifiers.

Ultimately it would depend on exactly why the header can’t be included from multiple translation units.
I’ve never encountered such a situation myself and I can only think of two things that could cause it: not marking functions as inline or some obscure macro usage.

If the library doesn’t have to be header-only then having an actual .c/.cpp file is a perfectly viable option and is typically preferred.
Of the libraries I’ve used in the past, most take this approach. (E.g. SDL2, Lua, the PokittoLib itself)
It has the added benefit of allowing the .c/.cpp files to be precompiled into static libraries that can be linked at compile time, which reduces the time required to compile the program.
(Though in this day and age CPUs are fast enough that a ‘long’ compile time would still be in the order of seconds.)

If for some reason .c/.cpp files aren’t a viable option,
the next best option is to put the code that can’t be included in multiple translation units into a separate header.
That way multiple translation units can include the ‘safe’ code and only one will include the ‘unsafe’ code.