2

I am looking for a builtin way with the eigen library to perform coordinate transformations by normal vectors in 2D space.

Mathematically, it's not difficult: Let v = (v_x, v_y) be a 2D column vector and n = (n_x, n_y) be a normal vector, then the transformation I am looking for is one by rotational matrix: v_T = N * v, with v_T being the transformed vector and N being the rotational matrix, which is

|  nx, ny |
| -ny, nx |

In my case, the data I need to transform is stored in an Array2Xd and the normal vectors are stored in a Matrix2Xd, with each column holding x- and y-component. I need to transform each column in the array by the corresponding normal vector in the matrix.

Currently, I'm doing it like this:

#include <Eigen/Dense>
#include <iostream>

using namespace Eigen;

/* transform a single vector, just for illustration */
Array2d transform_s( const Ref<const Array2d>& v, const Ref<const Vector2d>& n ){
    return {
        n.dot( v.matrix() ),
        -n.y() * v.x() + n.x() * v.y()
    };
}

/* transform multiple columns */
Array2Xd transform_m( const Ref<const Array2Xd>& v, const Ref<const Array2Xd>& n ){
    Array2Xd transformed ( 2, v.cols() );

    /* colwise dot product for first row */
    transformed.row(0) = (n * v).colwise().sum();
    /* even less elegant calculation for the second row */
    transformed.row(1) = n.row(0) * v.row(1) - n.row(1) * v.row(0);
    return transformed;
}

int main(){
    Array2Xd vals (2, 3);
    vals <<
        2, 0,-1,
        0, 3, 2;

    Matrix2Xd n;
    n.resizeLike(vals);
    n <<
        0, 0, 1, 
        1,-1, 1;
    n.colwise().normalize();

    std::cout
        << "single column:\n" << transform_s( vals.col(0), n.col(0) )
        << "\nall columns:\n" << transform_m( vals, n.array() )
        << "\n";

    return 0;
}

I'm aware of Eigen::Rotation2D, but it appears to either require an angle or a rotational matrix. I am specifically looking for a way to only provide the normal vectors. Otherwise I need to build the rotational matrices from the normal vectors myself, which doesn't really reduce the complexity on my end.

If there's no way to do this with eigen, I'll accept that as an answer. In that case, I'd be very happy about a more efficient implementation of what I wrote above.

RL-S
  • 734
  • 6
  • 21

1 Answers1

0

What you are doing is essentially a complex multiplication with conj(n).

There is no elegant way to reinterpret a Vector2d/Array2Xd to a complex<double>/ArrayXcd, but you can hack something together using Maps:

Array2Xd transform_complex( const Ref<const Array2Xd>& v, const Ref<const Array2Xd>& n ){
    Array2Xd transformed(2, v.cols());

    ArrayXcd::Map(reinterpret_cast<std::complex<double>*>(transformed.data()), v.cols())
       = ArrayXcd::Map(reinterpret_cast<std::complex<double> const*>(v.data()), v.cols())
         *  ArrayXcd::Map(reinterpret_cast<std::complex<double> const*>(n.data()), n.cols()).conjugate();
    return transformed;
}

You could write yourself a helper function which takes a const Ref<const Array2Xd>& and returns a Map<ArrayXcd> with the same content.

chtz
  • 17,329
  • 4
  • 26
  • 56
  • Is that safe when the ```const Ref&``` is a ```Block``` with ```InnerPanel=false``` or an ```IndexedView```? I thought that ```Map``` expects contiguous memory, because we're just giving it a pointer and a size. – RL-S Aug 12 '21 at 12:14
  • I can probably answer my comment myself: For ```Map```, one can specify a stride, so non-contiguous memory is fine, as long as it's regular. Not the same for ```IndexedView``` though. – RL-S Oct 05 '21 at 09:42