Skip to content

Commit

Permalink
More snapping options as project settings
Browse files Browse the repository at this point in the history
Defaults are better (and separate) for stretch_mode viewport and stretch_mode 2D. All rounding settings can be overridden giving the user full control.
  • Loading branch information
lawnjelly committed Mar 4, 2021
1 parent 7e2e96c commit 10b6c83
Show file tree
Hide file tree
Showing 10 changed files with 299 additions and 33 deletions.
10 changes: 7 additions & 3 deletions core/engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@

#include "core/list.h"
#include "core/os/main_loop.h"
#include "core/snappers.h"
#include "core/ustring.h"
#include "core/vector.h"

Expand All @@ -58,15 +59,17 @@ class Engine {
float _fps;
int _target_fps;
float _time_scale;
bool _gpu_pixel_snap;
bool _snap_2d_transforms;
bool _snap_2d_viewports;
uint64_t _physics_frames;
float _physics_interpolation_fraction;

uint64_t _idle_frames;
bool _in_physics;

bool _gpu_pixel_snap;
bool _snap_2d_transforms;
bool _snap_2d_viewports;
Snappers _snappers;

List<Singleton> singletons;
Map<StringName, Object *> singleton_ptrs;

Expand Down Expand Up @@ -111,6 +114,7 @@ class Engine {
_FORCE_INLINE_ bool get_use_gpu_pixel_snap() const { return _gpu_pixel_snap; }
bool get_snap_2d_transforms() const { return _snap_2d_transforms; }
bool get_snap_2d_viewports() const { return _snap_2d_viewports; }
const Snappers &get_snappers() const { return _snappers; }

#ifdef TOOLS_ENABLED
_FORCE_INLINE_ void set_editor_hint(bool p_enabled) { editor_hint = p_enabled; }
Expand Down
101 changes: 101 additions & 0 deletions core/snappers.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*************************************************************************/
/* snappers.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/

#include "snappers.h"
#include "core/project_settings.h"

void Snappers::snap_read_item(Vector2 &r_pos) const {
if (snapper_canvas_item_read.is_enabled()) {
snapper_canvas_item_read.snap(r_pos);
} else if (_gpu_snap_enabled) {
r_pos = r_pos.floor();
}
}

void Snappers::initialize(bool p_gpu_snap, bool p_snap_transforms, bool p_snap_viewports, bool p_stretch_mode_viewport) {

_gpu_snap_enabled = p_gpu_snap;

const char *sz_mode_selection = "Default,Disabled,Floor,Ceiling,Round";

#define GODOT_SNAP_DEFINE(MODE_STRING) \
GLOBAL_DEF(MODE_STRING, 0); \
ProjectSettings::get_singleton()->set_custom_property_info(MODE_STRING, PropertyInfo(Variant::INT, MODE_STRING, PROPERTY_HINT_ENUM, sz_mode_selection))

int item_pre_x = GODOT_SNAP_DEFINE("rendering/2d/snapping_modes/item_pre_x");
int item_pre_y = GODOT_SNAP_DEFINE("rendering/2d/snapping_modes/item_pre_y");
int item_post_x = GODOT_SNAP_DEFINE("rendering/2d/snapping_modes/item_post_x");
int item_post_y = GODOT_SNAP_DEFINE("rendering/2d/snapping_modes/item_post_y");
int item_read_x = GODOT_SNAP_DEFINE("rendering/2d/snapping_modes/item_read_x");
int item_read_y = GODOT_SNAP_DEFINE("rendering/2d/snapping_modes/item_read_y");

int camera_pre_x = GODOT_SNAP_DEFINE("rendering/2d/snapping_modes/camera_pre_x");
int camera_pre_y = GODOT_SNAP_DEFINE("rendering/2d/snapping_modes/camera_pre_y");
int camera_post_x = GODOT_SNAP_DEFINE("rendering/2d/snapping_modes/camera_post_x");
int camera_post_y = GODOT_SNAP_DEFINE("rendering/2d/snapping_modes/camera_post_y");
int camera_parent_pre_x = GODOT_SNAP_DEFINE("rendering/2d/snapping_modes/camera_parent_pre_x");
int camera_parent_pre_y = GODOT_SNAP_DEFINE("rendering/2d/snapping_modes/camera_parent_pre_y");

// defaults
if (p_snap_transforms) {
if (p_stretch_mode_viewport) {
snapper_canvas_item_pre.set_snap_modes(Snapper2D::SNAP_ROUND, Snapper2D::SNAP_CEILING);
} else {
snapper_canvas_item_post.set_snap_modes(Snapper2D::SNAP_ROUND, Snapper2D::SNAP_ROUND);
}
}

if (p_snap_viewports) {
if (p_stretch_mode_viewport) {
snapper_viewport_pre.set_snap_modes(Snapper2D::SNAP_ROUND, Snapper2D::SNAP_ROUND);
} else {
snapper_viewport_post.set_snap_modes(Snapper2D::SNAP_ROUND, Snapper2D::SNAP_ROUND);
}
}

// default actions for these derive from earlier types
snapper_canvas_item_read = snapper_canvas_item_pre;
snapper_viewport_parent_pre = snapper_viewport_pre;

// custom user overrides
if (p_snap_transforms) {
snapper_canvas_item_pre.set_custom_snap_modes((Snapper2D::SnapMode)item_pre_x, (Snapper2D::SnapMode)item_pre_y);
snapper_canvas_item_post.set_custom_snap_modes((Snapper2D::SnapMode)item_post_x, (Snapper2D::SnapMode)item_post_y);
snapper_canvas_item_read.set_custom_snap_modes((Snapper2D::SnapMode)item_read_x, (Snapper2D::SnapMode)item_read_y);
}

if (p_snap_viewports) {
snapper_viewport_pre.set_custom_snap_modes((Snapper2D::SnapMode)camera_pre_x, (Snapper2D::SnapMode)camera_pre_y);
snapper_viewport_post.set_custom_snap_modes((Snapper2D::SnapMode)camera_post_x, (Snapper2D::SnapMode)camera_post_y);
snapper_viewport_parent_pre.set_custom_snap_modes((Snapper2D::SnapMode)camera_parent_pre_x, (Snapper2D::SnapMode)camera_parent_pre_y);
}

#undef GODOT_SNAP_DEFINE
}
122 changes: 122 additions & 0 deletions core/snappers.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*************************************************************************/
/* snappers.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/

#ifndef SNAPPERS_H
#define SNAPPERS_H

#include "core/math/math_funcs.h"
#include "core/math/vector2.h"

// generic class for handling 2d snapping
class Snapper2D {
public:
enum SnapMode {
SNAP_DEFAULT,
SNAP_DISABLED,
SNAP_FLOOR,
SNAP_CEILING,
SNAP_ROUND,
};

void snap(Vector2 &r_pos) const {
if (!_enabled) {
return;
}

r_pos.x = snap_value(r_pos.x, _snap_mode_x);
r_pos.y = snap_value(r_pos.y, _snap_mode_y);
}

void set_snap_modes(SnapMode p_mode_x, SnapMode p_mode_y) {
_snap_mode_x = p_mode_x;
_snap_mode_y = p_mode_y;
_enabled = !((_snap_mode_x == SNAP_DISABLED) && (_snap_mode_y == SNAP_DISABLED));
}

// disabled is user choosing default, so we will ignore the choice
void set_custom_snap_modes(SnapMode p_mode_x, SnapMode p_mode_y) {
if (p_mode_x != SNAP_DEFAULT) {
_snap_mode_x = p_mode_x;
}
if (p_mode_y != SNAP_DEFAULT) {
_snap_mode_y = p_mode_y;
}
_enabled = !((_snap_mode_x == SNAP_DISABLED) && (_snap_mode_y == SNAP_DISABLED));
}

bool is_enabled() const { return _enabled; }

private:
real_t snap_value(real_t p_value, SnapMode p_mode) const {
switch (p_mode) {
default:
break;
case SNAP_FLOOR: {
return Math::floor(p_value);
} break;
case SNAP_CEILING: {
return Math::ceil(p_value);
} break;
case SNAP_ROUND: {
return Math::round(p_value);
} break;
}
return p_value;
}

bool _enabled = false;
SnapMode _snap_mode_x = SNAP_DISABLED;
SnapMode _snap_mode_y = SNAP_DISABLED;
};

// All the 2D snapping in one place.
// This is called from the various places it needs to be introduced, but the logic
// can be self contained here to make it easier to change / debug.
class Snappers {
public:
void initialize(bool p_gpu_snap, bool p_snap_transforms, bool p_snap_viewports, bool p_stretch_mode_viewport);

// for positioning of sprites etc, not the main draw call
void snap_read_item(Vector2 &r_pos) const;

Snapper2D snapper_canvas_item_pre;
Snapper2D snapper_canvas_item_post;
Snapper2D snapper_canvas_item_read;

Snapper2D snapper_viewport_pre;
Snapper2D snapper_viewport_post;
Snapper2D snapper_viewport_parent_pre;

private:
// local version
bool _gpu_snap_enabled = false;
};

#endif // SNAPPERS_H
40 changes: 38 additions & 2 deletions doc/classes/ProjectSettings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1045,7 +1045,7 @@
[b]Note:[/b] Antialiased software skinned polys are not supported, and will be rendered without antialiasing.
</member>
<member name="rendering/2d/snapping/use_camera_snap" type="bool" setter="" getter="" default="false">
If [code]true[/code], forces snapping of 2D viewports to the nearest whole coordinate.
[b]Experimental[/b] If [code]true[/code], forces snapping of 2D viewports to the nearest whole coordinate.
Can reduce unwanted camera relative movement in pixel art styles.
</member>
<member name="rendering/2d/snapping/use_gpu_pixel_snap" type="bool" setter="" getter="" default="false">
Expand All @@ -1054,9 +1054,45 @@
Consider using the project setting [member rendering/batching/precision/uv_contract] to prevent artifacts.
</member>
<member name="rendering/2d/snapping/use_transform_snap" type="bool" setter="" getter="" default="false">
If [code]true[/code], forces snapping of 2D object transforms to the nearest whole coordinate.
[b]Experimental[/b] If [code]true[/code], forces snapping of 2D object transforms to the nearest whole coordinate.
Can help prevent unwanted relative movement in pixel art styles.
</member>
<member name="rendering/2d/snapping_modes/camera_parent_pre_x" type="int" setter="" getter="" default="0">
[b]Experimental[/b] Canvas parent position x snapping mode.
</member>
<member name="rendering/2d/snapping_modes/camera_parent_pre_y" type="int" setter="" getter="" default="0">
[b]Experimental[/b] Canvas parent position y snapping mode.
</member>
<member name="rendering/2d/snapping_modes/camera_post_x" type="int" setter="" getter="" default="0">
[b]Experimental[/b] Canvas position x snapping mode (after applying global transform).
</member>
<member name="rendering/2d/snapping_modes/camera_post_y" type="int" setter="" getter="" default="0">
[b]Experimental[/b] Canvas position y snapping mode (after applying global transform).
</member>
<member name="rendering/2d/snapping_modes/camera_pre_x" type="int" setter="" getter="" default="0">
[b]Experimental[/b] Canvas local position x snapping mode.
</member>
<member name="rendering/2d/snapping_modes/camera_pre_y" type="int" setter="" getter="" default="0">
[b]Experimental[/b] Canvas local position y snapping mode.
</member>
<member name="rendering/2d/snapping_modes/item_post_x" type="int" setter="" getter="" default="0">
[b]Experimental[/b] Item position x snapping mode when drawing (after applying parent transform).
</member>
<member name="rendering/2d/snapping_modes/item_post_y" type="int" setter="" getter="" default="0">
[b]Experimental[/b] Item position y snapping mode when drawing (after applying parent transform).
</member>
<member name="rendering/2d/snapping_modes/item_pre_x" type="int" setter="" getter="" default="0">
[b]Experimental[/b] Item local position x snapping mode when drawing.
</member>
<member name="rendering/2d/snapping_modes/item_pre_y" type="int" setter="" getter="" default="0">
[b]Experimental[/b] Item local position y snapping mode when drawing.
</member>
<member name="rendering/2d/snapping_modes/item_read_x" type="int" setter="" getter="" default="0">
[b]Experimental[/b] Item local position x snapping mode when reading.
</member>
<member name="rendering/2d/snapping_modes/item_read_y" type="int" setter="" getter="" default="0">
[b]Experimental[/b] Item local position y snapping mode when reading.
</member>
<member name="rendering/batching/debug/diagnose_frame" type="bool" setter="" getter="" default="false">
When batching is on, this regularly prints a frame diagnosis log. Note that this will degrade performance.
</member>
Expand Down
8 changes: 7 additions & 1 deletion main/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1127,6 +1127,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
Engine::get_singleton()->_gpu_pixel_snap = GLOBAL_DEF("rendering/2d/snapping/use_gpu_pixel_snap", false);
Engine::get_singleton()->_snap_2d_transforms = GLOBAL_DEF("rendering/2d/snapping/use_transform_snap", false);
Engine::get_singleton()->_snap_2d_viewports = GLOBAL_DEF("rendering/2d/snapping/use_camera_snap", false);
Engine::get_singleton()->_snappers.initialize(Engine::get_singleton()->get_use_gpu_pixel_snap(), Engine::get_singleton()->get_snap_2d_transforms(), Engine::get_singleton()->get_snap_2d_viewports(), false);

OS::get_singleton()->_keep_screen_on = GLOBAL_DEF("display/window/energy_saving/keep_screen_on", true);
if (rtm == -1) {
rtm = GLOBAL_DEF("rendering/threads/thread_model", OS::RENDER_THREAD_SAFE);
Expand Down Expand Up @@ -1826,9 +1828,13 @@ bool Main::start() {
SceneTree::StretchMode sml_sm = SceneTree::STRETCH_MODE_DISABLED;
if (stretch_mode == "2d")
sml_sm = SceneTree::STRETCH_MODE_2D;
else if (stretch_mode == "viewport")
else if (stretch_mode == "viewport") {
sml_sm = SceneTree::STRETCH_MODE_VIEWPORT;

// reset the snappers in viewport mode
Engine::get_singleton()->_snappers.initialize(Engine::get_singleton()->get_use_gpu_pixel_snap(), Engine::get_singleton()->get_snap_2d_transforms(), Engine::get_singleton()->get_snap_2d_viewports(), true);
}

SceneTree::StretchAspect sml_aspect = SceneTree::STRETCH_ASPECT_IGNORE;
if (stretch_aspect == "keep")
sml_aspect = SceneTree::STRETCH_ASPECT_KEEP;
Expand Down
7 changes: 2 additions & 5 deletions scene/2d/animated_sprite.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -452,11 +452,8 @@ void AnimatedSprite::_notification(int p_what) {
if (centered)
ofs -= s / 2;

if (Engine::get_singleton()->get_snap_2d_transforms()) {
ofs = ofs.round();
} else if (Engine::get_singleton()->get_use_gpu_pixel_snap()) {
ofs = ofs.floor();
}
Engine::get_singleton()->get_snappers().snap_read_item(ofs);

Rect2 dst_rect(ofs, s);

if (hflip)
Expand Down
13 changes: 3 additions & 10 deletions scene/2d/sprite.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,7 @@ void Sprite::_get_rects(Rect2 &r_src_rect, Rect2 &r_dst_rect, bool &r_filter_cli
if (centered)
dest_offset -= frame_size / 2;

if (Engine::get_singleton()->get_snap_2d_transforms()) {
dest_offset = dest_offset.round();
} else if (Engine::get_singleton()->get_use_gpu_pixel_snap()) {
dest_offset = dest_offset.floor();
}
Engine::get_singleton()->get_snappers().snap_read_item(dest_offset);

r_dst_rect = Rect2(dest_offset, frame_size);

Expand Down Expand Up @@ -381,11 +377,8 @@ Rect2 Sprite::get_rect() const {
Point2 ofs = offset;
if (centered)
ofs -= Size2(s) / 2;
if (Engine::get_singleton()->get_snap_2d_transforms()) {
ofs = ofs.round();
} else if (Engine::get_singleton()->get_use_gpu_pixel_snap()) {
ofs = ofs.floor();
}

Engine::get_singleton()->get_snappers().snap_read_item(ofs);

if (s == Size2(0, 0))
s = Size2(1, 1);
Expand Down
Loading

0 comments on commit 10b6c83

Please sign in to comment.