153 lines
4.7 KiB
C++
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();
|
|
}
|
|
}
|