Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More snapping options as project settings #46615

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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