Show HN: My Vector, Matrix, Complex Number, Quaternion C++ Classes

These classes allow the user to simply code what they want without worrying about the implementation of these data types. For example, to multiply two 4×4 matrices (mat1 and mat2) together, just do mat1 * mat2. Ever hear the hype about quaternion rotation (being better than Euler rotation because of no gimbal lock, etc.) but…

2
Show HN: My Vector, Matrix, Complex Number, Quaternion C++ Classes

These classes allow the user to
simply code what they want without worrying about the implementation of these
data types. For example, to multiply two 4×4 matrices (mat1 and
mat2) together, just do mat1 * mat2. Ever hear the hype about
quaternion rotation (being better than Euler rotation because of no gimbal lock,
etc.) but find it too confusing… Just call
rotatept(point, angle, axisvector) – it will do the quaternion math for
you. How easy is that? Include my header files and enjoy coding.

I originally created these classes in order to more easily compile GLSL shaders
on the CPU instead of the GPU. But then I went beyond what I needed for that
purpose, adding complex numbers, quaternions, and 3d transformations
/ projections.

I purposely didn’t use features of C++11, C++14, C++17, C++20, etc. for my
classes, so they can be used in any environment. I suppose the use of
INFINITY in one of the functions might be an exception, but it’s contained
in an #ifdef, so the class will still compile.

Even though I don’t use any modern C++ features, I feel that my classes are
also very educational due to their versatility and especially due to the
various implementation versions.

Why So Many Versions?

As is typical in object-oriented languages, there are often many ways to do the
same thing. This tends to create camps of people who feel you should do
this, you shouldn’t do that, you should ALWAYS do this, and you should
NEVER do that, etc. Partly to head off criticism of that nature and partly
to show complete examples of some of the various ways of doing things, I’ve
provided many different versions. Choose for yourself which one/ones fit your
preferences. And hopefully, C++ will be a little less confusing to you.

All Friends

Making all overloaded operators friend functions is probably the easiest way to
do it. Many potential type conflicts would be eliminated by declaring these
overloaded operators as friends. However, there are pitfalls to this due to how
promiscuous it can be for everything to be friends. But in the case of my
classes, there’s no private data and no private functions, so I don’t feel it’s
as dangerous as it could be. Now, some would say you should always have your
class variables be private. But I feel most people would agree that using
getters and setters for a 3d vector class, for example, is needlessly tedious
and unnecessary. Besides, as stated above, my initial goal was to mimic GLSL,
and GLSL allows access to the class variables without getters and setters.
At any rate, if friends offend you, here’s some other ways:

Not Friends, No Templates

One way to avoid potential type conflicts, such as when multiplying a double
literal by a float 3d vector, is to eliminate the template. I’ve provided a
float version of this idea. If you want a double version, you could easily
use your text editor to automatically search for all float and replace with
double. But this approach to the type conflict problem can be limiting.
So, luckily there are other ways it can be done.

Not Friends, Not Members

Technically, even though the friend functions were implemented inside the class,
they are not member functions. So, in a sense this method isn’t much of a
departure from the All Friends method. But, it does require more work to
declare all the functions – each one needs it’s own template. And to avoid
the potential type conflicts, some functions require two parameter templates.
For example, some would be template and some would be
template (U being for the non-class variable (a
float variable, for example)). Hopefully, that made sense. Note, earlier
compilers (such as Microsoft Visual C/C++ 6 for example) do have a problem with
some two parameter templates with overloaded operators, so if using those
compilers, you would have to stick with template and make the
non-class variable be of type T. BTW, in many cases typename can
be used instead of class.

Not Friends, Mostly Members

You may want to have the overloaded operators be member functions. But
there’s a catch – not all overloaded operators can be member functions.
Member functions require the left-hand side operand to be the same type
as the class the function is a member of. So, if for example, you wanted
an overload of *, such that a float * vec3 could be done (as
opposed to vec3 * float), that overload could not be a member function.
This creates, in my opinion, a messy combination of member and non-member
overloads. But, if that’s your preference, go for it. But you might want to
try some other ways too…

Alternate Ways

