Skip to content
This repository was archived by the owner on Aug 2, 2024. It is now read-only.

Commit 57f9078

Browse files
committed
Add plane reflection action call operator
1 parent 65f3d05 commit 57f9078

File tree

10 files changed

+275
-25
lines changed

10 files changed

+275
-25
lines changed

docs/api/kln::entity.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ Ex: The exterior product $e_1 \wedge \mathbf{e}_{32}$ is $-\mathbf{e}_{123}$.
156156

157157
Poincaré Dual
158158

159-
The Poincaré Dual of an element is the "subspace complement" of the argument with respect to the pseudoscalar. In practice, it is a relabeling of the coordinates to their dual-coordinates and is used most often to implement a "join" operation in terms of the exterior product of the duals of each operand.
159+
The Poincaré Dual of an element is the "subspace complement" of the argument with respect to the pseudoscalar in the exterior algebra. In practice, it is a relabeling of the coordinates to their dual-coordinates and is used most often to implement a "join" operation in terms of the exterior product of the duals of each operand.
160160

161161
Ex: The dual of the point $\mathbf{e}_{123} + 3\mathbf{e}_{013} - 2\mathbf{e}_{021}$ (the point at $(0, 3, -2)$) is the plane $e_0 + 3e_2 - 2e_3$.
162162

