Convert from one 2D coordinates system to another with different (0,0)

Viewed 324

I am programming a software where I receive data for a 2D display in a given coordinate system, where (0,0) is the upper left corner, the x axis grows to the right, and the y axis grows to the bottom.

My graphing library is the Python library pyglet, which considers (0,0) to be the bottom left corner. The x axis grows to the right(no conversion needed) but the y axis grows upwards. Thus, I cannot pass the coordinates from the data I receive directly to my graphing library.

In which way could I convert the y-axis component?

1 Answers

If your graphing library has support for views then you should be able to set the graphing_height to -non_graphing_height. If the position of the view is the center, then it shouldn't need to be changed, if it is the bottom-left, then setting graphic_bottom_left to non_graphing_top_left + non_graphing_height should work.

This might look like this in C++ with the SFML library:

const auto& current_view = window.getView();
const auto& current_view_size = current_view.getSize();
window.setView(sf::View(current_view.getCenter(), { current_view_size.x, -current_view_size.y }));

If you are in 3D this could instead be applied to the projection matrix.

If there are no views, you should be able to recreate them with a view matrix. You should then be able to pass that view matrix into your graphics library.

If you can not pass in the view matrix then apply those to your non graphing transforms to get your graphing transforms. Once you have that you will have a transformation matrix that you can pass into your graphics library.

If your graphics library does not support passing in that either you can decompose the transformed matrix into a translation, rotation and scale.

Here is an example of a function in C++ without the SFML library that would do this with a view from 0,0 to 200,200 in the non graphics library:

/// transform the passed in position, rotation and scale
/// \param rotation radians
void transform(Vector2& position, float& rotation, Vector2& scale) {
    // create a transformation matrix from the position, rotation and scale
    const Matrix transform_matrix{ position, rotation, scale };
    // create a upside down view matrix at origin of size 200x200
    const Vector2 view_position{
        0.f,
        0.f
    };
    const Vector2 view_size{
        200.f,
        200.f
    };
const Matrix view_matrix =
        Matrix::translation({ -view_position.x, -(view_position.y + view_size.y) }) *
        Matrix::scale({ 1.f / view_size.x, -1.f / view_size.y });
    // multiply the transformation matrix with the view matrix
    const auto multiplied = transform_matrix * view_matrix;
    // update the passed in values
    position = multiplied.getTranslation();
    rotation = multiplied.getAngle();
    scale = multiplied.getScale();
}

Note that in 3D the rotation might be multiple values or a Quaternion, so make sure you have a Matrix class that supports that.

Here is the example Vector2 and Matrix class I used for the example above:

struct Vector2 {
    float x;
    float y;
};

class Matrix {
public:
    /// creates a matrix using passed in matrix
    Matrix(const std::vector<std::vector<float>>& matrix) :
        matrix_{ matrix }
    {}
    /// creates 2d transformation matrix
    /// \param angle radians
    Matrix(const Vector2& translation, const float angle, const Vector2& scale) :
        Matrix({
            { scale.x * cos(angle), scale.y * sin(angle), 0.f },
            { -scale.x * sin(angle), scale.x * cos(angle), 0.f },
            { translation.x, translation.y, 1.f }
        })
    {}
    /// create 2d translation matrix
    static Matrix translation(const Vector2& translation) {
        return Matrix({
            { 1.f, 0.f, 0.f },
            { 0.f, 1.f, 0.f },
            { translation.x, translation.y, 1.f }
        });
    }
    /// create 2d scale matrix
    static Matrix scale(const Vector2& scale) {
        return Matrix({
            { scale.x, 0.f, 0.f },
            { 0.f, scale.y, 0.f },
            { 0.f, 0.f, 1.f }
        });
    }
    /// multiplies two matrices together
    Matrix operator*(const Matrix& rhs) const {
        // get number of rows, columns and elements
        const auto rows = matrix_.size();
        const auto columns = rhs.matrix_[0].size();
        const auto elements = rhs.matrix_.size();
        // create a new matrix of the correct size, filled with zeroes
        Matrix new_matrix(
            std::vector<std::vector<float>>(
                rows, std::vector<float>(columns, 0.f)
            )
        );
        // go through each row of the new matrix
        for (size_t row = 0; row < rows; ++row) {
            // go through each column of the new matrix
            for (size_t column = 0; column < columns; ++column) {
                // set element in the new matrix to the dot product of this matrix's row and the rhs matrix's column
                for (size_t element = 0; element < elements; ++element) {
                    new_matrix.matrix_[row][column] +=
                        matrix_[row][element] * rhs.matrix_[element][column];
                }
            }
        }
        return new_matrix;
    }
    /// gets the translation from the matrix
    Vector2 getTranslation() const {
        return { matrix_[2][0], matrix_[2][1] };
    }
    /// gets the positive scale from the matrix
    Vector2 getScale() const {
        Vector2 scale{
            sqrt(pow(matrix_[0][0], 2) + pow(matrix_[0][1], 2)),
            sqrt(pow(matrix_[1][0], 2) + pow(matrix_[1][1], 2))
        };
        if (matrix_[0][0] < 0) {
            scale.x = -scale.x;
        }
        if (matrix_[1][1] < 0) {
            scale.y = -scale.y;
        }
        return scale;
    }
    /// gets the angle from the matrix
    /// \returns radians
    float getAngle() const {
        return atan2(matrix_[0][1], std::abs(matrix_[1][1]));
    }
private:
    std::vector<std::vector<float>> matrix_;
};
Related