You can have all member overloads be declared inline and inside the class,
and keep the non-member overloads as friends (vec2a.h). This is a little
less messy, but if you’re totally against friends, this will not do. So you
could put the non-member overloads outside the class as before (as in
Not Friends Not Members) (vec2b.h). Messy, but maybe not as messy as
Mostly Members above. And also a version that doesn’t have two parameter
templates (vec2c.h). You could also put the prototypes of the friend
functions in the class, but put the implementation outside the class, therefore
not inlined (vec2d.h). But that is a lot of extra work. You can also
make friend safer by doing it a different way, but it’s even more work
(vec2e.h). Here’s an excerpt:

Here, Vec2 doesn’t have the . Inside a templated class, C++
assumes all references to that class are templated, so no need to specify it.
But that’s not what makes it a little safer. The on the operator is.
This means only + operators of type T are friends with this class
of type T. Now that we know on Vec2 isn’t necessary, it
can be removed from the original All Friends version (vec2f.h).

I explain comp.h here (last item)

Test Programs And Building Them

I’ve provided various test programs that test every function of my classes.
testall.cpp tests most functions (doesn’t test some functions already
tested in testc.cpp, testq.cpp, and testmf.cpp). For
comparison purposes, testcc.c shows the results of calling (GNU?) C’s
(complex.h) complex number functions, and testccc.cpp shows the results
of calling C++’s complex number functions. These results can be compared with
my class (comp.h). And vgraph*.cpp shows what can be done with my
bonus 3d transformation / projection functions (matfunc.h). Refer to
vgraph*.cpp for keyboard and mouse controls (for example, left mouse drag
rotates the scene according to mouse position, and pressing c puts the
axis of rotation in the (X-Z) center of the house). And also qgraph*.cpp
shows what can be done with quaternion rotations. And refer to all test
programs for examples of how to code with my classes.

To get started…
Go to the directory you installed this repo in (using terminal or cmd window).
From there,

Then if using Linux, type:

to make everything, or:

to make the All Friends version, for example. Same for the other versions.

To clean up things (remove the executables):

Or If using Microsoft Visual Studio in Windows, make sure your MS-DOS
environment is set up correctly, and type:

to make everything, or:

nmake /f Makefile.win AllFriends

to make the All Friends version, for example. Same for the other versions.

To clean up things (remove the executables):

nmake /f Makefile.win clean

Note: if you are using an older version of Visual C (specifically version
6), you’ll need to use the other versions of CXXFLAGS and CFLAGS
in the Makefile.win file.

Some Things I Leave Up to the Users of This Repo

There are various things that can still be done, but I’m leaving them up to
the users of this repo to implement if they so choose. These include:

  • Add more typedef, or modify the ones already at the end of each
    include file. Like, for example, change float to double. Or you may even want
    to do away with typedef and use using (but that won’t work in older
    compilers).

  • Make full set versions (vec3, vec4, mat2, etc.) of the Alternate Ways
    methods. I created these for educational purposes to show even more ways of
    doing the same thing. But they’re not necessarily the best or easiest ways.
    Because of this, I saw no reason to implement a full set of each method. You
    may feel otherwise.

  • The Not Friends, Not Members version won’t compile as is in Visual C/C++ 6
    because of the two parameter templates. If you need it to compile in this
    compiler, get rid of the second parameter, You may even want to use the
    method I use in testc.cpp (using _MSC_VER).

  • You may want to break up each class file into two files – put the
    interface / definitions in a header (.h) file and the implementation in a
    source (.cpp) file.

  • You may want to put the various classes and functions into their own
    namespace or namespaces.

  • comp.h in AlternateWays (which is a modification of the
    NotFriends_MostlyMembers comp.h, though it can be implemented in any of the
    methods) shows how you could go about changing the
    complex number printing method dynamically in your program. No need to pick
    just one format by using a #define (before including comp.h or any
    include file that includes comp.h). You can either add a line like:
    comp::c_format = 1; to select format 1, for example, or you could pick
    any comp variable (for example c3) and add this: c3.setFormat(1);.
    Note: either of these methods changes all subsequent printings of any comp
    variable (perhaps more accurately, Comp) of the instantiated type to be this
    format (until another format setting occurs). This idea can also be applied
    to replace EPSILONCOMP if you desire.

