Skip to content

Instantly share code, notes, and snippets.

@JujuAdams
Last active January 26, 2023 22:22
Show Gist options
  • Save JujuAdams/3aa6402de1cffcb3c02f3b0f29cd9a55 to your computer and use it in GitHub Desktop.
Save JujuAdams/3aa6402de1cffcb3c02f3b0f29cd9a55 to your computer and use it in GitHub Desktop.
/// Transforms a position from one coordinate system to another
/// This function presumes you're using GameMaker's native camera/view system and are not
/// manually setting custom view/projection matrices. Also if you're drawing your application
/// surface yourself then the coordinate transform *may* not be accurate, but it usually is.
///
/// N.B. The struct returned from this function is declared as static for the sake of efficiency.
/// If you want to store values, don't keep a reference to the struct as it is liable to change!
///
/// N.B. This function does NOT take into account view_set_xport() or view_set_yport(). I've not
/// seen someone use those functions in many years.
///
/// Coordinate systems are selected by using one of the following integers:
/// 0: Room - Same coordinate system as device_mouse_x() / device_mouse_y()
/// 1: GUI - Same coordinate system as device_mouse_y_to_gui() / device_mouse_y_to_gui()
/// 2: Device - Same coordinate system as device_mouse_raw_x() / device_mouse_raw_y()
///
/// The "Device" coordinate system is the same as an application's window on PC.
///
/// @param x x-coordinate of the point to transform
/// @param y y-coordinate of the point to transform
/// @param inputSystem Original coordinate system for the point (0, 1, or 2)
/// @param outputSystem New coordinate system to transform into (0, 1, or 2)
/// @param [camera] Camera to use for the room coordinate system. If not specified, the currently active camera is used. If no camera is active, the camera for view 0 is used
///
/// @jujuadams 2023-01-26
function TransformCoordSystem(_x, _y, _inputSystem, _outputSystem, _camera = undefined)
{
static _result = {
x: 0,
y: 0,
};
//Build out lots of cached values
//We use these to detect changes that might trigger a recalculation of application_get_position()
//Doing all this work is faster than calling application_get_position() all the time
static _windowW = undefined;
static _windowH = undefined;
static _appSurfW = undefined;
static _appSurfH = undefined;
static _appSurfDrawL = undefined;
static _appSurfDrawT = undefined;
static _appSurfDrawW = undefined;
static _appSurfDrawH = undefined;
static _recacheTime = -infinity;
if (_inputSystem != _outputSystem) //Only do MATHS if the output system is different
{
//Only update the cached app surface draw parameters if we're going to need them
if ((_inputSystem == 2) || (_outputSystem == 2))
{
//Detect changes in application surface size
if ((_appSurfW != surface_get_width(application_surface))
|| (_appSurfH != surface_get_height(application_surface)))
{
_appSurfW = surface_get_width(application_surface);
_appSurfH = surface_get_height(application_surface);
//Recache application surface position immediately in this situation
_recacheTime = -infinity;
}
if (current_time > _recacheTime)
{
_recacheTime = infinity;
var _array = application_get_position();
_appSurfDrawL = _array[0];
_appSurfDrawT = _array[1];
_appSurfDrawW = _array[2] - _appSurfDrawL;
_appSurfDrawH = _array[3] - _appSurfDrawT;
}
//Detect changes in window size
if ((_windowW != window_get_width())
|| (_windowH != window_get_height()))
{
_windowW = window_get_width();
_windowH = window_get_height();
//Recache application surface position after 200ms to give GM time to do whatever it does
_recacheTime = current_time + 200;
}
}
if (_inputSystem == 0) //Input coordinate system is room-space
{
//Grab a camera. Multiple levels of fallback here to cope with different setups
_camera = _camera ?? camera_get_active();
if (_camera < 0) _camera = view_camera[0];
if (camera_get_view_angle(_camera) == 0) //Skip expensive rotation step if we can
{
//Reduce x/y to normalised values in the viewport
_x = (_x - camera_get_view_x(_camera)) / camera_get_view_width( _camera);
_y = (_y - camera_get_view_y(_camera)) / camera_get_view_height(_camera);
}
else
{
//Perform a rotation, eventually ending up with normalised values as above
var _viewW = camera_get_view_width( _camera);
var _viewH = camera_get_view_height(_camera);
var _viewCX = camera_get_view_x(_camera) + _viewW/2;
var _viewCY = camera_get_view_y(_camera) + _viewH/2;
var _angle = camera_get_view_angle(_camera);
var _sin = dsin(-_angle);
var _cos = dcos(-_angle);
var _x0 = _x - _viewCX;
var _y0 = _y - _viewCY;
_x = ((_x0*_cos - _y0*_sin) + _viewCX) / _viewW;
_y = ((_x0*_sin + _y0*_cos) + _viewCY) / _viewH;
}
if (_outputSystem == 1)
{
//If we're outputting to GUI-space then simply multiply up by the GUI size
_x *= display_get_gui_width();
_y *= display_get_gui_height();
}
else if (_outputSystem == 2)
{
//If we're outputting to device-space then perform a transform using the cached app surface draw parameters
_x = _appSurfDrawW*_x + _appSurfDrawL;
_y = _appSurfDrawH*_y + _appSurfDrawT;
}
else
{
show_error("Unhandled output coordinate system (" + string(_outputSystem) + ")\n ", true);
}
}
else if (_inputSystem == 1) //Input coordinate system is GUI-space
{
//Reduce x/y to normalised values in GUI-space
_x /= display_get_gui_width();
_y /= display_get_gui_height();
if (_outputSystem == 0)
{
//Grab a camera. Multiple levels of fallback here to cope with different setups
_camera = _camera ?? camera_get_active();
if (_camera < 0) _camera = view_camera[0];
if (camera_get_view_angle(_camera) == 0) //Skip expensive rotation step if we can
{
//Expand room-space x/y from normalised values in the viewport
_x = camera_get_view_width( _camera)*_x + camera_get_view_x(_camera);
_y = camera_get_view_height(_camera)*_y + camera_get_view_y(_camera);
}
else
{
//Perform a rotation, eventually ending up with room-space coordinates as above
var _viewW = camera_get_view_width( _camera);
var _viewH = camera_get_view_height(_camera);
var _viewCX = camera_get_view_x(_camera) + _viewW/2;
var _viewCY = camera_get_view_y(_camera) + _viewH/2;
var _angle = camera_get_view_angle(_camera);
var _sin = dsin(_angle);
var _cos = dcos(_angle);
var _x0 = _x*_viewW - _viewCX;
var _y0 = _y*_viewH - _viewCY;
_x = (_x0*_cos - _y0*_sin) + _viewCX;
_y = (_x0*_sin + _y0*_cos) + _viewCY;
}
}
else if (_outputSystem == 2)
{
//If we're outputting to device-space then perform a transform using the cached app surface draw parameters
_x = _appSurfDrawW*_x + _appSurfDrawL;
_y = _appSurfDrawH*_y + _appSurfDrawT;
}
else
{
show_error("Unhandled output coordinate system (" + string(_outputSystem) + ")\n ", true);
}
}
else if (_inputSystem == 2) //Input coordinate system is device-space
{
_x = (_x - _appSurfDrawL) / _appSurfDrawW;
_y = (_y - _appSurfDrawT) / _appSurfDrawH;
if (_outputSystem == 1)
{
//Reduce x/y to normalised values in GUI-space
_x *= display_get_gui_width();
_y *= display_get_gui_height();
}
else if (_outputSystem == 0)
{
//Grab a camera. Multiple levels of fallback here to cope with different setups
_camera = _camera ?? camera_get_active();
if (_camera < 0) _camera = view_camera[0];
if (camera_get_view_angle(_camera) == 0) //Skip expensive rotation step if we can
{
//Expand room-space x/y from normalised values in the viewport
_x = camera_get_view_width( _camera)*_x + camera_get_view_x(_camera);
_y = camera_get_view_height(_camera)*_y + camera_get_view_y(_camera);
}
else
{
//Perform a rotation, eventually ending up with room-space coordinates as above
var _viewW = camera_get_view_width( _camera);
var _viewH = camera_get_view_height(_camera);
var _viewCX = camera_get_view_x(_camera) + _viewW/2;
var _viewCY = camera_get_view_y(_camera) + _viewH/2;
var _angle = camera_get_view_angle(_camera);
var _sin = dsin(_angle);
var _cos = dcos(_angle);
var _x0 = _x*_viewW - _viewCX;
var _y0 = _y*_viewH - _viewCY;
_x = (_x0*_cos - _y0*_sin) + _viewCX;
_y = (_x0*_sin + _y0*_cos) + _viewCY;
}
}
else
{
show_error("Unhandled output coordinate system (" + string(_outputSystem) + ")\n ", true);
}
}
else
{
show_error("Unhandled input coordinate system (" + string(_inputSystem) + ")\n ", true);
}
}
//Set values and return!
_result.x = _x;
_result.y = _y;
return _result;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment