Skip to content

Commit 6dd26bb

Browse files
committed
moved operator <=> and support functions out of dsga.hxx and into an example file
1 parent 36cd4d3 commit 6dd26bb

File tree

7 files changed

+236
-891
lines changed

7 files changed

+236
-891
lines changed

README.md

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
**dsga** is a single header-only **c++20 library** that implements the **vectors** and **matrices** from the OpenGL Shading Language 4.6 specification ([pdf](https://www.khronos.org/registry/OpenGL/specs/gl/GLSLangSpec.4.60.pdf) | [html](https://registry.khronos.org/OpenGL/specs/gl/GLSLangSpec.4.60.html)). It is inspired by the spec, but does deviate in some small ways, mostly to make it work well in c++20. It is not intended to be used for rendering, just for sharing the fundamental data structures and associated functions. Our requirements in general are for things like 3D CAD/CAM applications and other geometric and algebraic things. See [motivation](docs/MOTIVATION.md) for more details. This library does not use SIMD instructions or types under the hood, beyond whatever the compiler provides through optimization.
44

55
## Current Version
6-
v1.0.7
6+
v1.1.0
77

88
## Contents
99
* [Some Quick Examples](#some-quick-examples)
@@ -288,10 +288,15 @@ Remember, this is a c++20 library, so that needs to be the minimum standard that
288288
289289
## Status
290290
291-
Current version: `v1.0.7`
291+
Current version: `v1.1.0`
292+
293+
* Breaking Change with v1.1.0
294+
* Removed ```default_comparison_weights()```, ```weighted_compare()```, and related functions
295+
* Removed ```operator <=>``` for vector and matrix structs
296+
* Functionality is still available in [```examples/compare.hxx```](examples/compare.hxx)
292297
293298
* Everything major has some tests, but code coverage is not 100%.
294-
* [Released v1.0.0](https://github.com/davidbrowne/dsga/releases/tag/v1.0.0)
299+
* [Last Released: v1.1.0](https://github.com/davidbrowne/dsga/releases/tag/v1.1.0)
295300
296301
### The next steps
297302
* Working on much better API documentation.
@@ -321,8 +326,8 @@ The tests have been most recently run on:
321326
[doctest] doctest version is "2.4.11"
322327
[doctest] run with "--help" for options
323328
===============================================================================
324-
[doctest] test cases: 101 | 101 passed | 0 failed | 0 skipped
325-
[doctest] assertions: 2403 | 2403 passed | 0 failed |
329+
[doctest] test cases: 96 | 96 passed | 0 failed | 0 skipped
330+
[doctest] assertions: 2113 | 2113 passed | 0 failed |
326331
[doctest] Status: SUCCESS!
327332
```
328333
@@ -332,8 +337,8 @@ The tests have been most recently run on:
332337
[doctest] doctest version is "2.4.11"
333338
[doctest] run with "--help" for options
334339
===============================================================================
335-
[doctest] test cases: 101 | 101 passed | 0 failed | 0 skipped
336-
[doctest] assertions: 2403 | 2403 passed | 0 failed |
340+
[doctest] test cases: 96 | 96 passed | 0 failed | 0 skipped
341+
[doctest] assertions: 2113 | 2113 passed | 0 failed |
337342
[doctest] Status: SUCCESS!
338343
```
339344
@@ -345,8 +350,8 @@ Performs all the unit tests except where there is lack of support for ```std::is
345350
[doctest] doctest version is "2.4.11"
346351
[doctest] run with "--help" for options
347352
===============================================================================
348-
[doctest] test cases: 101 | 101 passed | 0 failed | 0 skipped
349-
[doctest] assertions: 2387 | 2387 passed | 0 failed |
353+
[doctest] test cases: 96 | 96 passed | 0 failed | 0 skipped
354+
[doctest] assertions: 2097 | 2097 passed | 0 failed |
350355
[doctest] Status: SUCCESS!
351356
```
352357
@@ -358,8 +363,8 @@ Performs all the unit tests except where there is lack of support for ```std::is
358363
[doctest] doctest version is "2.4.11"
359364
[doctest] run with "--help" for options
360365
===============================================================================
361-
[doctest] test cases: 101 | 101 passed | 0 failed | 0 skipped
362-
[doctest] assertions: 2403 | 2403 passed | 0 failed |
366+
[doctest] test cases: 96 | 96 passed | 0 failed | 0 skipped
367+
[doctest] assertions: 2113 | 2113 passed | 0 failed |
363368
[doctest] Status: SUCCESS!
364369
```
365370
@@ -371,8 +376,8 @@ Performs all the unit tests except where there is lack of support for ```std::is
371376
[doctest] doctest version is "2.4.11"
372377
[doctest] run with "--help" for options
373378
===============================================================================
374-
[doctest] test cases: 101 | 101 passed | 0 failed | 0 skipped
375-
[doctest] assertions: 2387 | 2387 passed | 0 failed |
379+
[doctest] test cases: 96 | 96 passed | 0 failed | 0 skipped
380+
[doctest] assertions: 2097 | 2097 passed | 0 failed |
376381
[doctest] Status: SUCCESS!
377382
```
378383
@@ -384,8 +389,8 @@ Performs all the unit tests except where there is lack of support for ```std::is
384389
[doctest] doctest version is "2.4.11"
385390
[doctest] run with "--help" for options
386391
===============================================================================
387-
[doctest] test cases: 101 | 101 passed | 0 failed | 0 skipped
388-
[doctest] assertions: 2403 | 2403 passed | 0 failed |
392+
[doctest] test cases: 96 | 96 passed | 0 failed | 0 skipped
393+
[doctest] assertions: 2113 | 2113 passed | 0 failed |
389394
[doctest] Status: SUCCESS!
390395
```
391396
@@ -397,8 +402,8 @@ Performs all the unit tests except where there is lack of support for ```std::is
397402
[doctest] doctest version is "2.4.11"
398403
[doctest] run with "--help" for options
399404
===============================================================================
400-
[doctest] test cases: 101 | 101 passed | 0 failed | 0 skipped
401-
[doctest] assertions: 2387 | 2387 passed | 0 failed |
405+
[doctest] test cases: 96 | 96 passed | 0 failed | 0 skipped
406+
[doctest] assertions: 2097 | 2097 passed | 0 failed |
402407
[doctest] Status: SUCCESS!
403408
```
404409

VS2022/dsga.vcxproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@
175175
<ClInclude Include="..\examples\angle.hxx" />
176176
<ClInclude Include="..\examples\basic.hxx" />
177177
<ClInclude Include="..\examples\bezier.hxx" />
178+
<ClInclude Include="..\examples\compare.hxx" />
178179
<ClInclude Include="..\examples\format_output.hxx" />
179180
<ClInclude Include="..\examples\hash.hxx" />
180181
<ClInclude Include="..\examples\ostream_output.hxx" />

VS2022/dsga.vcxproj.filters

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@
4848
<ClInclude Include="..\examples\hash.hxx">
4949
<Filter>Header Files</Filter>
5050
</ClInclude>
51+
<ClInclude Include="..\examples\compare.hxx">
52+
<Filter>Header Files</Filter>
53+
</ClInclude>
5154
</ItemGroup>
5255
<ItemGroup>
5356
<None Include="..\README.md" />

docs/DOCUMENTATION.md

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ explicit constexpr basic_vector(U value) noexcept;
312312
// variadic constructor of scalar and vector arguments
313313
template <typename ... Args>
314314
constexpr basic_vector(const Args & ...args) noexcept;
315-
```
315+
```
316316
317317
This approach is exactly what ```basic_matrix``` does.
318318
@@ -367,14 +367,6 @@ The vector operators all work component-wise.
367367
* ```operator ==```
368368
* ```operator !=``` - created automatically from ```operator ==``` in ```c++20```
369369
370-
#### Relational Comparison Operators - not in GLSL
371-
The comparisons are not very strict. They also work when comparing types where one is convertible to the other. The component count must be the same, except when comparing a length 1 vector with a scalar. They don't work with bool.
372-
* ```operator <=>``` - lexicographic comparison of x, y, z, w components in that order (assuming vectors of length 4)
373-
* ```operator <``` - created automatically from ```operator <=>``` in ```c++20```
374-
* ```operator <=``` - created automatically from ```operator <=>``` in ```c++20```
375-
* ```operator >``` - created automatically from ```operator <=>``` in ```c++20```
376-
* ```operator >=``` - created automatically from ```operator <=>``` in ```c++20```
377-
378370
### Vector Free Functions
379371
The vector functions all work component-wise, except for the geometric functions and the two vector relational functions ```any()``` and ```all()```.
380372
There are scalar versions of these vector functions where it makes sense, i.e., angle and trignometry, exponential, and common functions.
@@ -467,8 +459,6 @@ The vector component type must be floating-point.
467459
* ```all()``` - result relies on the components' relationship with each other
468460
* ```none()``` - result relies on the components' relationship with each other - not in GLSL
469461
* ```logicalNot()``` - can't use keyword ```not``` as a function name in ```c++```, so using ```logicalNot()```
470-
* ```default_comparison_weights()``` - not in GLSL - default weights for ```weighted_compare()```, showing dimensional priority
471-
* ```weighted_compare()``` - not in GLSL - called by ```operator <=>``` with ```default_comparison_weights()```. The underlying comparison has each dimension weighted separately and summed up to a signed integral comparison result, where negative means ```<```, positive means ```>```, 0 means equal. It takes a vector of weights, which should be either ```default_comparison_weights()``` or a swizzle of it that uses all the dimensions. Changing the signs of some or all of the weighted components could be an option as well. Returns the ordering relation of the arguments. This function allows customization for comparing vectors if the default comparisons from ```operator <=>``` are insufficient.
472462
473463
#### The Rest of the Specification
474464
@@ -564,13 +554,6 @@ The matrix operators all work component-wise, except for ```operator *```, which
564554
* ```operator ==```
565555
* ```operator !=``` - created automatically from ```operator ==``` in ```c++20```
566556
567-
#### Relational Comparison Operators - not in GLSL
568-
* ```operator <=>``` - lexicographic comparison of the vectors
569-
* ```operator <``` - created automatically from ```operator <=>``` in ```c++20```
570-
* ```operator <=``` - created automatically from ```operator <=>``` in ```c++20```
571-
* ```operator >``` - created automatically from ```operator <=>``` in ```c++20```
572-
* ```operator >=``` - created automatically from ```operator <=>``` in ```c++20```
573-
574557
### Matrix Free Functions
575558
The matrix functions treat a matrix as an entity instead of as a collection of components, except for ```matrixCompMult()```, which works component-wise.
576559

examples/compare.hxx

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
2+
// Copyright David Browne 2020-2023.
3+
// Distributed under the Boost Software License, Version 1.0.
4+
// (See accompanying file LICENSE_1_0.txt or copy at
5+
// https://www.boost.org/LICENSE_1_0.txt)
6+
7+
#include "dsga.hxx"
8+
9+
//
10+
// performing weighted comparisons
11+
//
12+
13+
namespace dsga
14+
{
15+
// default weights for comparison - x has priority over y, which is over z, which is over w
16+
template <std::size_t C>
17+
requires (C >= 1 && C <= 4)
18+
[[nodiscard]] constexpr auto default_comparison_weights() noexcept
19+
{
20+
constexpr auto weights = basic_vector<int, 4>(1, 3, 9, 27); // reverse order
21+
return[&]<std::size_t ...Is>(std::index_sequence<Is...>) noexcept
22+
{
23+
return basic_vector<int, C>(weights[C - Is - 1] ...);
24+
}(std::make_index_sequence<C>{});
25+
}
26+
27+
namespace detail
28+
{
29+
// helper that evaluates a binary operation lambda
30+
template <bool W1, dimensional_scalar T1, std::size_t C, typename D1, bool W2, dimensional_scalar T2, typename D2, typename BinOp>
31+
[[nodiscard]] constexpr auto binary_op(BinOp lambda,
32+
const vector_base<W1, T1, C, D1> &lhs,
33+
const vector_base<W2, T2, C, D2> &rhs) noexcept
34+
{
35+
return binary_op_execute_no_convert(std::make_index_sequence<C>{}, lhs, rhs, lambda);
36+
}
37+
38+
// comparison lambdas that return -1 for less than, 0 for equal, and 1 for greater than.
39+
// these are for the types that don't work with the sign() function
40+
constexpr inline auto unsigned_compare_op = [](unsigned_scalar auto lhs, unsigned_scalar auto rhs) noexcept -> int
41+
{
42+
return (lhs < rhs) ? -1 : ((lhs > rhs) ? 1 : 0);
43+
};
44+
45+
constexpr inline auto signed_unsigned_compare_op = []<signed_scalar T1, unsigned_scalar T2>(T1 lhs, T2 rhs) noexcept -> int
46+
{
47+
return (lhs < 0) ? -1 : ((static_cast<unsigned long long>(lhs) < static_cast<unsigned long long>(rhs)) ? -1 : ((static_cast<unsigned long long>(lhs) > static_cast<unsigned long long>(rhs)) ? 1 : 0));
48+
};
49+
50+
constexpr inline auto unsigned_signed_compare_op = []<unsigned_scalar T1, signed_scalar T2>(T1 lhs, T2 rhs) noexcept -> int
51+
{
52+
return (rhs < 0) ? 1 : ((static_cast<unsigned long long>(lhs) < static_cast<unsigned long long>(rhs)) ? -1 : ((static_cast<unsigned long long>(lhs) > static_cast<unsigned long long>(rhs)) ? 1 : 0));
53+
};
54+
55+
template <bool W1, non_bool_scalar T1, std::size_t C, typename D1, bool W2, non_bool_scalar T2, typename D2, bool W3, numeric_integral_scalar T3, typename D3>
56+
requires (signed_scalar<T3>)
57+
[[nodiscard]] constexpr auto compare_impl(const vector_base<W1, T1, C, D1> &x,
58+
const vector_base<W2, T2, C, D2> &y,
59+
const vector_base<W3, T3, C, D3> &weights) noexcept
60+
{
61+
if constexpr (floating_point_scalar<T1> && floating_point_scalar<T2>)
62+
{
63+
return functions::innerProduct(weights, basic_vector<T3, C>(functions::sign(x - y)));
64+
}
65+
else if constexpr (signed_scalar<T1> && signed_scalar<T2>)
66+
{
67+
return functions::innerProduct(weights, basic_vector<T3, C>(functions::sign(x - y)));
68+
}
69+
else if constexpr (unsigned_scalar<T1> && unsigned_scalar<T2>)
70+
{
71+
return functions::innerProduct(weights, basic_vector<T3, C>(binary_op(unsigned_compare_op, x, y)));
72+
}
73+
else if constexpr (signed_scalar<T1> && unsigned_scalar<T2>)
74+
{
75+
return functions::innerProduct(weights, basic_vector<T3, C>(binary_op(signed_unsigned_compare_op, x, y)));
76+
}
77+
else if constexpr (unsigned_scalar<T1> && signed_scalar<T2>)
78+
{
79+
return functions::innerProduct(weights, basic_vector<T3, C>(binary_op(unsigned_signed_compare_op, x, y)));
80+
}
81+
else
82+
{
83+
using commontype = std::common_type_t<T1, T2>;
84+
return compare_impl(static_cast<dsga::basic_vector<commontype, C>>(x.as_derived()), static_cast<dsga::basic_vector<commontype, C>>(y.as_derived()), weights);
85+
}
86+
}
87+
88+
// interface function for three-way comparison operator for vectors, using default weighting
89+
template <bool W1, non_bool_scalar T1, std::size_t C, typename D1, bool W2, non_bool_scalar T2, typename D2>
90+
[[nodiscard]] constexpr auto compare(const vector_base<W1, T1, C, D1> &x,
91+
const vector_base<W2, T2, C, D2> &y) noexcept
92+
{
93+
constexpr auto weights = default_comparison_weights<C>();
94+
return compare_impl(x, y, weights);
95+
}
96+
97+
// interface function for three-way comparison operator for vectors, using user-defined weighting
98+
template <bool W1, non_bool_scalar T1, std::size_t C, typename D1, bool W2, non_bool_scalar T2, typename D2, bool W3, numeric_integral_scalar T3, typename D3>
99+
requires signed_scalar<T3>
100+
[[nodiscard]] constexpr auto compare(const vector_base<W1, T1, C, D1> &x,
101+
const vector_base<W2, T2, C, D2> &y,
102+
const vector_base<W3, T3, C, D3> &weights) noexcept
103+
{
104+
return compare_impl(x, y, weights);
105+
}
106+
}
107+
108+
//
109+
// non-operator weighted comparison for vectors - suggest using default_comparison_weights() or a swizzle
110+
// of it that uses all the components. the weights show the priority of the dimensions in the comparison.
111+
//
112+
// returns the ordering of the the arguments based on a signed integral value, calculated using the weights
113+
// applied to the differences between the pair-wise components via compare_impl(). It is similar but different
114+
// than std::lexicographical_compare_three_way(), where that function iterates over the components, and this
115+
// function operates on all the components at once to arrive at values to compare. It is lexicographical
116+
// if the absolute value of the weights is in decreasing order, such as using default_comparison_weights<>().
117+
//
118+
// this custom weighted comparison or the default operator <=> comparisons are important for sorting
119+
// vectors for algorithms, .e.g., convex hull, min-max, etc. Using the default_comparison_weights<>()
120+
// (or a swizzle of them where every component is used once) will give you a total ordering in the sort
121+
// (assuming no comparison is std::unordered()).
122+
//
123+
// This comparison uses exact data, not fuzzy equality within a tolerance.
124+
//
125+
126+
template <bool W1, non_bool_scalar T1, std::size_t C, typename D1, bool W2, non_bool_scalar T2, typename D2, bool W3, numeric_integral_scalar T3, typename D3>
127+
requires signed_scalar<T3>
128+
[[nodiscard]] constexpr auto weighted_compare(const vector_base<W1, T1, C, D1> &first,
129+
const vector_base<W2, T2, C, D2> &second,
130+
const vector_base<W3, T3, C, D3> &weights) noexcept
131+
{
132+
if constexpr (std::integral<T1> && std::integral<T2>)
133+
{
134+
if (auto comp = detail::compare(first, second, weights); comp < 0)
135+
{
136+
return std::strong_ordering::less;
137+
}
138+
else if (comp > 0)
139+
{
140+
return std::strong_ordering::greater;
141+
}
142+
else
143+
{
144+
return std::strong_ordering::equal;
145+
}
146+
}
147+
else if constexpr (std::floating_point<T1> && std::floating_point<T2>)
148+
{
149+
// if any component of either inputs is a NaN, then it is unordered
150+
if (functions::any(functions::isnan(first.as_derived())) || functions::any(functions::isnan(second.as_derived())))
151+
return std::partial_ordering::unordered;
152+
153+
if (auto comp = detail::compare(first, second, weights); comp < 0)
154+
{
155+
return std::partial_ordering::less;
156+
}
157+
else if (comp > 0)
158+
{
159+
return std::partial_ordering::greater;
160+
}
161+
else
162+
{
163+
return std::partial_ordering::equivalent;
164+
}
165+
}
166+
else
167+
{
168+
using commontype = std::common_type_t<T1, T2>;
169+
return weighted_compare(static_cast<basic_vector<commontype, C>>(first), static_cast<basic_vector<commontype, C>>(second), weights);
170+
}
171+
}
172+
173+
//
174+
// lexicographic-like comparisons for vectors -- x has highest priority, then y, then z, then w.
175+
// all components are compared up front though, so it doesn't just stop checking when it finds the
176+
// first component that compares as not equal/equivalent.
177+
//
178+
// not in GLSL
179+
//
180+
181+
template <bool W1, non_bool_scalar T1, std::size_t C, typename D1, bool W2, non_bool_scalar T2, typename D2>
182+
constexpr auto operator <=>(const vector_base<W1, T1, C, D1> &first,
183+
const vector_base<W2, T2, C, D2> &second) noexcept
184+
{
185+
constexpr auto weights = default_comparison_weights<C>();
186+
return weighted_compare(first, second, weights);
187+
}
188+
189+
//
190+
// lexicographic comparisons of vectors in matrices.
191+
// it will stop checking after first vector comparison that is not equal/equivalent.
192+
//
193+
194+
constexpr auto mat_vec_comp_op =
195+
[]<floating_point_scalar T1, std::size_t C, floating_point_scalar T2>(const basic_vector<T1, C> &v1, const basic_vector<T2, C> &v2)
196+
{
197+
return v1 <=> v2;
198+
};
199+
200+
template <floating_point_scalar T1, std::size_t C, std::size_t R, floating_point_scalar T2>
201+
constexpr auto operator <=>(const basic_matrix<T1, C, R> &lhs,
202+
const basic_matrix<T2, C, R> &rhs) noexcept
203+
{
204+
return std::lexicographical_compare_three_way(lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), mat_vec_comp_op);
205+
}
206+
207+
}

0 commit comments

Comments
 (0)