Disclaimers

  • My programming style in terms of formatting was forged during my (mainly) C
    programming days, which in turn may have been influenced by my Pascal
    programming days. Some of these preferences don’t always fit nicely with C++
    (Java as well). So, there may be some inconsistencies with formatting choices.
    However, I will make NO apologies for the use of tabs (versus spaces) and the
    refusal to use Egyptian braces. I guess my style is similar to Whitesmiths.
    BTW, I use a tab width of 4 spaces – the included .editorconfig files
    should take care of that (I will agree that the default 8 space tabs would be
    ridiculous).

  • I think I put const everywhere that’s appropriate, but you never know.
    It’s generally more efficient to pass a pointer to a variable (call by
    reference) rather than pass (copy) the variable (call by value), but this
    causes the need for const to tell the compiler that you guarantee the function
    will not change the value of the variable (after all, with const you can’t).

  • In my early programming days, low memory and disk space usage (floppy and
    harddrive) was critical. Nowadays, speed is considered more important than
    usage. I will never advocate the “Who cares how much disk space or memory it
    uses, you can always buy more” attitude. But, for 3d graphics (of which you
    may be using this repo for) speed is crucial. So, in most cases, I inlined
    functions and unrolled for loops – sacrificing disk space/memory for speed.
    I hope I got the balance right. But, maybe it doesn’t matter anyway, since
    there’s a pretty good chance the compiler (if modern) will choose the best way
    to do things.

  • The sample program vgraph*.cpp is not double buffered. It’s only
    meant to show how you can use the classes/functions – it’s not meant to be a
    3d viewer. If the flicker is a problem for you, press 1 or 2 to
    get rid of the parallel and perspective projection together setting or press
    left and right cursor keys to change the delay between frames.
    qgraph*.cpp also isn’t double buffered, but the flicker isn’t as bad.

  • There are some comments in vec2.h, vec3.h, comp.h, and
    quat.h that apply to all the headers, but are only mentioned in one of
    them. comp.h also has alternate ways (commented out) of calculating the values.
    Feel free to try the other ways. Both comp.h and quat.h have various options
    you can #define to change from the default (for example, you can
    customize how you want complex numbers and quaternions printed).

Demonstration/Tutorial Video

Hopefully, coming soon.

Author

Mark Craig
https://www.youtube.com/MrMcSoftware

Reference

For the sake of this reference section, I’ll assume a template instantiation
of type float (via a typedef) and I’ll remove all const.
Keep in mind it doesn’t have to be float.

Quick Jumps:

Complex Numbers – comp.h

  • Member Variable Access:
    • r, i, real, imaginary, re, im, or V[2]
  • Constructors:
    • comp() { r=i=0; }
    • comp(float cr, float ci=0) { r=cr; i=ci; }
    • comp(comp &s) { r=s.r; i=s.i; }
    • comp(float v[2]) { r=v[0]; i=v[1]; }
  • Inline Functions:
    • comp square()
    • float dot()
    • float dot(comp &q)
    • float mag() // mag aka abs
    • float phase() // phase aka arg
    • comp normalized()
    • float normalize()
    • comp conjugate()
  • operator Overloads:
    • inline float operator [] (int i)
    • inline float& operator [] (int i)
    • comp operator + (comp &L, comp &R)
    • comp operator + (comp &L, float &R)
    • comp operator + (float &L, comp &R)
    • comp operator – (comp &R)
    • comp operator – (comp &L, comp &R)
    • comp operator – (comp &L, float &R)
    • comp operator – (float &L, comp &R)
    • comp operator * (comp &L, comp &R)
    • comp operator * (float &L, comp &R)
    • comp operator * (comp &L, float &R)
    • comp operator / (comp &L, comp &R)
    • comp operator / (comp &L, float &R)
    • comp& operator += (comp &L, comp &R)
    • comp& operator += (comp &L, float &R)
    • comp& operator -= (comp &L, comp &R)
    • comp& operator -= (comp &L, float &R)
    • comp& operator *= (comp &L, comp &R)
    • comp& operator *= (comp &L, float &R)
    • comp& operator /= (comp &L, comp &R)
    • comp& operator /= (comp &L, float &R)
    • bool operator == (comp &L, comp &R)
    • bool operator != (comp &L, comp &R)
    • std::ostream& operator << (std::ostream &os, comp &R)
  • Extra Functions:
    • inline comp Mag

Join the pack! Join 8000+ others registered users, and get chat, make groups, post updates and make friends around the world!
www.knowasiak.com/register/
Read More

Charlie
WRITEN BY

Charlie

Fill your life with experiences so you always have a great story to tell

Leave a Reply