/* * 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 #include 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(width), static_cast(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(); } }