docs/api/kln::plane.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ The plane multivector in PGA looks like $d\mathbf{e}_0 + a\mathbf{e}_1 + b\mathb
1818
`public ` [`plane`](#structkln_1_1plane_1a29dbb1804fa3ec402f901ca8049d60bc)`(float * data) noexcept` | Data should point to four floats with memory layout `(d, a, b, c)` where `d` occupies the lowest address in memory.
1919
`public explicit ` [`plane`](#structkln_1_1plane_1a4fe52a0426bc881947909d0d9b1745d0)`(` [`entity`](/Klein/api/kln::entity#structkln_1_1entity)`< 0b1001 > const & other) noexcept` | Line to plane cast.
2020
`public void ` [`load`](#structkln_1_1plane_1a5a00871dbe19d7658b7e8cda71b326f2)`(float * data) noexcept` | Unaligned load of data. The `data` argument should point to 4 floats corresponding to the `(d, a, b, c)` components of the plane multivector where `d` occupies the lowest address in memory.
21+
`public ` [`plane`](#structkln_1_1plane)` KLN_VEC_CALL ` [`operator()`](#structkln_1_1plane_1a1c7a11e35d91c2aee88a4152b1799ca9)`(` [`plane`](#structkln_1_1plane)` const & p) const noexcept` | Reflect another plane $p_2$ through this plane $p_1$. The operation performed via this call operator is an optimized routine equivalent to the expression $p_1 p_2 p_1$.
22+
`public ` [`line`](/Klein/api/kln::line#structkln_1_1line)` KLN_VEC_CALL ` [`operator()`](#structkln_1_1plane_1a782e4e0b1b93ab5bffb2c972f6d7acfa)`(` [`line`](/Klein/api/kln::line#structkln_1_1line)` const & l) const noexcept` | Reflect line $\ell$ through this plane $p$. The operation performed via this call operator is an optimized routine equivalent to the expression $p \ell p$.
23+
`public ` [`point`](/Klein/api/kln::point#structkln_1_1point)` KLN_VEC_CALL ` [`operator()`](#structkln_1_1plane_1a20b6577f6d1717e1ec086314d3ebd497)`(` [`point`](/Klein/api/kln::point#structkln_1_1point)` const & p) const noexcept` | Reflect the point $P$ through this plane $p$. The operation performed via this call operator is an optimized routine equivalent to the expression $p P p$.
2124
`public float ` [`x`](#structkln_1_1plane_1ad2a57bafbd388d4ca9ca98d0074fee0a)`() const noexcept` |
2225
`public float & ` [`x`](#structkln_1_1plane_1a42cc26e2b8d3f4d620a9719716583e93)`() noexcept` |
2326
`public float ` [`y`](#structkln_1_1plane_1adce64826f47e1a017fba8fefab69c174)`() const noexcept` |
@@ -64,6 +67,18 @@ Unaligned load of data. The `data` argument should point to 4 floats correspond
6467
This is a faster mechanism for setting data compared to setting
6568
components one at a time.
6669

70+
#### [plane](#structkln_1_1plane) KLN_VEC_CALL [operator()](#structkln_1_1plane_1a1c7a11e35d91c2aee88a4152b1799ca9)( [plane](#structkln_1_1plane) const & p) const noexcept {#structkln_1_1plane_1a1c7a11e35d91c2aee88a4152b1799ca9}
71+
72+
Reflect another plane $p_2$ through this plane $p_1$. The operation performed via this call operator is an optimized routine equivalent to the expression $p_1 p_2 p_1$.
73+
74+
#### [line](/Klein/api/kln::line#structkln_1_1line) KLN_VEC_CALL [operator()](#structkln_1_1plane_1a782e4e0b1b93ab5bffb2c972f6d7acfa)( [line](/Klein/api/kln::line#structkln_1_1line) const & l) const noexcept {#structkln_1_1plane_1a782e4e0b1b93ab5bffb2c972f6d7acfa}
75+
76+
Reflect line $\ell$ through this plane $p$. The operation performed via this call operator is an optimized routine equivalent to the expression $p \ell p$.
77+
78+
#### [point](/Klein/api/kln::point#structkln_1_1point) KLN_VEC_CALL [operator()](#structkln_1_1plane_1a20b6577f6d1717e1ec086314d3ebd497)( [point](/Klein/api/kln::point#structkln_1_1point) const & p) const noexcept {#structkln_1_1plane_1a20b6577f6d1717e1ec086314d3ebd497}
79+
80+
Reflect the point $P$ through this plane $p$. The operation performed via this call operator is an optimized routine equivalent to the expression $p P p$.
81+
6782
#### float [x](#structkln_1_1plane_1ad2a57bafbd388d4ca9ca98d0074fee0a)() const noexcept {#structkln_1_1plane_1ad2a57bafbd388d4ca9ca98d0074fee0a}
6883

6984
#### float & [x](#structkln_1_1plane_1a42cc26e2b8d3f4d620a9719716583e93)() noexcept {#structkln_1_1plane_1a42cc26e2b8d3f4d620a9719716583e93}

docs/api/kln::point.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ A point is represented as the multivector $x\mathbf{e}_{032} + y\mathbf{e}_{013}
1313
--------------------------------|---------------------------------------------
1414
`public ` [`point`](#structkln_1_1point_1a4ea8cb4d3d80d9f50032061265ed32b6)`() = default` | Default constructor leaves memory uninitialized.
1515
`public ` [`point`](#structkln_1_1point_1adc78c30fe71a140259dd7add02d36df8)`(float x,float y,float z) noexcept` | Component-wise constructor (homogeneous coordinate is automatically initialized to 1)
16-
`public ` [`point`](#structkln_1_1point_1a1e262c4762927c28e7ba744acbe672bf)`(` [`entity`](/Klein/api/kln::entity#structkln_1_1entity)`< 0b1000 > const & e)` |
16+
`public ` [`point`](#structkln_1_1point_1a9b3754f1b4479d862d026cf55b91f7be)`(` [`entity`](/Klein/api/kln::entity#structkln_1_1entity)`< 0b1000 > const & e) noexcept` |
17+
`public explicit ` [`point`](#structkln_1_1point_1ad149cbbf9e9a7e77fc680b808e971ee0)`(` [`entity`](/Klein/api/kln::entity#structkln_1_1entity)`< 0b1001 > const & e) noexcept` | A point projected onto a line will have an extinguished partition-0, which necessitates this explicit cast when it is known to be safe.
1718
`public void ` [`load`](#structkln_1_1point_1a805739dde6d772f5b228cce5b95ef13f)`(float * data) noexcept` | Fast load from a pointer to an array of four floats with layout `(w, z, y, x)` where `w` occupies the lowest address in memory.
1819
`public float ` [`x`](#structkln_1_1point_1a0c100888d1b7edc3fdb81f8b7c22feb1)`() const noexcept` |
1920
`public float & ` [`x`](#structkln_1_1point_1a47e6deddfdd3939952ef80bb33236bfc)`() noexcept` |
@@ -35,7 +36,11 @@ Default constructor leaves memory uninitialized.
3536

3637
Component-wise constructor (homogeneous coordinate is automatically initialized to 1)
3738

38-
#### [point](#structkln_1_1point_1a1e262c4762927c28e7ba744acbe672bf)( [entity](/Klein/api/kln::entity#structkln_1_1entity)< 0b1000 > const & e) {#structkln_1_1point_1a1e262c4762927c28e7ba744acbe672bf}
39+
#### [point](#structkln_1_1point_1a9b3754f1b4479d862d026cf55b91f7be)( [entity](/Klein/api/kln::entity#structkln_1_1entity)< 0b1000 > const & e) noexcept {#structkln_1_1point_1a9b3754f1b4479d862d026cf55b91f7be}
40+
41+
#### explicit [point](#structkln_1_1point_1ad149cbbf9e9a7e77fc680b808e971ee0)( [entity](/Klein/api/kln::entity#structkln_1_1entity)< 0b1001 > const & e) noexcept {#structkln_1_1point_1ad149cbbf9e9a7e77fc680b808e971ee0}
42+
43+
A point projected onto a line will have an extinguished partition-0, which necessitates this explicit cast when it is known to be safe.
3944

4045
#### void [load](#structkln_1_1point_1a805739dde6d772f5b228cce5b95ef13f)(float * data) noexcept {#structkln_1_1point_1a805739dde6d772f5b228cce5b95ef13f}
4146

docs/overview.md

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,18 @@
22

33
Working with Klein is designed to be as simple as it is efficient. To "grok" the API, the main thing
44
to understand is that everything that has an underlying multivector representation is an
5-
[`entity`](/Klein/api/kln::entity) which means that all operations supported by the entity will be
5+
[`entity`](../api/kln::entity) which means that all operations supported by the entity will be
66
inherited.
77

8-
The entities provided are Euclidean objects ([`points`](/Klein/api/kln::point), [`lines`](/Klein/api/kln::line), [`planes`](/Klein/api/kln::plane)), objects that arise in
9-
Projective space ([`directions`](/Klein/api/kln::direction), [`ideal lines`](/Klein/api/kln::ideal_line)),
10-
and geometric actions ([`rotors`](/Klein/api/kln::rotor), [`translators`](/Klein/api/kln::translator),
11-
[`motors`](/Klein/api/kln::motor)).
8+
The entities provided are Euclidean objects ([`points`](../api/kln::point), [`lines`](../api/kln::line), [`planes`](../api/kln::plane)), objects that arise in
9+
Projective space ([`directions`](../api/kln::direction), [`ideal lines`](../api/kln::ideal_line)),
10+
and geometric actions ([`rotors`](../api/kln::rotor), [`translators`](../api/kln::translator),
11+
[`motors`](../api/kln::motor), and [`planes`](../api/kln::plane)).
12+
13+
!!! note
14+
15+
The call operator on a plane performs a _reflection_ of the passed entity through the plane. The call operator on a rotor performs a _rotation_ of the entity. The translator
16+
translates, and the motor performs a combination of a rotation and a translation.
1217

1318
The multivector operations such as the geometric product, exterior product, regressive product, etc.
1419
are supported for all the listed entities above via the following operator table:

public/klein/detail/sandwich.hpp

Lines changed: 147 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,149 @@ inline namespace detail
2727
// p2: (e0123, e01, e02, e03)
2828
// p3: (e123, e021, e013, e032)
2929

30+
// Reflect a plane through another plane
31+
// b * a * b
32+
KLN_INLINE void KLN_VEC_CALL sw00(__m128 const& a,
33+
__m128 const& b,
34+
__m128& p0_out)
35+
{
36+
// (2a0(a1 b1 + a2 b2 + a3 b3) - b0(a1^2 + a2^2 + a3^2)) e0 +
37+
// (2a1(a2 b2 + a3 b3) + b1(a1^2 - a2^2 - a3^2)) e1 +
38+
// (2a2(a1 b1 + a3 b3) + b2(a2^2 - a3^2 - a1^2)) e2 +
39+
// (2a3(a1 b1 + a2 b2) + b3(a3^2 - a1^2 - a2^2)) e3
40+
41+
// Left block
42+
__m128 tmp
43+
= _mm_mul_ps(KLN_SWIZZLE(a, 1, 1, 2, 1), KLN_SWIZZLE(b, 1, 1, 2, 1));
44+
tmp = _mm_add_ps(
45+
tmp,
46+
_mm_mul_ps(KLN_SWIZZLE(a, 2, 3, 3, 2), KLN_SWIZZLE(b, 2, 3, 3, 2)));
47+
tmp = _mm_add_ss(
48+
tmp,
49+
_mm_mul_ss(KLN_SWIZZLE(a, 0, 0, 0, 3), KLN_SWIZZLE(b, 0, 0, 0, 3)));
50+
tmp = _mm_mul_ps(tmp, _mm_mul_ps(a, _mm_set1_ps(2.f)));
51+
52+
// Right block
53+
__m128 a_tmp = KLN_SWIZZLE(a, 3, 2, 1, 1);
54+
__m128 tmp2 = _mm_xor_ps(_mm_mul_ps(a_tmp, a_tmp), _mm_set_ss(-0.f));
55+
a_tmp = KLN_SWIZZLE(a, 1, 3, 2, 2);
56+
tmp2 = _mm_sub_ps(tmp2, _mm_mul_ps(a_tmp, a_tmp));
57+
a_tmp = KLN_SWIZZLE(a, 2, 1, 3, 3);
58+
tmp2 = _mm_sub_ps(tmp2, _mm_mul_ps(a_tmp, a_tmp));
59+
tmp2 = _mm_mul_ps(tmp2, b);
60+
61+
p0_out = _mm_add_ps(tmp, tmp2);
62+
}
63+
64+
KLN_INLINE void KLN_VEC_CALL sw10(__m128 const& a,
65+
__m128 const& b,
66+
__m128& p1_out,
67+
__m128& p2_out)
68+
{
69+
//
70+
// b0(a1^2 + a2^2 + a3^2) +
71+
// (2a3(a1 b3 + a2 b2) + b1(a3^2 - a1^2 - a2^2)) e12 +
72+
// (2a2(a3 b1 + a1 b3) + b2(a2^2 - a3^2 - a1^2)) e31 +
73+
// (2a1(a2 b2 + a3 b1) + b3(a1^2 - a2^2 - a3^2)) e23 +
74+
//
75+
// 2a0(a2 b1 - a3 b2) e01 +
76+
// 2a0(a3 b3 - a1 b1) e02 +
77+
// 2a0(a1 b2 - a2 b3) e03
78+
79+
p1_out
80+
= _mm_mul_ps(KLN_SWIZZLE(a, 2, 3, 1, 0), KLN_SWIZZLE(b, 2, 1, 3, 0));
81+
p1_out = _mm_add_ps(
82+
p1_out,
83+
_mm_mul_ps(KLN_SWIZZLE(a, 3, 1, 2, 0), KLN_SWIZZLE(b, 1, 3, 2, 0)));
84+
p1_out = _mm_mul_ps(p1_out,
85+
_mm_mul_ps(KLN_SWIZZLE(a, 1, 2, 3, 0),
86+
_mm_set_ps(2.f, 2.f, 2.f, 0.f)));
87+
__m128 a_tmp = KLN_SWIZZLE(a, 1, 2, 3, 1);
88+
__m128 tmp = _mm_mul_ps(a_tmp, a_tmp);
89+
a_tmp = KLN_SWIZZLE(a, 2, 3, 1, 2);
90+
tmp = _mm_sub_ps(
91+
tmp, _mm_xor_ps(_mm_set_ss(-0.f), _mm_mul_ps(a_tmp, a_tmp)));
92+
a_tmp = KLN_SWIZZLE(a, 3, 1, 2, 3);
93+
tmp = _mm_sub_ps(
94+
tmp, _mm_xor_ps(_mm_set_ss(-0.f), _mm_mul_ps(a_tmp, a_tmp)));
95+
p1_out = _mm_add_ps(p1_out, _mm_mul_ps(b, tmp));
96+
97+
p2_out
98+
= _mm_mul_ps(KLN_SWIZZLE(a, 1, 3, 2, 0), KLN_SWIZZLE(b, 2, 3, 1, 0));
99+
p2_out = _mm_sub_ps(
100+
p2_out,
101+
_mm_mul_ps(KLN_SWIZZLE(a, 2, 1, 3, 0), KLN_SWIZZLE(b, 3, 1, 2, 0)));
102+
p2_out = _mm_mul_ps(p2_out,
103+
_mm_mul_ps(KLN_SWIZZLE(a, 0, 0, 0, 0),
104+
_mm_set_ps(2.f, 2.f, 2.f, 0.f)));
105+
}
106+
107+
KLN_INLINE void KLN_VEC_CALL sw20(__m128 const& a,
108+
__m128 const& b,
109+
__m128& p2_out)
110+
{
111+
// -b0(a1^2 + a2^2 + a3^2) e0123 +
112+
// (-2a1(a2 b2 + a3 b3) + b1(a2^2 + a3^2 - a1^2)) e01 +
113+
// (-2a2(a3 b3 + a1 b1) + b2(a3^2 + a1^2 - a2^2)) e02 +
114+
// (-2a3(a1 b1 + a2 b2) + b3(a1^2 + a2^2 - a3^2)) e03
115+
116+
p2_out
117+
= _mm_mul_ps(KLN_SWIZZLE(a, 1, 3, 2, 0), KLN_SWIZZLE(b, 1, 3, 2, 0));
118+
p2_out = _mm_add_ps(
119+
p2_out,
120+
_mm_mul_ps(KLN_SWIZZLE(a, 2, 1, 3, 0), KLN_SWIZZLE(b, 2, 1, 3, 0)));
121+
p2_out = _mm_mul_ps(
122+
p2_out, _mm_mul_ps(a, _mm_set_ps(-2.f, -2.f, -2.f, 0.f)));
123+
124+
__m128 a_tmp = KLN_SWIZZLE(a, 1, 3, 2, 1);
125+
__m128 tmp = _mm_mul_ps(a_tmp, a_tmp);
126+
a_tmp = KLN_SWIZZLE(a, 2, 1, 3, 2);
127+
tmp = _mm_xor_ps(
128+
_mm_set_ss(-0.f), _mm_add_ps(tmp, _mm_mul_ps(a_tmp, a_tmp)));
129+
a_tmp = KLN_SWIZZLE(a, 3, 2, 1, 3);
130+
tmp = _mm_sub_ps(tmp, _mm_mul_ps(a_tmp, a_tmp));
131+
p2_out = _mm_add_ps(p2_out, _mm_mul_ps(tmp, b));
132+
}
133+
134+
KLN_INLINE void KLN_VEC_CALL sw30(__m128 const& a,
135+
__m128 const& b,
136+
__m128& p3_out)
137+
{
138+
// b0(a1^2 + a2^2 + a3^2) e123 +
139+
// (-2a3(a0 b0 + a1 b3 + a2 b2) + b1(a1^2 + a2^2 - a3^2)) e021 +
140+
// (-2a2(a0 b0 + a1 b3 + a3 b1) + b2(a3^2 + a1^2 - a2^2)) e013 +
141+
// (-2a1(a0 b0 + a3 b1 + a2 b2) + b3(a2^2 + a3^2 - a1^2)) e032
142+
143+
p3_out
144+
= _mm_mul_ps(KLN_SWIZZLE(a, 0, 0, 0, 0), KLN_SWIZZLE(b, 0, 0, 0, 0));
145+
p3_out = _mm_add_ps(
146+
p3_out,
147+
_mm_mul_ps(KLN_SWIZZLE(a, 3, 1, 1, 0), KLN_SWIZZLE(b, 1, 3, 3, 0)));
148+
p3_out = _mm_add_ps(
149+
p3_out,
150+
_mm_mul_ps(KLN_SWIZZLE(a, 2, 3, 2, 0), KLN_SWIZZLE(b, 2, 1, 2, 0)));
151+
p3_out = _mm_mul_ps(p3_out,
152+
_mm_mul_ps(KLN_SWIZZLE(a, 1, 2, 3, 0),
153+
_mm_set_ps(-2.f, -2.f, -2.f, 0.f)));
154+
155+
__m128 a_tmp = KLN_SWIZZLE(a, 2, 3, 1, 1);
156+
__m128 tmp = _mm_mul_ps(a_tmp, a_tmp);
157+
a_tmp = KLN_SWIZZLE(a, 3, 1, 2, 2);
158+
tmp = _mm_add_ps(tmp, _mm_mul_ps(a_tmp, a_tmp));
159+
a_tmp = KLN_SWIZZLE(a, 1, 2, 3, 3);
160+
tmp = _mm_sub_ps(
161+
tmp, _mm_xor_ps(_mm_mul_ps(a_tmp, a_tmp), _mm_set_ss(-0.f)));
162+
163+
p3_out = _mm_add_ps(p3_out, _mm_mul_ps(b, tmp));
164+
}
165+
30166
// Apply a translator to a plane.
31167
// Assumes e0123 component of p2 is exactly 0
32168
// p0: (e0, e1, e2, e3)
33169
// p2: (e0123, e01, e02, e03)
34170
// b * a * ~b
35171
// The low component of p2 is expected to be the scalar component instead
36-
inline auto KLN_VEC_CALL sw02(__m128 const& a, __m128 const& b)
172+
KLN_INLINE auto KLN_VEC_CALL sw02(__m128 const& a, __m128 const& b)
37173
{
38174
// (a0 b0^2 + 2a1 b0 b1 + 2a2 b0 b2 + 2a3 b0 b3) e0 +
39175
// (a1 b0^2) e1 +
@@ -75,10 +211,10 @@ inline namespace detail
75211
// equivalent to __m128[count]
76212
template <bool Variadic = false, bool Translate = true>
77213
KLN_INLINE void KLN_VEC_CALL sw012(__m128 const* a,
78-
__m128 const& b,
79-
__m128 const* c,
80-
__m128* out,
81-
size_t count = 0)
214+
__m128 const& b,
215+
__m128 const* c,
216+
__m128* out,
217+
size_t count = 0)
82218
{
83219
// LSB
84220
//
@@ -232,7 +368,7 @@ inline namespace detail
232368
// p2: (e0123, e01, e02, e03)
233369
// p3: (e123, e021, e013, e032)
234370
// b * a * ~b
235-
inline auto KLN_VEC_CALL sw32(__m128 const& a, __m128 const& b) noexcept
371+
KLN_INLINE auto KLN_VEC_CALL sw32(__m128 const& a, __m128 const& b) noexcept
236372
{
237373
// (1 + b1*e01 + b2*e02 + b3*e03) *
238374
// (a0*e123 + a1*e021 + a2*e013 + a3*e032) *
@@ -256,10 +392,10 @@ inline namespace detail
256392
// Apply a motor to a point
257393
template <bool Variadic = false, bool Translate = true>
258394
KLN_INLINE void KLN_VEC_CALL sw312(__m128 const* a,
259-
__m128 const& b,
260-
__m128 const* c,
261-
__m128* out,
262-
size_t count = 0) noexcept
395+
__m128 const& b,
396+
__m128 const* c,
397+
__m128* out,
398+
size_t count = 0) noexcept
263399
{
264400
// LSB
265401
// a0(b0^2 + b1^2 + b2^2 + b3^2) e123 +
@@ -377,7 +513,7 @@ inline namespace detail
377513
// Conjugate origin with motor. Unlike other operations the motor MUST be
378514
// normalized prior to usage b is the rotor component (p1) c is the
379515
// translator component (p2)
380-
inline __m128 swo12(__m128 const& b, __m128 const& c)
516+
KLN_INLINE __m128 swo12(__m128 const& b, __m128 const& c)
381517
{
382518
// Rearranged to eliminate a b swizzle
383519
// (b1^2 + b0^2 + b2^2 + b3^2) e123 +

public/klein/entity.hpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -648,10 +648,10 @@ struct entity
648648
/// Poincaré Dual
649649
///
650650
/// The Poincaré Dual of an element is the "subspace complement" of the
651-
/// argument with respect to the pseudoscalar. In practice, it is a
652-
/// relabeling of the coordinates to their dual-coordinates and is used most
653-
/// often to implement a "join" operation in terms of the exterior product
654-
/// of the duals of each operand.
651+
/// argument with respect to the pseudoscalar in the exterior algebra. In
652+
/// practice, it is a relabeling of the coordinates to their
653+
/// dual-coordinates and is used most often to implement a "join" operation
654+
/// in terms of the exterior product of the duals of each operand.
655655
///
656656
/// Ex: The dual of the point $\mathbf{e}_{123} + 3\mathbf{e}_{013} -
657657
/// 2\mathbf{e}_{021}$ (the point at

0 commit comments

Comments
 (0)