#Math Vector2, Vector3 implementations

1 messages · Page 1 of 1 (latest)

merry ore
hard gazelle
#

nice implement

#
constexpr std::strong_ordering operator<=>(const Vector3& other) const noexcept {
        float len1 = length();
        float len2 = other.length();

        if (len1 < len2) return std::strong_ordering::less;
        if (len1 > len2) return std::strong_ordering::greater;
        return std::strong_ordering::equal;
    }
grizzled fern
#

I would

  • go for free functions only and avoid duplicates like v1.Cross(v2) and Cross(v1, v2)
  • not provide comparison of vectors as it does not really make any sense
  • never ever have vec3 auto-convert to vec2, that is just asking for trouble
  • have the value type as a template parameter defaulting to float
  • not provide a "Distance" of 2 vectors, seems misleading
merry ore
mystic spruce
#

Even ignoring the fact that it could be written as something like return length_square() <=> other.length_square();, it's still very confusing. It should've been a lexicographical comparison to work with maps better

#

Or shouldn't exist at all. Or be something like elementwise comparison returning a vector of bool

merry ore
#

Whats the difference between that and lexicohraphical comparison (if it aint already)?
Why shouldnt it exist or be like an elementwise comparison?

mystic spruce
#
std::map<vec2, std::string> dirs = {
    {{1,0}, "Right"},
    {{0,1}, "Down"},
    {{-1,0}, "Left"},
    {{0,-1}, "Up"},
};
#

Currently something like this would give you dirs.size() == 1, I believe

#

Because all vectors have the same length

mystic spruce
mystic spruce
#

They've got equal lengths, right?

#

Therefore < will return false for all pairs of vectors in the map

merry ore
#

Yes

merry ore
#

But also their values

mystic spruce
#

But std::map doesn't use ==

merry ore
#

Which shouldnt even matter as the size should get the actual size of the map

merry ore
mystic spruce
merry ore
#

Vector <=>*

mystic spruce
mystic spruce
merry ore
#

So what does it call

mystic spruce
#

<

merry ore
mystic spruce
#

I don't understand why <=> would call == or why it wouldn't matter

#

std::map considers vec(1,0) and vec(0,1) to be the same key for your vector, because !(a < b) && !(b < a) is the definition of equality it uses

merry ore
#

Well, how would you expect < to work with math vectors?

mystic spruce
#

And your code doesn't too. Why do you say Vector <=> calls operator== then? I'm confused

mystic spruce
merry ore
#

Oops sorry, I had a wrong assumption.

mystic spruce
#

Did you mean the opposite, that == calls <=>?

mystic spruce
#

And then to make your type work with maps, you specialize std::less. But this is a bit jank

merry ore
#

Nope. I meant that <=> first calls operator<
Is it false? Go next
<=> calls operator>. Is it false? Go next
<=> returns the result when a == b

#

That was my assumption

mystic spruce
#

I don't understand. You provided a custom <=>, so when someone calls <=> on your class, it just runs your custom implementation

merry ore
#

Yes

mystic spruce
#

What you described is the default behavior of <=> (or something similar to it), but it doesn't matter, since you don't use the default implementation?

merry ore
mystic spruce
#

Not std of course, YourVector<bool>

merry ore
#

I like that even less. How would that work?

mystic spruce
#
vec3<bool> operator<(vec3<T> a, vec3<T> b)
{
    return {a.x < b.x, a.y < b.y, a.z < b.z};
}
#

Or something

merry ore
merry ore
mystic spruce
#

What does "really <" mean?

#

Lexicographically? All values less? Some values less? Length less?

#

For lexicographically and length you'd have separate functions

#

