OpenFPGA/libs/EXTERNAL/libezgl/src/camera.cpp

153 lines
4.7 KiB
C++

/*
* Copyright 2019 University of Toronto
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Authors: Mario Badr, Sameh Attia and Tanner Young-Schultz
*/
#include "ezgl/camera.hpp"
#include <cmath>
#include <cstdio>
namespace ezgl {
static rectangle maintain_aspect_ratio(rectangle const &view, double widget_width, double widget_height)
{
double const x_scale = widget_width / view.width();
double const y_scale = widget_height / view.height();
double x_start = 0.0;
double y_start = 0.0;
double new_width;
double new_height;
if(x_scale * view.height() > widget_height) {
// Using x_scale causes the view to be larger than the widget's height.
// Keep the same height as the widget.
new_height = widget_height;
// Scale the width to maintain the aspect ratio.
new_width = view.width() * y_scale;
// Keep the view in the center of the widget.
x_start = 0.5 * std::fabs(widget_width - new_width);
} else {
// Using x_scale keeps the view within the widget's height.
// Keep the width the same as the widget.
new_width = widget_width;
// Scale the height to maintain the aspect ratio.
new_height = view.height() * x_scale;
// Keep the view in the center of the widget.
y_start = 0.5 * std::fabs(widget_height - new_height);
}
return {{x_start, y_start}, new_width, new_height};
}
camera::camera(rectangle bounds) : m_world(bounds), m_screen(bounds), m_initial_world(bounds)
{
}
point2d camera::widget_to_screen(point2d widget_coordinates) const
{
point2d const screen_origin = {m_screen.left(), m_screen.bottom()};
point2d screen_coordinates = widget_coordinates - screen_origin;
return screen_coordinates;
}
point2d camera::widget_to_world(point2d widget_coordinates) const
{
point2d const screen_coordinates = widget_to_screen(widget_coordinates);
point2d world_coordinates = screen_coordinates * m_screen_to_world;
world_coordinates.x += m_world.left();
// GTK and cairo use a flipped y-axis.
world_coordinates.y = (world_coordinates.y - m_world.top()) * -1.0;
return world_coordinates;
}
/**
* Some X11 implementations overflow with sufficiently large pixel
* coordinates and start drawing strangely. We will clip all pixels
* to lie in the range below.
* TODO: We can also switch to cairo for large pixel coordinates
*/
#define MAXPIXEL 10000.0
#define MINPIXEL -10000.0
point2d camera::world_to_screen(point2d world_coordinates) const
{
point2d const world_origin{m_world.left(), m_world.bottom()};
point2d widget_coordinates = (world_coordinates - world_origin) * m_world_to_widget;
// GTK and cairo use a flipped y-axis.
widget_coordinates.y = (widget_coordinates.y - m_widget.top()) * -1.0;
point2d screen_coordinates = widget_coordinates * m_widget_to_screen;
point2d const screen_origin = {m_screen.left(), m_screen.bottom()};
screen_coordinates = screen_coordinates + screen_origin;
screen_coordinates.x = std::max(screen_coordinates.x, MINPIXEL);
screen_coordinates.y = std::max(screen_coordinates.y, MINPIXEL);
screen_coordinates.x = std::min(screen_coordinates.x, MAXPIXEL);
screen_coordinates.y = std::min(screen_coordinates.y, MAXPIXEL);
return screen_coordinates;
}
void camera::set_world(rectangle new_world)
{
m_world = new_world;
update_scale_factors();
}
void camera::reset_world(rectangle new_world)
{
// Change the coordinates to the new bounds
m_world = new_world;
m_screen = new_world;
m_initial_world = new_world;
m_screen = maintain_aspect_ratio(m_screen, m_widget.width(), m_widget.height());
update_scale_factors();
}
void camera::update_widget(int width, int height)
{
m_widget = rectangle{{0, 0}, static_cast<double>(width), static_cast<double>(height)};
m_screen = maintain_aspect_ratio(m_screen, m_widget.width(), m_widget.height());
update_scale_factors();
}
void camera::update_scale_factors()
{
m_widget_to_screen.x = m_screen.width() / m_widget.width();
m_widget_to_screen.y = m_screen.height() / m_widget.height();
m_world_to_widget.x = m_widget.width() / m_world.width();
m_world_to_widget.y = m_widget.height() / m_world.height();
m_screen_to_world.x = m_world.width() / m_screen.width();
m_screen_to_world.y = m_world.height() / m_screen.height();
}
}