All values less and some values less -> .all() and .any() (which you'd have to your vec3<bool>)

mystic spruce
merry ore
#

How would you consider a vector in math to be less than other vector?

mystic spruce
#

I don't know if there's a standard < for vectors in math

#

It's also weird that you implement all of ==,!=,<,<=,>,>=,<=>.
You don't need <,<=,>,>= if you have <=>, that's its entire point

#

You also no longer need != if you have == since C++20

#

So all you really need is == and <=>

#

(assuming you do all comparisons lexicographically, I don't know if <=> would play well with elementwise)

#

I also heavily recommend making the vector classes templates, with the element type being the template parameter

merry ore
#

Right now its defaulted to float but good idea.
Do you also know a way to include inheritance without any... side effects here?

merry ore
mystic spruce
#

Yeah, kinda. a != b gets automatically rewritten as !(a == b) if there is == and no !=

merry ore
mystic spruce
#

Not much differently

#

a <= b becomes (a <=> b) <= 0. Where <= 0 probably just checks x == less || x == equal, or x != greater, or something else

merry ore
#

ah

mystic spruce
#

The key part is that <=> ofc isn't called twice

grizzled fern
#

guys, what's going on?
there just IS NO ordering of vectors
so just don't implement <=> if 'the user' wants to compare lengths he will have to spell it out, and rightly so

mystic spruce
#

For lengths it makes little sense, yeah

#

But having it do lexicographical order is nice

merry ore
#

What is the lexicographical order, really?

mystic spruce
#

Compare elementwise, stop on the first mismatching element. If it's less in the lhs, return "less", otherwise return "greater". If all elements are equal return "equal"

#

This is what e.g. std::tuple does, or all the containers

merry ore
#

hmm

#

for vectors ig only vector length makes sense but then maps might be broken

#

i.e. comparing lengths

mystic spruce
#

Tbh I don't think comparing length makes sense

#

At least I'd be confused if < did that

merry ore
#

why?

mystic spruce
#

🤷‍♂️

#

It's subjectively confusing, dunno what else to say

merry ore
#

Well, if you say its confusing its good to say why bing_shrug

#

but alright, dw

mystic spruce
#

What I ended up doing for my library is something like this:

fvec3 a, b;

a < b(all) // same as a(all) < b, and similarly for others
a < b(any)
a < b(elemwise)
#

I guess a < b(length) would look decent too

#

I don't think I needed the length comparison very often. Most of the time I used the comparison to check if a point is within or outside a rect

#

Like, p >= a(all) && p < b(all)

mystic spruce
#
struct all_t {};
inline constexpr all_t all{};
#

Dummy structs

#

Vectors then overload operator() to accept those

#

You can use any other syntax you want

pastel bough
#

why not just use method calls to represent this

merry ore
#

Like?

mystic spruce
#

I guess that's an option, but I have .all() do something else in my lib

merry ore
#

That would be more confusing than just normal a < b

#

I.e. a < b.all()

pastel bough
#

Just the idea of vectors having order doesn’t even really make sense

merry ore
#

Never said I want to give them order. I said I want to compare them

pastel bough
#

I mean, yeah that’s what I mean

#

Comparison implies order

merry ore
#

only for containers that stores the elements in that order

pastel bough
#

it still doesnt make sense, nor is it very practical

mystic spruce
pastel bough
#

you dont need operator overloading to do that

merry ore
#

its simpler and more intuitive for the end user

pastel bough
#

i disagree

merry ore
#

so how would you see the math vector class?

pastel bough
#

im not sure what type of answer you are looking for

#

like you want me to just give you a vector class or something?

merry ore
#

no, I want to know your opinion about how math vectors should work in C++

pastel bough
#

ah ok

merry ore
#

as thats what Im doing here (making a vector class)

pastel bough
#
  • public members: vectors have no invariance, there is no point to making the members protected
  • definitely no interface shenanigans
  • no operator or function overloading (this is very subjective, and i suppose is just personal taste)
  • equality on a vector is iffy, since check for equality on floats is not a great idea (especially since equality for vectors can mean a lot of things)
#

i might think up of more

mystic spruce
pastel bough
#

yeah for "math types" operator overloading is alright

mystic spruce
#

Also the equality operator is a must. Even for floats it can be ok, if you're comparing with a value you assigned as opposed to computed

pastel bough
#

but also dot versus multiply schenanigans

pastel bough
#

their lengths or element-wise?

merry ore
#

Equality for vector mean one thing only: do they represented the same point in space? If not, they are not equal

#

Unless you are talking about that or length

pastel bough
#

thats not a very useful comparison

merry ore
#

Well, you cant directly compare 2 math vectors (or can you?)

#

Only compare their lengths

mystic spruce
pastel bough
#

there isnt very good reason to compare vectors element-wise (from my experience, yours could differ)

#

you could make an equals method that lets the user pass in an epsilon, i think that would be a little more useful (but still not super useful)

mystic spruce
#

I do it often, often for comparing with zero

#

(Btw "elementwise" is ambiguous with returning a vector of bool)

pastel bough
#

because the elements in their vectors are floats

#

not bools

mystic spruce
#

bool operator==(fvec2 a, fvec2 b) {return a.x == b.x && a.y == b.y;}
vs
bool_vec2 operator==(fvec2 a, fvec2 b) {return {a.x == b.x, a.y == b.y};}

pastel bough
#

ah i understand what you mean

pastel bough
mystic spruce
#

E.g. you pass fvec2 direction, with a special behavior if it's zero

pastel bough
#

thats a fair use case, ive done that before. but i dont think it justifies being able to compare with any other vector

mystic spruce
#

Maybe you don't need it often, but if you give me a library that doesn't support == on vectors, I'm gonna say it's a shit library

pastel bough
#

that is a very exteme take imo

pastel bough
pastel bough
mystic spruce
pastel bough
#

i'd at least recommend some sort of epsilon for comparing equality

merry ore
pastel bough
#

just complicates things

pastel bough
merry ore
#

Not really

pastel bough
#

basically, floating points numbers are not entirely accurate

merry ore
#

Yes, but comparing 2 floats will compare their bytes in cpp, no?

pastel bough
#

values like 0.1 are impossible to represent for example

#

this is what im taking about

merry ore
#

Yeah I know the classic 0.1 + 0.2 = 0.300000004

pastel bough
#

an epsilon allows for some error

mystic spruce
#

Making == do this would break standard maps, unless you give them a custom equality comparator

pastel bough
#

why in the world would you hash a float

mystic spruce
mystic spruce
pastel bough
#

please refrain from hashing floats, it is scary

merry ore
#

There is no float hashing though or am I missing something dumbbell

#

Unless all 3 floats (vec3) are hashed as part of one vector

#

If the vector is a key that is

pastel bough
merry ore
#

So how would you think about having vectors in a map?

mystic spruce
#

I guess the way I think about it is:
The vector should be a template anyway, and it makes perfect sense to have integer vectors as map keys and for them to have == comparison

#

And then it just works for floats out of the box too

pastel bough
merry ore
#

So you wouldnt allow vectors to be stored anywhere and you wouldnt allow vectors to be compared. Defeats the purpose of having vectors in the first place bing_shrug

pastel bough
#

?

#

you can still store them

pastel bough
merry ore
mystic spruce
#

How what would work?

merry ore
#

Int vectors as keys. How would it look like when you would want to store a vector as key with some value to it? Like```cpp
map<vec3, string> directions {
{vec3(1,0,0), "right" },
{vec3(0,1,0), "up" },
{vec3(0,0,1), "forward" },
} ;

merry ore
#

I mean, this vec still stores floats

#

What if you want to store positions of players into a map as keys and some value to it?

#

Then it cant be an int vector

pastel bough
#

Just curious: do you have a specific use case for this library? Or are you making it as “generic” as possible?

merry ore
#

Both

#

I want to make it as generic as possible but I will use it in my hobby project

mystic spruce
#

As 2spooky2play said, floating vector keys aren't very useful. Integer vector keys are useful though

merry ore
mystic spruce
#

This wouldn't be a map

#

There are special data structures for this

#

Look up BVH trees and such. E.g. box2d has an implementation of one

pastel bough
#

Why not make this for a specific thing so you can tailor it to your needs?

merry ore
#

Because i may reuse the vector somewhere else. Dont want to write the same thing twice

pastel bough
merry ore
#

True, but making it as generic as possible and storing it in one place is far easier than looking in a 5GB project for one class and still making it very generic

pastel bough
#

I mean grepping is a thing lol

merry ore
#

Grep can be a little too excited sometimes

#

In any case I want to have it as generic as possible

pastel bough
#

I mean you could also think about it this way: you are writing code for a project you might not even do

#

Why not just do the project?

merry ore
pastel bough
#

I just think it’s a better use of your time

merry ore
merry ore
pastel bough
#

Also public members means you don’t need to write getters and setters which is nice

merry ore
#

Now I have other question, if I were to implement templates, how would they work with the constexpr static member in the code?

mystic spruce
#

Wdym how?

merry ore
#

well, the constexpr static will break

mystic spruce
#

constexpr static is implicitly inline, and inline members don't need definitions in the .cpp, if that's what you're asking

merry ore
#

but I cant make constexpr static in the class definition if it by itself contain a class```cpp
class A {
public:
// error
constexpr static A member{};
};

#

when I split into declaration and definition then it will compile```cpp
class A {
public:
constexpr static A member;
};
constexpr A A::member{}; // compiles fine

mystic spruce
#

Hmm, but this doesn't work regaradless of templates, right?

merry ore
#

this works if templates are not used

mystic spruce
#

;compile -std=c++20

struct A
{
    static constexpr A a;
};
constexpr A A::a{};

int main()
{
    A x;
    (void)x.a;
}
safe nebulaBOT
#
Compiler Output
<source>:3:24: error: 'constexpr' static data member 'a' must have an initializer
    3 |     static constexpr A a;
      |                        ^
Build failed
merry ore
#

ah wait, you need to have it const

mystic spruce
#

It's implicitly const

merry ore
#

i.e static const first and then static constexpr second

#

;compile -std=c++20

struct A
{
    static const A a;
};
constexpr A A::a{};

int main()
{
    A x;
    (void)x.a;
}
safe nebulaBOT
#
Compilation successful

No output.

merry ore
#

there ya go

#

if I introduce templates, this stops working

mystic spruce
#

;compile -std=c++20

template <typename T>
struct A
{
    static const A a;
};

template <typename T>
constexpr A<T> A<T>::a{};

int main()
{
    A<int> x;
    (void)x.a;
}
safe nebulaBOT
#
Compilation successful

No output.

merry ore
#

yeah but you remove constexpr here

mystic spruce
#

Added it back, still works

#

I didn't know you can have mismatching const vs constexpr like this btw, interesting

merry ore
#

hmm, can you try with custom concept here?

template <typename Value>
concept arithmetic = std::is_integral_v<Value> || std::is_floating_point_v<Value>;

template <arithmetic T = float>
struct A {...};
mystic spruce
#

You can try yourself 😛

#

Seems to work

merry ore
#

Alright, it works. Thanks for help. Will implement it in a sec

#

Now another issue, I have a dependency between Vector2 and Vector3```cpp
class Vector2 {
public:
// ...
constexpr Vector2(Vector3 other) noexcept;
};

class Vector3 {
public:
// ...
constexpr Vector3(Vector2 other) noexcept;
};

constexpr Vector2(Vector3 other) noexcept : Vector2(other.GetX(), other.GetY()) {}

constexpr Vector3(Vector2 other) noexcept : Vector3(other.GetX(), other.GetY(), 0) {}

However after adding templates, this dependency stopped working (<https://godbolt.org/z/o9aMjqKo6>)
mystic spruce
#

You need to forward-declare Vector3 before Vector2

merry ore
#

if I forward declare it then the declaration will be inconsistent for some reason

mystic spruce
#

Show how you forward-declare it

merry ore
#
template <arithmetic Value = float>
class Vector3;
mystic spruce
#

I did the same and it worked. But I also had to drop = float from the definition and keep it only in the declaration

merry ore
#

alr works fine, thanks

merry ore
mystic spruce
#

Nah, I don't think you can do any better than A<>::a. <> can only be omitted in some context

#

I recommend doing using fvec3 = vec3<float>; or something