summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/static_checks.yml4
-rw-r--r--core/io/image.cpp2
-rw-r--r--core/math/basis.cpp508
-rw-r--r--core/math/basis.h42
-rw-r--r--core/math/quaternion.cpp13
-rw-r--r--core/math/quaternion.h3
-rw-r--r--core/math/vector3.h27
-rw-r--r--core/variant/binder_common.h1
-rw-r--r--core/variant/variant.cpp4
-rw-r--r--core/variant/variant_call.cpp14
-rw-r--r--core/variant/variant_construct.cpp3
-rw-r--r--core/variant/variant_internal.h6
-rw-r--r--doc/classes/Animation.xml15
-rw-r--r--doc/classes/Basis.xml28
-rw-r--r--doc/classes/CanvasItem.xml6
-rw-r--r--doc/classes/DirectionalLight2D.xml2
-rw-r--r--doc/classes/DirectionalLight3D.xml12
-rw-r--r--doc/classes/Font.xml25
-rw-r--r--doc/classes/Node3D.xml32
-rw-r--r--doc/classes/PointLight2D.xml2
-rw-r--r--doc/classes/Quaternion.xml13
-rw-r--r--doc/classes/TileMap.xml11
-rw-r--r--doc/classes/Vector3.xml11
-rw-r--r--editor/action_map_editor.cpp10
-rw-r--r--editor/animation_track_editor.cpp177
-rw-r--r--editor/animation_track_editor.h4
-rw-r--r--editor/animation_track_editor_plugins.cpp4
-rw-r--r--editor/editor_fonts.cpp15
-rw-r--r--editor/editor_properties.cpp3
-rw-r--r--editor/icons/NewKey.svg1
-rw-r--r--editor/import/resource_importer_scene.cpp29
-rw-r--r--editor/import/resource_importer_scene.h1
-rw-r--r--editor/inspector_dock.cpp4
-rw-r--r--editor/plugins/canvas_item_editor_plugin.cpp6
-rw-r--r--editor/plugins/node_3d_editor_plugin.cpp61
-rw-r--r--editor/plugins/node_3d_editor_plugin.h6
-rw-r--r--editor/plugins/skeleton_3d_editor_plugin.cpp268
-rw-r--r--editor/plugins/skeleton_3d_editor_plugin.h38
-rw-r--r--editor/plugins/theme_editor_preview.cpp5
-rw-r--r--editor/plugins/theme_editor_preview.h1
-rw-r--r--editor/plugins/tiles/tile_data_editors.cpp15
-rw-r--r--editor/plugins/tiles/tile_map_editor.cpp786
-rw-r--r--editor/plugins/tiles/tile_map_editor.h58
-rw-r--r--methods.py4
-rwxr-xr-xmisc/hooks/pre-commit-clang-format4
-rwxr-xr-xmisc/scripts/clang_format.sh2
-rw-r--r--modules/csg/csg_shape.cpp1
-rw-r--r--modules/fbx/tools/import_utils.cpp24
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/stringify.gd2
-rw-r--r--modules/gltf/gltf_document.cpp45
-rw-r--r--modules/mono/csharp_script.cpp16
-rw-r--r--modules/mono/csharp_script.h4
-rw-r--r--modules/text_server_adv/text_server_adv.cpp1
-rw-r--r--modules/text_server_fb/text_server_fb.cpp1
-rw-r--r--scene/2d/tile_map.cpp557
-rw-r--r--scene/2d/tile_map.h49
-rw-r--r--scene/3d/area_3d.cpp2
-rw-r--r--scene/3d/audio_stream_player_3d.cpp2
-rw-r--r--scene/3d/bone_attachment_3d.cpp2
-rw-r--r--scene/3d/camera_3d.cpp2
-rw-r--r--scene/3d/cpu_particles_3d.cpp3
-rw-r--r--scene/3d/decal.cpp1
-rw-r--r--scene/3d/gpu_particles_3d.cpp2
-rw-r--r--scene/3d/light_3d.cpp36
-rw-r--r--scene/3d/light_3d.h1
-rw-r--r--scene/3d/lightmap_gi.cpp1
-rw-r--r--scene/3d/node_3d.cpp103
-rw-r--r--scene/3d/node_3d.h35
-rw-r--r--scene/3d/path_3d.cpp1
-rw-r--r--scene/3d/physics_body_3d.cpp4
-rw-r--r--scene/3d/reflection_probe.cpp1
-rw-r--r--scene/3d/remote_transform_3d.cpp4
-rw-r--r--scene/3d/skeleton_3d.cpp22
-rw-r--r--scene/3d/skeleton_ik_3d.cpp2
-rw-r--r--scene/3d/skeleton_ik_3d.h3
-rw-r--r--scene/3d/sprite_3d.cpp4
-rw-r--r--scene/3d/xr_nodes.cpp2
-rw-r--r--scene/animation/animation_player.cpp2
-rw-r--r--scene/audio/audio_stream_player.cpp2
-rw-r--r--scene/gui/code_edit.cpp2
-rw-r--r--scene/gui/control.cpp16
-rw-r--r--scene/gui/tree.cpp2
-rw-r--r--scene/main/canvas_item.cpp6
-rw-r--r--scene/main/canvas_item.h8
-rw-r--r--scene/resources/animation.cpp1653
-rw-r--r--scene/resources/animation.h95
-rw-r--r--scene/resources/default_theme/default_theme.cpp1
-rw-r--r--scene/resources/font.cpp98
-rw-r--r--scene/resources/font.h28
-rw-r--r--scene/resources/skeleton_modification_stack_3d.cpp1
-rw-r--r--scene/resources/theme.cpp4
-rw-r--r--scene/resources/tile_set.cpp150
-rw-r--r--scene/resources/tile_set.h12
-rw-r--r--servers/physics_3d/joints/godot_generic_6dof_joint_3d.cpp2
-rw-r--r--servers/rendering/renderer_rd/shaders/scene_forward_clustered.glsl5
-rw-r--r--servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl10
-rw-r--r--servers/rendering/renderer_scene_cull.cpp4
-rw-r--r--tests/test_basis.h28
-rw-r--r--thirdparty/README.md3
-rw-r--r--thirdparty/mbedtls/library/base64.c255
-rw-r--r--thirdparty/mbedtls/patches/pr4819-faster-base64.patch341
101 files changed, 4174 insertions, 1773 deletions
diff --git a/.github/workflows/static_checks.yml b/.github/workflows/static_checks.yml
index 88e5f65f22..0758aa90b1 100644
--- a/.github/workflows/static_checks.yml
+++ b/.github/workflows/static_checks.yml
@@ -22,9 +22,9 @@ jobs:
- name: Install dependencies
run: |
- sudo apt-get install -qq dos2unix recode clang-format-11
+ sudo apt-get install -qq dos2unix recode clang-format-12
sudo update-alternatives --remove-all clang-format
- sudo update-alternatives --install /usr/bin/clang-format clang-format /usr/bin/clang-format-11 100
+ sudo update-alternatives --install /usr/bin/clang-format clang-format /usr/bin/clang-format-12 100
sudo pip3 install black==20.8b1 pygments
- name: File formatting checks (file_format.sh)
diff --git a/core/io/image.cpp b/core/io/image.cpp
index c70f4b86bd..b82e6637b4 100644
--- a/core/io/image.cpp
+++ b/core/io/image.cpp
@@ -797,7 +797,7 @@ static void _scale_bilinear(const uint8_t *__restrict p_src, uint8_t *__restrict
uint32_t interp_down = p01 + (((p11 - p01) * src_xofs_frac) >> FRAC_BITS);
uint32_t interp = interp_up + (((interp_down - interp_up) * src_yofs_frac) >> FRAC_BITS);
interp >>= FRAC_BITS;
- p_dst[i * p_dst_width * CC + j * CC + l] = interp;
+ p_dst[i * p_dst_width * CC + j * CC + l] = uint8_t(interp);
} else if (sizeof(T) == 2) { //half float
float xofs_frac = float(src_xofs_frac) / (1 << FRAC_BITS);
diff --git a/core/math/basis.cpp b/core/math/basis.cpp
index a7f89522d7..0030cb1144 100644
--- a/core/math/basis.cpp
+++ b/core/math/basis.cpp
@@ -354,7 +354,7 @@ void Basis::rotate(const Quaternion &p_quaternion) {
*this = rotated(p_quaternion);
}
-Vector3 Basis::get_rotation_euler() const {
+Vector3 Basis::get_euler_normalized(EulerOrder p_order) const {
// Assumes that the matrix can be decomposed into a proper rotation and scaling matrix as M = R.S,
// and returns the Euler angles corresponding to the rotation part, complementing get_scale().
// See the comment in get_scale() for further information.
@@ -365,7 +365,7 @@ Vector3 Basis::get_rotation_euler() const {
m.scale(Vector3(-1, -1, -1));
}
- return m.get_euler();
+ return m.get_euler(p_order);
}
Quaternion Basis::get_rotation_quaternion() const {
@@ -424,218 +424,203 @@ void Basis::get_rotation_axis_angle_local(Vector3 &p_axis, real_t &p_angle) cons
p_angle = -p_angle;
}
-// get_euler_xyz returns a vector containing the Euler angles in the format
-// (a1,a2,a3), where a3 is the angle of the first rotation, and a1 is the last
-// (following the convention they are commonly defined in the literature).
-//
-// The current implementation uses XYZ convention (Z is the first rotation),
-// so euler.z is the angle of the (first) rotation around Z axis and so on,
-//
-// And thus, assuming the matrix is a rotation matrix, this function returns
-// the angles in the decomposition R = X(a1).Y(a2).Z(a3) where Z(a) rotates
-// around the z-axis by a and so on.
-Vector3 Basis::get_euler_xyz() const {
- // Euler angles in XYZ convention.
- // See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix
- //
- // rot = cy*cz -cy*sz sy
- // cz*sx*sy+cx*sz cx*cz-sx*sy*sz -cy*sx
- // -cx*cz*sy+sx*sz cz*sx+cx*sy*sz cx*cy
-
- Vector3 euler;
- real_t sy = elements[0][2];
- if (sy < (1.0 - CMP_EPSILON)) {
- if (sy > -(1.0 - CMP_EPSILON)) {
- // is this a pure Y rotation?
- if (elements[1][0] == 0.0 && elements[0][1] == 0.0 && elements[1][2] == 0 && elements[2][1] == 0 && elements[1][1] == 1) {
- // return the simplest form (human friendlier in editor and scripts)
- euler.x = 0;
- euler.y = atan2(elements[0][2], elements[0][0]);
- euler.z = 0;
+Vector3 Basis::get_euler(EulerOrder p_order) const {
+ switch (p_order) {
+ case EULER_ORDER_XYZ: {
+ // Euler angles in XYZ convention.
+ // See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix
+ //
+ // rot = cy*cz -cy*sz sy
+ // cz*sx*sy+cx*sz cx*cz-sx*sy*sz -cy*sx
+ // -cx*cz*sy+sx*sz cz*sx+cx*sy*sz cx*cy
+
+ Vector3 euler;
+ real_t sy = elements[0][2];
+ if (sy < (1.0 - CMP_EPSILON)) {
+ if (sy > -(1.0 - CMP_EPSILON)) {
+ // is this a pure Y rotation?
+ if (elements[1][0] == 0.0 && elements[0][1] == 0.0 && elements[1][2] == 0 && elements[2][1] == 0 && elements[1][1] == 1) {
+ // return the simplest form (human friendlier in editor and scripts)
+ euler.x = 0;
+ euler.y = atan2(elements[0][2], elements[0][0]);
+ euler.z = 0;
+ } else {
+ euler.x = Math::atan2(-elements[1][2], elements[2][2]);
+ euler.y = Math::asin(sy);
+ euler.z = Math::atan2(-elements[0][1], elements[0][0]);
+ }
+ } else {
+ euler.x = Math::atan2(elements[2][1], elements[1][1]);
+ euler.y = -Math_PI / 2.0;
+ euler.z = 0.0;
+ }
} else {
- euler.x = Math::atan2(-elements[1][2], elements[2][2]);
- euler.y = Math::asin(sy);
- euler.z = Math::atan2(-elements[0][1], elements[0][0]);
+ euler.x = Math::atan2(elements[2][1], elements[1][1]);
+ euler.y = Math_PI / 2.0;
+ euler.z = 0.0;
+ }
+ return euler;
+ } break;
+ case EULER_ORDER_XZY: {
+ // Euler angles in XZY convention.
+ // See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix
+ //
+ // rot = cz*cy -sz cz*sy
+ // sx*sy+cx*cy*sz cx*cz cx*sz*sy-cy*sx
+ // cy*sx*sz cz*sx cx*cy+sx*sz*sy
+
+ Vector3 euler;
+ real_t sz = elements[0][1];
+ if (sz < (1.0 - CMP_EPSILON)) {
+ if (sz > -(1.0 - CMP_EPSILON)) {
+ euler.x = Math::atan2(elements[2][1], elements[1][1]);
+ euler.y = Math::atan2(elements[0][2], elements[0][0]);
+ euler.z = Math::asin(-sz);
+ } else {
+ // It's -1
+ euler.x = -Math::atan2(elements[1][2], elements[2][2]);
+ euler.y = 0.0;
+ euler.z = Math_PI / 2.0;
+ }
+ } else {
+ // It's 1
+ euler.x = -Math::atan2(elements[1][2], elements[2][2]);
+ euler.y = 0.0;
+ euler.z = -Math_PI / 2.0;
+ }
+ return euler;
+ } break;
+ case EULER_ORDER_YXZ: {
+ // Euler angles in YXZ convention.
+ // See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix
+ //
+ // rot = cy*cz+sy*sx*sz cz*sy*sx-cy*sz cx*sy
+ // cx*sz cx*cz -sx
+ // cy*sx*sz-cz*sy cy*cz*sx+sy*sz cy*cx
+
+ Vector3 euler;
+
+ real_t m12 = elements[1][2];
+
+ if (m12 < (1 - CMP_EPSILON)) {
+ if (m12 > -(1 - CMP_EPSILON)) {
+ // is this a pure X rotation?
+ if (elements[1][0] == 0 && elements[0][1] == 0 && elements[0][2] == 0 && elements[2][0] == 0 && elements[0][0] == 1) {
+ // return the simplest form (human friendlier in editor and scripts)
+ euler.x = atan2(-m12, elements[1][1]);
+ euler.y = 0;
+ euler.z = 0;
+ } else {
+ euler.x = asin(-m12);
+ euler.y = atan2(elements[0][2], elements[2][2]);
+ euler.z = atan2(elements[1][0], elements[1][1]);
+ }
+ } else { // m12 == -1
+ euler.x = Math_PI * 0.5;
+ euler.y = atan2(elements[0][1], elements[0][0]);
+ euler.z = 0;
+ }
+ } else { // m12 == 1
+ euler.x = -Math_PI * 0.5;
+ euler.y = -atan2(elements[0][1], elements[0][0]);
+ euler.z = 0;
}
- } else {
- euler.x = Math::atan2(elements[2][1], elements[1][1]);
- euler.y = -Math_PI / 2.0;
- euler.z = 0.0;
- }
- } else {
- euler.x = Math::atan2(elements[2][1], elements[1][1]);
- euler.y = Math_PI / 2.0;
- euler.z = 0.0;
- }
- return euler;
-}
-
-// set_euler_xyz expects a vector containing the Euler angles in the format
-// (ax,ay,az), where ax is the angle of rotation around x axis,
-// and similar for other axes.
-// The current implementation uses XYZ convention (Z is the first rotation).
-void Basis::set_euler_xyz(const Vector3 &p_euler) {
- real_t c, s;
-
- c = Math::cos(p_euler.x);
- s = Math::sin(p_euler.x);
- Basis xmat(1.0, 0.0, 0.0, 0.0, c, -s, 0.0, s, c);
-
- c = Math::cos(p_euler.y);
- s = Math::sin(p_euler.y);
- Basis ymat(c, 0.0, s, 0.0, 1.0, 0.0, -s, 0.0, c);
-
- c = Math::cos(p_euler.z);
- s = Math::sin(p_euler.z);
- Basis zmat(c, -s, 0.0, s, c, 0.0, 0.0, 0.0, 1.0);
-
- //optimizer will optimize away all this anyway
- *this = xmat * (ymat * zmat);
-}
-
-Vector3 Basis::get_euler_xzy() const {
- // Euler angles in XZY convention.
- // See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix
- //
- // rot = cz*cy -sz cz*sy
- // sx*sy+cx*cy*sz cx*cz cx*sz*sy-cy*sx
- // cy*sx*sz cz*sx cx*cy+sx*sz*sy
-
- Vector3 euler;
- real_t sz = elements[0][1];
- if (sz < (1.0 - CMP_EPSILON)) {
- if (sz > -(1.0 - CMP_EPSILON)) {
- euler.x = Math::atan2(elements[2][1], elements[1][1]);
- euler.y = Math::atan2(elements[0][2], elements[0][0]);
- euler.z = Math::asin(-sz);
- } else {
- // It's -1
- euler.x = -Math::atan2(elements[1][2], elements[2][2]);
- euler.y = 0.0;
- euler.z = Math_PI / 2.0;
- }
- } else {
- // It's 1
- euler.x = -Math::atan2(elements[1][2], elements[2][2]);
- euler.y = 0.0;
- euler.z = -Math_PI / 2.0;
- }
- return euler;
-}
-
-void Basis::set_euler_xzy(const Vector3 &p_euler) {
- real_t c, s;
-
- c = Math::cos(p_euler.x);
- s = Math::sin(p_euler.x);
- Basis xmat(1.0, 0.0, 0.0, 0.0, c, -s, 0.0, s, c);
-
- c = Math::cos(p_euler.y);
- s = Math::sin(p_euler.y);
- Basis ymat(c, 0.0, s, 0.0, 1.0, 0.0, -s, 0.0, c);
-
- c = Math::cos(p_euler.z);
- s = Math::sin(p_euler.z);
- Basis zmat(c, -s, 0.0, s, c, 0.0, 0.0, 0.0, 1.0);
-
- *this = xmat * zmat * ymat;
-}
-
-Vector3 Basis::get_euler_yzx() const {
- // Euler angles in YZX convention.
- // See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix
- //
- // rot = cy*cz sy*sx-cy*cx*sz cx*sy+cy*sz*sx
- // sz cz*cx -cz*sx
- // -cz*sy cy*sx+cx*sy*sz cy*cx-sy*sz*sx
-
- Vector3 euler;
- real_t sz = elements[1][0];
- if (sz < (1.0 - CMP_EPSILON)) {
- if (sz > -(1.0 - CMP_EPSILON)) {
- euler.x = Math::atan2(-elements[1][2], elements[1][1]);
- euler.y = Math::atan2(-elements[2][0], elements[0][0]);
- euler.z = Math::asin(sz);
- } else {
- // It's -1
- euler.x = Math::atan2(elements[2][1], elements[2][2]);
- euler.y = 0.0;
- euler.z = -Math_PI / 2.0;
- }
- } else {
- // It's 1
- euler.x = Math::atan2(elements[2][1], elements[2][2]);
- euler.y = 0.0;
- euler.z = Math_PI / 2.0;
- }
- return euler;
-}
-
-void Basis::set_euler_yzx(const Vector3 &p_euler) {
- real_t c, s;
-
- c = Math::cos(p_euler.x);
- s = Math::sin(p_euler.x);
- Basis xmat(1.0, 0.0, 0.0, 0.0, c, -s, 0.0, s, c);
-
- c = Math::cos(p_euler.y);
- s = Math::sin(p_euler.y);
- Basis ymat(c, 0.0, s, 0.0, 1.0, 0.0, -s, 0.0, c);
-
- c = Math::cos(p_euler.z);
- s = Math::sin(p_euler.z);
- Basis zmat(c, -s, 0.0, s, c, 0.0, 0.0, 0.0, 1.0);
-
- *this = ymat * zmat * xmat;
-}
-
-// get_euler_yxz returns a vector containing the Euler angles in the YXZ convention,
-// as in first-Z, then-X, last-Y. The angles for X, Y, and Z rotations are returned
-// as the x, y, and z components of a Vector3 respectively.
-Vector3 Basis::get_euler_yxz() const {
- // Euler angles in YXZ convention.
- // See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix
- //
- // rot = cy*cz+sy*sx*sz cz*sy*sx-cy*sz cx*sy
- // cx*sz cx*cz -sx
- // cy*sx*sz-cz*sy cy*cz*sx+sy*sz cy*cx
-
- Vector3 euler;
-
- real_t m12 = elements[1][2];
- if (m12 < (1 - CMP_EPSILON)) {
- if (m12 > -(1 - CMP_EPSILON)) {
- // is this a pure X rotation?
- if (elements[1][0] == 0 && elements[0][1] == 0 && elements[0][2] == 0 && elements[2][0] == 0 && elements[0][0] == 1) {
- // return the simplest form (human friendlier in editor and scripts)
- euler.x = atan2(-m12, elements[1][1]);
- euler.y = 0;
+ return euler;
+ } break;
+ case EULER_ORDER_YZX: {
+ // Euler angles in YZX convention.
+ // See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix
+ //
+ // rot = cy*cz sy*sx-cy*cx*sz cx*sy+cy*sz*sx
+ // sz cz*cx -cz*sx
+ // -cz*sy cy*sx+cx*sy*sz cy*cx-sy*sz*sx
+
+ Vector3 euler;
+ real_t sz = elements[1][0];
+ if (sz < (1.0 - CMP_EPSILON)) {
+ if (sz > -(1.0 - CMP_EPSILON)) {
+ euler.x = Math::atan2(-elements[1][2], elements[1][1]);
+ euler.y = Math::atan2(-elements[2][0], elements[0][0]);
+ euler.z = Math::asin(sz);
+ } else {
+ // It's -1
+ euler.x = Math::atan2(elements[2][1], elements[2][2]);
+ euler.y = 0.0;
+ euler.z = -Math_PI / 2.0;
+ }
+ } else {
+ // It's 1
+ euler.x = Math::atan2(elements[2][1], elements[2][2]);
+ euler.y = 0.0;
+ euler.z = Math_PI / 2.0;
+ }
+ return euler;
+ } break;
+ case EULER_ORDER_ZXY: {
+ // Euler angles in ZXY convention.
+ // See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix
+ //
+ // rot = cz*cy-sz*sx*sy -cx*sz cz*sy+cy*sz*sx
+ // cy*sz+cz*sx*sy cz*cx sz*sy-cz*cy*sx
+ // -cx*sy sx cx*cy
+ Vector3 euler;
+ real_t sx = elements[2][1];
+ if (sx < (1.0 - CMP_EPSILON)) {
+ if (sx > -(1.0 - CMP_EPSILON)) {
+ euler.x = Math::asin(sx);
+ euler.y = Math::atan2(-elements[2][0], elements[2][2]);
+ euler.z = Math::atan2(-elements[0][1], elements[1][1]);
+ } else {
+ // It's -1
+ euler.x = -Math_PI / 2.0;
+ euler.y = Math::atan2(elements[0][2], elements[0][0]);
+ euler.z = 0;
+ }
+ } else {
+ // It's 1
+ euler.x = Math_PI / 2.0;
+ euler.y = Math::atan2(elements[0][2], elements[0][0]);
euler.z = 0;
+ }
+ return euler;
+ } break;
+ case EULER_ORDER_ZYX: {
+ // Euler angles in ZYX convention.
+ // See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix
+ //
+ // rot = cz*cy cz*sy*sx-cx*sz sz*sx+cz*cx*cy
+ // cy*sz cz*cx+sz*sy*sx cx*sz*sy-cz*sx
+ // -sy cy*sx cy*cx
+ Vector3 euler;
+ real_t sy = elements[2][0];
+ if (sy < (1.0 - CMP_EPSILON)) {
+ if (sy > -(1.0 - CMP_EPSILON)) {
+ euler.x = Math::atan2(elements[2][1], elements[2][2]);
+ euler.y = Math::asin(-sy);
+ euler.z = Math::atan2(elements[1][0], elements[0][0]);
+ } else {
+ // It's -1
+ euler.x = 0;
+ euler.y = Math_PI / 2.0;
+ euler.z = -Math::atan2(elements[0][1], elements[1][1]);
+ }
} else {
- euler.x = asin(-m12);
- euler.y = atan2(elements[0][2], elements[2][2]);
- euler.z = atan2(elements[1][0], elements[1][1]);
+ // It's 1
+ euler.x = 0;
+ euler.y = -Math_PI / 2.0;
+ euler.z = -Math::atan2(elements[0][1], elements[1][1]);
}
- } else { // m12 == -1
- euler.x = Math_PI * 0.5;
- euler.y = atan2(elements[0][1], elements[0][0]);
- euler.z = 0;
+ return euler;
+ } break;
+ default: {
+ ERR_FAIL_V_MSG(Vector3(), "Invalid parameter for get_euler(order)");
}
- } else { // m12 == 1
- euler.x = -Math_PI * 0.5;
- euler.y = -atan2(elements[0][1], elements[0][0]);
- euler.z = 0;
}
-
- return euler;
+ return Vector3();
}
-// set_euler_yxz expects a vector containing the Euler angles in the format
-// (ax,ay,az), where ax is the angle of rotation around x axis,
-// and similar for other axes.
-// The current implementation uses YXZ convention (Z is the first rotation).
-void Basis::set_euler_yxz(const Vector3 &p_euler) {
+void Basis::set_euler(const Vector3 &p_euler, EulerOrder p_order) {
real_t c, s;
c = Math::cos(p_euler.x);
@@ -650,102 +635,29 @@ void Basis::set_euler_yxz(const Vector3 &p_euler) {
s = Math::sin(p_euler.z);
Basis zmat(c, -s, 0.0, s, c, 0.0, 0.0, 0.0, 1.0);
- //optimizer will optimize away all this anyway
- *this = ymat * xmat * zmat;
-}
-
-Vector3 Basis::get_euler_zxy() const {
- // Euler angles in ZXY convention.
- // See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix
- //
- // rot = cz*cy-sz*sx*sy -cx*sz cz*sy+cy*sz*sx
- // cy*sz+cz*sx*sy cz*cx sz*sy-cz*cy*sx
- // -cx*sy sx cx*cy
- Vector3 euler;
- real_t sx = elements[2][1];
- if (sx < (1.0 - CMP_EPSILON)) {
- if (sx > -(1.0 - CMP_EPSILON)) {
- euler.x = Math::asin(sx);
- euler.y = Math::atan2(-elements[2][0], elements[2][2]);
- euler.z = Math::atan2(-elements[0][1], elements[1][1]);
- } else {
- // It's -1
- euler.x = -Math_PI / 2.0;
- euler.y = Math::atan2(elements[0][2], elements[0][0]);
- euler.z = 0;
+ switch (p_order) {
+ case EULER_ORDER_XYZ: {
+ *this = xmat * (ymat * zmat);
+ } break;
+ case EULER_ORDER_XZY: {
+ *this = xmat * zmat * ymat;
+ } break;
+ case EULER_ORDER_YXZ: {
+ *this = ymat * xmat * zmat;
+ } break;
+ case EULER_ORDER_YZX: {
+ *this = ymat * zmat * xmat;
+ } break;
+ case EULER_ORDER_ZXY: {
+ *this = zmat * xmat * ymat;
+ } break;
+ case EULER_ORDER_ZYX: {
+ *this = zmat * ymat * xmat;
+ } break;
+ default: {
+ ERR_FAIL_MSG("Invalid order parameter for set_euler(vec3,order)");
}
- } else {
- // It's 1
- euler.x = Math_PI / 2.0;
- euler.y = Math::atan2(elements[0][2], elements[0][0]);
- euler.z = 0;
}
- return euler;
-}
-
-void Basis::set_euler_zxy(const Vector3 &p_euler) {
- real_t c, s;
-
- c = Math::cos(p_euler.x);
- s = Math::sin(p_euler.x);
- Basis xmat(1.0, 0.0, 0.0, 0.0, c, -s, 0.0, s, c);
-
- c = Math::cos(p_euler.y);
- s = Math::sin(p_euler.y);
- Basis ymat(c, 0.0, s, 0.0, 1.0, 0.0, -s, 0.0, c);
-
- c = Math::cos(p_euler.z);
- s = Math::sin(p_euler.z);
- Basis zmat(c, -s, 0.0, s, c, 0.0, 0.0, 0.0, 1.0);
-
- *this = zmat * xmat * ymat;
-}
-
-Vector3 Basis::get_euler_zyx() const {
- // Euler angles in ZYX convention.
- // See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix
- //
- // rot = cz*cy cz*sy*sx-cx*sz sz*sx+cz*cx*cy
- // cy*sz cz*cx+sz*sy*sx cx*sz*sy-cz*sx
- // -sy cy*sx cy*cx
- Vector3 euler;
- real_t sy = elements[2][0];
- if (sy < (1.0 - CMP_EPSILON)) {
- if (sy > -(1.0 - CMP_EPSILON)) {
- euler.x = Math::atan2(elements[2][1], elements[2][2]);
- euler.y = Math::asin(-sy);
- euler.z = Math::atan2(elements[1][0], elements[0][0]);
- } else {
- // It's -1
- euler.x = 0;
- euler.y = Math_PI / 2.0;
- euler.z = -Math::atan2(elements[0][1], elements[1][1]);
- }
- } else {
- // It's 1
- euler.x = 0;
- euler.y = -Math_PI / 2.0;
- euler.z = -Math::atan2(elements[0][1], elements[1][1]);
- }
- return euler;
-}
-
-void Basis::set_euler_zyx(const Vector3 &p_euler) {
- real_t c, s;
-
- c = Math::cos(p_euler.x);
- s = Math::sin(p_euler.x);
- Basis xmat(1.0, 0.0, 0.0, 0.0, c, -s, 0.0, s, c);
-
- c = Math::cos(p_euler.y);
- s = Math::sin(p_euler.y);
- Basis ymat(c, 0.0, s, 0.0, 1.0, 0.0, -s, 0.0, c);
-
- c = Math::cos(p_euler.z);
- s = Math::sin(p_euler.z);
- Basis zmat(c, -s, 0.0, s, c, 0.0, 0.0, 0.0, 1.0);
-
- *this = zmat * ymat * xmat;
}
bool Basis::is_equal_approx(const Basis &p_basis) const {
diff --git a/core/math/basis.h b/core/math/basis.h
index eb107d7e4e..617d005f19 100644
--- a/core/math/basis.h
+++ b/core/math/basis.h
@@ -85,40 +85,35 @@ public:
void rotate(const Quaternion &p_quaternion);
Basis rotated(const Quaternion &p_quaternion) const;
- Vector3 get_rotation_euler() const;
+ enum EulerOrder {
+ EULER_ORDER_XYZ,
+ EULER_ORDER_XZY,
+ EULER_ORDER_YXZ,
+ EULER_ORDER_YZX,
+ EULER_ORDER_ZXY,
+ EULER_ORDER_ZYX
+ };
+
+ Vector3 get_euler_normalized(EulerOrder p_order = EULER_ORDER_YXZ) const;
void get_rotation_axis_angle(Vector3 &p_axis, real_t &p_angle) const;
void get_rotation_axis_angle_local(Vector3 &p_axis, real_t &p_angle) const;
Quaternion get_rotation_quaternion() const;
- Vector3 get_rotation() const { return get_rotation_euler(); };
void rotate_to_align(Vector3 p_start_direction, Vector3 p_end_direction);
Vector3 rotref_posscale_decomposition(Basis &rotref) const;
- Vector3 get_euler_xyz() const;
- void set_euler_xyz(const Vector3 &p_euler);
-
- Vector3 get_euler_xzy() const;
- void set_euler_xzy(const Vector3 &p_euler);
-
- Vector3 get_euler_yzx() const;
- void set_euler_yzx(const Vector3 &p_euler);
-
- Vector3 get_euler_yxz() const;
- void set_euler_yxz(const Vector3 &p_euler);
-
- Vector3 get_euler_zxy() const;
- void set_euler_zxy(const Vector3 &p_euler);
-
- Vector3 get_euler_zyx() const;
- void set_euler_zyx(const Vector3 &p_euler);
+ Vector3 get_euler(EulerOrder p_order = EULER_ORDER_YXZ) const;
+ void set_euler(const Vector3 &p_euler, EulerOrder p_order = EULER_ORDER_YXZ);
+ static Basis from_euler(const Vector3 &p_euler, EulerOrder p_order = EULER_ORDER_YXZ) {
+ Basis b;
+ b.set_euler(p_euler, p_order);
+ return b;
+ }
Quaternion get_quaternion() const;
void set_quaternion(const Quaternion &p_quaternion);
- Vector3 get_euler() const { return get_euler_yxz(); }
- void set_euler(const Vector3 &p_euler) { set_euler_yxz(p_euler); }
-
void get_axis_angle(Vector3 &r_axis, real_t &r_angle) const;
void set_axis_angle(const Vector3 &p_axis, real_t p_phi);
@@ -250,9 +245,6 @@ public:
Basis(const Quaternion &p_quaternion) { set_quaternion(p_quaternion); };
Basis(const Quaternion &p_quaternion, const Vector3 &p_scale) { set_quaternion_scale(p_quaternion, p_scale); }
- Basis(const Vector3 &p_euler) { set_euler(p_euler); }
- Basis(const Vector3 &p_euler, const Vector3 &p_scale) { set_euler_scale(p_euler, p_scale); }
-
Basis(const Vector3 &p_axis, real_t p_phi) { set_axis_angle(p_axis, p_phi); }
Basis(const Vector3 &p_axis, real_t p_phi, const Vector3 &p_scale) { set_axis_angle_scale(p_axis, p_phi, p_scale); }
static Basis from_scale(const Vector3 &p_scale);
diff --git a/core/math/quaternion.cpp b/core/math/quaternion.cpp
index 3f1d2c58e5..944474686a 100644
--- a/core/math/quaternion.cpp
+++ b/core/math/quaternion.cpp
@@ -44,7 +44,7 @@ real_t Quaternion::angle_to(const Quaternion &p_to) const {
// This implementation uses XYZ convention (Z is the first rotation).
Vector3 Quaternion::get_euler_xyz() const {
Basis m(*this);
- return m.get_euler_xyz();
+ return m.get_euler(Basis::EULER_ORDER_XYZ);
}
// get_euler_yxz returns a vector containing the Euler angles in the format
@@ -56,7 +56,7 @@ Vector3 Quaternion::get_euler_yxz() const {
ERR_FAIL_COND_V_MSG(!is_normalized(), Vector3(0, 0, 0), "The quaternion must be normalized.");
#endif
Basis m(*this);
- return m.get_euler_yxz();
+ return m.get_euler(Basis::EULER_ORDER_YXZ);
}
void Quaternion::operator*=(const Quaternion &p_q) {
@@ -189,6 +189,15 @@ Quaternion::operator String() const {
return "(" + String::num_real(x, false) + ", " + String::num_real(y, false) + ", " + String::num_real(z, false) + ", " + String::num_real(w, false) + ")";
}
+Vector3 Quaternion::get_axis() const {
+ real_t r = ((real_t)1) / Math::sqrt(1 - w * w);
+ return Vector3(x * r, y * r, z * r);
+}
+
+float Quaternion::get_angle() const {
+ return 2 * Math::acos(w);
+}
+
Quaternion::Quaternion(const Vector3 &p_axis, real_t p_angle) {
#ifdef MATH_CHECKS
ERR_FAIL_COND_MSG(!p_axis.is_normalized(), "The axis Vector3 must be normalized.");
diff --git a/core/math/quaternion.h b/core/math/quaternion.h
index 35324323b3..e20ea74eb4 100644
--- a/core/math/quaternion.h
+++ b/core/math/quaternion.h
@@ -72,6 +72,9 @@ public:
Quaternion slerpni(const Quaternion &p_to, const real_t &p_weight) const;
Quaternion cubic_slerp(const Quaternion &p_b, const Quaternion &p_pre_a, const Quaternion &p_post_b, const real_t &p_weight) const;
+ Vector3 get_axis() const;
+ float get_angle() const;
+
_FORCE_INLINE_ void get_axis_angle(Vector3 &r_axis, real_t &r_angle) const {
r_angle = 2 * Math::acos(w);
real_t r = ((real_t)1) / Math::sqrt(1 - w * w);
diff --git a/core/math/vector3.h b/core/math/vector3.h
index e65ac31c02..dc9aa60458 100644
--- a/core/math/vector3.h
+++ b/core/math/vector3.h
@@ -32,9 +32,9 @@
#define VECTOR3_H
#include "core/math/math_funcs.h"
+#include "core/math/vector2.h"
#include "core/math/vector3i.h"
#include "core/string/ustring.h"
-
class Basis;
struct Vector3 {
@@ -103,6 +103,31 @@ struct Vector3 {
Vector3 cubic_interpolate(const Vector3 &p_b, const Vector3 &p_pre_a, const Vector3 &p_post_b, const real_t p_weight) const;
Vector3 move_toward(const Vector3 &p_to, const real_t p_delta) const;
+ _FORCE_INLINE_ Vector2 octahedron_encode() const {
+ Vector3 n = *this;
+ n /= Math::abs(n.x) + Math::abs(n.y) + Math::abs(n.z);
+ Vector2 o;
+ if (n.z >= 0.0) {
+ o.x = n.x;
+ o.y = n.y;
+ } else {
+ o.x = (1.0 - Math::abs(n.y)) * (n.x >= 0.0 ? 1.0 : -1.0);
+ o.y = (1.0 - Math::abs(n.x)) * (n.y >= 0.0 ? 1.0 : -1.0);
+ }
+ o.x = o.x * 0.5 + 0.5;
+ o.y = o.y * 0.5 + 0.5;
+ return o;
+ }
+
+ static _FORCE_INLINE_ Vector3 octahedron_decode(const Vector2 &p_oct) {
+ Vector2 f(p_oct.x * 2.0 - 1.0, p_oct.y * 2.0 - 1.0);
+ Vector3 n(f.x, f.y, 1.0f - Math::abs(f.x) - Math::abs(f.y));
+ float t = CLAMP(-n.z, 0.0, 1.0);
+ n.x += n.x >= 0 ? -t : t;
+ n.y += n.y >= 0 ? -t : t;
+ return n.normalized();
+ }
+
_FORCE_INLINE_ Vector3 cross(const Vector3 &p_b) const;
_FORCE_INLINE_ real_t dot(const Vector3 &p_b) const;
Basis outer(const Vector3 &p_b) const;
diff --git a/core/variant/binder_common.h b/core/variant/binder_common.h
index 8592a1dc62..f06d767cf5 100644
--- a/core/variant/binder_common.h
+++ b/core/variant/binder_common.h
@@ -88,6 +88,7 @@ struct VariantCaster<const T &> {
VARIANT_ENUM_CAST(Object::ConnectFlags);
VARIANT_ENUM_CAST(Vector3::Axis);
+VARIANT_ENUM_CAST(Basis::EulerOrder);
VARIANT_ENUM_CAST(Error);
VARIANT_ENUM_CAST(Side);
diff --git a/core/variant/variant.cpp b/core/variant/variant.cpp
index 3214fc125d..81428caca1 100644
--- a/core/variant/variant.cpp
+++ b/core/variant/variant.cpp
@@ -313,7 +313,6 @@ bool Variant::can_convert(Variant::Type p_type_from, Variant::Type p_type_to) {
case BASIS: {
static const Type valid[] = {
QUATERNION,
- VECTOR3,
NIL
};
@@ -620,7 +619,6 @@ bool Variant::can_convert_strict(Variant::Type p_type_from, Variant::Type p_type
case BASIS: {
static const Type valid[] = {
QUATERNION,
- VECTOR3,
NIL
};
@@ -1889,8 +1887,6 @@ Variant::operator Basis() const {
return *_data._basis;
} else if (type == QUATERNION) {
return *reinterpret_cast<const Quaternion *>(_data._mem);
- } else if (type == VECTOR3) {
- return Basis(*reinterpret_cast<const Vector3 *>(_data._mem));
} else if (type == TRANSFORM3D) { // unexposed in Variant::can_convert?
return _data._transform3d->basis;
} else {
diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp
index 6284caae2d..ec3a6a5ca8 100644
--- a/core/variant/variant_call.cpp
+++ b/core/variant/variant_call.cpp
@@ -1581,6 +1581,8 @@ static void _register_variant_builtin_methods() {
bind_method(Vector3, bounce, sarray("n"), varray());
bind_method(Vector3, reflect, sarray("n"), varray());
bind_method(Vector3, sign, sarray(), varray());
+ bind_method(Vector3, octahedron_encode, sarray(), varray());
+ bind_static_method(Vector3, octahedron_decode, sarray("uv"), varray());
/* Vector3i */
@@ -1617,6 +1619,8 @@ static void _register_variant_builtin_methods() {
bind_method(Quaternion, slerpni, sarray("to", "weight"), varray());
bind_method(Quaternion, cubic_slerp, sarray("b", "pre_a", "post_b", "weight"), varray());
bind_method(Quaternion, get_euler, sarray(), varray());
+ bind_method(Quaternion, get_axis, sarray(), varray());
+ bind_method(Quaternion, get_angle, sarray(), varray());
/* Color */
@@ -1727,7 +1731,7 @@ static void _register_variant_builtin_methods() {
bind_methodv(Basis, rotated, static_cast<Basis (Basis::*)(const Vector3 &, real_t) const>(&Basis::rotated), sarray("axis", "phi"), varray());
bind_method(Basis, scaled, sarray("scale"), varray());
bind_method(Basis, get_scale, sarray(), varray());
- bind_method(Basis, get_euler, sarray(), varray());
+ bind_method(Basis, get_euler, sarray("order"), varray(Basis::EULER_ORDER_YXZ));
bind_method(Basis, tdotx, sarray("with"), varray());
bind_method(Basis, tdoty, sarray("with"), varray());
bind_method(Basis, tdotz, sarray("with"), varray());
@@ -1737,6 +1741,7 @@ static void _register_variant_builtin_methods() {
bind_method(Basis, get_rotation_quaternion, sarray(), varray());
bind_static_method(Basis, looking_at, sarray("target", "up"), varray(Vector3(0, 1, 0)));
bind_static_method(Basis, from_scale, sarray("scale"), varray());
+ bind_static_method(Basis, from_euler, sarray("euler", "order"), varray(Basis::EULER_ORDER_YXZ));
/* AABB */
@@ -2105,6 +2110,13 @@ static void _register_variant_builtin_methods() {
_VariantCall::add_variant_constant(Variant::VECTOR2I, "UP", Vector2i(0, -1));
_VariantCall::add_variant_constant(Variant::VECTOR2I, "DOWN", Vector2i(0, 1));
+ _VariantCall::add_constant(Variant::BASIS, "EULER_ORDER_XYZ", Basis::EULER_ORDER_XYZ);
+ _VariantCall::add_constant(Variant::BASIS, "EULER_ORDER_XZY", Basis::EULER_ORDER_XZY);
+ _VariantCall::add_constant(Variant::BASIS, "EULER_ORDER_YXZ", Basis::EULER_ORDER_YXZ);
+ _VariantCall::add_constant(Variant::BASIS, "EULER_ORDER_YZX", Basis::EULER_ORDER_YZX);
+ _VariantCall::add_constant(Variant::BASIS, "EULER_ORDER_ZXY", Basis::EULER_ORDER_ZXY);
+ _VariantCall::add_constant(Variant::BASIS, "EULER_ORDER_ZYX", Basis::EULER_ORDER_ZYX);
+
_VariantCall::add_variant_constant(Variant::TRANSFORM2D, "IDENTITY", Transform2D());
_VariantCall::add_variant_constant(Variant::TRANSFORM2D, "FLIP_X", Transform2D(-1, 0, 0, 1, 0, 0));
_VariantCall::add_variant_constant(Variant::TRANSFORM2D, "FLIP_Y", Transform2D(1, 0, 0, -1, 0, 0));
diff --git a/core/variant/variant_construct.cpp b/core/variant/variant_construct.cpp
index 6aba7d7d58..5c14f30180 100644
--- a/core/variant/variant_construct.cpp
+++ b/core/variant/variant_construct.cpp
@@ -128,10 +128,10 @@ void Variant::_register_variant_constructors() {
add_constructor<VariantConstructNoArgs<Quaternion>>(sarray());
add_constructor<VariantConstructor<Quaternion, Quaternion>>(sarray("from"));
add_constructor<VariantConstructor<Quaternion, Basis>>(sarray("from"));
- add_constructor<VariantConstructor<Quaternion, Vector3>>(sarray("euler"));
add_constructor<VariantConstructor<Quaternion, Vector3, double>>(sarray("axis", "angle"));
add_constructor<VariantConstructor<Quaternion, Vector3, Vector3>>(sarray("arc_from", "arc_to"));
add_constructor<VariantConstructor<Quaternion, double, double, double, double>>(sarray("x", "y", "z", "w"));
+ add_constructor<VariantConstructor<Quaternion, Vector3>>(sarray("euler_yxz"));
add_constructor<VariantConstructNoArgs<::AABB>>(sarray());
add_constructor<VariantConstructor<::AABB, ::AABB>>(sarray("from"));
@@ -140,7 +140,6 @@ void Variant::_register_variant_constructors() {
add_constructor<VariantConstructNoArgs<Basis>>(sarray());
add_constructor<VariantConstructor<Basis, Basis>>(sarray("from"));
add_constructor<VariantConstructor<Basis, Quaternion>>(sarray("from"));
- add_constructor<VariantConstructor<Basis, Vector3>>(sarray("euler"));
add_constructor<VariantConstructor<Basis, Vector3, double>>(sarray("axis", "phi"));
add_constructor<VariantConstructor<Basis, Vector3, Vector3, Vector3>>(sarray("x_axis", "y_axis", "z_axis"));
diff --git a/core/variant/variant_internal.h b/core/variant/variant_internal.h
index 37383ff2ec..2ba24b5af8 100644
--- a/core/variant/variant_internal.h
+++ b/core/variant/variant_internal.h
@@ -757,6 +757,12 @@ VARIANT_ACCESSOR_NUMBER(Error)
VARIANT_ACCESSOR_NUMBER(Side)
template <>
+struct VariantInternalAccessor<Basis::EulerOrder> {
+ static _FORCE_INLINE_ Basis::EulerOrder get(const Variant *v) { return Basis::EulerOrder(*VariantInternal::get_int(v)); }
+ static _FORCE_INLINE_ void set(Variant *v, Basis::EulerOrder p_value) { *VariantInternal::get_int(v) = p_value; }
+};
+
+template <>
struct VariantInternalAccessor<ObjectID> {
static _FORCE_INLINE_ ObjectID get(const Variant *v) { return ObjectID(*VariantInternal::get_int(v)); }
static _FORCE_INLINE_ void set(Variant *v, ObjectID p_value) { *VariantInternal::get_int(v) = p_value; }
diff --git a/doc/classes/Animation.xml b/doc/classes/Animation.xml
index ebcced7dc4..e3bb60f6de 100644
--- a/doc/classes/Animation.xml
+++ b/doc/classes/Animation.xml
@@ -215,6 +215,14 @@
Clear the animation (clear all tracks and reset all).
</description>
</method>
+ <method name="compress">
+ <return type="void" />
+ <argument index="0" name="page_size" type="int" default="8192" />
+ <argument index="1" name="fps" type="int" default="120" />
+ <argument index="2" name="split_tolerance" type="float" default="4.0" />
+ <description>
+ </description>
+ </method>
<method name="copy_track">
<return type="void" />
<argument index="0" name="track_idx" type="int" />
@@ -226,6 +234,7 @@
<method name="find_track" qualifiers="const">
<return type="int" />
<argument index="0" name="path" type="NodePath" />
+ <argument index="1" name="type" type="int" enum="Animation.TrackType" />
<description>
Returns the index of the specified track. If the track is not found, return -1.
</description>
@@ -370,6 +379,12 @@
Insert a generic key in a given track.
</description>
</method>
+ <method name="track_is_compressed" qualifiers="const">
+ <return type="bool" />
+ <argument index="0" name="track_idx" type="int" />
+ <description>
+ </description>
+ </method>
<method name="track_is_enabled" qualifiers="const">
<return type="bool" />
<argument index="0" name="track_idx" type="int" />
diff --git a/doc/classes/Basis.xml b/doc/classes/Basis.xml
index 7f0d4cbbe3..6215c658c3 100644
--- a/doc/classes/Basis.xml
+++ b/doc/classes/Basis.xml
@@ -42,14 +42,6 @@
</method>
<method name="Basis" qualifiers="constructor">
<return type="Basis" />
- <argument index="0" name="euler" type="Vector3" />
- <description>
- Constructs a pure rotation basis matrix from the given Euler angles (in the YXZ convention: when *composing*, first Y, then X, and Z last), given in the vector format as (X angle, Y angle, Z angle).
- Consider using the [Quaternion] constructor instead, which uses a quaternion instead of Euler angles.
- </description>
- </method>
- <method name="Basis" qualifiers="constructor">
- <return type="Basis" />
<argument index="0" name="from" type="Quaternion" />
<description>
Constructs a pure rotation basis matrix from the given quaternion.
@@ -71,6 +63,13 @@
A negative determinant means the basis has a negative scale. A zero determinant means the basis isn't invertible, and is usually considered invalid.
</description>
</method>
+ <method name="from_euler" qualifiers="static">
+ <return type="Basis" />
+ <argument index="0" name="euler" type="Vector3" />
+ <argument index="1" name="order" type="int" default="2" />
+ <description>
+ </description>
+ </method>
<method name="from_scale" qualifiers="static">
<return type="Basis" />
<argument index="0" name="scale" type="Vector3" />
@@ -80,6 +79,7 @@
</method>
<method name="get_euler" qualifiers="const">
<return type="Vector3" />
+ <argument index="0" name="order" type="int" default="2" />
<description>
Returns the basis's rotation in the form of Euler angles (in the YXZ convention: when decomposing, first Z, then X, and Y last). The returned vector contains the rotation angles in the format (X angle, Y angle, Z angle).
Consider using the [method get_rotation_quaternion] method instead, which returns a [Quaternion] quaternion instead of Euler angles.
@@ -248,6 +248,18 @@
</member>
</members>
<constants>
+ <constant name="EULER_ORDER_XYZ" value="0">
+ </constant>
+ <constant name="EULER_ORDER_XZY" value="1">
+ </constant>
+ <constant name="EULER_ORDER_YXZ" value="2">
+ </constant>
+ <constant name="EULER_ORDER_YZX" value="3">
+ </constant>
+ <constant name="EULER_ORDER_ZXY" value="4">
+ </constant>
+ <constant name="EULER_ORDER_ZYX" value="5">
+ </constant>
<constant name="IDENTITY" value="Basis(1, 0, 0, 0, 1, 0, 0, 0, 1)">
The identity basis, with no rotation or scaling applied.
This is identical to calling [code]Basis()[/code] without any parameters. This constant can be used to make your code clearer, and for consistency with C#.
diff --git a/doc/classes/CanvasItem.xml b/doc/classes/CanvasItem.xml
index 738594ea5d..7be18c1d5c 100644
--- a/doc/classes/CanvasItem.xml
+++ b/doc/classes/CanvasItem.xml
@@ -53,7 +53,7 @@
<argument index="1" name="pos" type="Vector2" />
<argument index="2" name="char" type="String" />
<argument index="3" name="next" type="String" default="&quot;&quot;" />
- <argument index="4" name="size" type="int" default="-1" />
+ <argument index="4" name="size" type="int" default="16" />
<argument index="5" name="modulate" type="Color" default="Color(1, 1, 1, 1)" />
<argument index="6" name="outline_size" type="int" default="0" />
<argument index="7" name="outline_modulate" type="Color" default="Color(1, 1, 1, 0)" />
@@ -146,7 +146,7 @@
<argument index="3" name="align" type="int" enum="HAlign" default="0" />
<argument index="4" name="width" type="float" default="-1" />
<argument index="5" name="max_lines" type="int" default="-1" />
- <argument index="6" name="size" type="int" default="-1" />
+ <argument index="6" name="size" type="int" default="16" />
<argument index="7" name="modulate" type="Color" default="Color(1, 1, 1, 1)" />
<argument index="8" name="outline_size" type="int" default="0" />
<argument index="9" name="outline_modulate" type="Color" default="Color(1, 1, 1, 0)" />
@@ -238,7 +238,7 @@
<argument index="2" name="text" type="String" />
<argument index="3" name="align" type="int" enum="HAlign" default="0" />
<argument index="4" name="width" type="float" default="-1" />
- <argument index="5" name="size" type="int" default="-1" />
+ <argument index="5" name="size" type="int" default="16" />
<argument index="6" name="modulate" type="Color" default="Color(1, 1, 1, 1)" />
<argument index="7" name="outline_size" type="int" default="0" />
<argument index="8" name="outline_modulate" type="Color" default="Color(1, 1, 1, 0)" />
diff --git a/doc/classes/DirectionalLight2D.xml b/doc/classes/DirectionalLight2D.xml
index 317cf6e66c..1e5c9bc09a 100644
--- a/doc/classes/DirectionalLight2D.xml
+++ b/doc/classes/DirectionalLight2D.xml
@@ -8,7 +8,7 @@
</tutorials>
<members>
<member name="height" type="float" setter="set_height" getter="get_height" default="0.0">
- The height of the light. Used with 2D normal mapping.
+ The height of the light. Used with 2D normal mapping. Ranges from 0 (parallel to the plane) to 1 (perpendicular to the plane).
</member>
<member name="max_distance" type="float" setter="set_max_distance" getter="get_max_distance" default="10000.0">
</member>
diff --git a/doc/classes/DirectionalLight3D.xml b/doc/classes/DirectionalLight3D.xml
index f2a6e5b6f8..44d982cbcc 100644
--- a/doc/classes/DirectionalLight3D.xml
+++ b/doc/classes/DirectionalLight3D.xml
@@ -11,13 +11,13 @@
</tutorials>
<members>
<member name="directional_shadow_blend_splits" type="bool" setter="set_blend_splits" getter="is_blend_splits_enabled" default="false">
- If [code]true[/code], shadow detail is sacrificed in exchange for smoother transitions between splits. This is ignored when [member directional_shadow_mode] is [code]SHADOW_ORTHOGONAL[/code].
+ If [code]true[/code], shadow detail is sacrificed in exchange for smoother transitions between splits. Enabling shadow blend splitting also has a moderate performance cost. This is ignored when [member directional_shadow_mode] is [constant SHADOW_ORTHOGONAL].
</member>
<member name="directional_shadow_fade_start" type="float" setter="set_param" getter="get_param" default="0.8">
- Proportion of [member directional_shadow_max_distance] at which point the shadow starts to fade. At [member directional_shadow_max_distance] the shadow will disappear.
+ Proportion of [member directional_shadow_max_distance] at which point the shadow starts to fade. At [member directional_shadow_max_distance], the shadow will disappear. The default value is a balance between smooth fading and distant shadow visibility. If the camera moves fast and the [member directional_shadow_max_distance] is low, consider lowering [member directional_shadow_fade_start] below [code]0.8[/code] to make shadow transitions less noticeable. On the other hand, if you tuned [member directional_shadow_max_distance] to cover the entire scene, you can set [member directional_shadow_fade_start] to [code]1.0[/code] to prevent the shadow from fading in the distance (it will suddenly cut off instead).
</member>
<member name="directional_shadow_max_distance" type="float" setter="set_param" getter="get_param" default="100.0">
- The maximum distance for shadow splits.
+ The maximum distance for shadow splits. Increasing this value will make directional shadows visible from further away, at the cost of lower overall shadow detail and performance (since more objects need to be included in the directional shadow rendering).
</member>
<member name="directional_shadow_mode" type="int" setter="set_shadow_mode" getter="get_shadow_mode" enum="DirectionalLight3D.ShadowMode" default="2">
The light's shadow rendering algorithm. See [enum ShadowMode].
@@ -26,13 +26,13 @@
Sets the size of the directional shadow pancake. The pancake offsets the start of the shadow's camera frustum to provide a higher effective depth resolution for the shadow. However, a high pancake size can cause artifacts in the shadows of large objects that are close to the edge of the frustum. Reducing the pancake size can help. Setting the size to [code]0[/code] turns off the pancaking effect.
</member>
<member name="directional_shadow_split_1" type="float" setter="set_param" getter="get_param" default="0.1">
- The distance from camera to shadow split 1. Relative to [member directional_shadow_max_distance]. Only used when [member directional_shadow_mode] is [code]SHADOW_PARALLEL_2_SPLITS[/code] or [code]SHADOW_PARALLEL_4_SPLITS[/code].
+ The distance from camera to shadow split 1. Relative to [member directional_shadow_max_distance]. Only used when [member directional_shadow_mode] is [constant SHADOW_PARALLEL_2_SPLITS] or [constant SHADOW_PARALLEL_4_SPLITS].
</member>
<member name="directional_shadow_split_2" type="float" setter="set_param" getter="get_param" default="0.2">
- The distance from shadow split 1 to split 2. Relative to [member directional_shadow_max_distance]. Only used when [member directional_shadow_mode] is [code]SHADOW_PARALLEL_4_SPLITS[/code].
+ The distance from shadow split 1 to split 2. Relative to [member directional_shadow_max_distance]. Only used when [member directional_shadow_mode] is [constant SHADOW_PARALLEL_4_SPLITS].
</member>
<member name="directional_shadow_split_3" type="float" setter="set_param" getter="get_param" default="0.5">
- The distance from shadow split 2 to split 3. Relative to [member directional_shadow_max_distance]. Only used when [member directional_shadow_mode] is [code]SHADOW_PARALLEL_4_SPLITS[/code].
+ The distance from shadow split 2 to split 3. Relative to [member directional_shadow_max_distance]. Only used when [member directional_shadow_mode] is [constant SHADOW_PARALLEL_4_SPLITS].
</member>
<member name="shadow_bias" type="float" setter="set_param" getter="get_param" override="true" default="0.1" />
<member name="use_in_sky_only" type="bool" setter="set_sky_only" getter="is_sky_only" default="false">
diff --git a/doc/classes/Font.xml b/doc/classes/Font.xml
index 39881e9eb7..01bc2837f1 100644
--- a/doc/classes/Font.xml
+++ b/doc/classes/Font.xml
@@ -82,7 +82,7 @@
<argument index="1" name="pos" type="Vector2" />
<argument index="2" name="char" type="int" />
<argument index="3" name="next" type="int" default="0" />
- <argument index="4" name="size" type="int" default="-1" />
+ <argument index="4" name="size" type="int" default="16" />
<argument index="5" name="modulate" type="Color" default="Color(1, 1, 1, 1)" />
<argument index="6" name="outline_size" type="int" default="0" />
<argument index="7" name="outline_modulate" type="Color" default="Color(1, 1, 1, 0)" />
@@ -99,7 +99,7 @@
<argument index="3" name="align" type="int" enum="HAlign" default="0" />
<argument index="4" name="width" type="float" default="-1" />
<argument index="5" name="max_lines" type="int" default="-1" />
- <argument index="6" name="size" type="int" default="-1" />
+ <argument index="6" name="size" type="int" default="16" />
<argument index="7" name="modulate" type="Color" default="Color(1, 1, 1, 1)" />
<argument index="8" name="outline_size" type="int" default="0" />
<argument index="9" name="outline_modulate" type="Color" default="Color(1, 1, 1, 0)" />
@@ -116,7 +116,7 @@
<argument index="2" name="text" type="String" />
<argument index="3" name="align" type="int" enum="HAlign" default="0" />
<argument index="4" name="width" type="float" default="-1" />
- <argument index="5" name="size" type="int" default="-1" />
+ <argument index="5" name="size" type="int" default="16" />
<argument index="6" name="modulate" type="Color" default="Color(1, 1, 1, 1)" />
<argument index="7" name="outline_size" type="int" default="0" />
<argument index="8" name="outline_modulate" type="Color" default="Color(1, 1, 1, 0)" />
@@ -128,7 +128,7 @@
</method>
<method name="get_ascent" qualifiers="const">
<return type="float" />
- <argument index="0" name="size" type="int" default="-1" />
+ <argument index="0" name="size" type="int" default="16" />
<description>
Returns the average font ascent (number of pixels above the baseline).
[b]Note:[/b] Real ascent of the string is context-dependent and can be significantly different from the value returned by this function. Use it only as rough estimate (e.g. as the ascent of empty line).
@@ -138,7 +138,7 @@
<return type="Vector2" />
<argument index="0" name="char" type="int" />
<argument index="1" name="next" type="int" default="0" />
- <argument index="2" name="size" type="int" default="-1" />
+ <argument index="2" name="size" type="int" default="16" />
<description>
Returns the size of a character, optionally taking kerning into account if the next character is provided.
[b]Note:[/b] Do not use this function to calculate width of the string character by character, use [method get_string_size] or [TextLine] instead. The height returned is the font height (see also [method get_height]) and has no relation to the glyph height.
@@ -166,7 +166,7 @@
</method>
<method name="get_descent" qualifiers="const">
<return type="float" />
- <argument index="0" name="size" type="int" default="-1" />
+ <argument index="0" name="size" type="int" default="16" />
<description>
Returns the average font descent (number of pixels below the baseline).
[b]Note:[/b] Real descent of the string is context-dependent and can be significantly different from the value returned by this function. Use it only as rough estimate (e.g. as the descent of empty line).
@@ -174,7 +174,7 @@
</method>
<method name="get_height" qualifiers="const">
<return type="float" />
- <argument index="0" name="size" type="int" default="-1" />
+ <argument index="0" name="size" type="int" default="16" />
<description>
Returns the total average font height (ascent plus descent) in pixels.
[b]Note:[/b] Real height of the string is context-dependent and can be significantly different from the value returned by this function. Use it only as rough estimate (e.g. as the height of empty line).
@@ -184,7 +184,7 @@
<return type="Vector2" />
<argument index="0" name="text" type="String" />
<argument index="1" name="width" type="float" default="-1" />
- <argument index="2" name="size" type="int" default="-1" />
+ <argument index="2" name="size" type="int" default="16" />
<argument index="3" name="flags" type="int" default="96" />
<description>
Returns the size of a bounding box of a string broken into the lines, taking kerning and advance into account.
@@ -201,7 +201,7 @@
<method name="get_string_size" qualifiers="const">
<return type="Vector2" />
<argument index="0" name="text" type="String" />
- <argument index="1" name="size" type="int" default="-1" />
+ <argument index="1" name="size" type="int" default="16" />
<argument index="2" name="align" type="int" enum="HAlign" default="0" />
<argument index="3" name="width" type="float" default="-1" />
<argument index="4" name="flags" type="int" default="3" />
@@ -220,7 +220,7 @@
</method>
<method name="get_underline_position" qualifiers="const">
<return type="float" />
- <argument index="0" name="size" type="int" default="-1" />
+ <argument index="0" name="size" type="int" default="16" />
<description>
Return average pixel offset of the underline below the baseline.
[b]Note:[/b] Real underline position of the string is context-dependent and can be significantly different from the value returned by this function. Use it only as rough estimate.
@@ -228,7 +228,7 @@
</method>
<method name="get_underline_thickness" qualifiers="const">
<return type="float" />
- <argument index="0" name="size" type="int" default="-1" />
+ <argument index="0" name="size" type="int" default="16" />
<description>
Return average thickness of the underline.
[b]Note:[/b] Real underline thickness of the string is context-dependent and can be significantly different from the value returned by this function. Use it only as rough estimate.
@@ -272,9 +272,6 @@
</method>
</methods>
<members>
- <member name="base_size" type="int" setter="set_base_size" getter="get_base_size" default="16">
- Default font size.
- </member>
<member name="spacing_bottom" type="int" setter="set_spacing" getter="get_spacing" default="0">
Extra spacing at the bottom of the line in pixels.
</member>
diff --git a/doc/classes/Node3D.xml b/doc/classes/Node3D.xml
index 28acd77a39..2ca664e2b5 100644
--- a/doc/classes/Node3D.xml
+++ b/doc/classes/Node3D.xml
@@ -264,16 +264,28 @@
</method>
</methods>
<members>
+ <member name="basis" type="Basis" setter="set_basis" getter="get_basis">
+ Direct access to the 3x3 basis of the [Transform3D] property.
+ </member>
<member name="global_transform" type="Transform3D" setter="set_global_transform" getter="get_global_transform">
World3D space (global) [Transform3D] of this node.
</member>
<member name="position" type="Vector3" setter="set_position" getter="get_position" default="Vector3(0, 0, 0)">
Local position or translation of this node relative to the parent. This is equivalent to [code]transform.origin[/code].
</member>
+ <member name="quaternion" type="Quaternion" setter="set_quaternion" getter="get_quaternion">
+ Access to the node rotation as a [Quaternion]. This property is ideal for tweening complex rotations.
+ </member>
<member name="rotation" type="Vector3" setter="set_rotation" getter="get_rotation" default="Vector3(0, 0, 0)">
- Rotation part of the local transformation in radians, specified in terms of YXZ-Euler angles in the format (X angle, Y angle, Z angle).
+ Rotation part of the local transformation in radians, specified in terms of Euler angles. The angles construct a rotaton in the order specified by the [member rotation_order] property.
[b]Note:[/b] In the mathematical sense, rotation is a matrix and not a vector. The three Euler angles, which are the three independent parameters of the Euler-angle parametrization of the rotation matrix, are stored in a [Vector3] data structure not because the rotation is a vector, but only because [Vector3] exists as a convenient data-structure to store 3 floating-point numbers. Therefore, applying affine operations on the rotation "vector" is not meaningful.
</member>
+ <member name="rotation_edit_mode" type="int" setter="set_rotation_edit_mode" getter="get_rotation_edit_mode" enum="Node3D.RotationEditMode" default="0">
+ Specify how rotation (and scale) will be presented in the editor.
+ </member>
+ <member name="rotation_order" type="int" setter="set_rotation_order" getter="get_rotation_order" enum="Node3D.RotationOrder" default="2">
+ Specify the axis rotation order of the [member rotation] property. The final orientation is constructed by rotating the Euler angles in the order specified by this property.
+ </member>
<member name="scale" type="Vector3" setter="set_scale" getter="get_scale" default="Vector3(1, 1, 1)">
Scale part of the local transformation.
</member>
@@ -311,5 +323,23 @@
<constant name="NOTIFICATION_VISIBILITY_CHANGED" value="43">
Node3D nodes receives this notification when their visibility changes.
</constant>
+ <constant name="ROTATION_EDIT_MODE_EULER" value="0" enum="RotationEditMode">
+ </constant>
+ <constant name="ROTATION_EDIT_MODE_QUATERNION" value="1" enum="RotationEditMode">
+ </constant>
+ <constant name="ROTATION_EDIT_MODE_BASIS" value="2" enum="RotationEditMode">
+ </constant>
+ <constant name="ROTATION_ORDER_XYZ" value="0" enum="RotationOrder">
+ </constant>
+ <constant name="ROTATION_ORDER_XZY" value="1" enum="RotationOrder">
+ </constant>
+ <constant name="ROTATION_ORDER_YXZ" value="2" enum="RotationOrder">
+ </constant>
+ <constant name="ROTATION_ORDER_YZX" value="3" enum="RotationOrder">
+ </constant>
+ <constant name="ROTATION_ORDER_ZXY" value="4" enum="RotationOrder">
+ </constant>
+ <constant name="ROTATION_ORDER_ZYX" value="5" enum="RotationOrder">
+ </constant>
</constants>
</class>
diff --git a/doc/classes/PointLight2D.xml b/doc/classes/PointLight2D.xml
index 9c13179056..ec809ed0a8 100644
--- a/doc/classes/PointLight2D.xml
+++ b/doc/classes/PointLight2D.xml
@@ -8,7 +8,7 @@
</tutorials>
<members>
<member name="height" type="float" setter="set_height" getter="get_height" default="0.0">
- The height of the light. Used with 2D normal mapping.
+ The height of the light. Used with 2D normal mapping. The units are in pixels, e.g. if the height is 100, then it will illuminate an object 100 pixels away at a 45° angle to the plane.
</member>
<member name="offset" type="Vector2" setter="set_texture_offset" getter="get_texture_offset" default="Vector2(0, 0)">
The offset of the light's [member texture].
diff --git a/doc/classes/Quaternion.xml b/doc/classes/Quaternion.xml
index 7858ac732b..9c948ca21a 100644
--- a/doc/classes/Quaternion.xml
+++ b/doc/classes/Quaternion.xml
@@ -43,9 +43,8 @@
</method>
<method name="Quaternion" qualifiers="constructor">
<return type="Quaternion" />
- <argument index="0" name="euler" type="Vector3" />
+ <argument index="0" name="euler_yxz" type="Vector3" />
<description>
- Constructs a quaternion that will perform a rotation specified by Euler angles (in the YXZ convention: when decomposing, first Z, then X, and Y last), given in the vector format as (X angle, Y angle, Z angle).
</description>
</method>
<method name="Quaternion" qualifiers="constructor">
@@ -90,6 +89,16 @@
Returns the dot product of two quaternions.
</description>
</method>
+ <method name="get_angle" qualifiers="const">
+ <return type="float" />
+ <description>
+ </description>
+ </method>
+ <method name="get_axis" qualifiers="const">
+ <return type="Vector3" />
+ <description>
+ </description>
+ </method>
<method name="get_euler" qualifiers="const">
<return type="Vector3" />
<description>
diff --git a/doc/classes/TileMap.xml b/doc/classes/TileMap.xml
index e65d5b4533..4ac5718e04 100644
--- a/doc/classes/TileMap.xml
+++ b/doc/classes/TileMap.xml
@@ -204,6 +204,17 @@
- The alternative tile identifier [code]alternative_tile[/code] identifies a tile alternative the source is a [TileSetAtlasSource], and the scene for a [TileSetScenesCollectionSource].
</description>
</method>
+ <method name="set_cells_from_surrounding_terrains">
+ <return type="void" />
+ <argument index="0" name="layer" type="int" />
+ <argument index="1" name="cells" type="Vector2i[]" />
+ <argument index="2" name="terrain_set" type="int" />
+ <argument index="3" name="ignore_empty_terrains" type="bool" default="true" />
+ <description>
+ Updates all the cells in the [code]cells[/code] coordinates array and replace them by tiles that matches the surrounding cells terrains. Only cells form the given [code]terrain_set[/code] are considered.
+ If [code]ignore_empty_terrains[/code] is true, zones with no terrain defined are ignored to select the tiles.
+ </description>
+ </method>
<method name="set_layer_enabled">
<return type="void" />
<argument index="0" name="layer" type="int" />
diff --git a/doc/classes/Vector3.xml b/doc/classes/Vector3.xml
index 267a0d2e9e..078ba1e1a4 100644
--- a/doc/classes/Vector3.xml
+++ b/doc/classes/Vector3.xml
@@ -208,6 +208,17 @@
Returns the vector scaled to unit length. Equivalent to [code]v / v.length()[/code].
</description>
</method>
+ <method name="octahedron_decode" qualifiers="static">
+ <return type="Vector3" />
+ <argument index="0" name="uv" type="Vector2" />
+ <description>
+ </description>
+ </method>
+ <method name="octahedron_encode" qualifiers="const">
+ <return type="Vector2" />
+ <description>
+ </description>
+ </method>
<method name="operator !=" qualifiers="operator">
<return type="bool" />
<description>
diff --git a/editor/action_map_editor.cpp b/editor/action_map_editor.cpp
index 363d542fd5..fc7e7389d5 100644
--- a/editor/action_map_editor.cpp
+++ b/editor/action_map_editor.cpp
@@ -571,7 +571,13 @@ void InputEventConfigurationDialog::popup_and_configure(const Ref<InputEvent> &p
for (int i = 0; i < MOD_MAX; i++) {
mod_checkboxes[i]->set_pressed(false);
}
- physical_key_checkbox->set_pressed(false);
+
+ // Enable the Physical Key checkbox by default to encourage its use.
+ // Physical Key should be used for most game inputs as it allows keys to work
+ // on non-QWERTY layouts out of the box.
+ // This is especially important for WASD movement layouts.
+ physical_key_checkbox->set_pressed(true);
+
store_command_checkbox->set_pressed(true);
_set_current_device(0);
@@ -702,7 +708,7 @@ InputEventConfigurationDialog::InputEventConfigurationDialog() {
physical_key_checkbox = memnew(CheckBox);
physical_key_checkbox->set_text(TTR("Use Physical Keycode"));
- physical_key_checkbox->set_tooltip(TTR("Stores the physical position of the key on the keyboard rather than the keys value. Used for compatibility with non-latin layouts."));
+ physical_key_checkbox->set_tooltip(TTR("Stores the physical position of the key on the keyboard rather than the key's value. Used for compatibility with non-latin layouts.\nThis should generally be enabled for most game shortcuts, but not in non-game applications."));
physical_key_checkbox->connect("toggled", callable_mp(this, &InputEventConfigurationDialog::_physical_keycode_toggled));
physical_key_checkbox->hide();
additional_options_container->add_child(physical_key_checkbox);
diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp
index 7f118532c9..a85a4450a6 100644
--- a/editor/animation_track_editor.cpp
+++ b/editor/animation_track_editor.cpp
@@ -584,7 +584,8 @@ public:
} break;
case Animation::TYPE_METHOD: {
p_list->push_back(PropertyInfo(Variant::STRING_NAME, "name"));
- p_list->push_back(PropertyInfo(Variant::INT, "arg_count", PROPERTY_HINT_RANGE, "0,5,1"));
+ static_assert(VARIANT_ARG_MAX == 8, "PROPERTY_HINT_RANGE needs to be updated if VARIANT_ARG_MAX != 8");
+ p_list->push_back(PropertyInfo(Variant::INT, "arg_count", PROPERTY_HINT_RANGE, "0,8,1"));
Dictionary d = animation->track_get_key_value(track, key);
ERR_FAIL_COND(!d.has("args"));
@@ -1247,7 +1248,8 @@ public:
} break;
case Animation::TYPE_METHOD: {
p_list->push_back(PropertyInfo(Variant::STRING_NAME, "name"));
- p_list->push_back(PropertyInfo(Variant::INT, "arg_count", PROPERTY_HINT_RANGE, "0,5,1"));
+ static_assert(VARIANT_ARG_MAX == 8, "PROPERTY_HINT_RANGE needs to be updated if VARIANT_ARG_MAX != 8");
+ p_list->push_back(PropertyInfo(Variant::INT, "arg_count", PROPERTY_HINT_RANGE, "0,8,1"));
Dictionary d = animation->track_get_key_value(first_track, first_key);
ERR_FAIL_COND(!d.has("args"));
@@ -2037,7 +2039,7 @@ void AnimationTrackEdit::_notification(int p_what) {
update_mode_rect.position.y = int(get_size().height - update_icon->get_height()) / 2;
update_mode_rect.size = update_icon->get_size();
- if (animation->track_get_type(track) == Animation::TYPE_VALUE) {
+ if (!animation->track_is_compressed(track) && animation->track_get_type(track) == Animation::TYPE_VALUE) {
draw_texture(update_icon, update_mode_rect.position);
}
// Make it easier to click.
@@ -2079,7 +2081,7 @@ void AnimationTrackEdit::_notification(int p_what) {
interp_mode_rect.position.y = int(get_size().height - icon->get_height()) / 2;
interp_mode_rect.size = icon->get_size();
- if (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_BLEND_SHAPE || animation->track_get_type(track) == Animation::TYPE_POSITION_3D || animation->track_get_type(track) == Animation::TYPE_SCALE_3D || animation->track_get_type(track) == Animation::TYPE_ROTATION_3D) {
+ if (!animation->track_is_compressed(track) && (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_BLEND_SHAPE || animation->track_get_type(track) == Animation::TYPE_POSITION_3D || animation->track_get_type(track) == Animation::TYPE_SCALE_3D || animation->track_get_type(track) == Animation::TYPE_ROTATION_3D)) {
draw_texture(icon, interp_mode_rect.position);
}
// Make it easier to click.
@@ -2089,7 +2091,7 @@ void AnimationTrackEdit::_notification(int p_what) {
ofs += icon->get_width() + hsep;
interp_mode_rect.size.x += hsep;
- if (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_BLEND_SHAPE || animation->track_get_type(track) == Animation::TYPE_POSITION_3D || animation->track_get_type(track) == Animation::TYPE_SCALE_3D || animation->track_get_type(track) == Animation::TYPE_ROTATION_3D) {
+ if (!animation->track_is_compressed(track) && (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_BLEND_SHAPE || animation->track_get_type(track) == Animation::TYPE_POSITION_3D || animation->track_get_type(track) == Animation::TYPE_SCALE_3D || animation->track_get_type(track) == Animation::TYPE_ROTATION_3D)) {
draw_texture(down_icon, Vector2(ofs, int(get_size().height - down_icon->get_height()) / 2));
interp_mode_rect.size.x += down_icon->get_width();
} else {
@@ -2112,7 +2114,7 @@ void AnimationTrackEdit::_notification(int p_what) {
loop_mode_rect.position.y = int(get_size().height - icon->get_height()) / 2;
loop_mode_rect.size = icon->get_size();
- if (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_BLEND_SHAPE || animation->track_get_type(track) == Animation::TYPE_POSITION_3D || animation->track_get_type(track) == Animation::TYPE_SCALE_3D || animation->track_get_type(track) == Animation::TYPE_ROTATION_3D) {
+ if (!animation->track_is_compressed(track) && (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_BLEND_SHAPE || animation->track_get_type(track) == Animation::TYPE_POSITION_3D || animation->track_get_type(track) == Animation::TYPE_SCALE_3D || animation->track_get_type(track) == Animation::TYPE_ROTATION_3D)) {
draw_texture(icon, loop_mode_rect.position);
}
@@ -2122,7 +2124,7 @@ void AnimationTrackEdit::_notification(int p_what) {
ofs += icon->get_width() + hsep;
loop_mode_rect.size.x += hsep;
- if (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_BLEND_SHAPE || animation->track_get_type(track) == Animation::TYPE_POSITION_3D || animation->track_get_type(track) == Animation::TYPE_SCALE_3D || animation->track_get_type(track) == Animation::TYPE_ROTATION_3D) {
+ if (!animation->track_is_compressed(track) && (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_BLEND_SHAPE || animation->track_get_type(track) == Animation::TYPE_POSITION_3D || animation->track_get_type(track) == Animation::TYPE_SCALE_3D || animation->track_get_type(track) == Animation::TYPE_ROTATION_3D)) {
draw_texture(down_icon, Vector2(ofs, int(get_size().height - down_icon->get_height()) / 2));
loop_mode_rect.size.x += down_icon->get_width();
} else {
@@ -2137,7 +2139,7 @@ void AnimationTrackEdit::_notification(int p_what) {
{
// Erase.
- Ref<Texture2D> icon = get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"));
+ Ref<Texture2D> icon = get_theme_icon(animation->track_is_compressed(track) ? SNAME("Lock") : SNAME("Remove"), SNAME("EditorIcons"));
remove_rect.position.x = ofs + ((get_size().width - ofs) - icon->get_width()) / 2;
remove_rect.position.y = int(get_size().height - icon->get_height()) / 2;
@@ -2709,60 +2711,63 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
// Check keyframes.
- float scale = timeline->get_zoom_scale();
- int limit = timeline->get_name_limit();
- int limit_end = get_size().width - timeline->get_buttons_width();
- // Left Border including space occupied by keyframes on t=0.
- int limit_start_hitbox = limit - type_icon->get_width();
-
- if (pos.x >= limit_start_hitbox && pos.x <= limit_end) {
- int key_idx = -1;
- float key_distance = 1e20;
+ if (!animation->track_is_compressed(track)) { // Selecting compressed keyframes for editing is not possible.
- // Select should happen in the opposite order of drawing for more accurate overlap select.
- for (int i = animation->track_get_key_count(track) - 1; i >= 0; i--) {
- Rect2 rect = get_key_rect(i, scale);
- float offset = animation->track_get_key_time(track, i) - timeline->get_value();
- offset = offset * scale + limit;
- rect.position.x += offset;
-
- if (rect.has_point(pos)) {
- if (is_key_selectable_by_distance()) {
- float distance = ABS(offset - pos.x);
- if (key_idx == -1 || distance < key_distance) {
+ float scale = timeline->get_zoom_scale();
+ int limit = timeline->get_name_limit();
+ int limit_end = get_size().width - timeline->get_buttons_width();
+ // Left Border including space occupied by keyframes on t=0.
+ int limit_start_hitbox = limit - type_icon->get_width();
+
+ if (pos.x >= limit_start_hitbox && pos.x <= limit_end) {
+ int key_idx = -1;
+ float key_distance = 1e20;
+
+ // Select should happen in the opposite order of drawing for more accurate overlap select.
+ for (int i = animation->track_get_key_count(track) - 1; i >= 0; i--) {
+ Rect2 rect = get_key_rect(i, scale);
+ float offset = animation->track_get_key_time(track, i) - timeline->get_value();
+ offset = offset * scale + limit;
+ rect.position.x += offset;
+
+ if (rect.has_point(pos)) {
+ if (is_key_selectable_by_distance()) {
+ float distance = ABS(offset - pos.x);
+ if (key_idx == -1 || distance < key_distance) {
+ key_idx = i;
+ key_distance = distance;
+ }
+ } else {
+ // First one does it.
key_idx = i;
- key_distance = distance;
+ break;
}
- } else {
- // First one does it.
- key_idx = i;
- break;
}
}
- }
- if (key_idx != -1) {
- if (mb->is_command_pressed() || mb->is_shift_pressed()) {
- if (editor->is_key_selected(track, key_idx)) {
- emit_signal(SNAME("deselect_key"), key_idx);
+ if (key_idx != -1) {
+ if (mb->is_command_pressed() || mb->is_shift_pressed()) {
+ if (editor->is_key_selected(track, key_idx)) {
+ emit_signal(SNAME("deselect_key"), key_idx);
+ } else {
+ emit_signal(SNAME("select_key"), key_idx, false);
+ moving_selection_attempt = true;
+ select_single_attempt = -1;
+ moving_selection_from_ofs = (mb->get_position().x - limit) / timeline->get_zoom_scale();
+ }
} else {
- emit_signal(SNAME("select_key"), key_idx, false);
+ if (!editor->is_key_selected(track, key_idx)) {
+ emit_signal(SNAME("select_key"), key_idx, true);
+ select_single_attempt = -1;
+ } else {
+ select_single_attempt = key_idx;
+ }
+
moving_selection_attempt = true;
- select_single_attempt = -1;
moving_selection_from_ofs = (mb->get_position().x - limit) / timeline->get_zoom_scale();
}
- } else {
- if (!editor->is_key_selected(track, key_idx)) {
- emit_signal(SNAME("select_key"), key_idx, true);
- select_single_attempt = -1;
- } else {
- select_single_attempt = key_idx;
- }
-
- moving_selection_attempt = true;
- moving_selection_from_ofs = (mb->get_position().x - limit) / timeline->get_zoom_scale();
+ accept_event();
}
- accept_event();
}
}
}
@@ -2994,6 +2999,9 @@ void AnimationTrackEdit::set_in_group(bool p_enable) {
}
void AnimationTrackEdit::append_to_selection(const Rect2 &p_box, bool p_deselection) {
+ if (animation->track_is_compressed(track)) {
+ return; // Compressed keyframes can't be edited
+ }
// Left Border including space occupied by keyframes on t=0.
int limit_start_hitbox = timeline->get_name_limit() - type_icon->get_width();
Rect2 select_rect(limit_start_hitbox, 0, get_size().width - timeline->get_name_limit() - timeline->get_buttons_width(), get_size().height);
@@ -3337,6 +3345,10 @@ void AnimationTrackEditor::_timeline_changed(float p_new_pos, bool p_drag, bool
}
void AnimationTrackEditor::_track_remove_request(int p_track) {
+ if (animation->track_is_compressed(p_track)) {
+ EditorNode::get_singleton()->show_warning(TTR("Compressed tracks can't be edited or removed. Re-import the animation with compression disabled in order to edit."));
+ return;
+ }
int idx = p_track;
if (idx >= 0 && idx < animation->get_track_count()) {
undo_redo->create_action(TTR("Remove Anim Track"));
@@ -3487,7 +3499,7 @@ void AnimationTrackEditor::_query_insert(const InsertData &p_id) {
for (const InsertData &E : insert_data) {
// Prevent insertion of multiple tracks.
- if (E.path == p_id.path) {
+ if (E.path == p_id.path && E.type == p_id.type) {
return; // Already inserted a track this frame.
}
}
@@ -3537,7 +3549,11 @@ void AnimationTrackEditor::_insert_track(bool p_create_reset, bool p_create_bezi
}
}
-void AnimationTrackEditor::insert_transform_key(Node3D *p_node, const String &p_sub, const Transform3D &p_xform) {
+void AnimationTrackEditor::insert_transform_key(Node3D *p_node, const String &p_sub, const Animation::TrackType p_type, const Variant p_value) {
+ ERR_FAIL_COND(!root);
+ ERR_FAIL_COND_MSG(
+ (p_type != Animation::TYPE_POSITION_3D && p_type != Animation::TYPE_ROTATION_3D && p_type != Animation::TYPE_SCALE_3D),
+ "Track type must be Position/Rotation/Scale 3D.");
if (!keying) {
return;
}
@@ -3545,7 +3561,6 @@ void AnimationTrackEditor::insert_transform_key(Node3D *p_node, const String &p_
return;
}
- ERR_FAIL_COND(!root);
// Let's build a node path.
String path = root->get_path_to(p_node);
if (p_sub != "") {
@@ -3554,24 +3569,16 @@ void AnimationTrackEditor::insert_transform_key(Node3D *p_node, const String &p_
NodePath np = path;
- int position_idx = -1;
- int rotation_idx = -1;
- int scale_idx = -1;
+ int track_idx = -1;
for (int i = 0; i < animation->get_track_count(); i++) {
if (animation->track_get_path(i) != np) {
continue;
}
-
- if (animation->track_get_type(i) == Animation::TYPE_POSITION_3D) {
- position_idx = i;
- }
- if (animation->track_get_type(i) == Animation::TYPE_ROTATION_3D) {
- rotation_idx = i;
- }
- if (animation->track_get_type(i) == Animation::TYPE_SCALE_3D) {
- scale_idx = i;
+ if (animation->track_get_type(i) != p_type) {
+ continue;
}
+ track_idx = i;
}
InsertData id;
@@ -3579,48 +3586,30 @@ void AnimationTrackEditor::insert_transform_key(Node3D *p_node, const String &p_
// TRANSLATORS: This describes the target of new animation track, will be inserted into another string.
id.query = vformat(TTR("node '%s'"), p_node->get_name());
id.advance = false;
-
- {
- id.track_idx = position_idx;
- id.value = p_xform.origin;
- id.type = Animation::TYPE_POSITION_3D;
- _query_insert(id);
- }
- {
- id.track_idx = rotation_idx;
- id.value = p_xform.basis.get_rotation_quaternion();
- id.type = Animation::TYPE_ROTATION_3D;
- _query_insert(id);
- }
- {
- id.track_idx = scale_idx;
- id.value = p_xform.basis.get_scale();
- id.type = Animation::TYPE_SCALE_3D;
- _query_insert(id);
- }
+ id.track_idx = track_idx;
+ id.value = p_value;
+ id.type = p_type;
+ _query_insert(id);
}
-bool AnimationTrackEditor::has_transform_track(Node3D *p_node, const String &p_sub) {
+bool AnimationTrackEditor::has_track(Node3D *p_node, const String &p_sub, const Animation::TrackType p_type) {
+ ERR_FAIL_COND_V(!root, false);
if (!keying) {
return false;
}
if (!animation.is_valid()) {
return false;
}
- if (!root) {
- return false;
- }
- //let's build a node path
+ // Let's build a node path.
String path = root->get_path_to(p_node);
if (p_sub != "") {
path += ":" + p_sub;
}
- int track_id = animation->find_track(path);
+
+ int track_id = animation->find_track(path, p_type);
if (track_id >= 0) {
- if (animation->track_get_type(track_id) == Animation::TYPE_POSITION_3D || animation->track_get_type(track_id) == Animation::TYPE_ROTATION_3D || animation->track_get_type(track_id) == Animation::TYPE_SCALE_3D) {
- return true;
- }
+ return true;
}
return false;
}
diff --git a/editor/animation_track_editor.h b/editor/animation_track_editor.h
index 2555901557..05cf91de1d 100644
--- a/editor/animation_track_editor.h
+++ b/editor/animation_track_editor.h
@@ -527,8 +527,8 @@ public:
void set_anim_pos(float p_pos);
void insert_node_value_key(Node *p_node, const String &p_property, const Variant &p_value, bool p_only_if_exists = false);
void insert_value_key(const String &p_property, const Variant &p_value, bool p_advance);
- void insert_transform_key(Node3D *p_node, const String &p_sub, const Transform3D &p_xform);
- bool has_transform_track(Node3D *p_node, const String &p_sub);
+ void insert_transform_key(Node3D *p_node, const String &p_sub, const Animation::TrackType p_type, const Variant p_value);
+ bool has_track(Node3D *p_node, const String &p_sub, const Animation::TrackType p_type);
void make_insert_queue();
void commit_insert_queue();
diff --git a/editor/animation_track_editor_plugins.cpp b/editor/animation_track_editor_plugins.cpp
index 4ee8b991e4..70ba806c37 100644
--- a/editor/animation_track_editor_plugins.cpp
+++ b/editor/animation_track_editor_plugins.cpp
@@ -419,7 +419,7 @@ Rect2 AnimationTrackEditSpriteFrame::get_key_rect(int p_index, float p_pixels_se
// Go through other track to find if animation is set
String animation_path = get_animation()->track_get_path(get_track());
animation_path = animation_path.replace(":frame", ":animation");
- int animation_track = get_animation()->find_track(animation_path);
+ int animation_track = get_animation()->find_track(animation_path, get_animation()->track_get_type(get_track()));
float track_time = get_animation()->track_get_key_time(get_track(), p_index);
int animaiton_index = get_animation()->track_find_key(animation_track, track_time);
animation = get_animation()->track_get_key_value(animation_track, animaiton_index);
@@ -511,7 +511,7 @@ void AnimationTrackEditSpriteFrame::draw_key(int p_index, float p_pixels_sec, in
// Go through other track to find if animation is set
String animation_path = get_animation()->track_get_path(get_track());
animation_path = animation_path.replace(":frame", ":animation");
- int animation_track = get_animation()->find_track(animation_path);
+ int animation_track = get_animation()->find_track(animation_path, get_animation()->track_get_type(get_track()));
float track_time = get_animation()->track_get_key_time(get_track(), p_index);
int animaiton_index = get_animation()->track_find_key(animation_track, track_time);
animation = get_animation()->track_get_key_value(animation_track, animaiton_index);
diff --git a/editor/editor_fonts.cpp b/editor/editor_fonts.cpp
index e5d6315ef7..a644e3e991 100644
--- a/editor/editor_fonts.cpp
+++ b/editor/editor_fonts.cpp
@@ -67,7 +67,7 @@
m_name->add_data(FontJapanese); \
m_name->add_data(FontFallback);
-#define MAKE_DEFAULT_FONT(m_name, m_variations, m_base_size) \
+#define MAKE_DEFAULT_FONT(m_name, m_variations) \
Ref<Font> m_name; \
m_name.instantiate(); \
if (CustomFont.is_valid()) { \
@@ -89,12 +89,11 @@
} \
m_name->set_variation_coordinates(variations); \
} \
- m_name->set_base_size(m_base_size); \
m_name->set_spacing(TextServer::SPACING_TOP, -EDSCALE); \
m_name->set_spacing(TextServer::SPACING_BOTTOM, -EDSCALE); \
MAKE_FALLBACKS(m_name);
-#define MAKE_BOLD_FONT(m_name, m_variations, m_base_size) \
+#define MAKE_BOLD_FONT(m_name, m_variations) \
Ref<Font> m_name; \
m_name.instantiate(); \
if (CustomFontBold.is_valid()) { \
@@ -116,12 +115,11 @@
} \
m_name->set_variation_coordinates(variations); \
} \
- m_name->set_base_size(m_base_size); \
m_name->set_spacing(TextServer::SPACING_TOP, -EDSCALE); \
m_name->set_spacing(TextServer::SPACING_BOTTOM, -EDSCALE); \
MAKE_FALLBACKS_BOLD(m_name);
-#define MAKE_SOURCE_FONT(m_name, m_variations, m_base_size) \
+#define MAKE_SOURCE_FONT(m_name, m_variations) \
Ref<Font> m_name; \
m_name.instantiate(); \
if (CustomFontSource.is_valid()) { \
@@ -143,7 +141,6 @@
} \
m_name->set_variation_coordinates(variations); \
} \
- m_name->set_base_size(m_base_size); \
m_name->set_spacing(TextServer::SPACING_TOP, -EDSCALE); \
m_name->set_spacing(TextServer::SPACING_BOTTOM, -EDSCALE); \
MAKE_FALLBACKS(m_name);
@@ -275,7 +272,7 @@ void editor_register_fonts(Ref<Theme> p_theme) {
Ref<FontData> dfmono = load_cached_internal_font(_font_Hack_Regular, _font_Hack_Regular_size, font_hinting, font_antialiased, true);
// Default font
- MAKE_DEFAULT_FONT(df, String(), default_font_size);
+ MAKE_DEFAULT_FONT(df, String());
p_theme->set_default_theme_font(df); // Default theme font
p_theme->set_default_theme_font_size(default_font_size);
@@ -283,7 +280,7 @@ void editor_register_fonts(Ref<Theme> p_theme) {
p_theme->set_font("main", "EditorFonts", df);
// Bold font
- MAKE_BOLD_FONT(df_bold, String(), default_font_size);
+ MAKE_BOLD_FONT(df_bold, String());
p_theme->set_font_size("bold_size", "EditorFonts", default_font_size);
p_theme->set_font("bold", "EditorFonts", df_bold);
@@ -310,7 +307,7 @@ void editor_register_fonts(Ref<Theme> p_theme) {
// Documentation fonts
String code_font_custom_variations = EditorSettings::get_singleton()->get("interface/editor/code_font_custom_variations");
- MAKE_SOURCE_FONT(df_code, code_font_custom_variations, default_font_size);
+ MAKE_SOURCE_FONT(df_code, code_font_custom_variations);
p_theme->set_font_size("doc_size", "EditorFonts", int(EDITOR_GET("text_editor/help/help_font_size")) * EDSCALE);
p_theme->set_font("doc", "EditorFonts", df);
p_theme->set_font_size("doc_bold_size", "EditorFonts", int(EDITOR_GET("text_editor/help/help_font_size")) * EDSCALE);
diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp
index 2d4a3ac788..6a36304ba4 100644
--- a/editor/editor_properties.cpp
+++ b/editor/editor_properties.cpp
@@ -884,10 +884,11 @@ public:
flag_rects.push_back(rect2);
Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label"));
+ int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label"));
Vector2 offset;
offset.y = rect2.size.y * 0.75;
- draw_string(font, rect2.position + offset, itos(layer_index + 1), HALIGN_CENTER, rect2.size.x, -1, on ? text_color_on : text_color);
+ draw_string(font, rect2.position + offset, itos(layer_index + 1), HALIGN_CENTER, rect2.size.x, font_size, on ? text_color_on : text_color);
ofs.x += bsize + 1;
diff --git a/editor/icons/NewKey.svg b/editor/icons/NewKey.svg
new file mode 100644
index 0000000000..fc8507e6c4
--- /dev/null
+++ b/editor/icons/NewKey.svg
@@ -0,0 +1 @@
+<svg enable-background="new 0 0 16 16" height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><g fill="#e0e0e0" fill-opacity=".9961"><path d="m13 9h-2v2h-2v2h2v2h2v-2h2v-2h-2z"/><path d="m10 9.723c-.596-.347-1-.985-1-1.723 0-1.104.896-2 2-2s2 .896 2 2h1v2h.445c.344-.591.555-1.268.555-2 0-2.209-1.791-4-4-4-1.822.002-3.414 1.235-3.869 3h-6.131v2h1v2h3v-2h2.133c.16.62.466 1.169.867 1.627v-.627h2z"/></g></svg>
diff --git a/editor/import/resource_importer_scene.cpp b/editor/import/resource_importer_scene.cpp
index 319e5ee25f..bebf05d481 100644
--- a/editor/import/resource_importer_scene.cpp
+++ b/editor/import/resource_importer_scene.cpp
@@ -950,6 +950,13 @@ Node *ResourceImporterScene::_post_fix_node(Node *p_node, Node *p_root, Map<Ref<
}
}
}
+
+ bool use_compression = node_settings["compression/enabled"];
+ int anim_compression_page_size = node_settings["compression/page_size"];
+
+ if (use_compression) {
+ _compress_animations(ap, anim_compression_page_size);
+ }
}
return p_node;
@@ -1149,6 +1156,15 @@ void ResourceImporterScene::_optimize_animations(AnimationPlayer *anim, float p_
}
}
+void ResourceImporterScene::_compress_animations(AnimationPlayer *anim, int p_page_size_kb) {
+ List<StringName> anim_names;
+ anim->get_animation_list(&anim_names);
+ for (const StringName &E : anim_names) {
+ Ref<Animation> a = anim->get_animation(E);
+ a->compress(p_page_size_kb * 1024);
+ }
+}
+
void ResourceImporterScene::get_internal_import_options(InternalImportCategory p_category, List<ImportOption> *r_options) const {
switch (p_category) {
case INTERNAL_IMPORT_CATEGORY_NODE: {
@@ -1212,6 +1228,8 @@ void ResourceImporterScene::get_internal_import_options(InternalImportCategory p
r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "optimizer/max_linear_error"), 0.05));
r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "optimizer/max_angular_error"), 0.01));
r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "optimizer/max_angle"), 22));
+ r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "compression/enabled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false));
+ r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "compression/page_size", PROPERTY_HINT_RANGE, "4,512,1,suffix:kb"), 8));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "import_tracks/position", PROPERTY_HINT_ENUM, "IfPresent,IfPresentForAll,Never"), 1));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "import_tracks/rotation", PROPERTY_HINT_ENUM, "IfPresent,IfPresentForAll,Never"), 1));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "import_tracks/scale", PROPERTY_HINT_ENUM, "IfPresent,IfPresentForAll,Never"), 1));
@@ -1320,13 +1338,16 @@ bool ResourceImporterScene::get_internal_option_visibility(InternalImportCategor
}
} break;
case INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE: {
- if (p_option.begins_with("animation/optimizer/") && p_option != "animation/optimizer/enabled" && !bool(p_options["animation/optimizer/enabled"])) {
+ if (p_option.begins_with("optimizer/") && p_option != "optimizer/enabled" && !bool(p_options["optimizer/enabled"])) {
+ return false;
+ }
+ if (p_option.begins_with("compression/") && p_option != "compression/enabled" && !bool(p_options["compression/enabled"])) {
return false;
}
- if (p_option.begins_with("animation/slice_")) {
- int max_slice = p_options["animation/slices/amount"];
- int slice = p_option.get_slice("/", 1).get_slice("_", 1).to_int() - 1;
+ if (p_option.begins_with("slice_")) {
+ int max_slice = p_options["slices/amount"];
+ int slice = p_option.get_slice("_", 1).to_int() - 1;
if (slice >= max_slice) {
return false;
}
diff --git a/editor/import/resource_importer_scene.h b/editor/import/resource_importer_scene.h
index e1e7046be5..a192921966 100644
--- a/editor/import/resource_importer_scene.h
+++ b/editor/import/resource_importer_scene.h
@@ -261,6 +261,7 @@ public:
Ref<Animation> _save_animation_to_file(Ref<Animation> anim, bool p_save_to_file, String p_save_to_path, bool p_keep_custom_tracks);
void _create_clips(AnimationPlayer *anim, const Array &p_clips, bool p_bake_all);
void _optimize_animations(AnimationPlayer *anim, float p_max_lin_error, float p_max_ang_error, float p_max_angle);
+ void _compress_animations(AnimationPlayer *anim, int p_page_size_kb);
Node *pre_import(const String &p_source_file);
virtual Error import(const String &p_source_file, const String &p_save_path, const Map<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override;
diff --git a/editor/inspector_dock.cpp b/editor/inspector_dock.cpp
index 59d0b92ba0..5622d0b145 100644
--- a/editor/inspector_dock.cpp
+++ b/editor/inspector_dock.cpp
@@ -391,7 +391,9 @@ void InspectorDock::_transform_keyed(Object *sp, const String &p_sub, const Tran
if (!s) {
return;
}
- AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_transform_key(s, p_sub, p_key);
+ AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_transform_key(s, p_sub, Animation::TYPE_POSITION_3D, p_key.origin);
+ AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_transform_key(s, p_sub, Animation::TYPE_ROTATION_3D, p_key.basis.get_rotation_quaternion());
+ AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_transform_key(s, p_sub, Animation::TYPE_SCALE_3D, p_key.basis.get_scale());
}
void InspectorDock::_warning_pressed() {
diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp
index 8935f715e6..5544d9261e 100644
--- a/editor/plugins/canvas_item_editor_plugin.cpp
+++ b/editor/plugins/canvas_item_editor_plugin.cpp
@@ -3565,7 +3565,7 @@ void CanvasItemEditor::_draw_hover() {
Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label"));
int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label"));
- Size2 node_name_size = font->get_string_size(node_name);
+ Size2 node_name_size = font->get_string_size(node_name, font_size);
Size2 item_size = Size2(node_icon->get_size().x + 4 + node_name_size.x, MAX(node_icon->get_size().y, node_name_size.y - 3));
Point2 pos = transform.xform(hovering_results[i].position) - Point2(0, item_size.y) + (Point2(node_icon->get_size().x, -node_icon->get_size().y) / 4);
@@ -4777,10 +4777,6 @@ void CanvasItemEditor::_popup_callback(int p_op) {
if (key_pos) {
ctrl->set_position(Point2());
}
- /*
- if (key_scale)
- AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(ctrl,"rect/size",ctrl->get_size());
- */
}
}
diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp
index 0096152cfe..af58d8bbdc 100644
--- a/editor/plugins/node_3d_editor_plugin.cpp
+++ b/editor/plugins/node_3d_editor_plugin.cpp
@@ -252,6 +252,14 @@ void ViewportRotationControl::set_viewport(Node3DEditorViewport *p_viewport) {
viewport = p_viewport;
}
+void Node3DEditorViewport::_view_settings_confirmed(real_t p_interp_delta) {
+ // Set FOV override multiplier back to the default, so that the FOV
+ // setting specified in the View menu is correctly applied.
+ cursor.fov_scale = 1.0;
+
+ _update_camera(p_interp_delta);
+}
+
void Node3DEditorViewport::_update_camera(real_t p_interp_delta) {
bool is_orthogonal = camera->get_projection() == Camera3D::PROJECTION_ORTHOGONAL;
@@ -318,6 +326,8 @@ void Node3DEditorViewport::_update_camera(real_t p_interp_delta) {
equal = false;
} else if (!Math::is_equal_approx(old_camera_cursor.distance, camera_cursor.distance, tolerance)) {
equal = false;
+ } else if (!Math::is_equal_approx(old_camera_cursor.fov_scale, camera_cursor.fov_scale, tolerance)) {
+ equal = false;
}
if (!equal || p_interp_delta == 0 || is_orthogonal != orthogonal) {
@@ -383,7 +393,7 @@ float Node3DEditorViewport::get_zfar() const {
}
float Node3DEditorViewport::get_fov() const {
- return CLAMP(spatial_editor->get_fov(), MIN_FOV, MAX_FOV);
+ return CLAMP(spatial_editor->get_fov() * cursor.fov_scale, MIN_FOV, MAX_FOV);
}
Transform3D Node3DEditorViewport::_get_camera_transform() const {
@@ -1327,17 +1337,25 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
const real_t zoom_factor = 1 + (ZOOM_FREELOOK_MULTIPLIER - 1) * b->get_factor();
switch (b->get_button_index()) {
case MOUSE_BUTTON_WHEEL_UP: {
- if (is_freelook_active()) {
- scale_freelook_speed(zoom_factor);
+ if (b->is_alt_pressed()) {
+ scale_fov(-0.05);
} else {
- scale_cursor_distance(1.0 / zoom_factor);
+ if (is_freelook_active()) {
+ scale_freelook_speed(zoom_factor);
+ } else {
+ scale_cursor_distance(1.0 / zoom_factor);
+ }
}
} break;
case MOUSE_BUTTON_WHEEL_DOWN: {
- if (is_freelook_active()) {
- scale_freelook_speed(1.0 / zoom_factor);
+ if (b->is_alt_pressed()) {
+ scale_fov(0.05);
} else {
- scale_cursor_distance(zoom_factor);
+ if (is_freelook_active()) {
+ scale_freelook_speed(1.0 / zoom_factor);
+ } else {
+ scale_cursor_distance(zoom_factor);
+ }
}
} break;
case MOUSE_BUTTON_RIGHT: {
@@ -2305,6 +2323,18 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
emit_signal(SNAME("toggle_maximize_view"), this);
}
}
+
+ if (ED_IS_SHORTCUT("spatial_editor/decrease_fov", p_event)) {
+ scale_fov(-0.05);
+ }
+
+ if (ED_IS_SHORTCUT("spatial_editor/increase_fov", p_event)) {
+ scale_fov(0.05);
+ }
+
+ if (ED_IS_SHORTCUT("spatial_editor/reset_fov", p_event)) {
+ reset_fov();
+ }
}
// freelook uses most of the useful shortcuts, like save, so its ok
@@ -2472,6 +2502,16 @@ void Node3DEditorViewport::set_freelook_active(bool active_now) {
freelook_active = active_now;
}
+void Node3DEditorViewport::scale_fov(real_t p_fov_offset) {
+ cursor.fov_scale = CLAMP(cursor.fov_scale + p_fov_offset, 0.1, 2.5);
+ surface->update();
+}
+
+void Node3DEditorViewport::reset_fov() {
+ cursor.fov_scale = 1.0;
+ surface->update();
+}
+
void Node3DEditorViewport::scale_cursor_distance(real_t scale) {
real_t min_distance = MAX(camera->get_near() * 4, ZOOM_FREELOOK_MIN);
real_t max_distance = MIN(camera->get_far() / 4, ZOOM_FREELOOK_MAX);
@@ -3213,7 +3253,7 @@ void Node3DEditorViewport::_menu_option(int p_option) {
continue;
}
- undo_redo->add_do_method(sp, "set_rotation", camera_transform.basis.get_rotation());
+ undo_redo->add_do_method(sp, "set_rotation", camera_transform.basis.get_euler_normalized());
undo_redo->add_undo_method(sp, "set_rotation", sp->get_rotation());
}
undo_redo->commit_action();
@@ -7343,6 +7383,9 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) {
ED_SHORTCUT("spatial_editor/align_transform_with_view", TTR("Align Transform with View"), KEY_MASK_ALT + KEY_MASK_CMD + KEY_M);
ED_SHORTCUT("spatial_editor/align_rotation_with_view", TTR("Align Rotation with View"), KEY_MASK_ALT + KEY_MASK_CMD + KEY_F);
ED_SHORTCUT("spatial_editor/freelook_toggle", TTR("Toggle Freelook"), KEY_MASK_SHIFT + KEY_F);
+ ED_SHORTCUT("spatial_editor/decrease_fov", TTR("Decrease Field of View"), KEY_MASK_CMD + KEY_EQUAL); // Usually direct access key for `KEY_PLUS`.
+ ED_SHORTCUT("spatial_editor/increase_fov", TTR("Increase Field of View"), KEY_MASK_CMD + KEY_MINUS);
+ ED_SHORTCUT("spatial_editor/reset_fov", TTR("Reset Field of View to Default"), KEY_MASK_CMD + KEY_0);
PopupMenu *p;
@@ -7490,7 +7533,7 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) {
settings_vbc->add_margin_child(TTR("View Z-Far:"), settings_zfar);
for (uint32_t i = 0; i < VIEWPORTS_COUNT; ++i) {
- settings_dialog->connect("confirmed", callable_mp(viewports[i], &Node3DEditorViewport::_update_camera), varray(0.0));
+ settings_dialog->connect("confirmed", callable_mp(viewports[i], &Node3DEditorViewport::_view_settings_confirmed), varray(0.0));
}
/* XFORM DIALOG */
diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h
index 5290a8804b..e1318f52a8 100644
--- a/editor/plugins/node_3d_editor_plugin.h
+++ b/editor/plugins/node_3d_editor_plugin.h
@@ -315,7 +315,7 @@ private:
struct Cursor {
Vector3 pos;
- real_t x_rot, y_rot, distance;
+ real_t x_rot, y_rot, distance, fov_scale;
Vector3 eye_pos; // Used in freelook mode
bool region_select;
Point2 region_begin, region_end;
@@ -325,6 +325,7 @@ private:
x_rot = 0.5;
y_rot = -0.5;
distance = 4;
+ fov_scale = 1.0;
region_select = false;
}
};
@@ -333,6 +334,8 @@ private:
Cursor cursor; // Immediate cursor
Cursor camera_cursor; // That one may be interpolated (don't modify this one except for smoothing purposes)
+ void scale_fov(real_t p_fov_offset);
+ void reset_fov();
void scale_cursor_distance(real_t scale);
void set_freelook_active(bool active_now);
@@ -349,6 +352,7 @@ private:
void set_message(String p_message, float p_time = 5);
+ void _view_settings_confirmed(real_t p_interp_delta);
void _update_camera(real_t p_interp_delta);
Transform3D to_camera_transform(const Cursor &p_cursor) const;
void _draw();
diff --git a/editor/plugins/skeleton_3d_editor_plugin.cpp b/editor/plugins/skeleton_3d_editor_plugin.cpp
index 708eaf2c46..0b8a56503c 100644
--- a/editor/plugins/skeleton_3d_editor_plugin.cpp
+++ b/editor/plugins/skeleton_3d_editor_plugin.cpp
@@ -54,6 +54,7 @@ void BoneTransformEditor::create_editors() {
enabled_checkbox = memnew(EditorPropertyCheck());
enabled_checkbox->set_label("Pose Enabled");
+ enabled_checkbox->set_selectable(false);
enabled_checkbox->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed));
section->get_vbox()->add_child(enabled_checkbox);
@@ -61,21 +62,27 @@ void BoneTransformEditor::create_editors() {
position_property = memnew(EditorPropertyVector3());
position_property->setup(-10000, 10000, 0.001f, true);
position_property->set_label("Position");
+ position_property->set_selectable(false);
position_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed));
+ position_property->connect("property_keyed", callable_mp(this, &BoneTransformEditor::_property_keyed));
section->get_vbox()->add_child(position_property);
// Rotation property.
rotation_property = memnew(EditorPropertyQuaternion());
rotation_property->setup(-10000, 10000, 0.001f, true);
rotation_property->set_label("Rotation");
+ rotation_property->set_selectable(false);
rotation_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed));
+ rotation_property->connect("property_keyed", callable_mp(this, &BoneTransformEditor::_property_keyed));
section->get_vbox()->add_child(rotation_property);
// Scale property.
scale_property = memnew(EditorPropertyVector3());
scale_property->setup(-10000, 10000, 0.001f, true);
scale_property->set_label("Scale");
+ scale_property->set_selectable(false);
scale_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed));
+ scale_property->connect("property_keyed", callable_mp(this, &BoneTransformEditor::_property_keyed));
section->get_vbox()->add_child(scale_property);
// Transform/Matrix section.
@@ -87,6 +94,7 @@ void BoneTransformEditor::create_editors() {
rest_matrix = memnew(EditorPropertyTransform3D());
rest_matrix->setup(-10000, 10000, 0.001f, true);
rest_matrix->set_label("Transform");
+ rest_matrix->set_selectable(false);
rest_section->get_vbox()->add_child(rest_matrix);
}
@@ -116,6 +124,12 @@ BoneTransformEditor::BoneTransformEditor(Skeleton3D *p_skeleton) :
undo_redo = EditorNode::get_undo_redo();
}
+void BoneTransformEditor::set_keyable(const bool p_keyable) {
+ position_property->set_keying(p_keyable);
+ rotation_property->set_keying(p_keyable);
+ scale_property->set_keying(p_keyable);
+}
+
void BoneTransformEditor::set_target(const String &p_prop) {
enabled_checkbox->set_object_and_property(skeleton, p_prop + "enabled");
enabled_checkbox->update_property();
@@ -133,6 +147,23 @@ void BoneTransformEditor::set_target(const String &p_prop) {
rest_matrix->update_property();
}
+void BoneTransformEditor::_property_keyed(const String &p_path, bool p_advance) {
+ AnimationTrackEditor *te = AnimationPlayerEditor::get_singleton()->get_track_editor();
+ PackedStringArray split = p_path.split("/");
+ if (split.size() == 3 && split[0] == "bones") {
+ int bone_idx = split[1].to_int();
+ if (split[2] == "position") {
+ te->insert_transform_key(skeleton, skeleton->get_bone_name(bone_idx), Animation::TYPE_POSITION_3D, skeleton->get(p_path));
+ }
+ if (split[2] == "rotation") {
+ te->insert_transform_key(skeleton, skeleton->get_bone_name(bone_idx), Animation::TYPE_ROTATION_3D, skeleton->get(p_path));
+ }
+ if (split[2] == "scale") {
+ te->insert_transform_key(skeleton, skeleton->get_bone_name(bone_idx), Animation::TYPE_SCALE_3D, skeleton->get(p_path));
+ }
+ }
+}
+
void BoneTransformEditor::_update_properties() {
if (!skeleton) {
return;
@@ -141,30 +172,30 @@ void BoneTransformEditor::_update_properties() {
List<PropertyInfo> props;
skeleton->get_property_list(&props);
for (const PropertyInfo &E : props) {
- PackedStringArray spr = E.name.split("/");
- if (spr.size() == 3 && spr[0] == "bones") {
- if (spr[1].to_int() == selected) {
- if (spr[2] == "enabled") {
+ PackedStringArray split = E.name.split("/");
+ if (split.size() == 3 && split[0] == "bones") {
+ if (split[1].to_int() == selected) {
+ if (split[2] == "enabled") {
enabled_checkbox->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY);
enabled_checkbox->update_property();
enabled_checkbox->update();
}
- if (spr[2] == "position") {
+ if (split[2] == "position") {
position_property->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY);
position_property->update_property();
position_property->update();
}
- if (spr[2] == "rotation") {
+ if (split[2] == "rotation") {
rotation_property->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY);
rotation_property->update_property();
rotation_property->update();
}
- if (spr[2] == "scale") {
+ if (split[2] == "scale") {
scale_property->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY);
scale_property->update_property();
scale_property->update();
}
- if (spr[2] == "rest") {
+ if (split[2] == "rest") {
rest_matrix->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY);
rest_matrix->update_property();
rest_matrix->update();
@@ -178,12 +209,16 @@ Skeleton3DEditor *Skeleton3DEditor::singleton = nullptr;
void Skeleton3DEditor::set_keyable(const bool p_keyable) {
keyable = p_keyable;
- skeleton_options->get_popup()->set_item_disabled(SKELETON_OPTION_INSERT_KEYS, !p_keyable);
- skeleton_options->get_popup()->set_item_disabled(SKELETON_OPTION_INSERT_KEYS_EXISTED, !p_keyable);
+ if (p_keyable) {
+ animation_hb->show();
+ } else {
+ animation_hb->hide();
+ }
};
-void Skeleton3DEditor::set_rest_options_enabled(const bool p_rest_options_enabled) {
- rest_options->get_popup()->set_item_disabled(REST_OPTION_POSE_TO_REST, !p_rest_options_enabled);
+void Skeleton3DEditor::set_bone_options_enabled(const bool p_bone_options_enabled) {
+ skeleton_options->get_popup()->set_item_disabled(SKELETON_OPTION_INIT_SELECTED_POSES, !p_bone_options_enabled);
+ skeleton_options->get_popup()->set_item_disabled(SKELETON_OPTION_SELECTED_POSES_TO_RESTS, !p_bone_options_enabled);
};
void Skeleton3DEditor::_on_click_skeleton_option(int p_skeleton_option) {
@@ -192,62 +227,76 @@ void Skeleton3DEditor::_on_click_skeleton_option(int p_skeleton_option) {
}
switch (p_skeleton_option) {
- case SKELETON_OPTION_CREATE_PHYSICAL_SKELETON: {
- create_physical_skeleton();
+ case SKELETON_OPTION_INIT_ALL_POSES: {
+ init_pose(true);
break;
}
- case SKELETON_OPTION_INIT_POSE: {
- init_pose();
+ case SKELETON_OPTION_INIT_SELECTED_POSES: {
+ init_pose(false);
break;
}
- case SKELETON_OPTION_INSERT_KEYS: {
- insert_keys(true);
+ case SKELETON_OPTION_ALL_POSES_TO_RESTS: {
+ pose_to_rest(true);
break;
}
- case SKELETON_OPTION_INSERT_KEYS_EXISTED: {
- insert_keys(false);
+ case SKELETON_OPTION_SELECTED_POSES_TO_RESTS: {
+ pose_to_rest(false);
+ break;
+ }
+ case SKELETON_OPTION_CREATE_PHYSICAL_SKELETON: {
+ create_physical_skeleton();
break;
}
}
}
-void Skeleton3DEditor::_on_click_rest_option(int p_rest_option) {
+void Skeleton3DEditor::init_pose(const bool p_all_bones) {
if (!skeleton) {
return;
}
-
- switch (p_rest_option) {
- case REST_OPTION_POSE_TO_REST: {
- pose_to_rest();
- break;
- }
- }
-}
-
-void Skeleton3DEditor::init_pose() {
const int bone_len = skeleton->get_bone_count();
if (!bone_len) {
return;
}
+
UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo();
ur->create_action(TTR("Set Bone Transform"), UndoRedo::MERGE_ENDS);
- for (int i = 0; i < bone_len; i++) {
- Transform3D rest = skeleton->get_bone_rest(i);
- ur->add_do_method(skeleton, "set_bone_pose_position", i, rest.origin);
- ur->add_do_method(skeleton, "set_bone_pose_rotation", i, rest.basis.get_rotation_quaternion());
- ur->add_do_method(skeleton, "set_bone_pose_scale", i, rest.basis.get_scale());
- ur->add_undo_method(skeleton, "set_bone_pose_position", i, skeleton->get_bone_pose_position(i));
- ur->add_undo_method(skeleton, "set_bone_pose_rotation", i, skeleton->get_bone_pose_rotation(i));
- ur->add_undo_method(skeleton, "set_bone_pose_scale", i, skeleton->get_bone_pose_scale(i));
+ if (p_all_bones) {
+ for (int i = 0; i < bone_len; i++) {
+ Transform3D rest = skeleton->get_bone_rest(i);
+ ur->add_do_method(skeleton, "set_bone_pose_position", i, rest.origin);
+ ur->add_do_method(skeleton, "set_bone_pose_rotation", i, rest.basis.get_rotation_quaternion());
+ ur->add_do_method(skeleton, "set_bone_pose_scale", i, rest.basis.get_scale());
+ ur->add_undo_method(skeleton, "set_bone_pose_position", i, skeleton->get_bone_pose_position(i));
+ ur->add_undo_method(skeleton, "set_bone_pose_rotation", i, skeleton->get_bone_pose_rotation(i));
+ ur->add_undo_method(skeleton, "set_bone_pose_scale", i, skeleton->get_bone_pose_scale(i));
+ }
+ } else {
+ // Todo: Do method with multiple bone selection.
+ if (selected_bone == -1) {
+ ur->commit_action();
+ return;
+ }
+ Transform3D rest = skeleton->get_bone_rest(selected_bone);
+ ur->add_do_method(skeleton, "set_bone_pose_position", selected_bone, rest.origin);
+ ur->add_do_method(skeleton, "set_bone_pose_rotation", selected_bone, rest.basis.get_rotation_quaternion());
+ ur->add_do_method(skeleton, "set_bone_pose_scale", selected_bone, rest.basis.get_scale());
+ ur->add_undo_method(skeleton, "set_bone_pose_position", selected_bone, skeleton->get_bone_pose_position(selected_bone));
+ ur->add_undo_method(skeleton, "set_bone_pose_rotation", selected_bone, skeleton->get_bone_pose_rotation(selected_bone));
+ ur->add_undo_method(skeleton, "set_bone_pose_scale", selected_bone, skeleton->get_bone_pose_scale(selected_bone));
}
ur->commit_action();
}
-void Skeleton3DEditor::insert_keys(bool p_all_bones) {
+void Skeleton3DEditor::insert_keys(const bool p_all_bones) {
if (!skeleton) {
return;
}
+ bool pos_enabled = key_loc_button->is_pressed();
+ bool rot_enabled = key_rot_button->is_pressed();
+ bool scl_enabled = key_scale_button->is_pressed();
+
int bone_len = skeleton->get_bone_count();
Node *root = EditorNode::get_singleton()->get_tree()->get_root();
String path = root->get_path_to(skeleton);
@@ -261,26 +310,44 @@ void Skeleton3DEditor::insert_keys(bool p_all_bones) {
continue;
}
- if (!p_all_bones && !te->has_transform_track(skeleton, name)) {
- continue;
+ if (pos_enabled && (p_all_bones || te->has_track(skeleton, name, Animation::TYPE_POSITION_3D))) {
+ te->insert_transform_key(skeleton, name, Animation::TYPE_POSITION_3D, skeleton->get_bone_pose_position(i));
+ }
+ if (rot_enabled && (p_all_bones || te->has_track(skeleton, name, Animation::TYPE_ROTATION_3D))) {
+ te->insert_transform_key(skeleton, name, Animation::TYPE_ROTATION_3D, skeleton->get_bone_pose_rotation(i));
+ }
+ if (scl_enabled && (p_all_bones || te->has_track(skeleton, name, Animation::TYPE_SCALE_3D))) {
+ te->insert_transform_key(skeleton, name, Animation::TYPE_SCALE_3D, skeleton->get_bone_pose_scale(i));
}
-
- Transform3D tform = skeleton->get_bone_pose(i);
- te->insert_transform_key(skeleton, name, tform);
}
te->commit_insert_queue();
}
-void Skeleton3DEditor::pose_to_rest() {
+void Skeleton3DEditor::pose_to_rest(const bool p_all_bones) {
if (!skeleton) {
return;
}
+ const int bone_len = skeleton->get_bone_count();
+ if (!bone_len) {
+ return;
+ }
- // Todo: Do method with multiple bone selection.
UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo();
ur->create_action(TTR("Set Bone Rest"), UndoRedo::MERGE_ENDS);
- ur->add_do_method(skeleton, "set_bone_rest", selected_bone, skeleton->get_bone_pose(selected_bone));
- ur->add_undo_method(skeleton, "set_bone_rest", selected_bone, skeleton->get_bone_rest(selected_bone));
+ if (p_all_bones) {
+ for (int i = 0; i < bone_len; i++) {
+ ur->add_do_method(skeleton, "set_bone_rest", i, skeleton->get_bone_pose(i));
+ ur->add_undo_method(skeleton, "set_bone_rest", i, skeleton->get_bone_rest(i));
+ }
+ } else {
+ // Todo: Do method with multiple bone selection.
+ if (selected_bone == -1) {
+ ur->commit_action();
+ return;
+ }
+ ur->add_do_method(skeleton, "set_bone_rest", selected_bone, skeleton->get_bone_pose(selected_bone));
+ ur->add_undo_method(skeleton, "set_bone_rest", selected_bone, skeleton->get_bone_rest(selected_bone));
+ }
ur->commit_action();
}
@@ -466,11 +533,12 @@ void Skeleton3DEditor::_joint_tree_selection_changed() {
const String bone_path = "bones/" + itos(b_idx) + "/";
pose_editor->set_target(bone_path);
+ pose_editor->set_keyable(keyable);
selected_bone = b_idx;
}
}
pose_editor->set_visible(selected);
- set_rest_options_enabled(selected);
+ set_bone_options_enabled(selected);
_update_properties();
_update_gizmo_visible();
}
@@ -549,43 +617,82 @@ void Skeleton3DEditor::create_editors() {
skeleton_options->set_text(TTR("Skeleton3D"));
skeleton_options->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Skeleton3D"), SNAME("EditorIcons")));
- skeleton_options->get_popup()->add_item(TTR("Init pose"), SKELETON_OPTION_INIT_POSE);
- skeleton_options->get_popup()->add_item(TTR("Insert key of all bone poses"), SKELETON_OPTION_INSERT_KEYS);
- skeleton_options->get_popup()->add_item(TTR("Insert key of bone poses already exist track"), SKELETON_OPTION_INSERT_KEYS_EXISTED);
- skeleton_options->get_popup()->add_item(TTR("Create physical skeleton"), SKELETON_OPTION_CREATE_PHYSICAL_SKELETON);
-
- skeleton_options->get_popup()->connect("id_pressed", callable_mp(this, &Skeleton3DEditor::_on_click_skeleton_option));
+ // Skeleton options.
+ PopupMenu *p = skeleton_options->get_popup();
+ p->add_shortcut(ED_SHORTCUT("skeleton_3d_editor/init_all_poses", TTR("Init all Poses")), SKELETON_OPTION_INIT_ALL_POSES);
+ p->add_shortcut(ED_SHORTCUT("skeleton_3d_editor/init_selected_poses", TTR("Init selected Poses")), SKELETON_OPTION_INIT_SELECTED_POSES);
+ p->add_shortcut(ED_SHORTCUT("skeleton_3d_editor/all_poses_to_rests", TTR("Apply all poses to rests")), SKELETON_OPTION_ALL_POSES_TO_RESTS);
+ p->add_shortcut(ED_SHORTCUT("skeleton_3d_editor/selected_poses_to_rests", TTR("Apply selected poses to rests")), SKELETON_OPTION_SELECTED_POSES_TO_RESTS);
+ p->add_item(TTR("Create physical skeleton"), SKELETON_OPTION_CREATE_PHYSICAL_SKELETON);
- // Create Rest Option in Top Menu Bar.
- rest_options = memnew(MenuButton);
- ne->add_control_to_menu_panel(rest_options);
-
- rest_options->set_text(TTR("Edit Rest"));
- rest_options->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("BoneAttachment3D"), SNAME("EditorIcons")));
-
- rest_options->get_popup()->add_item(TTR("Apply current pose to rest"), REST_OPTION_POSE_TO_REST);
- rest_options->get_popup()->connect("id_pressed", callable_mp(this, &Skeleton3DEditor::_on_click_rest_option));
- set_rest_options_enabled(false);
+ p->connect("id_pressed", callable_mp(this, &Skeleton3DEditor::_on_click_skeleton_option));
+ set_bone_options_enabled(false);
Vector<Variant> button_binds;
button_binds.resize(1);
edit_mode_button = memnew(Button);
ne->add_control_to_menu_panel(edit_mode_button);
- edit_mode_button->set_tooltip(TTR("Edit Mode\nShow buttons on joints."));
- edit_mode_button->set_toggle_mode(true);
edit_mode_button->set_flat(true);
+ edit_mode_button->set_toggle_mode(true);
+ edit_mode_button->set_focus_mode(FOCUS_NONE);
+ edit_mode_button->set_tooltip(TTR("Edit Mode\nShow buttons on joints."));
edit_mode_button->connect("toggled", callable_mp(this, &Skeleton3DEditor::edit_mode_toggled));
edit_mode = false;
- set_keyable(te->has_keying());
-
if (skeleton) {
skeleton->add_child(handles_mesh_instance);
handles_mesh_instance->set_skeleton_path(NodePath(""));
}
+ // Keying buttons.
+ animation_hb = memnew(HBoxContainer);
+ ne->add_control_to_menu_panel(animation_hb);
+ animation_hb->add_child(memnew(VSeparator));
+ animation_hb->hide();
+
+ key_loc_button = memnew(Button);
+ key_loc_button->set_flat(true);
+ key_loc_button->set_toggle_mode(true);
+ key_loc_button->set_pressed(false);
+ key_loc_button->set_focus_mode(FOCUS_NONE);
+ key_loc_button->set_tooltip(TTR("Translation mask for inserting keys."));
+ animation_hb->add_child(key_loc_button);
+
+ key_rot_button = memnew(Button);
+ key_rot_button->set_flat(true);
+ key_rot_button->set_toggle_mode(true);
+ key_rot_button->set_pressed(true);
+ key_rot_button->set_focus_mode(FOCUS_NONE);
+ key_rot_button->set_tooltip(TTR("Rotation mask for inserting keys."));
+ animation_hb->add_child(key_rot_button);
+
+ key_scale_button = memnew(Button);
+ key_scale_button->set_flat(true);
+ key_scale_button->set_toggle_mode(true);
+ key_scale_button->set_pressed(false);
+ key_scale_button->set_focus_mode(FOCUS_NONE);
+ key_scale_button->set_tooltip(TTR("Scale mask for inserting keys."));
+ animation_hb->add_child(key_scale_button);
+
+ key_insert_button = memnew(Button);
+ key_insert_button->set_flat(true);
+ key_insert_button->set_focus_mode(FOCUS_NONE);
+ key_insert_button->connect("pressed", callable_mp(this, &Skeleton3DEditor::insert_keys), varray(false));
+ key_insert_button->set_tooltip(TTR("Insert key of bone poses already exist track."));
+ key_insert_button->set_shortcut(ED_SHORTCUT("skeleton_3d_editor/insert_key_to_existing_tracks", TTR("Insert Key (Existing Tracks)"), KEY_INSERT));
+ animation_hb->add_child(key_insert_button);
+
+ key_insert_all_button = memnew(Button);
+ key_insert_all_button->set_flat(true);
+ key_insert_all_button->set_focus_mode(FOCUS_NONE);
+ key_insert_all_button->connect("pressed", callable_mp(this, &Skeleton3DEditor::insert_keys), varray(true));
+ key_insert_all_button->set_tooltip(TTR("Insert key of all bone poses."));
+ key_insert_all_button->set_shortcut(ED_SHORTCUT("skeleton_3d_editor/insert_key_of_all_bones", TTR("Insert Key (All Bones)"), KEY_MASK_CMD + KEY_INSERT));
+ animation_hb->add_child(key_insert_all_button);
+
+ // Bone tree.
const Color section_color = get_theme_color(SNAME("prop_subsection"), SNAME("Editor"));
EditorInspectorSection *bones_section = memnew(EditorInspectorSection);
@@ -613,12 +720,19 @@ void Skeleton3DEditor::create_editors() {
pose_editor->set_label(TTR("Bone Transform"));
pose_editor->set_visible(false);
add_child(pose_editor);
+
+ set_keyable(te->has_keying());
}
void Skeleton3DEditor::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_READY: {
- edit_mode_button->set_icon(get_theme_icon("ToolBoneSelect", "EditorIcons"));
+ edit_mode_button->set_icon(get_theme_icon(SNAME("ToolBoneSelect"), SNAME("EditorIcons")));
+ key_loc_button->set_icon(get_theme_icon(SNAME("KeyPosition"), SNAME("EditorIcons")));
+ key_rot_button->set_icon(get_theme_icon(SNAME("KeyRotation"), SNAME("EditorIcons")));
+ key_scale_button->set_icon(get_theme_icon(SNAME("KeyScale"), SNAME("EditorIcons")));
+ key_insert_button->set_icon(get_theme_icon(SNAME("Key"), SNAME("EditorIcons")));
+ key_insert_all_button->set_icon(get_theme_icon(SNAME("NewKey"), SNAME("EditorIcons")));
get_tree()->connect("node_removed", callable_mp(this, &Skeleton3DEditor::_node_removed), Vector<Variant>(), Object::CONNECT_ONESHOT);
break;
}
@@ -643,7 +757,6 @@ void Skeleton3DEditor::_node_removed(Node *p_node) {
if (skeleton && p_node == skeleton) {
skeleton = nullptr;
skeleton_options->hide();
- rest_options->hide();
}
_update_properties();
@@ -655,7 +768,6 @@ void Skeleton3DEditor::_bind_methods() {
ClassDB::bind_method(D_METHOD("_joint_tree_rmb_select"), &Skeleton3DEditor::_joint_tree_rmb_select);
ClassDB::bind_method(D_METHOD("_update_properties"), &Skeleton3DEditor::_update_properties);
ClassDB::bind_method(D_METHOD("_on_click_skeleton_option"), &Skeleton3DEditor::_on_click_skeleton_option);
- ClassDB::bind_method(D_METHOD("_on_click_rest_option"), &Skeleton3DEditor::_on_click_rest_option);
ClassDB::bind_method(D_METHOD("get_drag_data_fw"), &Skeleton3DEditor::get_drag_data_fw);
ClassDB::bind_method(D_METHOD("can_drop_data_fw"), &Skeleton3DEditor::can_drop_data_fw);
@@ -866,6 +978,11 @@ Skeleton3DEditor::~Skeleton3DEditor() {
Node3DEditor *ne = Node3DEditor::get_singleton();
+ if (animation_hb) {
+ ne->remove_control_from_menu_panel(animation_hb);
+ memdelete(animation_hb);
+ }
+
if (separator) {
ne->remove_control_from_menu_panel(separator);
memdelete(separator);
@@ -876,11 +993,6 @@ Skeleton3DEditor::~Skeleton3DEditor() {
memdelete(skeleton_options);
}
- if (rest_options) {
- ne->remove_control_from_menu_panel(rest_options);
- memdelete(rest_options);
- }
-
if (edit_mode_button) {
ne->remove_control_from_menu_panel(edit_mode_button);
memdelete(edit_mode_button);
diff --git a/editor/plugins/skeleton_3d_editor_plugin.h b/editor/plugins/skeleton_3d_editor_plugin.h
index 3b4dd362fb..1dd2d2281d 100644
--- a/editor/plugins/skeleton_3d_editor_plugin.h
+++ b/editor/plugins/skeleton_3d_editor_plugin.h
@@ -45,7 +45,6 @@ class Joint;
class PhysicalBone3D;
class Skeleton3DEditorPlugin;
class Button;
-class CheckBox;
class BoneTransformEditor : public VBoxContainer {
GDCLASS(BoneTransformEditor, VBoxContainer);
@@ -67,9 +66,6 @@ class BoneTransformEditor : public VBoxContainer {
UndoRedo *undo_redo;
- // Button *key_button = nullptr;
-
- bool keyable = false;
bool toggle_enabled = false;
bool updating = false;
@@ -79,6 +75,8 @@ class BoneTransformEditor : public VBoxContainer {
void _value_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing);
+ void _property_keyed(const String &p_path, bool p_advance);
+
protected:
void _notification(int p_what);
@@ -88,6 +86,7 @@ public:
// Which transform target to modify.
void set_target(const String &p_prop);
void set_label(const String &p_label) { label = p_label; }
+ void set_keyable(const bool p_keyable);
void _update_properties();
};
@@ -98,14 +97,11 @@ class Skeleton3DEditor : public VBoxContainer {
friend class Skeleton3DEditorPlugin;
enum SkeletonOption {
- SKELETON_OPTION_INIT_POSE,
- SKELETON_OPTION_INSERT_KEYS,
- SKELETON_OPTION_INSERT_KEYS_EXISTED,
- SKELETON_OPTION_CREATE_PHYSICAL_SKELETON
- };
-
- enum RestOption {
- REST_OPTION_POSE_TO_REST
+ SKELETON_OPTION_INIT_ALL_POSES,
+ SKELETON_OPTION_INIT_SELECTED_POSES,
+ SKELETON_OPTION_ALL_POSES_TO_RESTS,
+ SKELETON_OPTION_SELECTED_POSES_TO_RESTS,
+ SKELETON_OPTION_CREATE_PHYSICAL_SKELETON,
};
struct BoneInfo {
@@ -124,11 +120,17 @@ class Skeleton3DEditor : public VBoxContainer {
VSeparator *separator;
MenuButton *skeleton_options = nullptr;
- MenuButton *rest_options = nullptr;
Button *edit_mode_button;
bool edit_mode = false;
+ HBoxContainer *animation_hb;
+ Button *key_loc_button;
+ Button *key_rot_button;
+ Button *key_scale_button;
+ Button *key_insert_button;
+ Button *key_insert_all_button;
+
EditorFileDialog *file_dialog = nullptr;
bool keyable;
@@ -136,7 +138,6 @@ class Skeleton3DEditor : public VBoxContainer {
static Skeleton3DEditor *singleton;
void _on_click_skeleton_option(int p_skeleton_option);
- void _on_click_rest_option(int p_rest_option);
void _file_selected(const String &p_file);
TreeItem *_find(TreeItem *p_node, const NodePath &p_path);
void edit_mode_toggled(const bool pressed);
@@ -148,9 +149,10 @@ class Skeleton3DEditor : public VBoxContainer {
void create_editors();
- void init_pose();
- void insert_keys(bool p_all_bones);
- void pose_to_rest();
+ void init_pose(const bool p_all_bones);
+ void pose_to_rest(const bool p_all_bones);
+
+ void insert_keys(const bool p_all_bones);
void create_physical_skeleton();
PhysicalBone3D *create_physical_bone(int bone_id, int bone_child_id, const Vector<BoneInfo> &bones_infos);
@@ -160,7 +162,7 @@ class Skeleton3DEditor : public VBoxContainer {
void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from);
void set_keyable(const bool p_keyable);
- void set_rest_options_enabled(const bool p_rest_options_enabled);
+ void set_bone_options_enabled(const bool p_bone_options_enabled);
// Handle.
MeshInstance3D *handles_mesh_instance;
diff --git a/editor/plugins/theme_editor_preview.cpp b/editor/plugins/theme_editor_preview.cpp
index d26d0ec39d..e13a10fe3f 100644
--- a/editor/plugins/theme_editor_preview.cpp
+++ b/editor/plugins/theme_editor_preview.cpp
@@ -117,7 +117,7 @@ void ThemeEditorPreview::_draw_picker_overlay() {
}
Rect2 highlight_label_rect = highlight_rect;
- highlight_label_rect.size = theme_cache.preview_picker_font->get_string_size(highlight_name);
+ highlight_label_rect.size = theme_cache.preview_picker_font->get_string_size(highlight_name, theme_cache.font_size);
int margin_top = theme_cache.preview_picker_label->get_margin(SIDE_TOP);
int margin_left = theme_cache.preview_picker_label->get_margin(SIDE_LEFT);
@@ -133,7 +133,7 @@ void ThemeEditorPreview::_draw_picker_overlay() {
Point2 label_pos = highlight_label_rect.position;
label_pos.y += highlight_label_rect.size.y - margin_bottom;
label_pos.x += margin_left;
- picker_overlay->draw_string(theme_cache.preview_picker_font, label_pos, highlight_name);
+ picker_overlay->draw_string(theme_cache.preview_picker_font, label_pos, highlight_name, HALIGN_LEFT, -1, theme_cache.font_size);
}
}
@@ -188,6 +188,7 @@ void ThemeEditorPreview::_notification(int p_what) {
theme_cache.preview_picker_overlay_color = get_theme_color(SNAME("preview_picker_overlay_color"), SNAME("ThemeEditor"));
theme_cache.preview_picker_label = get_theme_stylebox(SNAME("preview_picker_label"), SNAME("ThemeEditor"));
theme_cache.preview_picker_font = get_theme_font(SNAME("status_source"), SNAME("EditorFonts"));
+ theme_cache.font_size = get_theme_font_size(SNAME("font_size"), SNAME("EditorFonts"));
} break;
case NOTIFICATION_PROCESS: {
time_left -= get_process_delta_time();
diff --git a/editor/plugins/theme_editor_preview.h b/editor/plugins/theme_editor_preview.h
index 4e1b149e70..f973119257 100644
--- a/editor/plugins/theme_editor_preview.h
+++ b/editor/plugins/theme_editor_preview.h
@@ -65,6 +65,7 @@ class ThemeEditorPreview : public VBoxContainer {
Color preview_picker_overlay_color;
Ref<StyleBox> preview_picker_label;
Ref<Font> preview_picker_font;
+ int font_size = 16;
} theme_cache;
double time_left = 0;
diff --git a/editor/plugins/tiles/tile_data_editors.cpp b/editor/plugins/tiles/tile_data_editors.cpp
index 104f46f773..e9d80bb4b8 100644
--- a/editor/plugins/tiles/tile_data_editors.cpp
+++ b/editor/plugins/tiles/tile_data_editors.cpp
@@ -987,6 +987,7 @@ void TileDataDefaultEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2
p_canvas_item->draw_rect(rect, value);
} else {
Ref<Font> font = TileSetEditor::get_singleton()->get_theme_font(SNAME("bold"), SNAME("EditorFonts"));
+ int font_size = TileSetEditor::get_singleton()->get_theme_font_size(SNAME("bold_size"), SNAME("EditorFonts"));
String text;
switch (value.get_type()) {
case Variant::INT:
@@ -1018,8 +1019,8 @@ void TileDataDefaultEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2
}
}
- Vector2 string_size = font->get_string_size(text);
- p_canvas_item->draw_string(font, p_transform.get_origin() + Vector2i(-string_size.x / 2, string_size.y / 2), text, HALIGN_CENTER, string_size.x, -1, color, 1, Color(0, 0, 0, 1));
+ Vector2 string_size = font->get_string_size(text, font_size);
+ p_canvas_item->draw_string(font, p_transform.get_origin() + Vector2i(-string_size.x / 2, string_size.y / 2), text, HALIGN_CENTER, string_size.x, font_size, color, 1, Color(0, 0, 0, 1));
}
}
@@ -1582,6 +1583,7 @@ void TileDataTerrainsEditor::forward_draw_over_atlas(TileAtlasView *p_tile_atlas
// Dim terrains with wrong terrain set.
Ref<Font> font = TileSetEditor::get_singleton()->get_theme_font(SNAME("bold"), SNAME("EditorFonts"));
+ int font_size = TileSetEditor::get_singleton()->get_theme_font_size(SNAME("bold_size"), SNAME("EditorFonts"));
for (int i = 0; i < p_tile_set_atlas_source->get_tiles_count(); i++) {
Vector2i coords = p_tile_set_atlas_source->get_tile_id(i);
if (coords != hovered_coords) {
@@ -1604,8 +1606,8 @@ void TileDataTerrainsEditor::forward_draw_over_atlas(TileAtlasView *p_tile_atlas
} else {
text = "-";
}
- Vector2 string_size = font->get_string_size(text);
- p_canvas_item->draw_string(font, p_transform.xform(position) + Vector2i(-string_size.x / 2, string_size.y / 2), text, HALIGN_CENTER, string_size.x, -1, color, 1, Color(0, 0, 0, 1));
+ Vector2 string_size = font->get_string_size(text, font_size);
+ p_canvas_item->draw_string(font, p_transform.xform(position) + Vector2i(-string_size.x / 2, string_size.y / 2), text, HALIGN_CENTER, string_size.x, font_size, color, 1, Color(0, 0, 0, 1));
}
}
}
@@ -1755,6 +1757,7 @@ void TileDataTerrainsEditor::forward_draw_over_alternatives(TileAtlasView *p_til
// Dim terrains with wrong terrain set.
Ref<Font> font = TileSetEditor::get_singleton()->get_theme_font(SNAME("bold"), SNAME("EditorFonts"));
+ int font_size = TileSetEditor::get_singleton()->get_theme_font_size(SNAME("bold_size"), SNAME("EditorFonts"));
for (int i = 0; i < p_tile_set_atlas_source->get_tiles_count(); i++) {
Vector2i coords = p_tile_set_atlas_source->get_tile_id(i);
for (int j = 1; j < p_tile_set_atlas_source->get_alternative_tiles_count(coords); j++) {
@@ -1779,8 +1782,8 @@ void TileDataTerrainsEditor::forward_draw_over_alternatives(TileAtlasView *p_til
} else {
text = "-";
}
- Vector2 string_size = font->get_string_size(text);
- p_canvas_item->draw_string(font, p_transform.xform(position) + Vector2i(-string_size.x / 2, string_size.y / 2), text, HALIGN_CENTER, string_size.x, -1, color, 1, Color(0, 0, 0, 1));
+ Vector2 string_size = font->get_string_size(text, font_size);
+ p_canvas_item->draw_string(font, p_transform.xform(position) + Vector2i(-string_size.x / 2, string_size.y / 2), text, HALIGN_CENTER, string_size.x, font_size, color, 1, Color(0, 0, 0, 1));
}
}
}
diff --git a/editor/plugins/tiles/tile_map_editor.cpp b/editor/plugins/tiles/tile_map_editor.cpp
index 5dfa44a353..5b7ec6c8db 100644
--- a/editor/plugins/tiles/tile_map_editor.cpp
+++ b/editor/plugins/tiles/tile_map_editor.cpp
@@ -2219,569 +2219,7 @@ Vector<TileMapEditorPlugin::TabData> TileMapEditorTerrainsPlugin::get_tabs() con
return tabs;
}
-Map<Vector2i, TileSet::CellNeighbor> TileMapEditorTerrainsPlugin::Constraint::get_overlapping_coords_and_peering_bits() const {
- Map<Vector2i, TileSet::CellNeighbor> output;
- Ref<TileSet> tile_set = tile_map->get_tileset();
- ERR_FAIL_COND_V(!tile_set.is_valid(), output);
-
- TileSet::TileShape shape = tile_set->get_tile_shape();
- if (shape == TileSet::TILE_SHAPE_SQUARE) {
- switch (bit) {
- case 0:
- output[base_cell_coords] = TileSet::CELL_NEIGHBOR_RIGHT_SIDE;
- output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_SIDE;
- break;
- case 1:
- output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER;
- output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER;
- output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER;
- output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER;
- break;
- case 2:
- output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_SIDE;
- output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_SIDE;
- break;
- case 3:
- output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER;
- output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER;
- output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER;
- output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER;
- break;
- default:
- ERR_FAIL_V(output);
- }
- } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC) {
- switch (bit) {
- case 0:
- output[base_cell_coords] = TileSet::CELL_NEIGHBOR_RIGHT_CORNER;
- output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_CORNER;
- output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_CORNER)] = TileSet::CELL_NEIGHBOR_LEFT_CORNER;
- output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_CORNER;
- break;
- case 1:
- output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE;
- output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE;
- break;
- case 2:
- output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_CORNER;
- output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_CORNER;
- output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_CORNER)] = TileSet::CELL_NEIGHBOR_TOP_CORNER;
- output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_RIGHT_CORNER;
- break;
- case 3:
- output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE;
- output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE;
- break;
- default:
- ERR_FAIL_V(output);
- }
- } else {
- // Half offset shapes.
- TileSet::TileOffsetAxis offset_axis = tile_set->get_tile_offset_axis();
- if (offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) {
- switch (bit) {
- case 0:
- output[base_cell_coords] = TileSet::CELL_NEIGHBOR_RIGHT_SIDE;
- output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_SIDE;
- break;
- case 1:
- output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER;
- output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER;
- output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_CORNER;
- break;
- case 2:
- output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE;
- output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE;
- break;
- case 3:
- output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_CORNER;
- output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER;
- output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER;
- break;
- case 4:
- output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE;
- output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE;
- break;
- default:
- ERR_FAIL_V(output);
- }
- } else {
- switch (bit) {
- case 0:
- output[base_cell_coords] = TileSet::CELL_NEIGHBOR_RIGHT_CORNER;
- output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER;
- output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER;
- break;
- case 1:
- output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE;
- output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE;
- break;
- case 2:
- output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER;
- output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_CORNER;
- output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER;
- break;
- case 3:
- output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_SIDE;
- output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_SIDE;
- break;
- case 4:
- output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE;
- output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE;
- break;
- default:
- ERR_FAIL_V(output);
- }
- }
- }
- return output;
-}
-
-TileMapEditorTerrainsPlugin::Constraint::Constraint(const TileMap *p_tile_map, const Vector2i &p_position, const TileSet::CellNeighbor &p_bit, int p_terrain) {
- // The way we build the constraint make it easy to detect conflicting constraints.
- tile_map = p_tile_map;
-
- Ref<TileSet> tile_set = tile_map->get_tileset();
- ERR_FAIL_COND(!tile_set.is_valid());
-
- TileSet::TileShape shape = tile_set->get_tile_shape();
- if (shape == TileSet::TILE_SHAPE_SQUARE || shape == TileSet::TILE_SHAPE_ISOMETRIC) {
- switch (p_bit) {
- case TileSet::CELL_NEIGHBOR_RIGHT_SIDE:
- case TileSet::CELL_NEIGHBOR_RIGHT_CORNER:
- bit = 0;
- base_cell_coords = p_position;
- break;
- case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER:
- case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE:
- bit = 1;
- base_cell_coords = p_position;
- break;
- case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE:
- case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER:
- bit = 2;
- base_cell_coords = p_position;
- break;
- case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER:
- case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE:
- bit = 3;
- base_cell_coords = p_position;
- break;
- case TileSet::CELL_NEIGHBOR_LEFT_SIDE:
- case TileSet::CELL_NEIGHBOR_LEFT_CORNER:
- bit = 0;
- base_cell_coords = p_tile_map->get_neighbor_cell(p_position, p_bit);
- break;
- case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER:
- case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE:
- bit = 1;
- base_cell_coords = p_tile_map->get_neighbor_cell(p_position, p_bit);
- break;
- case TileSet::CELL_NEIGHBOR_TOP_SIDE:
- case TileSet::CELL_NEIGHBOR_TOP_CORNER:
- bit = 2;
- base_cell_coords = p_tile_map->get_neighbor_cell(p_position, p_bit);
- break;
- case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER:
- case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE:
- bit = 3;
- base_cell_coords = p_tile_map->get_neighbor_cell(p_position, p_bit);
- break;
- default:
- ERR_FAIL();
- break;
- }
- } else {
- // Half-offset shapes
- TileSet::TileOffsetAxis offset_axis = tile_set->get_tile_offset_axis();
- if (offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) {
- switch (p_bit) {
- case TileSet::CELL_NEIGHBOR_RIGHT_SIDE:
- bit = 0;
- base_cell_coords = p_position;
- break;
- case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER:
- bit = 1;
- base_cell_coords = p_position;
- break;
- case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE:
- bit = 2;
- base_cell_coords = p_position;
- break;
- case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER:
- bit = 3;
- base_cell_coords = p_position;
- break;
- case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE:
- bit = 4;
- base_cell_coords = p_position;
- break;
- case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER:
- bit = 1;
- base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_LEFT_SIDE);
- break;
- case TileSet::CELL_NEIGHBOR_LEFT_SIDE:
- bit = 0;
- base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_LEFT_SIDE);
- break;
- case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER:
- bit = 3;
- base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE);
- break;
- case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE:
- bit = 2;
- base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE);
- break;
- case TileSet::CELL_NEIGHBOR_TOP_CORNER:
- bit = 1;
- base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE);
- break;
- case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE:
- bit = 4;
- base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE);
- break;
- case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER:
- bit = 3;
- base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE);
- break;
- default:
- ERR_FAIL();
- break;
- }
- } else {
- switch (p_bit) {
- case TileSet::CELL_NEIGHBOR_RIGHT_CORNER:
- bit = 0;
- base_cell_coords = p_position;
- break;
- case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE:
- bit = 1;
- base_cell_coords = p_position;
- break;
- case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER:
- bit = 2;
- base_cell_coords = p_position;
- break;
- case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE:
- bit = 3;
- base_cell_coords = p_position;
- break;
- case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER:
- bit = 0;
- base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE);
- break;
- case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE:
- bit = 4;
- base_cell_coords = p_position;
- break;
- case TileSet::CELL_NEIGHBOR_LEFT_CORNER:
- bit = 2;
- base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE);
- break;
- case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE:
- bit = 1;
- base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE);
- break;
- case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER:
- bit = 0;
- base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE);
- break;
- case TileSet::CELL_NEIGHBOR_TOP_SIDE:
- bit = 3;
- base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_SIDE);
- break;
- case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER:
- bit = 2;
- base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_SIDE);
- break;
- case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE:
- bit = 4;
- base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE);
- break;
- default:
- ERR_FAIL();
- break;
- }
- }
- }
- terrain = p_terrain;
-}
-
-Set<TileMapEditorTerrainsPlugin::TerrainsTilePattern> TileMapEditorTerrainsPlugin::_get_valid_terrains_tile_patterns_for_constraints(int p_terrain_set, const Vector2i &p_position, Set<Constraint> p_constraints) const {
- TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
- if (!tile_map) {
- return Set<TerrainsTilePattern>();
- }
-
- Ref<TileSet> tile_set = tile_map->get_tileset();
- if (!tile_set.is_valid()) {
- return Set<TerrainsTilePattern>();
- }
-
- // Returns all tiles compatible with the given constraints.
- Set<TerrainsTilePattern> compatible_terrain_tile_patterns;
- for (const KeyValue<TerrainsTilePattern, Set<TileMapCell>> &E : per_terrain_terrains_tile_patterns_tiles[p_terrain_set]) {
- int valid = true;
- int in_pattern_count = 0;
- for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) {
- TileSet::CellNeighbor bit = TileSet::CellNeighbor(i);
- if (tile_set->is_valid_peering_bit_terrain(p_terrain_set, bit)) {
- // Check if the bit is compatible with the constraints.
- Constraint terrain_bit_constraint = Constraint(tile_map, p_position, bit, E.key[in_pattern_count]);
-
- Set<Constraint>::Element *in_set_constraint_element = p_constraints.find(terrain_bit_constraint);
- if (in_set_constraint_element && in_set_constraint_element->get().get_terrain() != terrain_bit_constraint.get_terrain()) {
- valid = false;
- break;
- }
- in_pattern_count++;
- }
- }
-
- if (valid) {
- compatible_terrain_tile_patterns.insert(E.key);
- }
- }
-
- return compatible_terrain_tile_patterns;
-}
-
-Set<TileMapEditorTerrainsPlugin::Constraint> TileMapEditorTerrainsPlugin::_get_constraints_from_removed_cells_list(const Set<Vector2i> &p_to_replace, int p_terrain_set) const {
- TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
- if (!tile_map) {
- return Set<Constraint>();
- }
-
- Ref<TileSet> tile_set = tile_map->get_tileset();
- if (!tile_set.is_valid()) {
- return Set<Constraint>();
- }
-
- ERR_FAIL_INDEX_V(p_terrain_set, tile_set->get_terrain_sets_count(), Set<Constraint>());
- ERR_FAIL_INDEX_V(tile_map_layer, tile_map->get_layers_count(), Set<Constraint>());
-
- // Build a set of dummy constraints get the constrained points.
- Set<Constraint> dummy_constraints;
- for (Set<Vector2i>::Element *E = p_to_replace.front(); E; E = E->next()) {
- for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { // Iterates over sides.
- TileSet::CellNeighbor bit = TileSet::CellNeighbor(i);
- if (tile_set->is_valid_peering_bit_terrain(p_terrain_set, bit)) {
- dummy_constraints.insert(Constraint(tile_map, E->get(), bit, -1));
- }
- }
- }
-
- // For each constrained point, we get all overlapping tiles, and select the most adequate terrain for it.
- Set<Constraint> constraints;
- for (Set<Constraint>::Element *E = dummy_constraints.front(); E; E = E->next()) {
- Constraint c = E->get();
-
- Map<int, int> terrain_count;
-
- // Count the number of occurrences per terrain.
- Map<Vector2i, TileSet::CellNeighbor> overlapping_terrain_bits = c.get_overlapping_coords_and_peering_bits();
- for (const KeyValue<Vector2i, TileSet::CellNeighbor> &E_overlapping : overlapping_terrain_bits) {
- if (!p_to_replace.has(E_overlapping.key)) {
- TileMapCell neighbor_cell = tile_map->get_cell(tile_map_layer, E_overlapping.key);
- TileData *neighbor_tile_data = nullptr;
- if (terrain_tiles.has(neighbor_cell) && terrain_tiles[neighbor_cell]->get_terrain_set() == p_terrain_set) {
- neighbor_tile_data = terrain_tiles[neighbor_cell];
- }
-
- int terrain = neighbor_tile_data ? neighbor_tile_data->get_peering_bit_terrain(TileSet::CellNeighbor(E_overlapping.value)) : -1;
- if (terrain_count.has(terrain)) {
- terrain_count[terrain] = 0;
- }
- terrain_count[terrain] += 1;
- }
- }
-
- // Get the terrain with the max number of occurrences.
- int max = 0;
- int max_terrain = -1;
- for (const KeyValue<int, int> &E_terrain_count : terrain_count) {
- if (E_terrain_count.value > max) {
- max = E_terrain_count.value;
- max_terrain = E_terrain_count.key;
- }
- }
-
- // Set the adequate terrain.
- if (max > 0) {
- c.set_terrain(max_terrain);
- constraints.insert(c);
- }
- }
-
- return constraints;
-}
-
-Set<TileMapEditorTerrainsPlugin::Constraint> TileMapEditorTerrainsPlugin::_get_constraints_from_added_tile(Vector2i p_position, int p_terrain_set, TerrainsTilePattern p_terrains_tile_pattern) const {
- TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
- if (!tile_map) {
- return Set<TileMapEditorTerrainsPlugin::Constraint>();
- }
-
- Ref<TileSet> tile_set = tile_map->get_tileset();
- if (!tile_set.is_valid()) {
- return Set<TileMapEditorTerrainsPlugin::Constraint>();
- }
-
- // Compute the constraints needed from the surrounding tiles.
- Set<TileMapEditorTerrainsPlugin::Constraint> output;
- int in_pattern_count = 0;
- for (uint32_t i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) {
- TileSet::CellNeighbor side = TileSet::CellNeighbor(i);
- if (tile_set->is_valid_peering_bit_terrain(p_terrain_set, side)) {
- Constraint c = Constraint(tile_map, p_position, side, p_terrains_tile_pattern[in_pattern_count]);
- output.insert(c);
- in_pattern_count++;
- }
- }
-
- return output;
-}
-
-Map<Vector2i, TileMapEditorTerrainsPlugin::TerrainsTilePattern> TileMapEditorTerrainsPlugin::_wave_function_collapse(const Set<Vector2i> &p_to_replace, int p_terrain_set, const Set<TileMapEditorTerrainsPlugin::Constraint> p_constraints) const {
- TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
- if (!tile_map) {
- return Map<Vector2i, TerrainsTilePattern>();
- }
-
- Ref<TileSet> tile_set = tile_map->get_tileset();
- if (!tile_set.is_valid()) {
- return Map<Vector2i, TileMapEditorTerrainsPlugin::TerrainsTilePattern>();
- }
-
- // Copy the constraints set.
- Set<TileMapEditorTerrainsPlugin::Constraint> constraints = p_constraints;
-
- // Compute all acceptable tiles for each cell.
- Map<Vector2i, Set<TerrainsTilePattern>> per_cell_acceptable_tiles;
- for (Set<Vector2i>::Element *E = p_to_replace.front(); E; E = E->next()) {
- per_cell_acceptable_tiles[E->get()] = _get_valid_terrains_tile_patterns_for_constraints(p_terrain_set, E->get(), constraints);
- }
-
- // Output map.
- Map<Vector2i, TerrainsTilePattern> output;
-
- // Add all positions to a set.
- Set<Vector2i> to_replace = Set<Vector2i>(p_to_replace);
- while (!to_replace.is_empty()) {
- // Compute the minimum number of tile possibilities for each cell.
- int min_nb_possibilities = 100000000;
- for (const KeyValue<Vector2i, Set<TerrainsTilePattern>> &E : per_cell_acceptable_tiles) {
- min_nb_possibilities = MIN(min_nb_possibilities, E.value.size());
- }
-
- // Get the set of possible cells to fill.
- LocalVector<Vector2i> to_choose_from;
- for (const KeyValue<Vector2i, Set<TerrainsTilePattern>> &E : per_cell_acceptable_tiles) {
- if (E.value.size() == min_nb_possibilities) {
- to_choose_from.push_back(E.key);
- }
- }
-
- // Randomly pick a tile out of the most constrained.
- Vector2i selected_cell_to_replace = to_choose_from[Math::random(0, to_choose_from.size() - 1)];
-
- // Randomly select a tile out of them the put it in the grid.
- Set<TerrainsTilePattern> valid_tiles = per_cell_acceptable_tiles[selected_cell_to_replace];
- if (valid_tiles.is_empty()) {
- // No possibilities :/
- break;
- }
- int random_terrain_tile_pattern_index = Math::random(0, valid_tiles.size() - 1);
- Set<TerrainsTilePattern>::Element *E = valid_tiles.front();
- for (int i = 0; i < random_terrain_tile_pattern_index; i++) {
- E = E->next();
- }
- TerrainsTilePattern selected_terrain_tile_pattern = E->get();
-
- // Set the selected cell into the output.
- output[selected_cell_to_replace] = selected_terrain_tile_pattern;
- to_replace.erase(selected_cell_to_replace);
- per_cell_acceptable_tiles.erase(selected_cell_to_replace);
-
- // Add the new constraints from the added tiles.
- Set<TileMapEditorTerrainsPlugin::Constraint> new_constraints = _get_constraints_from_added_tile(selected_cell_to_replace, p_terrain_set, selected_terrain_tile_pattern);
- for (Set<TileMapEditorTerrainsPlugin::Constraint>::Element *E_constraint = new_constraints.front(); E_constraint; E_constraint = E_constraint->next()) {
- constraints.insert(E_constraint->get());
- }
-
- // Compute valid tiles again for neighbors.
- for (uint32_t i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) {
- TileSet::CellNeighbor side = TileSet::CellNeighbor(i);
- if (tile_map->is_existing_neighbor(side)) {
- Vector2i neighbor = tile_map->get_neighbor_cell(selected_cell_to_replace, side);
- if (to_replace.has(neighbor)) {
- per_cell_acceptable_tiles[neighbor] = _get_valid_terrains_tile_patterns_for_constraints(p_terrain_set, neighbor, constraints);
- }
- }
- }
- }
- return output;
-}
-
-TileMapCell TileMapEditorTerrainsPlugin::_get_random_tile_from_pattern(int p_terrain_set, TerrainsTilePattern p_terrain_tile_pattern) const {
- TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
- if (!tile_map) {
- return TileMapCell();
- }
-
- Ref<TileSet> tile_set = tile_map->get_tileset();
- if (!tile_set.is_valid()) {
- return TileMapCell();
- }
-
- // Count the sum of probabilities.
- double sum = 0.0;
- Set<TileMapCell> set = per_terrain_terrains_tile_patterns_tiles[p_terrain_set][p_terrain_tile_pattern];
- for (Set<TileMapCell>::Element *E = set.front(); E; E = E->next()) {
- if (E->get().source_id >= 0) {
- Ref<TileSetSource> source = tile_set->get_source(E->get().source_id);
-
- Ref<TileSetAtlasSource> atlas_source = source;
- if (atlas_source.is_valid()) {
- TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(E->get().get_atlas_coords(), E->get().alternative_tile));
- sum += tile_data->get_probability();
- } else {
- sum += 1.0;
- }
- } else {
- sum += 1.0;
- }
- }
-
- // Generate a random number.
- double count = 0.0;
- double picked = Math::random(0.0, sum);
-
- // Pick the tile.
- for (Set<TileMapCell>::Element *E = set.front(); E; E = E->next()) {
- if (E->get().source_id >= 0) {
- Ref<TileSetSource> source = tile_set->get_source(E->get().source_id);
-
- Ref<TileSetAtlasSource> atlas_source = source;
- if (atlas_source.is_valid()) {
- TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(E->get().get_atlas_coords(), E->get().alternative_tile));
- count += tile_data->get_probability();
- } else {
- count += 1.0;
- }
- } else {
- count += 1.0;
- }
-
- if (count >= picked) {
- return E->get();
- }
- }
-
- ERR_FAIL_V(TileMapCell());
-}
-
-Map<Vector2i, TileMapCell> TileMapEditorTerrainsPlugin::_draw_terrains(const Map<Vector2i, TerrainsTilePattern> &p_to_paint, int p_terrain_set) const {
+Map<Vector2i, TileMapCell> TileMapEditorTerrainsPlugin::_draw_terrains(const Map<Vector2i, TileSet::TerrainsPattern> &p_to_paint, int p_terrain_set) const {
TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
if (!tile_map) {
return Map<Vector2i, TileMapCell>();
@@ -2795,20 +2233,20 @@ Map<Vector2i, TileMapCell> TileMapEditorTerrainsPlugin::_draw_terrains(const Map
Map<Vector2i, TileMapCell> output;
// Add the constraints from the added tiles.
- Set<TileMapEditorTerrainsPlugin::Constraint> added_tiles_constraints_set;
- for (const KeyValue<Vector2i, TerrainsTilePattern> &E_to_paint : p_to_paint) {
+ Set<TileMap::TerrainConstraint> added_tiles_constraints_set;
+ for (const KeyValue<Vector2i, TileSet::TerrainsPattern> &E_to_paint : p_to_paint) {
Vector2i coords = E_to_paint.key;
- TerrainsTilePattern terrains_tile_pattern = E_to_paint.value;
+ TileSet::TerrainsPattern terrains_pattern = E_to_paint.value;
- Set<TileMapEditorTerrainsPlugin::Constraint> cell_constraints = _get_constraints_from_added_tile(coords, p_terrain_set, terrains_tile_pattern);
- for (Set<TileMapEditorTerrainsPlugin::Constraint>::Element *E = cell_constraints.front(); E; E = E->next()) {
+ Set<TileMap::TerrainConstraint> cell_constraints = tile_map->get_terrain_constraints_from_added_tile(coords, p_terrain_set, terrains_pattern);
+ for (Set<TileMap::TerrainConstraint>::Element *E = cell_constraints.front(); E; E = E->next()) {
added_tiles_constraints_set.insert(E->get());
}
}
// Build the list of potential tiles to replace.
Set<Vector2i> potential_to_replace;
- for (const KeyValue<Vector2i, TerrainsTilePattern> &E_to_paint : p_to_paint) {
+ for (const KeyValue<Vector2i, TileSet::TerrainsPattern> &E_to_paint : p_to_paint) {
Vector2i coords = E_to_paint.key;
for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) {
if (tile_map->is_existing_neighbor(TileSet::CellNeighbor(i))) {
@@ -2823,42 +2261,42 @@ Map<Vector2i, TileMapCell> TileMapEditorTerrainsPlugin::_draw_terrains(const Map
// Set of tiles to replace
Set<Vector2i> to_replace;
- // Add the central tiles to the one to replace. TODO: maybe change that.
- for (const KeyValue<Vector2i, TerrainsTilePattern> &E_to_paint : p_to_paint) {
+ // Add the central tiles to the one to replace.
+ for (const KeyValue<Vector2i, TileSet::TerrainsPattern> &E_to_paint : p_to_paint) {
to_replace.insert(E_to_paint.key);
}
// Add the constraints from the surroundings of the modified areas.
- Set<TileMapEditorTerrainsPlugin::Constraint> removed_cells_constraints_set;
+ Set<TileMap::TerrainConstraint> removed_cells_constraints_set;
bool to_replace_modified = true;
while (to_replace_modified) {
// Get the constraints from the removed cells.
- removed_cells_constraints_set = _get_constraints_from_removed_cells_list(to_replace, p_terrain_set);
+ removed_cells_constraints_set = tile_map->get_terrain_constraints_from_removed_cells_list(tile_map_layer, to_replace, p_terrain_set);
// Filter the sources to make sure they are in the potential_to_replace.
- Map<Constraint, Set<Vector2i>> source_tiles_of_constraint;
- for (Set<Constraint>::Element *E = removed_cells_constraints_set.front(); E; E = E->next()) {
+ Map<TileMap::TerrainConstraint, Set<Vector2i>> per_constraint_tiles;
+ for (Set<TileMap::TerrainConstraint>::Element *E = removed_cells_constraints_set.front(); E; E = E->next()) {
Map<Vector2i, TileSet::CellNeighbor> sources_of_constraint = E->get().get_overlapping_coords_and_peering_bits();
for (const KeyValue<Vector2i, TileSet::CellNeighbor> &E_source_tile_of_constraint : sources_of_constraint) {
if (potential_to_replace.has(E_source_tile_of_constraint.key)) {
- source_tiles_of_constraint[E->get()].insert(E_source_tile_of_constraint.key);
+ per_constraint_tiles[E->get()].insert(E_source_tile_of_constraint.key);
}
}
}
to_replace_modified = false;
- for (Set<TileMapEditorTerrainsPlugin::Constraint>::Element *E = added_tiles_constraints_set.front(); E; E = E->next()) {
- Constraint c = E->get();
+ for (Set<TileMap::TerrainConstraint>::Element *E = added_tiles_constraints_set.front(); E; E = E->next()) {
+ TileMap::TerrainConstraint c = E->get();
// Check if we have a conflict in constraints.
if (removed_cells_constraints_set.has(c) && removed_cells_constraints_set.find(c)->get().get_terrain() != c.get_terrain()) {
// If we do, we search for a neighbor to remove.
- if (source_tiles_of_constraint.has(c) && !source_tiles_of_constraint[c].is_empty()) {
+ if (per_constraint_tiles.has(c) && !per_constraint_tiles[c].is_empty()) {
// Remove it.
- Vector2i to_add_to_remove = source_tiles_of_constraint[c].front()->get();
+ Vector2i to_add_to_remove = per_constraint_tiles[c].front()->get();
potential_to_replace.erase(to_add_to_remove);
to_replace.insert(to_add_to_remove);
to_replace_modified = true;
- for (KeyValue<Constraint, Set<Vector2i>> &E_source_tiles_of_constraint : source_tiles_of_constraint) {
+ for (KeyValue<TileMap::TerrainConstraint, Set<Vector2i>> &E_source_tiles_of_constraint : per_constraint_tiles) {
E_source_tiles_of_constraint.value.erase(to_add_to_remove);
}
break;
@@ -2868,22 +2306,27 @@ Map<Vector2i, TileMapCell> TileMapEditorTerrainsPlugin::_draw_terrains(const Map
}
// Combine all constraints together.
- Set<TileMapEditorTerrainsPlugin::Constraint> constraints = removed_cells_constraints_set;
- for (Set<TileMapEditorTerrainsPlugin::Constraint>::Element *E = added_tiles_constraints_set.front(); E; E = E->next()) {
+ Set<TileMap::TerrainConstraint> constraints = removed_cells_constraints_set;
+ for (Set<TileMap::TerrainConstraint>::Element *E = added_tiles_constraints_set.front(); E; E = E->next()) {
constraints.insert(E->get());
}
+ // Remove the central tiles from the ones to replace.
+ for (const KeyValue<Vector2i, TileSet::TerrainsPattern> &E_to_paint : p_to_paint) {
+ to_replace.erase(E_to_paint.key);
+ }
+
// Run WFC to fill the holes with the constraints.
- Map<Vector2i, TerrainsTilePattern> wfc_output = _wave_function_collapse(to_replace, p_terrain_set, constraints);
+ Map<Vector2i, TileSet::TerrainsPattern> wfc_output = tile_map->terrain_wave_function_collapse(to_replace, p_terrain_set, constraints);
- // Use the WFC run for the output.
- for (const KeyValue<Vector2i, TerrainsTilePattern> &E : wfc_output) {
- output[E.key] = _get_random_tile_from_pattern(p_terrain_set, E.value);
+ // Actually paint the tiles.
+ for (const KeyValue<Vector2i, TileSet::TerrainsPattern> &E_to_paint : p_to_paint) {
+ output[E_to_paint.key] = tile_set->get_random_tile_from_pattern(p_terrain_set, E_to_paint.value);
}
- // Override the WFC results to make sure at least the painted tiles are actually painted.
- for (const KeyValue<Vector2i, TerrainsTilePattern> &E_to_paint : p_to_paint) {
- output[E_to_paint.key] = _get_random_tile_from_pattern(p_terrain_set, E_to_paint.value);
+ // Use the WFC run for the output.
+ for (const KeyValue<Vector2i, TileSet::TerrainsPattern> &E : wfc_output) {
+ output[E.key] = tile_set->get_random_tile_from_pattern(p_terrain_set, E.value);
}
return output;
@@ -2901,10 +2344,23 @@ void TileMapEditorTerrainsPlugin::_stop_dragging() {
switch (drag_type) {
case DRAG_TYPE_PICK: {
Vector2i coords = tile_map->world_to_map(mpos);
- TileMapCell tile = tile_map->get_cell(tile_map_layer, coords);
+ TileMapCell cell = tile_map->get_cell(tile_map_layer, coords);
+
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ if (!tile_set.is_valid()) {
+ return;
+ }
- if (terrain_tiles.has(tile)) {
- Array terrains_tile_pattern = _build_terrains_tile_pattern(terrain_tiles[tile]);
+ TileData *tile_data = nullptr;
+
+ Ref<TileSetSource> source = tile_set->get_source(cell.source_id);
+ Ref<TileSetAtlasSource> atlas_source = source;
+ if (atlas_source.is_valid()) {
+ tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(cell.get_atlas_coords(), cell.alternative_tile));
+ }
+
+ if (tile_data) {
+ Array terrains_pattern = tile_data->get_terrains_pattern();
// Find the tree item for the right terrain set.
bool need_tree_item_switch = true;
@@ -2914,7 +2370,7 @@ void TileMapEditorTerrainsPlugin::_stop_dragging() {
if (metadata_dict.has("terrain_set") && metadata_dict.has("terrain_id")) {
int terrain_set = metadata_dict["terrain_set"];
int terrain_id = metadata_dict["terrain_id"];
- if (per_terrain_terrains_tile_patterns[terrain_set][terrain_id].has(terrains_tile_pattern)) {
+ if (per_terrain_terrains_patterns[terrain_set][terrain_id].has(terrains_pattern)) {
need_tree_item_switch = false;
}
}
@@ -2926,7 +2382,7 @@ void TileMapEditorTerrainsPlugin::_stop_dragging() {
if (metadata_dict.has("terrain_set") && metadata_dict.has("terrain_id")) {
int terrain_set = metadata_dict["terrain_set"];
int terrain_id = metadata_dict["terrain_id"];
- if (per_terrain_terrains_tile_patterns[terrain_set][terrain_id].has(terrains_tile_pattern)) {
+ if (per_terrain_terrains_patterns[terrain_set][terrain_id].has(terrains_pattern)) {
// Found
tree_item->select(0);
_update_tiles_list();
@@ -2940,10 +2396,10 @@ void TileMapEditorTerrainsPlugin::_stop_dragging() {
if (tree_item) {
for (int i = 0; i < terrains_tile_list->get_item_count(); i++) {
Dictionary metadata_dict = terrains_tile_list->get_item_metadata(i);
- TerrainsTilePattern in_meta_terrains_tile_pattern = metadata_dict["terrains_tile_pattern"];
+ TileSet::TerrainsPattern in_meta_terrains_pattern = metadata_dict["terrains_pattern"];
bool equals = true;
- for (int j = 0; j < terrains_tile_pattern.size(); j++) {
- if (terrains_tile_pattern[j] != in_meta_terrains_tile_pattern[j]) {
+ for (int j = 0; j < terrains_pattern.size(); j++) {
+ if (terrains_pattern[j] != in_meta_terrains_pattern[j]) {
equals = false;
break;
}
@@ -2999,7 +2455,7 @@ bool TileMapEditorTerrainsPlugin::forward_canvas_gui_input(const Ref<InputEvent>
ERR_FAIL_COND_V(tile_map_layer >= tile_map->get_layers_count(), false);
// Get the selected terrain.
- TerrainsTilePattern selected_terrains_tile_pattern;
+ TileSet::TerrainsPattern selected_terrains_pattern;
int selected_terrain_set = -1;
TreeItem *selected_tree_item = terrains_tree->get_selected();
@@ -3010,16 +2466,16 @@ bool TileMapEditorTerrainsPlugin::forward_canvas_gui_input(const Ref<InputEvent>
// Selected tile
if (erase_button->is_pressed()) {
- selected_terrains_tile_pattern.clear();
+ selected_terrains_pattern.clear();
for (uint32_t i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) {
TileSet::CellNeighbor side = TileSet::CellNeighbor(i);
if (tile_set->is_valid_peering_bit_terrain(selected_terrain_set, side)) {
- selected_terrains_tile_pattern.push_back(-1);
+ selected_terrains_pattern.push_back(-1);
}
}
} else if (terrains_tile_list->is_anything_selected()) {
metadata_dict = terrains_tile_list->get_item_metadata(terrains_tile_list->get_selected_items()[0]);
- selected_terrains_tile_pattern = metadata_dict["terrains_tile_pattern"];
+ selected_terrains_pattern = metadata_dict["terrains_pattern"];
}
}
@@ -3032,9 +2488,9 @@ bool TileMapEditorTerrainsPlugin::forward_canvas_gui_input(const Ref<InputEvent>
case DRAG_TYPE_PAINT: {
if (selected_terrain_set >= 0) {
Vector<Vector2i> line = TileMapEditor::get_line(tile_map, tile_map->world_to_map(drag_last_mouse_pos), tile_map->world_to_map(mpos));
- Map<Vector2i, TerrainsTilePattern> to_draw;
+ Map<Vector2i, TileSet::TerrainsPattern> to_draw;
for (int i = 0; i < line.size(); i++) {
- to_draw[line[i]] = selected_terrains_tile_pattern;
+ to_draw[line[i]] = selected_terrains_pattern;
}
Map<Vector2i, TileMapCell> modified = _draw_terrains(to_draw, selected_terrain_set);
for (const KeyValue<Vector2i, TileMapCell> &E : modified) {
@@ -3066,14 +2522,14 @@ bool TileMapEditorTerrainsPlugin::forward_canvas_gui_input(const Ref<InputEvent>
drag_type = DRAG_TYPE_PICK;
} else {
// Paint otherwise.
- if (selected_terrain_set >= 0 && !selected_terrains_tile_pattern.is_empty() && tool_buttons_group->get_pressed_button() == paint_tool_button) {
+ if (selected_terrain_set >= 0 && !selected_terrains_pattern.is_empty() && tool_buttons_group->get_pressed_button() == paint_tool_button) {
drag_type = DRAG_TYPE_PAINT;
drag_start_mouse_pos = mpos;
drag_modified.clear();
- Map<Vector2i, TerrainsTilePattern> terrains_to_draw;
- terrains_to_draw[tile_map->world_to_map(mpos)] = selected_terrains_tile_pattern;
+ Map<Vector2i, TileSet::TerrainsPattern> terrains_to_draw;
+ terrains_to_draw[tile_map->world_to_map(mpos)] = selected_terrains_pattern;
Map<Vector2i, TileMapCell> to_draw = _draw_terrains(terrains_to_draw, selected_terrain_set);
for (const KeyValue<Vector2i, TileMapCell> &E : to_draw) {
@@ -3097,26 +2553,6 @@ bool TileMapEditorTerrainsPlugin::forward_canvas_gui_input(const Ref<InputEvent>
return false;
}
-TileMapEditorTerrainsPlugin::TerrainsTilePattern TileMapEditorTerrainsPlugin::_build_terrains_tile_pattern(TileData *p_tile_data) {
- TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
- if (!tile_map) {
- return TerrainsTilePattern();
- }
-
- Ref<TileSet> tile_set = tile_map->get_tileset();
- if (!tile_set.is_valid()) {
- return TerrainsTilePattern();
- }
-
- TerrainsTilePattern output;
- for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) {
- if (tile_set->is_valid_peering_bit_terrain(p_tile_data->get_terrain_set(), TileSet::CellNeighbor(i))) {
- output.push_back(p_tile_data->get_peering_bit_terrain(TileSet::CellNeighbor(i)));
- }
- }
- return output;
-}
-
void TileMapEditorTerrainsPlugin::_update_terrains_cache() {
TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
if (!tile_map) {
@@ -3128,45 +2564,12 @@ void TileMapEditorTerrainsPlugin::_update_terrains_cache() {
return;
}
- // Compute the tile sides.
- tile_sides.clear();
- TileSet::TileShape shape = tile_set->get_tile_shape();
- if (shape == TileSet::TILE_SHAPE_SQUARE) {
- tile_sides.push_back(TileSet::CELL_NEIGHBOR_RIGHT_SIDE);
- tile_sides.push_back(TileSet::CELL_NEIGHBOR_BOTTOM_SIDE);
- tile_sides.push_back(TileSet::CELL_NEIGHBOR_LEFT_SIDE);
- tile_sides.push_back(TileSet::CELL_NEIGHBOR_TOP_SIDE);
- } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC) {
- tile_sides.push_back(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE);
- tile_sides.push_back(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE);
- tile_sides.push_back(TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE);
- tile_sides.push_back(TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE);
- } else {
- if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) {
- tile_sides.push_back(TileSet::CELL_NEIGHBOR_RIGHT_SIDE);
- tile_sides.push_back(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE);
- tile_sides.push_back(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE);
- tile_sides.push_back(TileSet::CELL_NEIGHBOR_LEFT_SIDE);
- tile_sides.push_back(TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE);
- tile_sides.push_back(TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE);
- } else {
- tile_sides.push_back(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE);
- tile_sides.push_back(TileSet::CELL_NEIGHBOR_BOTTOM_SIDE);
- tile_sides.push_back(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE);
- tile_sides.push_back(TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE);
- tile_sides.push_back(TileSet::CELL_NEIGHBOR_TOP_SIDE);
- tile_sides.push_back(TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE);
- }
- }
-
// Organizes tiles into structures.
- per_terrain_terrains_tile_patterns_tiles.resize(tile_set->get_terrain_sets_count());
- per_terrain_terrains_tile_patterns.resize(tile_set->get_terrain_sets_count());
+ per_terrain_terrains_patterns.resize(tile_set->get_terrain_sets_count());
for (int i = 0; i < tile_set->get_terrain_sets_count(); i++) {
- per_terrain_terrains_tile_patterns_tiles[i].clear();
- per_terrain_terrains_tile_patterns[i].resize(tile_set->get_terrains_count(i));
- for (int j = 0; j < (int)per_terrain_terrains_tile_patterns[i].size(); j++) {
- per_terrain_terrains_tile_patterns[i][j].clear();
+ per_terrain_terrains_patterns[i].resize(tile_set->get_terrains_count(i));
+ for (int j = 0; j < (int)per_terrain_terrains_patterns[i].size(); j++) {
+ per_terrain_terrains_patterns[i][j].clear();
}
}
@@ -3184,22 +2587,20 @@ void TileMapEditorTerrainsPlugin::_update_terrains_cache() {
TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(tile_id, alternative_id));
int terrain_set = tile_data->get_terrain_set();
if (terrain_set >= 0) {
- ERR_FAIL_INDEX(terrain_set, (int)per_terrain_terrains_tile_patterns.size());
+ ERR_FAIL_INDEX(terrain_set, (int)per_terrain_terrains_patterns.size());
TileMapCell cell;
cell.source_id = source_id;
cell.set_atlas_coords(tile_id);
cell.alternative_tile = alternative_id;
- TerrainsTilePattern terrains_tile_pattern = _build_terrains_tile_pattern(tile_data);
+ TileSet::TerrainsPattern terrains_pattern = tile_data->get_terrains_pattern();
// Terrain bits.
- for (int i = 0; i < terrains_tile_pattern.size(); i++) {
- int terrain = terrains_tile_pattern[i];
- if (terrain >= 0 && terrain < (int)per_terrain_terrains_tile_patterns[terrain_set].size()) {
- per_terrain_terrains_tile_patterns[terrain_set][terrain].insert(terrains_tile_pattern);
- terrain_tiles[cell] = tile_data;
- per_terrain_terrains_tile_patterns_tiles[terrain_set][terrains_tile_pattern].insert(cell);
+ for (int i = 0; i < terrains_pattern.size(); i++) {
+ int terrain = terrains_pattern[i];
+ if (terrain >= 0 && terrain < (int)per_terrain_terrains_patterns[terrain_set].size()) {
+ per_terrain_terrains_patterns[terrain_set][terrain].insert(terrains_pattern);
}
}
}
@@ -3207,22 +2608,6 @@ void TileMapEditorTerrainsPlugin::_update_terrains_cache() {
}
}
}
-
- // Add the empty cell in the possible patterns and cells.
- for (int i = 0; i < tile_set->get_terrain_sets_count(); i++) {
- TerrainsTilePattern empty_pattern;
- for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) {
- if (tile_set->is_valid_peering_bit_terrain(i, TileSet::CellNeighbor(j))) {
- empty_pattern.push_back(-1);
- }
- }
-
- TileMapCell empty_cell;
- empty_cell.source_id = TileSet::INVALID_SOURCE;
- empty_cell.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS);
- empty_cell.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE;
- per_terrain_terrains_tile_patterns_tiles[i][empty_pattern].insert(empty_cell);
- }
}
void TileMapEditorTerrainsPlugin::_update_terrains_tree() {
@@ -3291,12 +2676,13 @@ void TileMapEditorTerrainsPlugin::_update_tiles_list() {
Dictionary metadata_dict = selected_tree_item->get_metadata(0);
int selected_terrain_set = metadata_dict["terrain_set"];
int selected_terrain_id = metadata_dict["terrain_id"];
- ERR_FAIL_INDEX(selected_terrain_set, (int)per_terrain_terrains_tile_patterns.size());
- ERR_FAIL_INDEX(selected_terrain_id, (int)per_terrain_terrains_tile_patterns[selected_terrain_set].size());
+ ERR_FAIL_INDEX(selected_terrain_set, tile_set->get_terrain_sets_count());
+ ERR_FAIL_INDEX(selected_terrain_id, tile_set->get_terrains_count(selected_terrain_set));
// Sort the items in a map by the number of corresponding terrains.
- Map<int, Set<TerrainsTilePattern>> sorted;
- for (Set<TerrainsTilePattern>::Element *E = per_terrain_terrains_tile_patterns[selected_terrain_set][selected_terrain_id].front(); E; E = E->next()) {
+ Map<int, Set<TileSet::TerrainsPattern>> sorted;
+
+ for (Set<TileSet::TerrainsPattern>::Element *E = per_terrain_terrains_patterns[selected_terrain_set][selected_terrain_id].front(); E; E = E->next()) {
// Count the number of matching sides/terrains.
int count = 0;
@@ -3308,9 +2694,9 @@ void TileMapEditorTerrainsPlugin::_update_tiles_list() {
sorted[count].insert(E->get());
}
- for (Map<int, Set<TerrainsTilePattern>>::Element *E_set = sorted.back(); E_set; E_set = E_set->prev()) {
- for (Set<TerrainsTilePattern>::Element *E = E_set->get().front(); E; E = E->next()) {
- TerrainsTilePattern terrains_tile_pattern = E->get();
+ for (Map<int, Set<TileSet::TerrainsPattern>>::Element *E_set = sorted.back(); E_set; E_set = E_set->prev()) {
+ for (Set<TileSet::TerrainsPattern>::Element *E = E_set->get().front(); E; E = E->next()) {
+ TileSet::TerrainsPattern terrains_pattern = E->get();
// Get the icon.
Ref<Texture2D> icon;
@@ -3318,15 +2704,15 @@ void TileMapEditorTerrainsPlugin::_update_tiles_list() {
bool transpose = false;
double max_probability = -1.0;
- for (Set<TileMapCell>::Element *E_tile_map_cell = per_terrain_terrains_tile_patterns_tiles[selected_terrain_set][terrains_tile_pattern].front(); E_tile_map_cell; E_tile_map_cell = E_tile_map_cell->next()) {
- Ref<TileSetSource> source = tile_set->get_source(E_tile_map_cell->get().source_id);
+ for (const TileMapCell &cell : tile_set->get_tiles_for_terrains_pattern(selected_terrain_set, terrains_pattern)) {
+ Ref<TileSetSource> source = tile_set->get_source(cell.source_id);
Ref<TileSetAtlasSource> atlas_source = source;
if (atlas_source.is_valid()) {
- TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(E_tile_map_cell->get().get_atlas_coords(), E_tile_map_cell->get().alternative_tile));
+ TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(cell.get_atlas_coords(), cell.alternative_tile));
if (tile_data->get_probability() > max_probability) {
icon = atlas_source->get_texture();
- region = atlas_source->get_tile_texture_region(E_tile_map_cell->get().get_atlas_coords());
+ region = atlas_source->get_tile_texture_region(cell.get_atlas_coords());
if (tile_data->get_flip_h()) {
region.position.x += region.size.x;
region.size.x = -region.size.x;
@@ -3347,7 +2733,7 @@ void TileMapEditorTerrainsPlugin::_update_tiles_list() {
terrains_tile_list->set_item_icon_region(item_index, region);
terrains_tile_list->set_item_icon_transposed(item_index, transpose);
Dictionary list_metadata_dict;
- list_metadata_dict["terrains_tile_pattern"] = terrains_tile_pattern;
+ list_metadata_dict["terrains_pattern"] = terrains_pattern;
terrains_tile_list->set_item_metadata(item_index, list_metadata_dict);
}
}
diff --git a/editor/plugins/tiles/tile_map_editor.h b/editor/plugins/tiles/tile_map_editor.h
index 1f1a560113..0513a7b365 100644
--- a/editor/plugins/tiles/tile_map_editor.h
+++ b/editor/plugins/tiles/tile_map_editor.h
@@ -243,66 +243,16 @@ private:
Map<Vector2i, TileMapCell> drag_modified;
// Painting
- class Constraint {
- private:
- const TileMap *tile_map;
- Vector2i base_cell_coords = Vector2i();
- int bit = -1;
- int terrain = -1;
-
- public:
- // TODO implement difference operator.
- bool operator<(const Constraint &p_other) const {
- if (base_cell_coords == p_other.base_cell_coords) {
- return bit < p_other.bit;
- }
- return base_cell_coords < p_other.base_cell_coords;
- }
-
- String to_string() const {
- return vformat("Constraint {pos:%s, bit:%d, terrain:%d}", base_cell_coords, bit, terrain);
- }
-
- Vector2i get_base_cell_coords() const {
- return base_cell_coords;
- }
-
- Map<Vector2i, TileSet::CellNeighbor> get_overlapping_coords_and_peering_bits() const;
-
- void set_terrain(int p_terrain) {
- terrain = p_terrain;
- }
-
- int get_terrain() const {
- return terrain;
- }
-
- Constraint(const TileMap *p_tile_map, const Vector2i &p_position, const TileSet::CellNeighbor &p_bit, int p_terrain);
- Constraint() {}
- };
-
- typedef Array TerrainsTilePattern;
-
- Set<TerrainsTilePattern> _get_valid_terrains_tile_patterns_for_constraints(int p_terrain_set, const Vector2i &p_position, Set<TileMapEditorTerrainsPlugin::Constraint> p_constraints) const;
- Set<TileMapEditorTerrainsPlugin::Constraint> _get_constraints_from_removed_cells_list(const Set<Vector2i> &p_to_replace, int p_terrain_set) const;
- Set<TileMapEditorTerrainsPlugin::Constraint> _get_constraints_from_added_tile(Vector2i p_position, int p_terrain_set, TerrainsTilePattern p_terrains_tile_pattern) const;
- Map<Vector2i, TerrainsTilePattern> _wave_function_collapse(const Set<Vector2i> &p_to_replace, int p_terrain_set, const Set<TileMapEditorTerrainsPlugin::Constraint> p_constraints) const;
- TileMapCell _get_random_tile_from_pattern(int p_terrain_set, TerrainsTilePattern p_terrain_tile_pattern) const;
- Map<Vector2i, TileMapCell> _draw_terrains(const Map<Vector2i, TerrainsTilePattern> &p_to_paint, int p_terrain_set) const;
+ Map<Vector2i, TileMapCell> _draw_terrains(const Map<Vector2i, TileSet::TerrainsPattern> &p_to_paint, int p_terrain_set) const;
void _stop_dragging();
- // Cached data.
- TerrainsTilePattern _build_terrains_tile_pattern(TileData *p_tile_data);
- LocalVector<Map<TerrainsTilePattern, Set<TileMapCell>>> per_terrain_terrains_tile_patterns_tiles;
- LocalVector<LocalVector<Set<TerrainsTilePattern>>> per_terrain_terrains_tile_patterns;
-
- Map<TileMapCell, TileData *> terrain_tiles;
- LocalVector<TileSet::CellNeighbor> tile_sides;
-
// Bottom panel.
Tree *terrains_tree;
ItemList *terrains_tile_list;
+ // Cache.
+ LocalVector<LocalVector<Set<TileSet::TerrainsPattern>>> per_terrain_terrains_patterns;
+
// Update functions.
void _update_terrains_cache();
void _update_terrains_tree();
diff --git a/methods.py b/methods.py
index 77a1085baf..b2eb7b7c77 100644
--- a/methods.py
+++ b/methods.py
@@ -1,9 +1,9 @@
-import collections
import os
import re
import glob
import subprocess
from collections import OrderedDict
+from collections.abc import Mapping
from typing import Iterator
# We need to define our own `Action` method to control the verbosity of output
@@ -662,7 +662,7 @@ def generate_vs_project(env, num_jobs):
batch_file = find_visual_c_batch_file(env)
if batch_file:
- class ModuleConfigs(collections.Mapping):
+ class ModuleConfigs(Mapping):
# This version information (Win32, x64, Debug, Release, Release_Debug seems to be
# required for Visual Studio to understand that it needs to generate an NMAKE
# project. Do not modify without knowing what you are doing.
diff --git a/misc/hooks/pre-commit-clang-format b/misc/hooks/pre-commit-clang-format
index 3112f1af5f..de5d9c3f06 100755
--- a/misc/hooks/pre-commit-clang-format
+++ b/misc/hooks/pre-commit-clang-format
@@ -76,7 +76,7 @@ fi
# To get consistent formatting, we recommend contributors to use the same
# clang-format version as CI.
-RECOMMENDED_CLANG_FORMAT_MAJOR_MIN="11"
+RECOMMENDED_CLANG_FORMAT_MAJOR_MIN="12"
RECOMMENDED_CLANG_FORMAT_MAJOR_MAX="13"
if [ ! -x "$CLANG_FORMAT" ] ; then
@@ -146,7 +146,7 @@ do
# +++ - timestamp
# to both lines working on the same file and having a/ and b/ prefix.
# Else it can not be applied with 'git apply'.
- "$CLANG_FORMAT" -style=file "$file" | \
+ "$CLANG_FORMAT" -style=file "$file" --Wno-error=unknown | \
diff -u "$file" - | \
sed -e "1s|--- |--- a/|" -e "2s|+++ -|+++ b/$file|" >> "$patch"
done
diff --git a/misc/scripts/clang_format.sh b/misc/scripts/clang_format.sh
index bcd63aa73b..b0020da597 100755
--- a/misc/scripts/clang_format.sh
+++ b/misc/scripts/clang_format.sh
@@ -23,7 +23,7 @@ while IFS= read -rd '' f; do
for extension in ${CLANG_FORMAT_FILE_EXTS[@]}; do
if [[ "$f" == *"$extension" ]]; then
# Run clang-format.
- clang-format -i "$f"
+ clang-format --Wno-error=unknown -i "$f"
# Fix copyright headers, but not all files get them.
if [[ "$f" == *"inc" ]]; then
continue 2
diff --git a/modules/csg/csg_shape.cpp b/modules/csg/csg_shape.cpp
index 14e7896295..863936ab8c 100644
--- a/modules/csg/csg_shape.cpp
+++ b/modules/csg/csg_shape.cpp
@@ -576,6 +576,7 @@ void CSGShape3D::_validate_property(PropertyInfo &property) const {
} else if (is_collision_prefixed && !bool(get("use_collision"))) {
property.usage = PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL;
}
+ GeometryInstance3D::_validate_property(property);
}
Array CSGShape3D::get_meshes() const {
diff --git a/modules/fbx/tools/import_utils.cpp b/modules/fbx/tools/import_utils.cpp
index 66b0153308..bb95d120af 100644
--- a/modules/fbx/tools/import_utils.cpp
+++ b/modules/fbx/tools/import_utils.cpp
@@ -45,27 +45,27 @@ Basis ImportUtils::EulerToBasis(FBXDocParser::Model::RotOrder mode, const Vector
// by simply invert its order: https://www.cs.utexas.edu/~theshark/courses/cs354/lectures/cs354-14.pdf
switch (mode) {
case FBXDocParser::Model::RotOrder_EulerXYZ:
- ret.set_euler_zyx(p_rotation);
+ ret.set_euler(p_rotation, Basis::EULER_ORDER_XYZ);
break;
case FBXDocParser::Model::RotOrder_EulerXZY:
- ret.set_euler_yzx(p_rotation);
+ ret.set_euler(p_rotation, Basis::EULER_ORDER_XZY);
break;
case FBXDocParser::Model::RotOrder_EulerYZX:
- ret.set_euler_xzy(p_rotation);
+ ret.set_euler(p_rotation, Basis::EULER_ORDER_YZX);
break;
case FBXDocParser::Model::RotOrder_EulerYXZ:
- ret.set_euler_zxy(p_rotation);
+ ret.set_euler(p_rotation, Basis::EULER_ORDER_YXZ);
break;
case FBXDocParser::Model::RotOrder_EulerZXY:
- ret.set_euler_yxz(p_rotation);
+ ret.set_euler(p_rotation, Basis::EULER_ORDER_ZXY);
break;
case FBXDocParser::Model::RotOrder_EulerZYX:
- ret.set_euler_xyz(p_rotation);
+ ret.set_euler(p_rotation, Basis::EULER_ORDER_ZYX);
break;
case FBXDocParser::Model::RotOrder_SphericXYZ:
@@ -89,22 +89,22 @@ Vector3 ImportUtils::BasisToEuler(FBXDocParser::Model::RotOrder mode, const Basi
// by simply invert its order: https://www.cs.utexas.edu/~theshark/courses/cs354/lectures/cs354-14.pdf
switch (mode) {
case FBXDocParser::Model::RotOrder_EulerXYZ:
- return p_rotation.get_euler_zyx();
+ return p_rotation.get_euler(Basis::EULER_ORDER_XYZ);
case FBXDocParser::Model::RotOrder_EulerXZY:
- return p_rotation.get_euler_yzx();
+ return p_rotation.get_euler(Basis::EULER_ORDER_XZY);
case FBXDocParser::Model::RotOrder_EulerYZX:
- return p_rotation.get_euler_xzy();
+ return p_rotation.get_euler(Basis::EULER_ORDER_YZX);
case FBXDocParser::Model::RotOrder_EulerYXZ:
- return p_rotation.get_euler_zxy();
+ return p_rotation.get_euler(Basis::EULER_ORDER_YXZ);
case FBXDocParser::Model::RotOrder_EulerZXY:
- return p_rotation.get_euler_yxz();
+ return p_rotation.get_euler(Basis::EULER_ORDER_ZXY);
case FBXDocParser::Model::RotOrder_EulerZYX:
- return p_rotation.get_euler_xyz();
+ return p_rotation.get_euler(Basis::EULER_ORDER_ZYX);
case FBXDocParser::Model::RotOrder_SphericXYZ:
// TODO
diff --git a/modules/gdscript/tests/scripts/runtime/features/stringify.gd b/modules/gdscript/tests/scripts/runtime/features/stringify.gd
index de269b92b3..fead2df854 100644
--- a/modules/gdscript/tests/scripts/runtime/features/stringify.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/stringify.gd
@@ -17,7 +17,7 @@ func test():
print(Plane(1, 2, 3, 4))
print(Quaternion(1, 2, 3, 4))
print(AABB(Vector3.ZERO, Vector3.ONE))
- print(Basis(Vector3(0, 0, 0)))
+ print(Basis.from_euler(Vector3(0, 0, 0)))
print(Transform3D.IDENTITY)
print(Color(1, 2, 3, 4))
diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp
index b8da3a713b..3a8cab7e7e 100644
--- a/modules/gltf/gltf_document.cpp
+++ b/modules/gltf/gltf_document.cpp
@@ -120,26 +120,26 @@ Error GLTFDocument::serialize(Ref<GLTFState> state, Node *p_root, const String &
return Error::FAILED;
}
- /* STEP 7 SERIALIZE IMAGES */
- err = _serialize_images(state, p_path);
+ /* STEP 7 SERIALIZE ANIMATIONS */
+ err = _serialize_animations(state);
if (err != OK) {
return Error::FAILED;
}
- /* STEP 8 SERIALIZE TEXTURES */
- err = _serialize_textures(state);
+ /* STEP 8 SERIALIZE ACCESSORS */
+ err = _encode_accessors(state);
if (err != OK) {
return Error::FAILED;
}
- // /* STEP 9 SERIALIZE ANIMATIONS */
- err = _serialize_animations(state);
+ /* STEP 9 SERIALIZE IMAGES */
+ err = _serialize_images(state, p_path);
if (err != OK) {
return Error::FAILED;
}
- /* STEP 10 SERIALIZE ACCESSORS */
- err = _encode_accessors(state);
+ /* STEP 10 SERIALIZE TEXTURES */
+ err = _serialize_textures(state);
if (err != OK) {
return Error::FAILED;
}
@@ -4369,6 +4369,10 @@ Error GLTFDocument::_serialize_skins(Ref<GLTFState> state) {
json_skin["name"] = gltf_skin->get_name();
json_skins.push_back(json_skin);
}
+ if (!state->skins.size()) {
+ return OK;
+ }
+
state->json["skins"] = json_skins;
return OK;
}
@@ -6460,7 +6464,7 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> state, AnimationPlayer *ap,
for (int32_t shape_i = 0; shape_i < mesh->get_blend_shape_count(); shape_i++) {
String shape_name = mesh->get_blend_shape_name(shape_i);
NodePath shape_path = String(path) + ":" + shape_name;
- int32_t shape_track_i = animation->find_track(shape_path);
+ int32_t shape_track_i = animation->find_track(shape_path, Animation::TYPE_BLEND_SHAPE);
if (shape_track_i == -1) {
GLTFAnimation::Channel<float> weight;
weight.interpolation = GLTFAnimation::INTERP_LINEAR;
@@ -6756,33 +6760,36 @@ Error GLTFDocument::_serialize_file(Ref<GLTFState> state, const String p_path) {
const uint32_t magic = 0x46546C67; // GLTF
const int32_t header_size = 12;
const int32_t chunk_header_size = 8;
-
- for (int32_t pad_i = 0; pad_i < (chunk_header_size + json.utf8().length()) % 4; pad_i++) {
- json += " ";
- }
CharString cs = json.utf8();
- const uint32_t text_chunk_length = cs.length();
-
+ const uint32_t text_data_length = cs.length();
+ const uint32_t text_chunk_length = ((text_data_length + 3) & (~3));
const uint32_t text_chunk_type = 0x4E4F534A; //JSON
- int32_t binary_data_length = 0;
+
+ uint32_t binary_data_length = 0;
if (state->buffers.size()) {
binary_data_length = state->buffers[0].size();
}
- const int32_t binary_chunk_length = binary_data_length;
- const int32_t binary_chunk_type = 0x004E4942; //BIN
+ const uint32_t binary_chunk_length = ((binary_data_length + 3) & (~3));
+ const uint32_t binary_chunk_type = 0x004E4942; //BIN
f->create(FileAccess::ACCESS_RESOURCES);
f->store_32(magic);
f->store_32(state->major_version); // version
- f->store_32(header_size + chunk_header_size + text_chunk_length + chunk_header_size + binary_data_length); // length
+ f->store_32(header_size + chunk_header_size + text_chunk_length + chunk_header_size + binary_chunk_length); // length
f->store_32(text_chunk_length);
f->store_32(text_chunk_type);
f->store_buffer((uint8_t *)&cs[0], cs.length());
+ for (uint32_t pad_i = text_data_length; pad_i < text_chunk_length; pad_i++) {
+ f->store_8(' ');
+ }
if (binary_chunk_length) {
f->store_32(binary_chunk_length);
f->store_32(binary_chunk_type);
f->store_buffer(state->buffers[0].ptr(), binary_data_length);
}
+ for (uint32_t pad_i = binary_data_length; pad_i < binary_chunk_length; pad_i++) {
+ f->store_8(0);
+ }
f->close();
} else {
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp
index 26a04a358d..531f600c3f 100644
--- a/modules/mono/csharp_script.cpp
+++ b/modules/mono/csharp_script.cpp
@@ -1813,8 +1813,8 @@ void CSharpInstance::get_event_signals_state_for_reloading(List<Pair<StringName,
}
void CSharpInstance::get_property_list(List<PropertyInfo> *p_properties) const {
- for (const KeyValue<StringName, PropertyInfo> &E : script->member_info) {
- p_properties->push_back(E.value);
+ for (OrderedHashMap<StringName, PropertyInfo>::ConstElement E = script->member_info.front(); E; E = E.next()) {
+ p_properties->push_front(E.value());
}
// Call _get_property_list
@@ -1839,10 +1839,9 @@ void CSharpInstance::get_property_list(List<PropertyInfo> *p_properties) const {
for (int i = 0, size = array.size(); i < size; i++) {
p_properties->push_back(PropertyInfo::from_dict(array.get(i)));
}
- return;
}
- break;
+ return;
}
top = top->get_parent_class();
@@ -1865,8 +1864,9 @@ Variant::Type CSharpInstance::get_property_type(const StringName &p_name, bool *
}
void CSharpInstance::get_method_list(List<MethodInfo> *p_list) const {
- if (!script->is_valid() || !script->script_class)
+ if (!script->is_valid() || !script->script_class) {
return;
+ }
GD_MONO_SCOPE_THREAD_ATTACH;
@@ -3499,9 +3499,9 @@ Ref<Script> CSharpScript::get_base_script() const {
return Ref<Script>();
}
-void CSharpScript::get_script_property_list(List<PropertyInfo> *p_list) const {
- for (const KeyValue<StringName, PropertyInfo> &E : member_info) {
- p_list->push_back(E.value);
+void CSharpScript::get_script_property_list(List<PropertyInfo> *r_list) const {
+ for (OrderedHashMap<StringName, PropertyInfo>::ConstElement E = member_info.front(); E; E = E.next()) {
+ r_list->push_front(E.value());
}
}
diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h
index afc17f694a..c998d9c1e4 100644
--- a/modules/mono/csharp_script.h
+++ b/modules/mono/csharp_script.h
@@ -154,7 +154,7 @@ private:
Set<StringName> exported_members_names;
#endif
- Map<StringName, PropertyInfo> member_info;
+ OrderedHashMap<StringName, PropertyInfo> member_info;
void _clear();
@@ -215,7 +215,7 @@ public:
void get_script_signal_list(List<MethodInfo> *r_signals) const override;
bool get_property_default_value(const StringName &p_property, Variant &r_value) const override;
- void get_script_property_list(List<PropertyInfo> *p_list) const override;
+ void get_script_property_list(List<PropertyInfo> *r_list) const override;
void update_exports() override;
void get_members(Set<StringName> *p_members) override;
diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp
index e95369ead7..c459141265 100644
--- a/modules/text_server_adv/text_server_adv.cpp
+++ b/modules/text_server_adv/text_server_adv.cpp
@@ -1211,6 +1211,7 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_glyph(FontDataAdvanced *p_font_d
}
_FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontDataAdvanced *p_font_data, const Vector2i &p_size) const {
+ ERR_FAIL_COND_V(p_size.x <= 0, false);
if (p_font_data->cache.has(p_size)) {
return true;
}
diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp
index 193588c4ed..f598eb80ea 100644
--- a/modules/text_server_fb/text_server_fb.cpp
+++ b/modules/text_server_fb/text_server_fb.cpp
@@ -670,6 +670,7 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_glyph(FontDataFallback *p_font_d
}
_FORCE_INLINE_ bool TextServerFallback::_ensure_cache_for_size(FontDataFallback *p_font_data, const Vector2i &p_size) const {
+ ERR_FAIL_COND_V(p_size.x <= 0, false);
if (p_font_data->cache.has(p_size)) {
return true;
}
diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp
index b546eaefa6..87c94b7628 100644
--- a/scene/2d/tile_map.cpp
+++ b/scene/2d/tile_map.cpp
@@ -34,6 +34,324 @@
#include "servers/navigation_server_2d.h"
+Map<Vector2i, TileSet::CellNeighbor> TileMap::TerrainConstraint::get_overlapping_coords_and_peering_bits() const {
+ Map<Vector2i, TileSet::CellNeighbor> output;
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ ERR_FAIL_COND_V(!tile_set.is_valid(), output);
+
+ TileSet::TileShape shape = tile_set->get_tile_shape();
+ if (shape == TileSet::TILE_SHAPE_SQUARE) {
+ switch (bit) {
+ case 0:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_RIGHT_SIDE;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_SIDE;
+ break;
+ case 1:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER;
+ break;
+ case 2:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_SIDE;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_SIDE;
+ break;
+ case 3:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER;
+ break;
+ default:
+ ERR_FAIL_V(output);
+ }
+ } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC) {
+ switch (bit) {
+ case 0:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_RIGHT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_CORNER)] = TileSet::CELL_NEIGHBOR_LEFT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_CORNER;
+ break;
+ case 1:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE;
+ break;
+ case 2:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_CORNER)] = TileSet::CELL_NEIGHBOR_TOP_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_RIGHT_CORNER;
+ break;
+ case 3:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE;
+ break;
+ default:
+ ERR_FAIL_V(output);
+ }
+ } else {
+ // Half offset shapes.
+ TileSet::TileOffsetAxis offset_axis = tile_set->get_tile_offset_axis();
+ if (offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) {
+ switch (bit) {
+ case 0:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_RIGHT_SIDE;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_SIDE;
+ break;
+ case 1:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_CORNER;
+ break;
+ case 2:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE;
+ break;
+ case 3:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER;
+ break;
+ case 4:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE;
+ break;
+ default:
+ ERR_FAIL_V(output);
+ }
+ } else {
+ switch (bit) {
+ case 0:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_RIGHT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER;
+ break;
+ case 1:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE;
+ break;
+ case 2:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER;
+ break;
+ case 3:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_SIDE;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_SIDE;
+ break;
+ case 4:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE;
+ break;
+ default:
+ ERR_FAIL_V(output);
+ }
+ }
+ }
+ return output;
+}
+
+TileMap::TerrainConstraint::TerrainConstraint(const TileMap *p_tile_map, const Vector2i &p_position, const TileSet::CellNeighbor &p_bit, int p_terrain) {
+ // The way we build the constraint make it easy to detect conflicting constraints.
+ tile_map = p_tile_map;
+
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ ERR_FAIL_COND(!tile_set.is_valid());
+
+ TileSet::TileShape shape = tile_set->get_tile_shape();
+ if (shape == TileSet::TILE_SHAPE_SQUARE) {
+ switch (p_bit) {
+ case TileSet::CELL_NEIGHBOR_RIGHT_SIDE:
+ bit = 0;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER:
+ bit = 1;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE:
+ bit = 2;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER:
+ bit = 1;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_LEFT_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_LEFT_SIDE:
+ bit = 0;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_LEFT_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER:
+ bit = 1;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_SIDE:
+ bit = 2;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER:
+ bit = 1;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_SIDE);
+ break;
+ default:
+ ERR_FAIL();
+ break;
+ }
+ } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC) {
+ switch (p_bit) {
+ case TileSet::CELL_NEIGHBOR_RIGHT_CORNER:
+ bit = 1;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE:
+ bit = 0;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER:
+ bit = 1;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE:
+ bit = 2;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_LEFT_CORNER:
+ bit = 1;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE:
+ bit = 0;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_CORNER:
+ bit = 1;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_CORNER);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE:
+ bit = 2;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE);
+ break;
+ default:
+ ERR_FAIL();
+ break;
+ }
+ } else {
+ // Half-offset shapes
+ TileSet::TileOffsetAxis offset_axis = tile_set->get_tile_offset_axis();
+ if (offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) {
+ switch (p_bit) {
+ case TileSet::CELL_NEIGHBOR_RIGHT_SIDE:
+ bit = 0;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER:
+ bit = 1;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE:
+ bit = 2;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER:
+ bit = 3;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE:
+ bit = 4;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER:
+ bit = 1;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_LEFT_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_LEFT_SIDE:
+ bit = 0;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_LEFT_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER:
+ bit = 3;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE:
+ bit = 2;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_CORNER:
+ bit = 1;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE:
+ bit = 4;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER:
+ bit = 3;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE);
+ break;
+ default:
+ ERR_FAIL();
+ break;
+ }
+ } else {
+ switch (p_bit) {
+ case TileSet::CELL_NEIGHBOR_RIGHT_CORNER:
+ bit = 0;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE:
+ bit = 1;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER:
+ bit = 2;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE:
+ bit = 3;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER:
+ bit = 0;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE:
+ bit = 4;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_LEFT_CORNER:
+ bit = 2;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE:
+ bit = 1;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER:
+ bit = 0;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_SIDE:
+ bit = 3;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER:
+ bit = 2;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE:
+ bit = 4;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE);
+ break;
+ default:
+ ERR_FAIL();
+ break;
+ }
+ }
+ }
+ terrain = p_terrain;
+}
+
Vector2i TileMap::transform_coords_layout(Vector2i p_coords, TileSet::TileOffsetAxis p_offset_axis, TileSet::TileLayout p_from_layout, TileSet::TileLayout p_to_layout) {
// Transform to stacked layout.
Vector2i output = p_coords;
@@ -1784,6 +2102,243 @@ void TileMap::set_pattern(int p_layer, Vector2i p_position, const Ref<TileMapPat
}
}
+Set<TileSet::TerrainsPattern> TileMap::_get_valid_terrains_patterns_for_constraints(int p_terrain_set, const Vector2i &p_position, Set<TerrainConstraint> p_constraints) {
+ if (!tile_set.is_valid()) {
+ return Set<TileSet::TerrainsPattern>();
+ }
+
+ // Returns all tiles compatible with the given constraints.
+ Set<TileSet::TerrainsPattern> compatible_terrain_tile_patterns;
+ for (TileSet::TerrainsPattern &terrain_pattern : tile_set->get_terrains_pattern_set(p_terrain_set)) {
+ int valid = true;
+ int in_pattern_count = 0;
+ for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) {
+ TileSet::CellNeighbor bit = TileSet::CellNeighbor(i);
+ if (tile_set->is_valid_peering_bit_terrain(p_terrain_set, bit)) {
+ // Check if the bit is compatible with the constraints.
+ TerrainConstraint terrain_bit_constraint = TerrainConstraint(this, p_position, bit, terrain_pattern[in_pattern_count]);
+ Set<TerrainConstraint>::Element *in_set_constraint_element = p_constraints.find(terrain_bit_constraint);
+ if (in_set_constraint_element && in_set_constraint_element->get().get_terrain() != terrain_bit_constraint.get_terrain()) {
+ valid = false;
+ break;
+ }
+ in_pattern_count++;
+ }
+ }
+
+ if (valid) {
+ compatible_terrain_tile_patterns.insert(terrain_pattern);
+ }
+ }
+
+ return compatible_terrain_tile_patterns;
+}
+
+Set<TileMap::TerrainConstraint> TileMap::get_terrain_constraints_from_removed_cells_list(int p_layer, const Set<Vector2i> &p_to_replace, int p_terrain_set, bool p_ignore_empty_terrains) const {
+ if (!tile_set.is_valid()) {
+ return Set<TerrainConstraint>();
+ }
+
+ ERR_FAIL_INDEX_V(p_terrain_set, tile_set->get_terrain_sets_count(), Set<TerrainConstraint>());
+ ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), Set<TerrainConstraint>());
+
+ // Build a set of dummy constraints get the constrained points.
+ Set<TerrainConstraint> dummy_constraints;
+ for (Set<Vector2i>::Element *E = p_to_replace.front(); E; E = E->next()) {
+ for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { // Iterates over sides.
+ TileSet::CellNeighbor bit = TileSet::CellNeighbor(i);
+ if (tile_set->is_valid_peering_bit_terrain(p_terrain_set, bit)) {
+ dummy_constraints.insert(TerrainConstraint(this, E->get(), bit, -1));
+ }
+ }
+ }
+
+ // For each constrained point, we get all overlapping tiles, and select the most adequate terrain for it.
+ Set<TerrainConstraint> constraints;
+ for (Set<TerrainConstraint>::Element *E = dummy_constraints.front(); E; E = E->next()) {
+ TerrainConstraint c = E->get();
+
+ Map<int, int> terrain_count;
+
+ // Count the number of occurrences per terrain.
+ Map<Vector2i, TileSet::CellNeighbor> overlapping_terrain_bits = c.get_overlapping_coords_and_peering_bits();
+ for (const KeyValue<Vector2i, TileSet::CellNeighbor> &E_overlapping : overlapping_terrain_bits) {
+ if (!p_to_replace.has(E_overlapping.key)) {
+ TileData *neighbor_tile_data = nullptr;
+ TileMapCell neighbor_cell = get_cell(p_layer, E_overlapping.key);
+ if (neighbor_cell.source_id != TileSet::INVALID_SOURCE) {
+ Ref<TileSetSource> source = tile_set->get_source(neighbor_cell.source_id);
+ Ref<TileSetAtlasSource> atlas_source = source;
+ if (atlas_source.is_valid()) {
+ TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(neighbor_cell.get_atlas_coords(), neighbor_cell.alternative_tile));
+ if (tile_data && tile_data->get_terrain_set() == p_terrain_set) {
+ neighbor_tile_data = tile_data;
+ }
+ }
+ }
+
+ int terrain = neighbor_tile_data ? neighbor_tile_data->get_peering_bit_terrain(TileSet::CellNeighbor(E_overlapping.value)) : -1;
+ if (!p_ignore_empty_terrains || terrain >= 0) {
+ if (!terrain_count.has(terrain)) {
+ terrain_count[terrain] = 0;
+ }
+ terrain_count[terrain] += 1;
+ }
+ }
+ }
+
+ // Get the terrain with the max number of occurrences.
+ int max = 0;
+ int max_terrain = -1;
+ for (const KeyValue<int, int> &E_terrain_count : terrain_count) {
+ if (E_terrain_count.value > max) {
+ max = E_terrain_count.value;
+ max_terrain = E_terrain_count.key;
+ }
+ }
+
+ // Set the adequate terrain.
+ if (max > 0) {
+ c.set_terrain(max_terrain);
+ constraints.insert(c);
+ }
+ }
+
+ return constraints;
+}
+
+Set<TileMap::TerrainConstraint> TileMap::get_terrain_constraints_from_added_tile(Vector2i p_position, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern) const {
+ if (!tile_set.is_valid()) {
+ return Set<TerrainConstraint>();
+ }
+
+ // Compute the constraints needed from the surrounding tiles.
+ Set<TerrainConstraint> output;
+ int in_pattern_count = 0;
+ for (uint32_t i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) {
+ TileSet::CellNeighbor side = TileSet::CellNeighbor(i);
+ if (tile_set->is_valid_peering_bit_terrain(p_terrain_set, side)) {
+ TerrainConstraint c = TerrainConstraint(this, p_position, side, p_terrains_pattern[in_pattern_count]);
+ output.insert(c);
+ in_pattern_count++;
+ }
+ }
+
+ return output;
+}
+
+Map<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_wave_function_collapse(const Set<Vector2i> &p_to_replace, int p_terrain_set, const Set<TerrainConstraint> p_constraints) {
+ if (!tile_set.is_valid()) {
+ return Map<Vector2i, TileSet::TerrainsPattern>();
+ }
+
+ // Copy the constraints set.
+ Set<TerrainConstraint> constraints = p_constraints;
+
+ // Compute all acceptable patterns for each cell.
+ Map<Vector2i, Set<TileSet::TerrainsPattern>> per_cell_acceptable_tiles;
+ for (Vector2i cell : p_to_replace) {
+ per_cell_acceptable_tiles[cell] = _get_valid_terrains_patterns_for_constraints(p_terrain_set, cell, constraints);
+ }
+
+ // Output map.
+ Map<Vector2i, TileSet::TerrainsPattern> output;
+
+ // Add all positions to a set.
+ Set<Vector2i> to_replace = Set<Vector2i>(p_to_replace);
+ while (!to_replace.is_empty()) {
+ // Compute the minimum number of tile possibilities for each cell.
+ int min_nb_possibilities = 100000000;
+ for (const KeyValue<Vector2i, Set<TileSet::TerrainsPattern>> &E : per_cell_acceptable_tiles) {
+ min_nb_possibilities = MIN(min_nb_possibilities, E.value.size());
+ }
+
+ // Get the set of possible cells to fill, out of the most constrained ones.
+ LocalVector<Vector2i> to_choose_from;
+ for (const KeyValue<Vector2i, Set<TileSet::TerrainsPattern>> &E : per_cell_acceptable_tiles) {
+ if (E.value.size() == min_nb_possibilities) {
+ to_choose_from.push_back(E.key);
+ }
+ }
+
+ // Randomly a cell to fill out of the most constrained.
+ Vector2i selected_cell_to_replace = to_choose_from[Math::random(0, to_choose_from.size() - 1)];
+
+ // Get the list of acceptable pattens for the given cell.
+ Set<TileSet::TerrainsPattern> valid_tiles = per_cell_acceptable_tiles[selected_cell_to_replace];
+ if (valid_tiles.is_empty()) {
+ break; // No possibilities :/
+ }
+
+ // Out of the possible patterns, prioritize the one which have the least amount of different terrains.
+ LocalVector<TileSet::TerrainsPattern> valid_tiles_with_least_amount_of_terrains;
+ int min_terrain_count = 10000;
+ LocalVector<int> terrains_counts;
+ int pattern_index = 0;
+ for (const TileSet::TerrainsPattern &pattern : valid_tiles) {
+ Set<int> terrains;
+ for (int i = 0; i < pattern.size(); i++) {
+ terrains.insert(pattern[i]);
+ }
+ min_terrain_count = MIN(min_terrain_count, terrains.size());
+ terrains_counts.push_back(terrains.size());
+ pattern_index++;
+ }
+ pattern_index = 0;
+ for (const TileSet::TerrainsPattern &pattern : valid_tiles) {
+ if (terrains_counts[pattern_index] == min_terrain_count) {
+ valid_tiles_with_least_amount_of_terrains.push_back(pattern);
+ }
+ pattern_index++;
+ }
+
+ // Randomly select a pattern out of the remaining ones.
+ TileSet::TerrainsPattern selected_terrain_tile_pattern = valid_tiles_with_least_amount_of_terrains[Math::random(0, valid_tiles_with_least_amount_of_terrains.size() - 1)];
+
+ // Set the selected cell into the output.
+ output[selected_cell_to_replace] = selected_terrain_tile_pattern;
+ to_replace.erase(selected_cell_to_replace);
+ per_cell_acceptable_tiles.erase(selected_cell_to_replace);
+
+ // Add the new constraints from the added tiles.
+ Set<TerrainConstraint> new_constraints = get_terrain_constraints_from_added_tile(selected_cell_to_replace, p_terrain_set, selected_terrain_tile_pattern);
+ for (Set<TerrainConstraint>::Element *E_constraint = new_constraints.front(); E_constraint; E_constraint = E_constraint->next()) {
+ constraints.insert(E_constraint->get());
+ }
+
+ // Compute valid tiles again for neighbors.
+ for (uint32_t i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) {
+ TileSet::CellNeighbor side = TileSet::CellNeighbor(i);
+ if (is_existing_neighbor(side)) {
+ Vector2i neighbor = get_neighbor_cell(selected_cell_to_replace, side);
+ if (to_replace.has(neighbor)) {
+ per_cell_acceptable_tiles[neighbor] = _get_valid_terrains_patterns_for_constraints(p_terrain_set, neighbor, constraints);
+ }
+ }
+ }
+ }
+ return output;
+}
+
+void TileMap::set_cells_from_surrounding_terrains(int p_layer, TypedArray<Vector2i> p_coords_array, int p_terrain_set, bool p_ignore_empty_terrains) {
+ ERR_FAIL_COND(!tile_set.is_valid());
+ ERR_FAIL_INDEX(p_layer, (int)layers.size());
+ ERR_FAIL_INDEX(p_terrain_set, tile_set->get_terrain_sets_count());
+
+ Set<Vector2i> coords_set;
+ for (int i = 0; i < p_coords_array.size(); i++) {
+ coords_set.insert(p_coords_array[i]);
+ }
+
+ Set<TileMap::TerrainConstraint> constraints = get_terrain_constraints_from_removed_cells_list(p_layer, coords_set, p_terrain_set, p_ignore_empty_terrains);
+
+ Map<Vector2i, TileSet::TerrainsPattern> wfc_output = terrain_wave_function_collapse(coords_set, p_terrain_set, constraints);
+ for (const KeyValue<Vector2i, TileSet::TerrainsPattern> &kv : wfc_output) {
+ TileMapCell cell = tile_set->get_random_tile_from_pattern(p_terrain_set, kv.value);
+ set_cell(p_layer, kv.key, cell.source_id, cell.get_atlas_coords(), cell.alternative_tile);
+ }
+}
+
TileMapCell TileMap::get_cell(int p_layer, const Vector2i &p_coords, bool p_use_proxies) const {
ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), TileMapCell());
const Map<Vector2i, TileMapCell> &tile_map = layers[p_layer].tile_map;
@@ -2989,6 +3544,8 @@ void TileMap::_bind_methods() {
ClassDB::bind_method(D_METHOD("map_pattern", "position_in_tilemap", "coords_in_pattern", "pattern"), &TileMap::map_pattern);
ClassDB::bind_method(D_METHOD("set_pattern", "layer", "position", "pattern"), &TileMap::set_pattern);
+ ClassDB::bind_method(D_METHOD("set_cells_from_surrounding_terrains", "layer", "cells", "terrain_set", "ignore_empty_terrains"), &TileMap::set_cells_from_surrounding_terrains, DEFVAL(true));
+
ClassDB::bind_method(D_METHOD("fix_invalid_tiles"), &TileMap::fix_invalid_tiles);
ClassDB::bind_method(D_METHOD("clear_layer", "layer"), &TileMap::clear_layer);
ClassDB::bind_method(D_METHOD("clear"), &TileMap::clear);
diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h
index e1f38a314c..4677b813fa 100644
--- a/scene/2d/tile_map.h
+++ b/scene/2d/tile_map.h
@@ -109,6 +109,43 @@ class TileMap : public Node2D {
GDCLASS(TileMap, Node2D);
public:
+ class TerrainConstraint {
+ private:
+ const TileMap *tile_map;
+ Vector2i base_cell_coords = Vector2i();
+ int bit = -1;
+ int terrain = -1;
+
+ public:
+ bool operator<(const TerrainConstraint &p_other) const {
+ if (base_cell_coords == p_other.base_cell_coords) {
+ return bit < p_other.bit;
+ }
+ return base_cell_coords < p_other.base_cell_coords;
+ }
+
+ String to_string() const {
+ return vformat("Constraint {pos:%s, bit:%d, terrain:%d}", base_cell_coords, bit, terrain);
+ }
+
+ Vector2i get_base_cell_coords() const {
+ return base_cell_coords;
+ }
+
+ Map<Vector2i, TileSet::CellNeighbor> get_overlapping_coords_and_peering_bits() const;
+
+ void set_terrain(int p_terrain) {
+ terrain = p_terrain;
+ }
+
+ int get_terrain() const {
+ return terrain;
+ }
+
+ TerrainConstraint(const TileMap *p_tile_map, const Vector2i &p_position, const TileSet::CellNeighbor &p_bit, int p_terrain);
+ TerrainConstraint() {}
+ };
+
enum VisibilityMode {
VISIBILITY_MODE_DEFAULT,
VISIBILITY_MODE_FORCE_SHOW,
@@ -209,6 +246,9 @@ private:
void _scenes_cleanup_quadrant(TileMapQuadrant *p_quadrant);
void _scenes_draw_quadrant_debug(TileMapQuadrant *p_quadrant);
+ // Terrains.
+ Set<TileSet::TerrainsPattern> _get_valid_terrains_patterns_for_constraints(int p_terrain_set, const Vector2i &p_position, Set<TerrainConstraint> p_constraints);
+
// Set and get tiles from data arrays.
void _set_tile_data(int p_layer, const Vector<int> &p_data);
Vector<int> _get_tile_data(int p_layer) const;
@@ -267,21 +307,30 @@ public:
void set_collision_animatable(bool p_enabled);
bool is_collision_animatable() const;
+ // Debug visibility modes.
void set_collision_visibility_mode(VisibilityMode p_show_collision);
VisibilityMode get_collision_visibility_mode();
void set_navigation_visibility_mode(VisibilityMode p_show_navigation);
VisibilityMode get_navigation_visibility_mode();
+ // Cells accessors.
void set_cell(int p_layer, const Vector2i &p_coords, int p_source_id = -1, const Vector2i p_atlas_coords = TileSetSource::INVALID_ATLAS_COORDS, int p_alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE);
int get_cell_source_id(int p_layer, const Vector2i &p_coords, bool p_use_proxies = false) const;
Vector2i get_cell_atlas_coords(int p_layer, const Vector2i &p_coords, bool p_use_proxies = false) const;
int get_cell_alternative_tile(int p_layer, const Vector2i &p_coords, bool p_use_proxies = false) const;
+ // Patterns.
Ref<TileMapPattern> get_pattern(int p_layer, TypedArray<Vector2i> p_coords_array);
Vector2i map_pattern(Vector2i p_position_in_tilemap, Vector2i p_coords_in_pattern, Ref<TileMapPattern> p_pattern);
void set_pattern(int p_layer, Vector2i p_position, const Ref<TileMapPattern> p_pattern);
+ // Terrains.
+ Set<TerrainConstraint> get_terrain_constraints_from_removed_cells_list(int p_layer, const Set<Vector2i> &p_to_replace, int p_terrain_set, bool p_ignore_empty_terrains = true) const; // Not exposed.
+ Set<TerrainConstraint> get_terrain_constraints_from_added_tile(Vector2i p_position, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern) const; // Not exposed.
+ Map<Vector2i, TileSet::TerrainsPattern> terrain_wave_function_collapse(const Set<Vector2i> &p_to_replace, int p_terrain_set, const Set<TerrainConstraint> p_constraints); // Not exposed.
+ void set_cells_from_surrounding_terrains(int p_layer, TypedArray<Vector2i> p_coords_array, int p_terrain_set, bool p_ignore_empty_terrains = true);
+
// Not exposed to users
TileMapCell get_cell(int p_layer, const Vector2i &p_coords, bool p_use_proxies = false) const;
Map<Vector2i, TileMapQuadrant> *get_quadrant_map(int p_layer);
diff --git a/scene/3d/area_3d.cpp b/scene/3d/area_3d.cpp
index d411525707..9179983220 100644
--- a/scene/3d/area_3d.cpp
+++ b/scene/3d/area_3d.cpp
@@ -580,6 +580,8 @@ void Area3D::_validate_property(PropertyInfo &property) const {
property.hint_string = options;
}
+
+ CollisionObject3D::_validate_property(property);
}
void Area3D::_bind_methods() {
diff --git a/scene/3d/audio_stream_player_3d.cpp b/scene/3d/audio_stream_player_3d.cpp
index 44a685f506..261acbeeb9 100644
--- a/scene/3d/audio_stream_player_3d.cpp
+++ b/scene/3d/audio_stream_player_3d.cpp
@@ -638,6 +638,8 @@ void AudioStreamPlayer3D::_validate_property(PropertyInfo &property) const {
property.hint_string = options;
}
+
+ Node3D::_validate_property(property);
}
void AudioStreamPlayer3D::_bus_layout_changed() {
diff --git a/scene/3d/bone_attachment_3d.cpp b/scene/3d/bone_attachment_3d.cpp
index 8e89f4fc54..5dc7382197 100644
--- a/scene/3d/bone_attachment_3d.cpp
+++ b/scene/3d/bone_attachment_3d.cpp
@@ -58,6 +58,8 @@ void BoneAttachment3D::_validate_property(PropertyInfo &property) const {
property.hint_string = "";
}
}
+
+ Node3D::_validate_property(property);
}
bool BoneAttachment3D::_set(const StringName &p_path, const Variant &p_value) {
diff --git a/scene/3d/camera_3d.cpp b/scene/3d/camera_3d.cpp
index 588d2b5018..af3b3ae5bc 100644
--- a/scene/3d/camera_3d.cpp
+++ b/scene/3d/camera_3d.cpp
@@ -71,6 +71,8 @@ void Camera3D::_validate_property(PropertyInfo &p_property) const {
p_property.usage = PROPERTY_USAGE_NOEDITOR;
}
}
+
+ Node3D::_validate_property(p_property);
}
void Camera3D::_update_camera() {
diff --git a/scene/3d/cpu_particles_3d.cpp b/scene/3d/cpu_particles_3d.cpp
index 48ef41e015..f1e800988d 100644
--- a/scene/3d/cpu_particles_3d.cpp
+++ b/scene/3d/cpu_particles_3d.cpp
@@ -73,6 +73,7 @@ void CPUParticles3D::set_amount(int p_amount) {
}
particle_data.resize((12 + 4 + 4) * p_amount);
+ RS::get_singleton()->multimesh_set_visible_instances(multimesh, -1);
RS::get_singleton()->multimesh_allocate_data(multimesh, p_amount, RS::MULTIMESH_TRANSFORM_3D, true, true);
particle_order.resize(p_amount);
@@ -531,6 +532,8 @@ void CPUParticles3D::_validate_property(PropertyInfo &property) const {
if (property.name.begins_with("scale_curve_") && !split_scale) {
property.usage = PROPERTY_USAGE_NONE;
}
+
+ Node3D::_validate_property(property);
}
static uint32_t idhash(uint32_t x) {
diff --git a/scene/3d/decal.cpp b/scene/3d/decal.cpp
index c94a99a203..e3c63d62f9 100644
--- a/scene/3d/decal.cpp
+++ b/scene/3d/decal.cpp
@@ -160,6 +160,7 @@ void Decal::_validate_property(PropertyInfo &property) const {
if (!distance_fade_enabled && (property.name == "distance_fade_begin" || property.name == "distance_fade_length")) {
property.usage = PROPERTY_USAGE_NOEDITOR;
}
+ VisualInstance3D::_validate_property(property);
}
TypedArray<String> Decal::get_configuration_warnings() const {
diff --git a/scene/3d/gpu_particles_3d.cpp b/scene/3d/gpu_particles_3d.cpp
index ea6242b669..13cb8b7dfb 100644
--- a/scene/3d/gpu_particles_3d.cpp
+++ b/scene/3d/gpu_particles_3d.cpp
@@ -388,6 +388,8 @@ void GPUParticles3D::_validate_property(PropertyInfo &property) const {
return;
}
}
+
+ GeometryInstance3D::_validate_property(property);
}
void GPUParticles3D::emit_particle(const Transform3D &p_transform, const Vector3 &p_velocity, const Color &p_color, const Color &p_custom, uint32_t p_emit_flags) {
diff --git a/scene/3d/light_3d.cpp b/scene/3d/light_3d.cpp
index c787ba5087..fbdbd79c0c 100644
--- a/scene/3d/light_3d.cpp
+++ b/scene/3d/light_3d.cpp
@@ -200,21 +200,11 @@ void Light3D::_validate_property(PropertyInfo &property) const {
property.usage = PROPERTY_USAGE_NOEDITOR;
}
- if (get_light_type() == RS::LIGHT_DIRECTIONAL && property.name == "light_size") {
- property.usage = PROPERTY_USAGE_NONE;
- }
-
- if (get_light_type() == RS::LIGHT_DIRECTIONAL && property.name == "light_specular") {
- property.usage = PROPERTY_USAGE_NONE;
- }
-
- if (get_light_type() == RS::LIGHT_DIRECTIONAL && property.name == "light_projector") {
- property.usage = PROPERTY_USAGE_NONE;
- }
-
if (get_light_type() != RS::LIGHT_DIRECTIONAL && property.name == "light_angular_distance") {
+ // Angular distance is only used in DirectionalLight3D.
property.usage = PROPERTY_USAGE_NONE;
}
+ VisualInstance3D::_validate_property(property);
}
void Light3D::_bind_methods() {
@@ -361,6 +351,7 @@ Light3D::~Light3D() {
void DirectionalLight3D::set_shadow_mode(ShadowMode p_mode) {
shadow_mode = p_mode;
RS::get_singleton()->light_directional_set_shadow_mode(light, RS::LightDirectionalShadowMode(p_mode));
+ notify_property_list_changed();
}
DirectionalLight3D::ShadowMode DirectionalLight3D::get_shadow_mode() const {
@@ -385,6 +376,25 @@ bool DirectionalLight3D::is_sky_only() const {
return sky_only;
}
+void DirectionalLight3D::_validate_property(PropertyInfo &property) const {
+ if (shadow_mode == SHADOW_ORTHOGONAL && (property.name == "directional_shadow_split_1" || property.name == "directional_shadow_blend_splits")) {
+ // Split 2 and split blending are only used with the PSSM 2 Splits and PSSM 4 Splits shadow modes.
+ property.usage = PROPERTY_USAGE_NOEDITOR;
+ }
+
+ if ((shadow_mode == SHADOW_ORTHOGONAL || shadow_mode == SHADOW_PARALLEL_2_SPLITS) && (property.name == "directional_shadow_split_2" || property.name == "directional_shadow_split_3")) {
+ // Splits 3 and 4 are only used with the PSSM 4 Splits shadow mode.
+ property.usage = PROPERTY_USAGE_NOEDITOR;
+ }
+
+ if (property.name == "light_size" || property.name == "light_projector" || property.name == "light_specular") {
+ // Not implemented in DirectionalLight3D (`light_size` is replaced by `light_angular_distance`).
+ property.usage = PROPERTY_USAGE_NONE;
+ }
+
+ Light3D::_validate_property(property);
+}
+
void DirectionalLight3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_shadow_mode", "mode"), &DirectionalLight3D::set_shadow_mode);
ClassDB::bind_method(D_METHOD("get_shadow_mode"), &DirectionalLight3D::get_shadow_mode);
@@ -400,8 +410,8 @@ void DirectionalLight3D::_bind_methods() {
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "directional_shadow_split_1", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_param", "get_param", PARAM_SHADOW_SPLIT_1_OFFSET);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "directional_shadow_split_2", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_param", "get_param", PARAM_SHADOW_SPLIT_2_OFFSET);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "directional_shadow_split_3", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_param", "get_param", PARAM_SHADOW_SPLIT_3_OFFSET);
- ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "directional_shadow_fade_start", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param", "get_param", PARAM_SHADOW_FADE_START);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "directional_shadow_blend_splits"), "set_blend_splits", "is_blend_splits_enabled");
+ ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "directional_shadow_fade_start", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param", "get_param", PARAM_SHADOW_FADE_START);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "directional_shadow_max_distance", PROPERTY_HINT_RANGE, "0,8192,0.1,or_greater,exp"), "set_param", "get_param", PARAM_SHADOW_MAX_DISTANCE);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "directional_shadow_pancake_size", PROPERTY_HINT_RANGE, "0,1024,0.1,or_greater,exp"), "set_param", "get_param", PARAM_SHADOW_PANCAKE_SIZE);
diff --git a/scene/3d/light_3d.h b/scene/3d/light_3d.h
index f788c323f7..a9f5ce27b4 100644
--- a/scene/3d/light_3d.h
+++ b/scene/3d/light_3d.h
@@ -152,6 +152,7 @@ private:
protected:
static void _bind_methods();
+ virtual void _validate_property(PropertyInfo &property) const override;
public:
void set_shadow_mode(ShadowMode p_mode);
diff --git a/scene/3d/lightmap_gi.cpp b/scene/3d/lightmap_gi.cpp
index b56f0fd145..3bcb6add76 100644
--- a/scene/3d/lightmap_gi.cpp
+++ b/scene/3d/lightmap_gi.cpp
@@ -1370,6 +1370,7 @@ void LightmapGI::_validate_property(PropertyInfo &property) const {
if (property.name == "environment_custom_energy" && environment_mode != ENVIRONMENT_MODE_CUSTOM_COLOR && environment_mode != ENVIRONMENT_MODE_CUSTOM_SKY) {
property.usage = PROPERTY_USAGE_NONE;
}
+ VisualInstance3D::_validate_property(property);
}
void LightmapGI::_bind_methods() {
diff --git a/scene/3d/node_3d.cpp b/scene/3d/node_3d.cpp
index 5293dd3f0a..8a39d4d30f 100644
--- a/scene/3d/node_3d.cpp
+++ b/scene/3d/node_3d.cpp
@@ -220,6 +220,13 @@ void Node3D::_notification(int p_what) {
}
}
+void Node3D::set_basis(const Basis &p_basis) {
+ set_transform(Transform3D(p_basis, data.local_transform.origin));
+}
+void Node3D::set_quaternion(const Quaternion &p_quaternion) {
+ set_transform(Transform3D(Basis(p_quaternion), data.local_transform.origin));
+}
+
void Node3D::set_transform(const Transform3D &p_transform) {
data.local_transform = p_transform;
data.dirty |= DIRTY_VECTORS;
@@ -229,6 +236,13 @@ void Node3D::set_transform(const Transform3D &p_transform) {
}
}
+Basis Node3D::get_basis() const {
+ return get_transform().basis;
+}
+Quaternion Node3D::get_quaternion() const {
+ return Quaternion(get_transform().basis);
+}
+
void Node3D::set_global_transform(const Transform3D &p_transform) {
Transform3D xform =
(data.parent && !data.top_level_active) ?
@@ -308,6 +322,45 @@ void Node3D::set_position(const Vector3 &p_position) {
}
}
+void Node3D::set_rotation_edit_mode(RotationEditMode p_mode) {
+ if (data.rotation_edit_mode == p_mode) {
+ return;
+ }
+ data.rotation_edit_mode = p_mode;
+ notify_property_list_changed();
+}
+
+Node3D::RotationEditMode Node3D::get_rotation_edit_mode() const {
+ return data.rotation_edit_mode;
+}
+
+void Node3D::set_rotation_order(RotationOrder p_order) {
+ Basis::EulerOrder order = Basis::EulerOrder(p_order);
+
+ if (data.rotation_order == order) {
+ return;
+ }
+
+ ERR_FAIL_INDEX(int32_t(order), 6);
+
+ if (data.dirty & DIRTY_VECTORS) {
+ data.rotation = data.local_transform.basis.get_euler_normalized(order);
+ data.scale = data.local_transform.basis.get_scale();
+ data.dirty &= ~DIRTY_VECTORS;
+ } else {
+ data.rotation = Basis::from_euler(data.rotation, data.rotation_order).get_euler_normalized(order);
+ }
+
+ data.rotation_order = order;
+ //changing rotation order should not affect transform
+
+ notify_property_list_changed(); //will change rotation
+}
+
+Node3D::RotationOrder Node3D::get_rotation_order() const {
+ return RotationOrder(data.rotation_order);
+}
+
void Node3D::set_rotation(const Vector3 &p_euler_rad) {
if (data.dirty & DIRTY_VECTORS) {
data.scale = data.local_transform.basis.get_scale();
@@ -324,7 +377,7 @@ void Node3D::set_rotation(const Vector3 &p_euler_rad) {
void Node3D::set_scale(const Vector3 &p_scale) {
if (data.dirty & DIRTY_VECTORS) {
- data.rotation = data.local_transform.basis.get_rotation();
+ data.rotation = data.local_transform.basis.get_euler_normalized(data.rotation_order);
data.dirty &= ~DIRTY_VECTORS;
}
@@ -343,7 +396,7 @@ Vector3 Node3D::get_position() const {
Vector3 Node3D::get_rotation() const {
if (data.dirty & DIRTY_VECTORS) {
data.scale = data.local_transform.basis.get_scale();
- data.rotation = data.local_transform.basis.get_rotation();
+ data.rotation = data.local_transform.basis.get_euler_normalized(data.rotation_order);
data.dirty &= ~DIRTY_VECTORS;
}
@@ -354,7 +407,7 @@ Vector3 Node3D::get_rotation() const {
Vector3 Node3D::get_scale() const {
if (data.dirty & DIRTY_VECTORS) {
data.scale = data.local_transform.basis.get_scale();
- data.rotation = data.local_transform.basis.get_rotation();
+ data.rotation = data.local_transform.basis.get_euler_normalized(data.rotation_order);
data.dirty &= ~DIRTY_VECTORS;
}
@@ -780,6 +833,24 @@ NodePath Node3D::get_visibility_parent() const {
return visibility_parent_path;
}
+void Node3D::_validate_property(PropertyInfo &property) const {
+ if (data.rotation_edit_mode != ROTATION_EDIT_MODE_BASIS && property.name == "basis") {
+ property.usage = 0;
+ }
+ if (data.rotation_edit_mode == ROTATION_EDIT_MODE_BASIS && property.name == "scale") {
+ property.usage = 0;
+ }
+ if (data.rotation_edit_mode != ROTATION_EDIT_MODE_QUATERNION && property.name == "quaternion") {
+ property.usage = 0;
+ }
+ if (data.rotation_edit_mode != ROTATION_EDIT_MODE_EULER && property.name == "rotation") {
+ property.usage = 0;
+ }
+ if (data.rotation_edit_mode != ROTATION_EDIT_MODE_EULER && property.name == "rotation_order") {
+ property.usage = 0;
+ }
+}
+
void Node3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_transform", "local"), &Node3D::set_transform);
ClassDB::bind_method(D_METHOD("get_transform"), &Node3D::get_transform);
@@ -787,8 +858,16 @@ void Node3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_position"), &Node3D::get_position);
ClassDB::bind_method(D_METHOD("set_rotation", "euler"), &Node3D::set_rotation);
ClassDB::bind_method(D_METHOD("get_rotation"), &Node3D::get_rotation);
+ ClassDB::bind_method(D_METHOD("set_rotation_order", "order"), &Node3D::set_rotation_order);
+ ClassDB::bind_method(D_METHOD("get_rotation_order"), &Node3D::get_rotation_order);
+ ClassDB::bind_method(D_METHOD("set_rotation_edit_mode", "edit_mode"), &Node3D::set_rotation_edit_mode);
+ ClassDB::bind_method(D_METHOD("get_rotation_edit_mode"), &Node3D::get_rotation_edit_mode);
ClassDB::bind_method(D_METHOD("set_scale", "scale"), &Node3D::set_scale);
ClassDB::bind_method(D_METHOD("get_scale"), &Node3D::get_scale);
+ ClassDB::bind_method(D_METHOD("set_quaternion", "quaternion"), &Node3D::set_quaternion);
+ ClassDB::bind_method(D_METHOD("get_quaternion"), &Node3D::get_quaternion);
+ ClassDB::bind_method(D_METHOD("set_basis", "basis"), &Node3D::set_basis);
+ ClassDB::bind_method(D_METHOD("get_basis"), &Node3D::get_basis);
ClassDB::bind_method(D_METHOD("set_global_transform", "global"), &Node3D::set_global_transform);
ClassDB::bind_method(D_METHOD("get_global_transform"), &Node3D::get_global_transform);
ClassDB::bind_method(D_METHOD("get_parent_node_3d"), &Node3D::get_parent_node_3d);
@@ -848,15 +927,29 @@ void Node3D::_bind_methods() {
BIND_CONSTANT(NOTIFICATION_EXIT_WORLD);
BIND_CONSTANT(NOTIFICATION_VISIBILITY_CHANGED);
+ BIND_ENUM_CONSTANT(ROTATION_EDIT_MODE_EULER);
+ BIND_ENUM_CONSTANT(ROTATION_EDIT_MODE_QUATERNION);
+ BIND_ENUM_CONSTANT(ROTATION_EDIT_MODE_BASIS);
+
+ BIND_ENUM_CONSTANT(ROTATION_ORDER_XYZ);
+ BIND_ENUM_CONSTANT(ROTATION_ORDER_XZY);
+ BIND_ENUM_CONSTANT(ROTATION_ORDER_YXZ);
+ BIND_ENUM_CONSTANT(ROTATION_ORDER_YZX);
+ BIND_ENUM_CONSTANT(ROTATION_ORDER_ZXY);
+ BIND_ENUM_CONSTANT(ROTATION_ORDER_ZYX);
+
//ADD_PROPERTY( PropertyInfo(Variant::TRANSFORM3D,"transform/global",PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR ), "set_global_transform", "get_global_transform") ;
ADD_GROUP("Transform", "");
ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM3D, "global_transform", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_global_transform", "get_global_transform");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "position", PROPERTY_HINT_RANGE, "-99999,99999,0,or_greater,or_lesser,noslider,suffix:m", PROPERTY_USAGE_EDITOR), "set_position", "get_position");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "rotation", PROPERTY_HINT_RANGE, "-360,360,0.1,or_lesser,or_greater,radians", PROPERTY_USAGE_EDITOR), "set_rotation", "get_rotation");
+ ADD_PROPERTY(PropertyInfo(Variant::QUATERNION, "quaternion", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_quaternion", "get_quaternion");
+ ADD_PROPERTY(PropertyInfo(Variant::BASIS, "basis", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_basis", "get_basis");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "scale", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_scale", "get_scale");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "rotation_edit_mode", PROPERTY_HINT_ENUM, "Euler,Quaternion,Basis"), "set_rotation_edit_mode", "get_rotation_edit_mode");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "rotation_order", PROPERTY_HINT_ENUM, "XYZ,XZY,YXZ,YZX,ZXY,ZYX"), "set_rotation_order", "get_rotation_order");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "top_level"), "set_as_top_level", "is_set_as_top_level");
- ADD_GROUP("Matrix", "");
- ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM3D, "transform", PROPERTY_HINT_NONE, ""), "set_transform", "get_transform");
+ ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM3D, "transform", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_transform", "get_transform");
ADD_GROUP("Visibility", "");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "visible"), "set_visible", "is_visible");
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "visibility_parent", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "GeometryInstance3D"), "set_visibility_parent", "get_visibility_parent");
diff --git a/scene/3d/node_3d.h b/scene/3d/node_3d.h
index d6dcdd96fe..3e21eb12be 100644
--- a/scene/3d/node_3d.h
+++ b/scene/3d/node_3d.h
@@ -51,6 +51,23 @@ class Node3D : public Node {
GDCLASS(Node3D, Node);
OBJ_CATEGORY("3D");
+public:
+ enum RotationEditMode {
+ ROTATION_EDIT_MODE_EULER,
+ ROTATION_EDIT_MODE_QUATERNION,
+ ROTATION_EDIT_MODE_BASIS,
+ };
+
+ enum RotationOrder {
+ ROTATION_ORDER_XYZ,
+ ROTATION_ORDER_XZY,
+ ROTATION_ORDER_YXZ,
+ ROTATION_ORDER_YZX,
+ ROTATION_ORDER_ZXY,
+ ROTATION_ORDER_ZYX
+ };
+
+private:
enum TransformDirty {
DIRTY_NONE = 0,
DIRTY_VECTORS = 1,
@@ -63,8 +80,10 @@ class Node3D : public Node {
struct Data {
mutable Transform3D global_transform;
mutable Transform3D local_transform;
+ mutable Basis::EulerOrder rotation_order = Basis::EULER_ORDER_YXZ;
mutable Vector3 rotation;
mutable Vector3 scale = Vector3(1, 1, 1);
+ mutable RotationEditMode rotation_edit_mode = ROTATION_EDIT_MODE_EULER;
mutable int dirty = DIRTY_NONE;
@@ -116,6 +135,8 @@ protected:
void _notification(int p_what);
static void _bind_methods();
+ virtual void _validate_property(PropertyInfo &property) const override;
+
public:
enum {
NOTIFICATION_TRANSFORM_CHANGED = SceneTree::NOTIFICATION_TRANSFORM_CHANGED,
@@ -130,17 +151,28 @@ public:
Ref<World3D> get_world_3d() const;
void set_position(const Vector3 &p_position);
+
+ void set_rotation_edit_mode(RotationEditMode p_mode);
+ RotationEditMode get_rotation_edit_mode() const;
+
+ void set_rotation_order(RotationOrder p_order);
void set_rotation(const Vector3 &p_euler_rad);
void set_scale(const Vector3 &p_scale);
Vector3 get_position() const;
+
+ RotationOrder get_rotation_order() const;
Vector3 get_rotation() const;
Vector3 get_scale() const;
void set_transform(const Transform3D &p_transform);
+ void set_basis(const Basis &p_basis);
+ void set_quaternion(const Quaternion &p_quaternion);
void set_global_transform(const Transform3D &p_transform);
Transform3D get_transform() const;
+ Basis get_basis() const;
+ Quaternion get_quaternion() const;
Transform3D get_global_transform() const;
#ifdef TOOLS_ENABLED
@@ -214,4 +246,7 @@ public:
Node3D();
};
+VARIANT_ENUM_CAST(Node3D::RotationEditMode)
+VARIANT_ENUM_CAST(Node3D::RotationOrder)
+
#endif // NODE_3D_H
diff --git a/scene/3d/path_3d.cpp b/scene/3d/path_3d.cpp
index 9ea37e4bfa..a0eac76e39 100644
--- a/scene/3d/path_3d.cpp
+++ b/scene/3d/path_3d.cpp
@@ -248,6 +248,7 @@ void PathFollow3D::_validate_property(PropertyInfo &property) const {
property.hint_string = "0," + rtos(max) + ",0.01,or_lesser,or_greater";
}
+ Node3D::_validate_property(property);
}
TypedArray<String> PathFollow3D::get_configuration_warnings() const {
diff --git a/scene/3d/physics_body_3d.cpp b/scene/3d/physics_body_3d.cpp
index 8cb348d6e3..4f1003839e 100644
--- a/scene/3d/physics_body_3d.cpp
+++ b/scene/3d/physics_body_3d.cpp
@@ -1059,6 +1059,7 @@ void RigidDynamicBody3D::_validate_property(PropertyInfo &property) const {
property.usage = PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL;
}
}
+ PhysicsBody3D::_validate_property(property);
}
RigidDynamicBody3D::RigidDynamicBody3D() :
@@ -1933,6 +1934,7 @@ void CharacterBody3D::_validate_property(PropertyInfo &property) const {
property.usage = PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL;
}
}
+ PhysicsBody3D::_validate_property(property);
}
CharacterBody3D::CharacterBody3D() :
@@ -3060,7 +3062,7 @@ void PhysicalBone3D::set_joint_rotation(const Vector3 &p_euler_rad) {
}
Vector3 PhysicalBone3D::get_joint_rotation() const {
- return joint_offset.basis.get_rotation();
+ return joint_offset.basis.get_euler_normalized();
}
const Transform3D &PhysicalBone3D::get_body_offset() const {
diff --git a/scene/3d/reflection_probe.cpp b/scene/3d/reflection_probe.cpp
index 719dbedd94..29c382cd05 100644
--- a/scene/3d/reflection_probe.cpp
+++ b/scene/3d/reflection_probe.cpp
@@ -188,6 +188,7 @@ void ReflectionProbe::_validate_property(PropertyInfo &property) const {
property.usage = PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL;
}
}
+ VisualInstance3D::_validate_property(property);
}
void ReflectionProbe::_bind_methods() {
diff --git a/scene/3d/remote_transform_3d.cpp b/scene/3d/remote_transform_3d.cpp
index d5fb1fa6ab..d890609e23 100644
--- a/scene/3d/remote_transform_3d.cpp
+++ b/scene/3d/remote_transform_3d.cpp
@@ -68,7 +68,7 @@ void RemoteTransform3D::_update_remote() {
Transform3D our_trans = get_global_transform();
if (update_remote_rotation) {
- n->set_rotation(our_trans.basis.get_rotation());
+ n->set_rotation(our_trans.basis.get_euler_normalized(Basis::EulerOrder(n->get_rotation_order())));
}
if (update_remote_scale) {
@@ -90,7 +90,7 @@ void RemoteTransform3D::_update_remote() {
Transform3D our_trans = get_transform();
if (update_remote_rotation) {
- n->set_rotation(our_trans.basis.get_rotation());
+ n->set_rotation(our_trans.basis.get_euler_normalized(Basis::EulerOrder(n->get_rotation_order())));
}
if (update_remote_scale) {
diff --git a/scene/3d/skeleton_3d.cpp b/scene/3d/skeleton_3d.cpp
index fbc3767151..e3744ad5e9 100644
--- a/scene/3d/skeleton_3d.cpp
+++ b/scene/3d/skeleton_3d.cpp
@@ -178,32 +178,32 @@ void Skeleton3D::_get_property_list(List<PropertyInfo> *p_list) const {
}
void Skeleton3D::_validate_property(PropertyInfo &property) const {
- PackedStringArray spr = property.name.split("/");
- if (spr.size() == 3 && spr[0] == "bones") {
- if (spr[2] == "rest") {
+ PackedStringArray split = property.name.split("/");
+ if (split.size() == 3 && split[0] == "bones") {
+ if (split[2] == "rest") {
property.usage |= PROPERTY_USAGE_READ_ONLY;
}
if (is_show_rest_only()) {
- if (spr[2] == "enabled") {
+ if (split[2] == "enabled") {
property.usage |= PROPERTY_USAGE_READ_ONLY;
}
- if (spr[2] == "position") {
+ if (split[2] == "position") {
property.usage |= PROPERTY_USAGE_READ_ONLY;
}
- if (spr[2] == "rotation") {
+ if (split[2] == "rotation") {
property.usage |= PROPERTY_USAGE_READ_ONLY;
}
- if (spr[2] == "scale") {
+ if (split[2] == "scale") {
property.usage |= PROPERTY_USAGE_READ_ONLY;
}
- } else if (!is_bone_enabled(spr[1].to_int())) {
- if (spr[2] == "position") {
+ } else if (!is_bone_enabled(split[1].to_int())) {
+ if (split[2] == "position") {
property.usage |= PROPERTY_USAGE_READ_ONLY;
}
- if (spr[2] == "rotation") {
+ if (split[2] == "rotation") {
property.usage |= PROPERTY_USAGE_READ_ONLY;
}
- if (spr[2] == "scale") {
+ if (split[2] == "scale") {
property.usage |= PROPERTY_USAGE_READ_ONLY;
}
}
diff --git a/scene/3d/skeleton_ik_3d.cpp b/scene/3d/skeleton_ik_3d.cpp
index 2e788051f4..1498955ec0 100644
--- a/scene/3d/skeleton_ik_3d.cpp
+++ b/scene/3d/skeleton_ik_3d.cpp
@@ -351,6 +351,8 @@ void SkeletonIK3D::_validate_property(PropertyInfo &property) const {
property.hint_string = "";
}
}
+
+ Node::_validate_property(property);
}
void SkeletonIK3D::_bind_methods() {
diff --git a/scene/3d/skeleton_ik_3d.h b/scene/3d/skeleton_ik_3d.h
index 4cf08e7c99..ccb25bcd4c 100644
--- a/scene/3d/skeleton_ik_3d.h
+++ b/scene/3d/skeleton_ik_3d.h
@@ -137,8 +137,7 @@ class SkeletonIK3D : public Node {
FabrikInverseKinematic::Task *task = nullptr;
protected:
- virtual void
- _validate_property(PropertyInfo &property) const override;
+ virtual void _validate_property(PropertyInfo &property) const override;
static void _bind_methods();
virtual void _notification(int p_what);
diff --git a/scene/3d/sprite_3d.cpp b/scene/3d/sprite_3d.cpp
index 349a534680..90af70e7c2 100644
--- a/scene/3d/sprite_3d.cpp
+++ b/scene/3d/sprite_3d.cpp
@@ -743,6 +743,8 @@ void Sprite3D::_validate_property(PropertyInfo &property) const {
if (property.name == "frame_coords") {
property.usage |= PROPERTY_USAGE_KEYING_INCREMENTS;
}
+
+ SpriteBase3D::_validate_property(property);
}
void Sprite3D::_bind_methods() {
@@ -1015,6 +1017,8 @@ void AnimatedSprite3D::_validate_property(PropertyInfo &property) const {
}
property.usage |= PROPERTY_USAGE_KEYING_INCREMENTS;
}
+
+ SpriteBase3D::_validate_property(property);
}
void AnimatedSprite3D::_notification(int p_what) {
diff --git a/scene/3d/xr_nodes.cpp b/scene/3d/xr_nodes.cpp
index 9dbee58f0e..a16820cbdc 100644
--- a/scene/3d/xr_nodes.cpp
+++ b/scene/3d/xr_nodes.cpp
@@ -261,6 +261,8 @@ void XRNode3D::_validate_property(PropertyInfo &property) const {
}
property.hint_string = hint_string;
}
+
+ Node3D::_validate_property(property);
}
void XRNode3D::set_tracker(const StringName p_tracker_name) {
diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp
index 1f000d5f39..26caf826a7 100644
--- a/scene/animation/animation_player.cpp
+++ b/scene/animation/animation_player.cpp
@@ -166,6 +166,8 @@ void AnimationPlayer::_validate_property(PropertyInfo &property) const {
property.hint_string = hint;
}
+
+ Node::_validate_property(property);
}
void AnimationPlayer::_get_property_list(List<PropertyInfo> *p_list) const {
diff --git a/scene/audio/audio_stream_player.cpp b/scene/audio/audio_stream_player.cpp
index d2fd65d3d9..5065f21604 100644
--- a/scene/audio/audio_stream_player.cpp
+++ b/scene/audio/audio_stream_player.cpp
@@ -292,6 +292,8 @@ void AudioStreamPlayer::_validate_property(PropertyInfo &property) const {
property.hint_string = options;
}
+
+ Node::_validate_property(property);
}
void AudioStreamPlayer::_bus_layout_changed() {
diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp
index c1db684d9b..046d256867 100644
--- a/scene/gui/code_edit.cpp
+++ b/scene/gui/code_edit.cpp
@@ -129,7 +129,7 @@ void CodeEdit::_notification(int p_what) {
}
const int scroll_width = code_completion_options_count > code_completion_max_lines ? code_completion_scroll_width : 0;
- const int code_completion_base_width = font->get_string_size(code_completion_base).width;
+ const int code_completion_base_width = font->get_string_size(code_completion_base, font_size).width;
if (caret_pos.x - code_completion_base_width + code_completion_rect.size.width + scroll_width > get_size().width) {
code_completion_rect.position.x = get_size().width - code_completion_rect.size.width - scroll_width;
} else {
diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp
index e4048fbf09..77a1efd021 100644
--- a/scene/gui/control.cpp
+++ b/scene/gui/control.cpp
@@ -400,7 +400,7 @@ void Control::_get_property_list(List<PropertyInfo> *p_list) const {
usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED;
}
- p_list->push_back(PropertyInfo(Variant::INT, "theme_override_font_sizes/" + E, PROPERTY_HINT_NONE, "", usage));
+ p_list->push_back(PropertyInfo(Variant::INT, "theme_override_font_sizes/" + E, PROPERTY_HINT_RANGE, "1,256,1,or_greater", usage));
}
}
{
@@ -985,7 +985,7 @@ Ref<StyleBox> Control::get_theme_stylebox(const StringName &p_name, const String
Ref<Font> Control::get_theme_font(const StringName &p_name, const StringName &p_theme_type) const {
if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) {
const Ref<Font> *font = data.font_override.getptr(p_name);
- if (font) {
+ if (font && (*font)->get_data_count() > 0) {
return *font;
}
}
@@ -998,7 +998,7 @@ Ref<Font> Control::get_theme_font(const StringName &p_name, const StringName &p_
int Control::get_theme_font_size(const StringName &p_name, const StringName &p_theme_type) const {
if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) {
const int *font_size = data.font_size_override.getptr(p_name);
- if (font_size) {
+ if (font_size && (*font_size) > 0) {
return *font_size;
}
}
@@ -2586,16 +2586,6 @@ void Control::warp_mouse(const Point2 &p_to_pos) {
}
bool Control::is_text_field() const {
- /*
- if (get_script_instance()) {
- Variant v=p_point;
- const Variant *p[2]={&v,&p_data};
- Callable::CallError ce;
- Variant ret = get_script_instance()->call("is_text_field",p,2,ce);
- if (ce.error==Callable::CallError::CALL_OK)
- return ret;
- }
- */
return false;
}
diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp
index 992d364464..1245a37c4d 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -4037,7 +4037,7 @@ int Tree::get_column_minimum_width(int p_column) const {
// Check if the visible title of the column is wider.
if (show_column_titles) {
- min_width = MAX(cache.font->get_string_size(columns[p_column].title).width + cache.bg->get_margin(SIDE_LEFT) + cache.bg->get_margin(SIDE_RIGHT), min_width);
+ min_width = MAX(cache.font->get_string_size(columns[p_column].title, cache.font_size).width + cache.bg->get_margin(SIDE_LEFT) + cache.bg->get_margin(SIDE_RIGHT), min_width);
}
if (!columns[p_column].clip_content) {
diff --git a/scene/main/canvas_item.cpp b/scene/main/canvas_item.cpp
index 916833c9a7..5065684839 100644
--- a/scene/main/canvas_item.cpp
+++ b/scene/main/canvas_item.cpp
@@ -892,9 +892,9 @@ void CanvasItem::_bind_methods() {
ClassDB::bind_method(D_METHOD("draw_primitive", "points", "colors", "uvs", "texture", "width"), &CanvasItem::draw_primitive, DEFVAL(Ref<Texture2D>()), DEFVAL(1.0));
ClassDB::bind_method(D_METHOD("draw_polygon", "points", "colors", "uvs", "texture"), &CanvasItem::draw_polygon, DEFVAL(PackedVector2Array()), DEFVAL(Ref<Texture2D>()));
ClassDB::bind_method(D_METHOD("draw_colored_polygon", "points", "color", "uvs", "texture"), &CanvasItem::draw_colored_polygon, DEFVAL(PackedVector2Array()), DEFVAL(Ref<Texture2D>()));
- ClassDB::bind_method(D_METHOD("draw_string", "font", "pos", "text", "align", "width", "size", "modulate", "outline_size", "outline_modulate", "flags"), &CanvasItem::draw_string, DEFVAL(HALIGN_LEFT), DEFVAL(-1), DEFVAL(-1), DEFVAL(Color(1, 1, 1)), DEFVAL(0), DEFVAL(Color(1, 1, 1, 0)), DEFVAL(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND));
- ClassDB::bind_method(D_METHOD("draw_multiline_string", "font", "pos", "text", "align", "width", "max_lines", "size", "modulate", "outline_size", "outline_modulate", "flags"), &CanvasItem::draw_multiline_string, DEFVAL(HALIGN_LEFT), DEFVAL(-1), DEFVAL(-1), DEFVAL(-1), DEFVAL(Color(1, 1, 1)), DEFVAL(0), DEFVAL(Color(1, 1, 1, 0)), DEFVAL(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND));
- ClassDB::bind_method(D_METHOD("draw_char", "font", "pos", "char", "next", "size", "modulate", "outline_size", "outline_modulate"), &CanvasItem::draw_char, DEFVAL(""), DEFVAL(-1), DEFVAL(Color(1, 1, 1)), DEFVAL(0), DEFVAL(Color(1, 1, 1, 0)));
+ ClassDB::bind_method(D_METHOD("draw_string", "font", "pos", "text", "align", "width", "size", "modulate", "outline_size", "outline_modulate", "flags"), &CanvasItem::draw_string, DEFVAL(HALIGN_LEFT), DEFVAL(-1), DEFVAL(Font::DEFAULT_FONT_SIZE), DEFVAL(Color(1, 1, 1)), DEFVAL(0), DEFVAL(Color(1, 1, 1, 0)), DEFVAL(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND));
+ ClassDB::bind_method(D_METHOD("draw_multiline_string", "font", "pos", "text", "align", "width", "max_lines", "size", "modulate", "outline_size", "outline_modulate", "flags"), &CanvasItem::draw_multiline_string, DEFVAL(HALIGN_LEFT), DEFVAL(-1), DEFVAL(-1), DEFVAL(Font::DEFAULT_FONT_SIZE), DEFVAL(Color(1, 1, 1)), DEFVAL(0), DEFVAL(Color(1, 1, 1, 0)), DEFVAL(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND));
+ ClassDB::bind_method(D_METHOD("draw_char", "font", "pos", "char", "next", "size", "modulate", "outline_size", "outline_modulate"), &CanvasItem::draw_char, DEFVAL(""), DEFVAL(Font::DEFAULT_FONT_SIZE), DEFVAL(Color(1, 1, 1)), DEFVAL(0), DEFVAL(Color(1, 1, 1, 0)));
ClassDB::bind_method(D_METHOD("draw_mesh", "mesh", "texture", "transform", "modulate"), &CanvasItem::draw_mesh, DEFVAL(Transform2D()), DEFVAL(Color(1, 1, 1, 1)));
ClassDB::bind_method(D_METHOD("draw_multimesh", "multimesh", "texture"), &CanvasItem::draw_multimesh);
ClassDB::bind_method(D_METHOD("draw_set_transform", "position", "rotation", "scale"), &CanvasItem::draw_set_transform, DEFVAL(0.0), DEFVAL(Size2(1.0, 1.0)));
diff --git a/scene/main/canvas_item.h b/scene/main/canvas_item.h
index ba9f47119d..04376ad809 100644
--- a/scene/main/canvas_item.h
+++ b/scene/main/canvas_item.h
@@ -34,10 +34,10 @@
#include "scene/main/node.h"
#include "scene/main/scene_tree.h"
#include "scene/resources/canvas_item_material.h"
+#include "scene/resources/font.h"
#include "servers/text_server.h"
class CanvasLayer;
-class Font;
class MultiMesh;
class StyleBox;
class Window;
@@ -235,9 +235,9 @@ public:
void draw_mesh(const Ref<Mesh> &p_mesh, const Ref<Texture2D> &p_texture, const Transform2D &p_transform = Transform2D(), const Color &p_modulate = Color(1, 1, 1));
void draw_multimesh(const Ref<MultiMesh> &p_multimesh, const Ref<Texture2D> &p_texture);
- void draw_string(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_text, HAlign p_align = HALIGN_LEFT, real_t p_width = -1, int p_size = -1, const Color &p_modulate = Color(1, 1, 1), int p_outline_size = 0, const Color &p_outline_modulate = Color(1, 1, 1, 0), uint16_t p_flags = TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND) const;
- void draw_multiline_string(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_text, HAlign p_align = HALIGN_LEFT, real_t p_width = -1, int p_max_lines = -1, int p_size = -1, const Color &p_modulate = Color(1, 1, 1), int p_outline_size = 0, const Color &p_outline_modulate = Color(1, 1, 1, 0), uint16_t p_flags = TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND) const;
- real_t draw_char(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_char, const String &p_next = "", int p_size = -1, const Color &p_modulate = Color(1, 1, 1), int p_outline_size = 0, const Color &p_outline_modulate = Color(1, 1, 1, 0)) const;
+ void draw_string(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_text, HAlign p_align = HALIGN_LEFT, real_t p_width = -1, int p_size = Font::DEFAULT_FONT_SIZE, const Color &p_modulate = Color(1, 1, 1), int p_outline_size = 0, const Color &p_outline_modulate = Color(1, 1, 1, 0), uint16_t p_flags = TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND) const;
+ void draw_multiline_string(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_text, HAlign p_align = HALIGN_LEFT, real_t p_width = -1, int p_max_lines = -1, int p_size = Font::DEFAULT_FONT_SIZE, const Color &p_modulate = Color(1, 1, 1), int p_outline_size = 0, const Color &p_outline_modulate = Color(1, 1, 1, 0), uint16_t p_flags = TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND) const;
+ real_t draw_char(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_char, const String &p_next = "", int p_size = Font::DEFAULT_FONT_SIZE, const Color &p_modulate = Color(1, 1, 1), int p_outline_size = 0, const Color &p_outline_modulate = Color(1, 1, 1, 0)) const;
void draw_set_transform(const Point2 &p_offset, real_t p_rot = 0.0, const Size2 &p_scale = Size2(1.0, 1.0));
void draw_set_transform_matrix(const Transform2D &p_matrix);
diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp
index 08b78a39b1..f8e4845ae6 100644
--- a/scene/resources/animation.cpp
+++ b/scene/resources/animation.cpp
@@ -30,13 +30,40 @@
#include "animation.h"
+#include "core/io/marshalls.h"
#include "core/math/geometry_3d.h"
#include "scene/scene_string_names.h"
bool Animation::_set(const StringName &p_name, const Variant &p_value) {
String name = p_name;
- if (name.begins_with("tracks/")) {
+ if (p_name == SNAME("_compression")) {
+ ERR_FAIL_COND_V(tracks.size() > 0, false); //can only set compression if no tracks exist
+ Dictionary comp = p_value;
+ ERR_FAIL_COND_V(!comp.has("fps"), false);
+ ERR_FAIL_COND_V(!comp.has("bounds"), false);
+ ERR_FAIL_COND_V(!comp.has("pages"), false);
+ ERR_FAIL_COND_V(!comp.has("format_version"), false);
+ uint32_t format_version = comp["format_version"];
+ ERR_FAIL_COND_V(format_version > Compression::FORMAT_VERSION, false); // version does not match this supported version
+ compression.fps = comp["fps"];
+ Array bounds = comp["bounds"];
+ compression.bounds.resize(bounds.size());
+ for (int i = 0; i < bounds.size(); i++) {
+ compression.bounds[i] = bounds[i];
+ }
+ Array pages = comp["pages"];
+ compression.pages.resize(pages.size());
+ for (int i = 0; i < pages.size(); i++) {
+ Dictionary page = pages[i];
+ ERR_FAIL_COND_V(!page.has("data"), false);
+ ERR_FAIL_COND_V(!page.has("time_offset"), false);
+ compression.pages[i].data = page["data"];
+ compression.pages[i].time_offset = page["time_offset"];
+ }
+ compression.enabled = true;
+ return true;
+ } else if (name.begins_with("tracks/")) {
int track = name.get_slicec('/', 1).to_int();
String what = name.get_slicec('/', 2);
@@ -72,6 +99,34 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) {
if (what == "path") {
track_set_path(track, p_value);
+ } else if (what == "compressed_track") {
+ int index = p_value;
+ ERR_FAIL_COND_V(!compression.enabled, false);
+ ERR_FAIL_UNSIGNED_INDEX_V((uint32_t)index, compression.bounds.size(), false);
+ Track *t = tracks[track];
+ t->interpolation = INTERPOLATION_LINEAR; //only linear supported
+ switch (t->type) {
+ case TYPE_POSITION_3D: {
+ PositionTrack *tt = static_cast<PositionTrack *>(t);
+ tt->compressed_track = index;
+ } break;
+ case TYPE_ROTATION_3D: {
+ RotationTrack *rt = static_cast<RotationTrack *>(t);
+ rt->compressed_track = index;
+ } break;
+ case TYPE_SCALE_3D: {
+ ScaleTrack *st = static_cast<ScaleTrack *>(t);
+ st->compressed_track = index;
+ } break;
+ case TYPE_BLEND_SHAPE: {
+ BlendShapeTrack *bst = static_cast<BlendShapeTrack *>(t);
+ bst->compressed_track = index;
+ } break;
+ default: {
+ return false;
+ }
+ }
+ return true;
} else if (what == "interp") {
track_set_interpolation_type(track, InterpolationType(p_value.operator int()));
} else if (what == "loop_wrap") {
@@ -369,7 +424,30 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) {
bool Animation::_get(const StringName &p_name, Variant &r_ret) const {
String name = p_name;
- if (name == "length") {
+ if (p_name == SNAME("_compression")) {
+ ERR_FAIL_COND_V(!compression.enabled, false);
+ Dictionary comp;
+ comp["fps"] = compression.fps;
+ Array bounds;
+ bounds.resize(compression.bounds.size());
+ for (uint32_t i = 0; i < compression.bounds.size(); i++) {
+ bounds[i] = compression.bounds[i];
+ }
+ comp["bounds"] = bounds;
+ Array pages;
+ pages.resize(compression.pages.size());
+ for (uint32_t i = 0; i < compression.pages.size(); i++) {
+ Dictionary page;
+ page["data"] = compression.pages[i].data;
+ page["time_offset"] = compression.pages[i].time_offset;
+ pages[i] = page;
+ }
+ comp["pages"] = pages;
+ comp["format_version"] = Compression::FORMAT_VERSION;
+
+ r_ret = comp;
+ return true;
+ } else if (name == "length") {
r_ret = length;
} else if (name == "loop") {
r_ret = loop;
@@ -414,6 +492,34 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const {
} else if (what == "path") {
r_ret = track_get_path(track);
+ } else if (what == "compressed_track") {
+ ERR_FAIL_COND_V(!compression.enabled, false);
+ Track *t = tracks[track];
+ switch (t->type) {
+ case TYPE_POSITION_3D: {
+ PositionTrack *tt = static_cast<PositionTrack *>(t);
+ r_ret = tt->compressed_track;
+ } break;
+ case TYPE_ROTATION_3D: {
+ RotationTrack *rt = static_cast<RotationTrack *>(t);
+ r_ret = rt->compressed_track;
+ } break;
+ case TYPE_SCALE_3D: {
+ ScaleTrack *st = static_cast<ScaleTrack *>(t);
+ r_ret = st->compressed_track;
+ } break;
+ case TYPE_BLEND_SHAPE: {
+ BlendShapeTrack *bst = static_cast<BlendShapeTrack *>(t);
+ r_ret = bst->compressed_track;
+ } break;
+ default: {
+ r_ret = Variant();
+ ERR_FAIL_V(false);
+ }
+ }
+
+ return true;
+
} else if (what == "interp") {
r_ret = track_get_interpolation_type(track);
} else if (what == "loop_wrap") {
@@ -692,14 +798,21 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const {
}
void Animation::_get_property_list(List<PropertyInfo> *p_list) const {
+ if (compression.enabled) {
+ p_list->push_back(PropertyInfo(Variant::DICTIONARY, "_compression", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
+ }
for (int i = 0; i < tracks.size(); i++) {
p_list->push_back(PropertyInfo(Variant::STRING, "tracks/" + itos(i) + "/type", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
- p_list->push_back(PropertyInfo(Variant::NODE_PATH, "tracks/" + itos(i) + "/path", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
- p_list->push_back(PropertyInfo(Variant::INT, "tracks/" + itos(i) + "/interp", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
- p_list->push_back(PropertyInfo(Variant::BOOL, "tracks/" + itos(i) + "/loop_wrap", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
p_list->push_back(PropertyInfo(Variant::BOOL, "tracks/" + itos(i) + "/imported", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
p_list->push_back(PropertyInfo(Variant::BOOL, "tracks/" + itos(i) + "/enabled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
- p_list->push_back(PropertyInfo(Variant::ARRAY, "tracks/" + itos(i) + "/keys", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
+ p_list->push_back(PropertyInfo(Variant::NODE_PATH, "tracks/" + itos(i) + "/path", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
+ if (track_is_compressed(i)) {
+ p_list->push_back(PropertyInfo(Variant::INT, "tracks/" + itos(i) + "/compressed_track", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
+ } else {
+ p_list->push_back(PropertyInfo(Variant::INT, "tracks/" + itos(i) + "/interp", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
+ p_list->push_back(PropertyInfo(Variant::BOOL, "tracks/" + itos(i) + "/loop_wrap", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
+ p_list->push_back(PropertyInfo(Variant::ARRAY, "tracks/" + itos(i) + "/keys", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
+ }
}
}
@@ -765,21 +878,25 @@ void Animation::remove_track(int p_track) {
switch (t->type) {
case TYPE_POSITION_3D: {
PositionTrack *tt = static_cast<PositionTrack *>(t);
+ ERR_FAIL_COND_MSG(tt->compressed_track >= 0, "Compressed tracks can't be manually removed. Call clear() to get rid of compression first.");
_clear(tt->positions);
} break;
case TYPE_ROTATION_3D: {
RotationTrack *rt = static_cast<RotationTrack *>(t);
+ ERR_FAIL_COND_MSG(rt->compressed_track >= 0, "Compressed tracks can't be manually removed. Call clear() to get rid of compression first.");
_clear(rt->rotations);
} break;
case TYPE_SCALE_3D: {
ScaleTrack *st = static_cast<ScaleTrack *>(t);
+ ERR_FAIL_COND_MSG(st->compressed_track >= 0, "Compressed tracks can't be manually removed. Call clear() to get rid of compression first.");
_clear(st->scales);
} break;
case TYPE_BLEND_SHAPE: {
BlendShapeTrack *bst = static_cast<BlendShapeTrack *>(t);
+ ERR_FAIL_COND_MSG(bst->compressed_track >= 0, "Compressed tracks can't be manually removed. Call clear() to get rid of compression first.");
_clear(bst->blend_shapes);
} break;
@@ -837,9 +954,9 @@ NodePath Animation::track_get_path(int p_track) const {
return tracks[p_track]->path;
}
-int Animation::find_track(const NodePath &p_path) const {
+int Animation::find_track(const NodePath &p_path, const TrackType p_type) const {
for (int i = 0; i < tracks.size(); i++) {
- if (tracks[i]->path == p_path) {
+ if (tracks[i]->path == p_path && tracks[i]->type == p_type) {
return i;
}
};
@@ -907,6 +1024,8 @@ int Animation::position_track_insert_key(int p_track, double p_time, const Vecto
PositionTrack *tt = static_cast<PositionTrack *>(t);
+ ERR_FAIL_COND_V(tt->compressed_track >= 0, -1);
+
TKey<Vector3> tkey;
tkey.time = p_time;
tkey.value = p_position;
@@ -922,6 +1041,19 @@ Error Animation::position_track_get_key(int p_track, int p_key, Vector3 *r_posit
PositionTrack *tt = static_cast<PositionTrack *>(t);
ERR_FAIL_COND_V(t->type != TYPE_POSITION_3D, ERR_INVALID_PARAMETER);
+
+ if (tt->compressed_track >= 0) {
+ Vector3i key;
+ double time;
+ bool fetch_success = _fetch_compressed_by_index<3>(tt->compressed_track, p_key, key, time);
+ if (!fetch_success) {
+ return ERR_INVALID_PARAMETER;
+ }
+
+ *r_position = _uncompress_pos_scale(tt->compressed_track, key);
+ return OK;
+ }
+
ERR_FAIL_INDEX_V(p_key, tt->positions.size(), ERR_INVALID_PARAMETER);
*r_position = tt->positions[p_key].value;
@@ -936,6 +1068,14 @@ Error Animation::position_track_interpolate(int p_track, double p_time, Vector3
PositionTrack *tt = static_cast<PositionTrack *>(t);
+ if (tt->compressed_track >= 0) {
+ if (_pos_scale_interpolate_compressed(tt->compressed_track, p_time, *r_interpolation)) {
+ return OK;
+ } else {
+ return ERR_UNAVAILABLE;
+ }
+ }
+
bool ok = false;
Vector3 tk = _interpolate(tt->positions, p_time, tt->interpolation, tt->loop_wrap, &ok);
@@ -956,6 +1096,8 @@ int Animation::rotation_track_insert_key(int p_track, double p_time, const Quate
RotationTrack *rt = static_cast<RotationTrack *>(t);
+ ERR_FAIL_COND_V(rt->compressed_track >= 0, -1);
+
TKey<Quaternion> tkey;
tkey.time = p_time;
tkey.value = p_rotation;
@@ -971,6 +1113,19 @@ Error Animation::rotation_track_get_key(int p_track, int p_key, Quaternion *r_ro
RotationTrack *rt = static_cast<RotationTrack *>(t);
ERR_FAIL_COND_V(t->type != TYPE_ROTATION_3D, ERR_INVALID_PARAMETER);
+
+ if (rt->compressed_track >= 0) {
+ Vector3i key;
+ double time;
+ bool fetch_success = _fetch_compressed_by_index<3>(rt->compressed_track, p_key, key, time);
+ if (!fetch_success) {
+ return ERR_INVALID_PARAMETER;
+ }
+
+ *r_rotation = _uncompress_quaternion(key);
+ return OK;
+ }
+
ERR_FAIL_INDEX_V(p_key, rt->rotations.size(), ERR_INVALID_PARAMETER);
*r_rotation = rt->rotations[p_key].value;
@@ -985,6 +1140,14 @@ Error Animation::rotation_track_interpolate(int p_track, double p_time, Quaterni
RotationTrack *rt = static_cast<RotationTrack *>(t);
+ if (rt->compressed_track >= 0) {
+ if (_rotation_interpolate_compressed(rt->compressed_track, p_time, *r_interpolation)) {
+ return OK;
+ } else {
+ return ERR_UNAVAILABLE;
+ }
+ }
+
bool ok = false;
Quaternion tk = _interpolate(rt->rotations, p_time, rt->interpolation, rt->loop_wrap, &ok);
@@ -1005,6 +1168,8 @@ int Animation::scale_track_insert_key(int p_track, double p_time, const Vector3
ScaleTrack *st = static_cast<ScaleTrack *>(t);
+ ERR_FAIL_COND_V(st->compressed_track >= 0, -1);
+
TKey<Vector3> tkey;
tkey.time = p_time;
tkey.value = p_scale;
@@ -1020,6 +1185,19 @@ Error Animation::scale_track_get_key(int p_track, int p_key, Vector3 *r_scale) c
ScaleTrack *st = static_cast<ScaleTrack *>(t);
ERR_FAIL_COND_V(t->type != TYPE_SCALE_3D, ERR_INVALID_PARAMETER);
+
+ if (st->compressed_track >= 0) {
+ Vector3i key;
+ double time;
+ bool fetch_success = _fetch_compressed_by_index<3>(st->compressed_track, p_key, key, time);
+ if (!fetch_success) {
+ return ERR_INVALID_PARAMETER;
+ }
+
+ *r_scale = _uncompress_pos_scale(st->compressed_track, key);
+ return OK;
+ }
+
ERR_FAIL_INDEX_V(p_key, st->scales.size(), ERR_INVALID_PARAMETER);
*r_scale = st->scales[p_key].value;
@@ -1034,6 +1212,14 @@ Error Animation::scale_track_interpolate(int p_track, double p_time, Vector3 *r_
ScaleTrack *st = static_cast<ScaleTrack *>(t);
+ if (st->compressed_track >= 0) {
+ if (_pos_scale_interpolate_compressed(st->compressed_track, p_time, *r_interpolation)) {
+ return OK;
+ } else {
+ return ERR_UNAVAILABLE;
+ }
+ }
+
bool ok = false;
Vector3 tk = _interpolate(st->scales, p_time, st->interpolation, st->loop_wrap, &ok);
@@ -1052,6 +1238,8 @@ int Animation::blend_shape_track_insert_key(int p_track, double p_time, float p_
BlendShapeTrack *st = static_cast<BlendShapeTrack *>(t);
+ ERR_FAIL_COND_V(st->compressed_track >= 0, -1);
+
TKey<float> tkey;
tkey.time = p_time;
tkey.value = p_blend_shape;
@@ -1065,11 +1253,24 @@ Error Animation::blend_shape_track_get_key(int p_track, int p_key, float *r_blen
ERR_FAIL_INDEX_V(p_track, tracks.size(), ERR_INVALID_PARAMETER);
Track *t = tracks[p_track];
- BlendShapeTrack *st = static_cast<BlendShapeTrack *>(t);
+ BlendShapeTrack *bst = static_cast<BlendShapeTrack *>(t);
ERR_FAIL_COND_V(t->type != TYPE_BLEND_SHAPE, ERR_INVALID_PARAMETER);
- ERR_FAIL_INDEX_V(p_key, st->blend_shapes.size(), ERR_INVALID_PARAMETER);
- *r_blend_shape = st->blend_shapes[p_key].value;
+ if (bst->compressed_track >= 0) {
+ Vector3i key;
+ double time;
+ bool fetch_success = _fetch_compressed_by_index<1>(bst->compressed_track, p_key, key, time);
+ if (!fetch_success) {
+ return ERR_INVALID_PARAMETER;
+ }
+
+ *r_blend_shape = _uncompress_blend_shape(key);
+ return OK;
+ }
+
+ ERR_FAIL_INDEX_V(p_key, bst->blend_shapes.size(), ERR_INVALID_PARAMETER);
+
+ *r_blend_shape = bst->blend_shapes[p_key].value;
return OK;
}
@@ -1079,11 +1280,19 @@ Error Animation::blend_shape_track_interpolate(int p_track, double p_time, float
Track *t = tracks[p_track];
ERR_FAIL_COND_V(t->type != TYPE_BLEND_SHAPE, ERR_INVALID_PARAMETER);
- BlendShapeTrack *st = static_cast<BlendShapeTrack *>(t);
+ BlendShapeTrack *bst = static_cast<BlendShapeTrack *>(t);
+
+ if (bst->compressed_track >= 0) {
+ if (_blend_shape_interpolate_compressed(bst->compressed_track, p_time, *r_interpolation)) {
+ return OK;
+ } else {
+ return ERR_UNAVAILABLE;
+ }
+ }
bool ok = false;
- float tk = _interpolate(st->blend_shapes, p_time, st->interpolation, st->loop_wrap, &ok);
+ float tk = _interpolate(bst->blend_shapes, p_time, bst->interpolation, bst->loop_wrap, &ok);
if (!ok) {
return ERR_UNAVAILABLE;
@@ -1105,24 +1314,36 @@ void Animation::track_remove_key(int p_track, int p_idx) {
switch (t->type) {
case TYPE_POSITION_3D: {
PositionTrack *tt = static_cast<PositionTrack *>(t);
+
+ ERR_FAIL_COND(tt->compressed_track >= 0);
+
ERR_FAIL_INDEX(p_idx, tt->positions.size());
tt->positions.remove(p_idx);
} break;
case TYPE_ROTATION_3D: {
RotationTrack *rt = static_cast<RotationTrack *>(t);
+
+ ERR_FAIL_COND(rt->compressed_track >= 0);
+
ERR_FAIL_INDEX(p_idx, rt->rotations.size());
rt->rotations.remove(p_idx);
} break;
case TYPE_SCALE_3D: {
ScaleTrack *st = static_cast<ScaleTrack *>(t);
+
+ ERR_FAIL_COND(st->compressed_track >= 0);
+
ERR_FAIL_INDEX(p_idx, st->scales.size());
st->scales.remove(p_idx);
} break;
case TYPE_BLEND_SHAPE: {
BlendShapeTrack *bst = static_cast<BlendShapeTrack *>(t);
+
+ ERR_FAIL_COND(bst->compressed_track >= 0);
+
ERR_FAIL_INDEX(p_idx, bst->blend_shapes.size());
bst->blend_shapes.remove(p_idx);
@@ -1169,6 +1390,21 @@ int Animation::track_find_key(int p_track, double p_time, bool p_exact) const {
switch (t->type) {
case TYPE_POSITION_3D: {
PositionTrack *tt = static_cast<PositionTrack *>(t);
+
+ if (tt->compressed_track >= 0) {
+ double time;
+ double time_next;
+ Vector3i key;
+ Vector3i key_next;
+ uint32_t key_index;
+ bool fetch_compressed_success = _fetch_compressed<3>(tt->compressed_track, p_time, key, time, key_next, time_next, &key_index);
+ ERR_FAIL_COND_V(!fetch_compressed_success, -1);
+ if (p_exact && time != p_time) {
+ return -1;
+ }
+ return key_index;
+ }
+
int k = _find(tt->positions, p_time);
if (k < 0 || k >= tt->positions.size()) {
return -1;
@@ -1181,6 +1417,21 @@ int Animation::track_find_key(int p_track, double p_time, bool p_exact) const {
} break;
case TYPE_ROTATION_3D: {
RotationTrack *rt = static_cast<RotationTrack *>(t);
+
+ if (rt->compressed_track >= 0) {
+ double time;
+ double time_next;
+ Vector3i key;
+ Vector3i key_next;
+ uint32_t key_index;
+ bool fetch_compressed_success = _fetch_compressed<3>(rt->compressed_track, p_time, key, time, key_next, time_next, &key_index);
+ ERR_FAIL_COND_V(!fetch_compressed_success, -1);
+ if (p_exact && time != p_time) {
+ return -1;
+ }
+ return key_index;
+ }
+
int k = _find(rt->rotations, p_time);
if (k < 0 || k >= rt->rotations.size()) {
return -1;
@@ -1193,6 +1444,21 @@ int Animation::track_find_key(int p_track, double p_time, bool p_exact) const {
} break;
case TYPE_SCALE_3D: {
ScaleTrack *st = static_cast<ScaleTrack *>(t);
+
+ if (st->compressed_track >= 0) {
+ double time;
+ double time_next;
+ Vector3i key;
+ Vector3i key_next;
+ uint32_t key_index;
+ bool fetch_compressed_success = _fetch_compressed<3>(st->compressed_track, p_time, key, time, key_next, time_next, &key_index);
+ ERR_FAIL_COND_V(!fetch_compressed_success, -1);
+ if (p_exact && time != p_time) {
+ return -1;
+ }
+ return key_index;
+ }
+
int k = _find(st->scales, p_time);
if (k < 0 || k >= st->scales.size()) {
return -1;
@@ -1205,6 +1471,21 @@ int Animation::track_find_key(int p_track, double p_time, bool p_exact) const {
} break;
case TYPE_BLEND_SHAPE: {
BlendShapeTrack *bst = static_cast<BlendShapeTrack *>(t);
+
+ if (bst->compressed_track >= 0) {
+ double time;
+ double time_next;
+ Vector3i key;
+ Vector3i key_next;
+ uint32_t key_index;
+ bool fetch_compressed_success = _fetch_compressed<1>(bst->compressed_track, p_time, key, time, key_next, time_next, &key_index);
+ ERR_FAIL_COND_V(!fetch_compressed_success, -1);
+ if (p_exact && time != p_time) {
+ return -1;
+ }
+ return key_index;
+ }
+
int k = _find(bst->blend_shapes, p_time);
if (k < 0 || k >= bst->blend_shapes.size()) {
return -1;
@@ -1392,18 +1673,30 @@ int Animation::track_get_key_count(int p_track) const {
switch (t->type) {
case TYPE_POSITION_3D: {
PositionTrack *tt = static_cast<PositionTrack *>(t);
+ if (tt->compressed_track >= 0) {
+ return _get_compressed_key_count(tt->compressed_track);
+ }
return tt->positions.size();
} break;
case TYPE_ROTATION_3D: {
RotationTrack *rt = static_cast<RotationTrack *>(t);
+ if (rt->compressed_track >= 0) {
+ return _get_compressed_key_count(rt->compressed_track);
+ }
return rt->rotations.size();
} break;
case TYPE_SCALE_3D: {
ScaleTrack *st = static_cast<ScaleTrack *>(t);
+ if (st->compressed_track >= 0) {
+ return _get_compressed_key_count(st->compressed_track);
+ }
return st->scales.size();
} break;
case TYPE_BLEND_SHAPE: {
BlendShapeTrack *bst = static_cast<BlendShapeTrack *>(t);
+ if (bst->compressed_track >= 0) {
+ return _get_compressed_key_count(bst->compressed_track);
+ }
return bst->blend_shapes.size();
} break;
case TYPE_VALUE: {
@@ -1438,28 +1731,24 @@ Variant Animation::track_get_key_value(int p_track, int p_key_idx) const {
switch (t->type) {
case TYPE_POSITION_3D: {
- PositionTrack *tt = static_cast<PositionTrack *>(t);
- ERR_FAIL_INDEX_V(p_key_idx, tt->positions.size(), Variant());
-
- return tt->positions[p_key_idx].value;
+ Vector3 value;
+ position_track_get_key(p_track, p_key_idx, &value);
+ return value;
} break;
case TYPE_ROTATION_3D: {
- RotationTrack *rt = static_cast<RotationTrack *>(t);
- ERR_FAIL_INDEX_V(p_key_idx, rt->rotations.size(), Variant());
-
- return rt->rotations[p_key_idx].value;
+ Quaternion value;
+ rotation_track_get_key(p_track, p_key_idx, &value);
+ return value;
} break;
case TYPE_SCALE_3D: {
- ScaleTrack *st = static_cast<ScaleTrack *>(t);
- ERR_FAIL_INDEX_V(p_key_idx, st->scales.size(), Variant());
-
- return st->scales[p_key_idx].value;
+ Vector3 value;
+ scale_track_get_key(p_track, p_key_idx, &value);
+ return value;
} break;
case TYPE_BLEND_SHAPE: {
- BlendShapeTrack *bst = static_cast<BlendShapeTrack *>(t);
- ERR_FAIL_INDEX_V(p_key_idx, bst->blend_shapes.size(), Variant());
-
- return bst->blend_shapes[p_key_idx].value;
+ float value;
+ blend_shape_track_get_key(p_track, p_key_idx, &value);
+ return value;
} break;
case TYPE_VALUE: {
ValueTrack *vt = static_cast<ValueTrack *>(t);
@@ -1520,21 +1809,49 @@ double Animation::track_get_key_time(int p_track, int p_key_idx) const {
switch (t->type) {
case TYPE_POSITION_3D: {
PositionTrack *tt = static_cast<PositionTrack *>(t);
+ if (tt->compressed_track >= 0) {
+ Vector3i value;
+ double time;
+ bool fetch_compressed_success = _fetch_compressed_by_index<3>(tt->compressed_track, p_key_idx, value, time);
+ ERR_FAIL_COND_V(!fetch_compressed_success, false);
+ return time;
+ }
ERR_FAIL_INDEX_V(p_key_idx, tt->positions.size(), -1);
return tt->positions[p_key_idx].time;
} break;
case TYPE_ROTATION_3D: {
RotationTrack *rt = static_cast<RotationTrack *>(t);
+ if (rt->compressed_track >= 0) {
+ Vector3i value;
+ double time;
+ bool fetch_compressed_success = _fetch_compressed_by_index<3>(rt->compressed_track, p_key_idx, value, time);
+ ERR_FAIL_COND_V(!fetch_compressed_success, false);
+ return time;
+ }
ERR_FAIL_INDEX_V(p_key_idx, rt->rotations.size(), -1);
return rt->rotations[p_key_idx].time;
} break;
case TYPE_SCALE_3D: {
ScaleTrack *st = static_cast<ScaleTrack *>(t);
+ if (st->compressed_track >= 0) {
+ Vector3i value;
+ double time;
+ bool fetch_compressed_success = _fetch_compressed_by_index<3>(st->compressed_track, p_key_idx, value, time);
+ ERR_FAIL_COND_V(!fetch_compressed_success, false);
+ return time;
+ }
ERR_FAIL_INDEX_V(p_key_idx, st->scales.size(), -1);
return st->scales[p_key_idx].time;
} break;
case TYPE_BLEND_SHAPE: {
BlendShapeTrack *bst = static_cast<BlendShapeTrack *>(t);
+ if (bst->compressed_track >= 0) {
+ Vector3i value;
+ double time;
+ bool fetch_compressed_success = _fetch_compressed_by_index<1>(bst->compressed_track, p_key_idx, value, time);
+ ERR_FAIL_COND_V(!fetch_compressed_success, false);
+ return time;
+ }
ERR_FAIL_INDEX_V(p_key_idx, bst->blend_shapes.size(), -1);
return bst->blend_shapes[p_key_idx].time;
} break;
@@ -1580,6 +1897,7 @@ void Animation::track_set_key_time(int p_track, int p_key_idx, double p_time) {
switch (t->type) {
case TYPE_POSITION_3D: {
PositionTrack *tt = static_cast<PositionTrack *>(t);
+ ERR_FAIL_COND(tt->compressed_track >= 0);
ERR_FAIL_INDEX(p_key_idx, tt->positions.size());
TKey<Vector3> key = tt->positions[p_key_idx];
key.time = p_time;
@@ -1589,6 +1907,7 @@ void Animation::track_set_key_time(int p_track, int p_key_idx, double p_time) {
}
case TYPE_ROTATION_3D: {
RotationTrack *tt = static_cast<RotationTrack *>(t);
+ ERR_FAIL_COND(tt->compressed_track >= 0);
ERR_FAIL_INDEX(p_key_idx, tt->rotations.size());
TKey<Quaternion> key = tt->rotations[p_key_idx];
key.time = p_time;
@@ -1598,6 +1917,7 @@ void Animation::track_set_key_time(int p_track, int p_key_idx, double p_time) {
}
case TYPE_SCALE_3D: {
ScaleTrack *tt = static_cast<ScaleTrack *>(t);
+ ERR_FAIL_COND(tt->compressed_track >= 0);
ERR_FAIL_INDEX(p_key_idx, tt->scales.size());
TKey<Vector3> key = tt->scales[p_key_idx];
key.time = p_time;
@@ -1607,6 +1927,7 @@ void Animation::track_set_key_time(int p_track, int p_key_idx, double p_time) {
}
case TYPE_BLEND_SHAPE: {
BlendShapeTrack *tt = static_cast<BlendShapeTrack *>(t);
+ ERR_FAIL_COND(tt->compressed_track >= 0);
ERR_FAIL_INDEX(p_key_idx, tt->blend_shapes.size());
TKey<float> key = tt->blend_shapes[p_key_idx];
key.time = p_time;
@@ -1671,21 +1992,33 @@ real_t Animation::track_get_key_transition(int p_track, int p_key_idx) const {
switch (t->type) {
case TYPE_POSITION_3D: {
PositionTrack *tt = static_cast<PositionTrack *>(t);
+ if (tt->compressed_track >= 0) {
+ return 1.0;
+ }
ERR_FAIL_INDEX_V(p_key_idx, tt->positions.size(), -1);
return tt->positions[p_key_idx].transition;
} break;
case TYPE_ROTATION_3D: {
RotationTrack *rt = static_cast<RotationTrack *>(t);
+ if (rt->compressed_track >= 0) {
+ return 1.0;
+ }
ERR_FAIL_INDEX_V(p_key_idx, rt->rotations.size(), -1);
return rt->rotations[p_key_idx].transition;
} break;
case TYPE_SCALE_3D: {
ScaleTrack *st = static_cast<ScaleTrack *>(t);
+ if (st->compressed_track >= 0) {
+ return 1.0;
+ }
ERR_FAIL_INDEX_V(p_key_idx, st->scales.size(), -1);
return st->scales[p_key_idx].transition;
} break;
case TYPE_BLEND_SHAPE: {
BlendShapeTrack *bst = static_cast<BlendShapeTrack *>(t);
+ if (bst->compressed_track >= 0) {
+ return 1.0;
+ }
ERR_FAIL_INDEX_V(p_key_idx, bst->blend_shapes.size(), -1);
return bst->blend_shapes[p_key_idx].transition;
} break;
@@ -1715,6 +2048,35 @@ real_t Animation::track_get_key_transition(int p_track, int p_key_idx) const {
ERR_FAIL_V(0);
}
+bool Animation::track_is_compressed(int p_track) const {
+ ERR_FAIL_INDEX_V(p_track, tracks.size(), false);
+ Track *t = tracks[p_track];
+
+ switch (t->type) {
+ case TYPE_POSITION_3D: {
+ PositionTrack *tt = static_cast<PositionTrack *>(t);
+ return tt->compressed_track >= 0;
+ } break;
+ case TYPE_ROTATION_3D: {
+ RotationTrack *rt = static_cast<RotationTrack *>(t);
+ return rt->compressed_track >= 0;
+ } break;
+ case TYPE_SCALE_3D: {
+ ScaleTrack *st = static_cast<ScaleTrack *>(t);
+ return st->compressed_track >= 0;
+ } break;
+ case TYPE_BLEND_SHAPE: {
+ BlendShapeTrack *bst = static_cast<BlendShapeTrack *>(t);
+ return bst->compressed_track >= 0;
+ } break;
+ default: {
+ return false; //animation does not really use transitions
+ } break;
+ }
+
+ ERR_FAIL_V(false);
+}
+
void Animation::track_set_key_value(int p_track, int p_key_idx, const Variant &p_value) {
ERR_FAIL_INDEX(p_track, tracks.size());
Track *t = tracks[p_track];
@@ -1723,6 +2085,7 @@ void Animation::track_set_key_value(int p_track, int p_key_idx, const Variant &p
case TYPE_POSITION_3D: {
ERR_FAIL_COND((p_value.get_type() != Variant::VECTOR3) && (p_value.get_type() != Variant::VECTOR3I));
PositionTrack *tt = static_cast<PositionTrack *>(t);
+ ERR_FAIL_COND(tt->compressed_track >= 0);
ERR_FAIL_INDEX(p_key_idx, tt->positions.size());
tt->positions.write[p_key_idx].value = p_value;
@@ -1731,6 +2094,7 @@ void Animation::track_set_key_value(int p_track, int p_key_idx, const Variant &p
case TYPE_ROTATION_3D: {
ERR_FAIL_COND((p_value.get_type() != Variant::QUATERNION) && (p_value.get_type() != Variant::BASIS));
RotationTrack *rt = static_cast<RotationTrack *>(t);
+ ERR_FAIL_COND(rt->compressed_track >= 0);
ERR_FAIL_INDEX(p_key_idx, rt->rotations.size());
rt->rotations.write[p_key_idx].value = p_value;
@@ -1739,6 +2103,7 @@ void Animation::track_set_key_value(int p_track, int p_key_idx, const Variant &p
case TYPE_SCALE_3D: {
ERR_FAIL_COND((p_value.get_type() != Variant::VECTOR3) && (p_value.get_type() != Variant::VECTOR3I));
ScaleTrack *st = static_cast<ScaleTrack *>(t);
+ ERR_FAIL_COND(st->compressed_track >= 0);
ERR_FAIL_INDEX(p_key_idx, st->scales.size());
st->scales.write[p_key_idx].value = p_value;
@@ -1747,6 +2112,7 @@ void Animation::track_set_key_value(int p_track, int p_key_idx, const Variant &p
case TYPE_BLEND_SHAPE: {
ERR_FAIL_COND((p_value.get_type() != Variant::FLOAT) && (p_value.get_type() != Variant::INT));
BlendShapeTrack *bst = static_cast<BlendShapeTrack *>(t);
+ ERR_FAIL_COND(bst->compressed_track >= 0);
ERR_FAIL_INDEX(p_key_idx, bst->blend_shapes.size());
bst->blend_shapes.write[p_key_idx].value = p_value;
@@ -1820,21 +2186,25 @@ void Animation::track_set_key_transition(int p_track, int p_key_idx, real_t p_tr
switch (t->type) {
case TYPE_POSITION_3D: {
PositionTrack *tt = static_cast<PositionTrack *>(t);
+ ERR_FAIL_COND(tt->compressed_track >= 0);
ERR_FAIL_INDEX(p_key_idx, tt->positions.size());
tt->positions.write[p_key_idx].transition = p_transition;
} break;
case TYPE_ROTATION_3D: {
RotationTrack *rt = static_cast<RotationTrack *>(t);
+ ERR_FAIL_COND(rt->compressed_track >= 0);
ERR_FAIL_INDEX(p_key_idx, rt->rotations.size());
rt->rotations.write[p_key_idx].transition = p_transition;
} break;
case TYPE_SCALE_3D: {
ScaleTrack *st = static_cast<ScaleTrack *>(t);
+ ERR_FAIL_COND(st->compressed_track >= 0);
ERR_FAIL_INDEX(p_key_idx, st->scales.size());
st->scales.write[p_key_idx].transition = p_transition;
} break;
case TYPE_BLEND_SHAPE: {
BlendShapeTrack *bst = static_cast<BlendShapeTrack *>(t);
+ ERR_FAIL_COND(bst->compressed_track >= 0);
ERR_FAIL_INDEX(p_key_idx, bst->blend_shapes.size());
bst->blend_shapes.write[p_key_idx].transition = p_transition;
} break;
@@ -2334,26 +2704,50 @@ void Animation::track_get_key_indices_in_range(int p_track, double p_time, doubl
switch (t->type) {
case TYPE_POSITION_3D: {
const PositionTrack *tt = static_cast<const PositionTrack *>(t);
- _track_get_key_indices_in_range(tt->positions, from_time, length, p_indices);
- _track_get_key_indices_in_range(tt->positions, 0, to_time, p_indices);
+ if (tt->compressed_track >= 0) {
+ _get_compressed_key_indices_in_range<3>(tt->compressed_track, from_time, length, p_indices);
+ _get_compressed_key_indices_in_range<3>(tt->compressed_track, 0, to_time, p_indices);
+
+ } else {
+ _track_get_key_indices_in_range(tt->positions, from_time, length, p_indices);
+ _track_get_key_indices_in_range(tt->positions, 0, to_time, p_indices);
+ }
} break;
case TYPE_ROTATION_3D: {
const RotationTrack *rt = static_cast<const RotationTrack *>(t);
- _track_get_key_indices_in_range(rt->rotations, from_time, length, p_indices);
- _track_get_key_indices_in_range(rt->rotations, 0, to_time, p_indices);
+ if (rt->compressed_track >= 0) {
+ _get_compressed_key_indices_in_range<3>(rt->compressed_track, from_time, length, p_indices);
+ _get_compressed_key_indices_in_range<3>(rt->compressed_track, 0, to_time, p_indices);
+
+ } else {
+ _track_get_key_indices_in_range(rt->rotations, from_time, length, p_indices);
+ _track_get_key_indices_in_range(rt->rotations, 0, to_time, p_indices);
+ }
} break;
case TYPE_SCALE_3D: {
const ScaleTrack *st = static_cast<const ScaleTrack *>(t);
- _track_get_key_indices_in_range(st->scales, from_time, length, p_indices);
- _track_get_key_indices_in_range(st->scales, 0, to_time, p_indices);
+ if (st->compressed_track >= 0) {
+ _get_compressed_key_indices_in_range<3>(st->compressed_track, from_time, length, p_indices);
+ _get_compressed_key_indices_in_range<3>(st->compressed_track, 0, to_time, p_indices);
+
+ } else {
+ _track_get_key_indices_in_range(st->scales, from_time, length, p_indices);
+ _track_get_key_indices_in_range(st->scales, 0, to_time, p_indices);
+ }
} break;
case TYPE_BLEND_SHAPE: {
const BlendShapeTrack *bst = static_cast<const BlendShapeTrack *>(t);
- _track_get_key_indices_in_range(bst->blend_shapes, from_time, length, p_indices);
- _track_get_key_indices_in_range(bst->blend_shapes, 0, to_time, p_indices);
+ if (bst->compressed_track >= 0) {
+ _get_compressed_key_indices_in_range<1>(bst->compressed_track, from_time, length, p_indices);
+ _get_compressed_key_indices_in_range<1>(bst->compressed_track, 0, to_time, p_indices);
+
+ } else {
+ _track_get_key_indices_in_range(bst->blend_shapes, from_time, length, p_indices);
+ _track_get_key_indices_in_range(bst->blend_shapes, 0, to_time, p_indices);
+ }
} break;
case TYPE_VALUE: {
@@ -2408,22 +2802,38 @@ void Animation::track_get_key_indices_in_range(int p_track, double p_time, doubl
switch (t->type) {
case TYPE_POSITION_3D: {
const PositionTrack *tt = static_cast<const PositionTrack *>(t);
- _track_get_key_indices_in_range(tt->positions, from_time, to_time, p_indices);
+ if (tt->compressed_track >= 0) {
+ _get_compressed_key_indices_in_range<3>(tt->compressed_track, from_time, to_time - from_time, p_indices);
+ } else {
+ _track_get_key_indices_in_range(tt->positions, from_time, to_time, p_indices);
+ }
} break;
case TYPE_ROTATION_3D: {
const RotationTrack *rt = static_cast<const RotationTrack *>(t);
- _track_get_key_indices_in_range(rt->rotations, from_time, to_time, p_indices);
+ if (rt->compressed_track >= 0) {
+ _get_compressed_key_indices_in_range<3>(rt->compressed_track, from_time, to_time - from_time, p_indices);
+ } else {
+ _track_get_key_indices_in_range(rt->rotations, from_time, to_time, p_indices);
+ }
} break;
case TYPE_SCALE_3D: {
const ScaleTrack *st = static_cast<const ScaleTrack *>(t);
- _track_get_key_indices_in_range(st->scales, from_time, to_time, p_indices);
+ if (st->compressed_track >= 0) {
+ _get_compressed_key_indices_in_range<3>(st->compressed_track, from_time, to_time - from_time, p_indices);
+ } else {
+ _track_get_key_indices_in_range(st->scales, from_time, to_time, p_indices);
+ }
} break;
case TYPE_BLEND_SHAPE: {
const BlendShapeTrack *bst = static_cast<const BlendShapeTrack *>(t);
- _track_get_key_indices_in_range(bst->blend_shapes, from_time, to_time, p_indices);
+ if (bst->compressed_track >= 0) {
+ _get_compressed_key_indices_in_range<1>(bst->compressed_track, from_time, to_time - from_time, p_indices);
+ } else {
+ _track_get_key_indices_in_range(bst->blend_shapes, from_time, to_time, p_indices);
+ }
} break;
case TYPE_VALUE: {
@@ -3027,7 +3437,7 @@ void Animation::_bind_methods() {
ClassDB::bind_method(D_METHOD("track_get_type", "track_idx"), &Animation::track_get_type);
ClassDB::bind_method(D_METHOD("track_get_path", "track_idx"), &Animation::track_get_path);
ClassDB::bind_method(D_METHOD("track_set_path", "track_idx", "path"), &Animation::track_set_path);
- ClassDB::bind_method(D_METHOD("find_track", "path"), &Animation::find_track);
+ ClassDB::bind_method(D_METHOD("find_track", "path", "type"), &Animation::find_track);
ClassDB::bind_method(D_METHOD("track_move_up", "track_idx"), &Animation::track_move_up);
ClassDB::bind_method(D_METHOD("track_move_down", "track_idx"), &Animation::track_move_down);
@@ -3064,6 +3474,8 @@ void Animation::_bind_methods() {
ClassDB::bind_method(D_METHOD("track_set_interpolation_loop_wrap", "track_idx", "interpolation"), &Animation::track_set_interpolation_loop_wrap);
ClassDB::bind_method(D_METHOD("track_get_interpolation_loop_wrap", "track_idx"), &Animation::track_get_interpolation_loop_wrap);
+ ClassDB::bind_method(D_METHOD("track_is_compressed", "track_idx"), &Animation::track_is_compressed);
+
ClassDB::bind_method(D_METHOD("value_track_set_update_mode", "track_idx", "mode"), &Animation::value_track_set_update_mode);
ClassDB::bind_method(D_METHOD("value_track_get_update_mode", "track_idx"), &Animation::value_track_get_update_mode);
@@ -3110,6 +3522,8 @@ void Animation::_bind_methods() {
ClassDB::bind_method(D_METHOD("clear"), &Animation::clear);
ClassDB::bind_method(D_METHOD("copy_track", "track_idx", "to_animation"), &Animation::copy_track);
+ ClassDB::bind_method(D_METHOD("compress", "page_size", "fps", "split_tolerance"), &Animation::compress, DEFVAL(8192), DEFVAL(120), DEFVAL(4.0));
+
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "length", PROPERTY_HINT_RANGE, "0.001,99999,0.001"), "set_length", "get_length");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "loop"), "set_loop", "has_loop");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "step", PROPERTY_HINT_RANGE, "0,4096,0.001"), "set_step", "get_step");
@@ -3143,6 +3557,10 @@ void Animation::clear() {
tracks.clear();
loop = false;
length = 1;
+ compression.enabled = false;
+ compression.bounds.clear();
+ compression.pages.clear();
+ compression.fps = 120;
emit_changed();
emit_signal(SceneStringNames::get_singleton()->tracks_changed);
}
@@ -3447,6 +3865,9 @@ void Animation::_blend_shape_track_optimize(int p_idx, real_t p_allowed_linear_e
void Animation::optimize(real_t p_allowed_linear_err, real_t p_allowed_angular_err, real_t p_max_optimizable_angle) {
for (int i = 0; i < tracks.size(); i++) {
+ if (track_is_compressed(i)) {
+ continue; //not possible to optimize compressed track
+ }
if (tracks[i]->type == TYPE_POSITION_3D) {
_position_track_optimize(i, p_allowed_linear_err, p_allowed_angular_err);
} else if (tracks[i]->type == TYPE_ROTATION_3D) {
@@ -3459,6 +3880,1154 @@ void Animation::optimize(real_t p_allowed_linear_err, real_t p_allowed_angular_e
}
}
+#define print_animc(m_str)
+//#define print_animc(m_str) print_line(m_str);
+
+struct AnimationCompressionDataState {
+ enum {
+ MIN_OPTIMIZE_PACKETS = 5,
+ MAX_PACKETS = 16
+ };
+
+ uint32_t components = 3;
+ LocalVector<uint8_t> data; //commited packets
+ struct PacketData {
+ int32_t data[3] = { 0, 0, 0 };
+ uint32_t frame = 0;
+ };
+
+ float split_tolerance = 1.5;
+
+ LocalVector<PacketData> temp_packets;
+
+ //used for rollback if the new frame does not fit
+ int32_t validated_packet_count = -1;
+
+ static int32_t _compute_delta16_signed(int32_t p_from, int32_t p_to) {
+ int32_t delta = p_to - p_from;
+ if (delta > 32767) {
+ return delta - 65536; // use wrap around
+ } else if (delta < -32768) {
+ return 65536 + delta; // use wrap around
+ }
+ return delta;
+ }
+
+ static uint32_t _compute_shift_bits_signed(int32_t p_delta) {
+ if (p_delta == 0) {
+ return 0;
+ } else if (p_delta < 0) {
+ p_delta = ABS(p_delta) - 1;
+ if (p_delta == 0) {
+ return 1;
+ }
+ }
+ return nearest_shift(p_delta);
+ }
+
+ void _compute_max_shifts(uint32_t p_from, uint32_t p_to, uint32_t *max_shifts, uint32_t &max_frame_delta_shift) const {
+ for (uint32_t j = 0; j < components; j++) {
+ max_shifts[j] = 0;
+ }
+ max_frame_delta_shift = 0;
+
+ for (uint32_t i = p_from + 1; i <= p_to; i++) {
+ int32_t frame_delta = temp_packets[i].frame - temp_packets[i - 1].frame;
+ max_frame_delta_shift = MAX(max_frame_delta_shift, nearest_shift(frame_delta));
+ for (uint32_t j = 0; j < components; j++) {
+ int32_t diff = _compute_delta16_signed(temp_packets[i - 1].data[j], temp_packets[i].data[j]);
+ uint32_t shift = _compute_shift_bits_signed(diff);
+ max_shifts[j] = MAX(shift, max_shifts[j]);
+ }
+ }
+ }
+
+ bool insert_key(uint32_t p_frame, const Vector3i &p_key) {
+ if (temp_packets.size() == MAX_PACKETS) {
+ commit_temp_packets();
+ }
+ PacketData packet;
+ packet.frame = p_frame;
+ for (int i = 0; i < 3; i++) {
+ ERR_FAIL_COND_V(p_key[i] > 65535, false); // Sanity check
+ packet.data[i] = p_key[i];
+ }
+
+ temp_packets.push_back(packet);
+
+ if (temp_packets.size() >= MIN_OPTIMIZE_PACKETS) {
+ uint32_t max_shifts[3] = { 0, 0, 0 }; // Base sizes, 16 bit
+ uint32_t max_frame_delta_shift = 0;
+ // Compute the average shift before the packet was added
+ _compute_max_shifts(0, temp_packets.size() - 2, max_shifts, max_frame_delta_shift);
+
+ float prev_packet_size_avg = 0;
+ prev_packet_size_avg = float(1 << max_frame_delta_shift);
+ for (uint32_t i = 0; i < components; i++) {
+ prev_packet_size_avg += float(1 << max_shifts[i]);
+ }
+ prev_packet_size_avg /= float(1 + components);
+
+ _compute_max_shifts(temp_packets.size() - 2, temp_packets.size() - 1, max_shifts, max_frame_delta_shift);
+
+ float new_packet_size_avg = 0;
+ new_packet_size_avg = float(1 << max_frame_delta_shift);
+ for (uint32_t i = 0; i < components; i++) {
+ new_packet_size_avg += float(1 << max_shifts[i]);
+ }
+ new_packet_size_avg /= float(1 + components);
+
+ print_animc("packet count: " + rtos(temp_packets.size() - 1) + " size avg " + rtos(prev_packet_size_avg) + " new avg " + rtos(new_packet_size_avg));
+ float ratio = (prev_packet_size_avg < new_packet_size_avg) ? (new_packet_size_avg / prev_packet_size_avg) : (prev_packet_size_avg / new_packet_size_avg);
+
+ if (ratio > split_tolerance) {
+ print_animc("split!");
+ temp_packets.resize(temp_packets.size() - 1);
+ commit_temp_packets();
+ temp_packets.push_back(packet);
+ }
+ }
+
+ return temp_packets.size() == 1; // First key
+ }
+
+ uint32_t get_temp_packet_size() const {
+ if (temp_packets.size() == 0) {
+ return 0;
+ } else if (temp_packets.size() == 1) {
+ return components == 1 ? 4 : 8; // 1 component packet is 16 bits and 16 bits unused. 3 component packets is 48 bits and 16 bits unused
+ }
+ uint32_t max_shifts[3] = { 0, 0, 0 }; //base sizes, 16 bit
+ uint32_t max_frame_delta_shift = 0;
+
+ _compute_max_shifts(0, temp_packets.size() - 1, max_shifts, max_frame_delta_shift);
+
+ uint32_t size_bits = 16; //base value (all 4 bits of shift sizes for x,y,z,time)
+ size_bits += max_frame_delta_shift * (temp_packets.size() - 1); //times
+ for (uint32_t j = 0; j < components; j++) {
+ size_bits += 16; //base value
+ uint32_t shift = max_shifts[j];
+ if (shift > 0) {
+ shift += 1; //if not zero, add sign bit
+ }
+ size_bits += shift * (temp_packets.size() - 1);
+ }
+ if (size_bits % 8 != 0) { //wrap to 8 bits
+ size_bits += 8 - (size_bits % 8);
+ }
+ uint32_t size_bytes = size_bits / 8; //wrap to words
+ if (size_bytes % 4 != 0) {
+ size_bytes += 4 - (size_bytes % 4);
+ }
+ return size_bytes;
+ }
+
+ static void _push_bits(LocalVector<uint8_t> &data, uint32_t &r_buffer, uint32_t &r_bits_used, uint32_t p_value, uint32_t p_bits) {
+ r_buffer |= p_value << r_bits_used;
+ r_bits_used += p_bits;
+ while (r_bits_used >= 8) {
+ uint8_t byte = r_buffer & 0xFF;
+ data.push_back(byte);
+ r_buffer >>= 8;
+ r_bits_used -= 8;
+ }
+ }
+
+ void commit_temp_packets() {
+ if (temp_packets.size() == 0) {
+ return; //nohing to do
+ }
+#define DEBUG_PACKET_PUSH
+#ifdef DEBUG_PACKET_PUSH
+#ifndef _MSC_VER
+#warning Debugging packet push, disable this code in production to gain a bit more import performance.
+#endif
+ uint32_t debug_packet_push = get_temp_packet_size();
+ uint32_t debug_data_size = data.size();
+#endif
+ // Store header
+
+ uint8_t header[8];
+ uint32_t header_bytes = 0;
+ for (uint32_t i = 0; i < components; i++) {
+ encode_uint16(temp_packets[0].data[i], &header[header_bytes]);
+ header_bytes += 2;
+ }
+
+ uint32_t max_shifts[3] = { 0, 0, 0 }; //base sizes, 16 bit
+ uint32_t max_frame_delta_shift = 0;
+
+ if (temp_packets.size() > 1) {
+ _compute_max_shifts(0, temp_packets.size() - 1, max_shifts, max_frame_delta_shift);
+ uint16_t shift_header = (max_frame_delta_shift - 1) << 12;
+ for (uint32_t i = 0; i < components; i++) {
+ shift_header |= max_shifts[i] << (4 * i);
+ }
+
+ encode_uint16(shift_header, &header[header_bytes]);
+ header_bytes += 2;
+ }
+
+ while (header_bytes % 4 != 0) {
+ header[header_bytes++] = 0;
+ }
+
+ for (uint32_t i = 0; i < header_bytes; i++) {
+ data.push_back(header[i]);
+ }
+
+ if (temp_packets.size() == 1) {
+ temp_packets.clear();
+ validated_packet_count = 0;
+ return; //only header stored, nothing else to do
+ }
+
+ uint32_t bit_buffer = 0;
+ uint32_t bits_used = 0;
+
+ for (uint32_t i = 1; i < temp_packets.size(); i++) {
+ uint32_t frame_delta = temp_packets[i].frame - temp_packets[i - 1].frame;
+ _push_bits(data, bit_buffer, bits_used, frame_delta, max_frame_delta_shift);
+
+ for (uint32_t j = 0; j < components; j++) {
+ if (max_shifts[j] == 0) {
+ continue; // Zero delta, do not store
+ }
+ int32_t delta = _compute_delta16_signed(temp_packets[i - 1].data[j], temp_packets[i].data[j]);
+
+ ERR_FAIL_COND(delta < -32768 || delta > 32767); //sanity check
+
+ uint16_t deltau;
+ if (delta < 0) {
+ deltau = (ABS(delta) - 1) | (1 << max_shifts[j]);
+ } else {
+ deltau = delta;
+ }
+ _push_bits(data, bit_buffer, bits_used, deltau, max_shifts[j] + 1); // Include sign bit
+ }
+ }
+ if (bits_used != 0) {
+ ERR_FAIL_COND(bit_buffer > 0xFF); // Sanity check
+ data.push_back(bit_buffer);
+ }
+
+ while (data.size() % 4 != 0) {
+ data.push_back(0); //pad to align with 4
+ }
+
+ temp_packets.clear();
+ validated_packet_count = 0;
+
+#ifdef DEBUG_PACKET_PUSH
+ ERR_FAIL_COND((data.size() - debug_data_size) != debug_packet_push);
+#endif
+ }
+};
+
+struct AnimationCompressionTimeState {
+ struct Packet {
+ uint32_t frame;
+ uint32_t offset;
+ uint32_t count;
+ };
+
+ LocalVector<Packet> packets;
+ //used for rollback
+ int32_t key_index = 0;
+ int32_t validated_packet_count = 0;
+ int32_t validated_key_index = -1;
+ bool needs_start_frame = false;
+};
+
+Vector3i Animation::_compress_key(uint32_t p_track, const AABB &p_bounds, int32_t p_key, float p_time) {
+ Vector3i values;
+ TrackType tt = track_get_type(p_track);
+ switch (tt) {
+ case TYPE_POSITION_3D: {
+ Vector3 pos;
+ if (p_key >= 0) {
+ position_track_get_key(p_track, p_key, &pos);
+ } else {
+ position_track_interpolate(p_track, p_time, &pos);
+ }
+ pos = (pos - p_bounds.position) / p_bounds.size;
+ for (int j = 0; j < 3; j++) {
+ values[j] = CLAMP(int32_t(pos[j] * 65535.0), 0, 65535);
+ }
+ } break;
+ case TYPE_ROTATION_3D: {
+ Quaternion rot;
+ if (p_key >= 0) {
+ rotation_track_get_key(p_track, p_key, &rot);
+ } else {
+ rotation_track_interpolate(p_track, p_time, &rot);
+ }
+ Vector3 axis = rot.get_axis();
+ float angle = rot.get_angle();
+ angle = Math::fposmod(double(angle), double(Math_PI * 2.0));
+ Vector2 oct = axis.octahedron_encode();
+ Vector3 rot_norm(oct.x, oct.y, angle / (Math_PI * 2.0)); // high resolution rotation in 0-1 angle.
+
+ for (int j = 0; j < 3; j++) {
+ values[j] = CLAMP(int32_t(rot_norm[j] * 65535.0), 0, 65535);
+ }
+ } break;
+ case TYPE_SCALE_3D: {
+ Vector3 scale;
+ if (p_key >= 0) {
+ scale_track_get_key(p_track, p_key, &scale);
+ } else {
+ scale_track_interpolate(p_track, p_time, &scale);
+ }
+ scale = (scale - p_bounds.position) / p_bounds.size;
+ for (int j = 0; j < 3; j++) {
+ values[j] = CLAMP(int32_t(scale[j] * 65535.0), 0, 65535);
+ }
+ } break;
+ case TYPE_BLEND_SHAPE: {
+ float blend;
+ if (p_key >= 0) {
+ blend_shape_track_get_key(p_track, p_key, &blend);
+ } else {
+ blend_shape_track_interpolate(p_track, p_time, &blend);
+ }
+
+ blend = (blend / float(Compression::BLEND_SHAPE_RANGE)) * 0.5 + 0.5;
+ values[0] = CLAMP(int32_t(blend * 65535.0), 0, 65535);
+ } break;
+ default: {
+ ERR_FAIL_V(Vector3i()); //sanity check
+ } break;
+ }
+
+ return values;
+}
+
+struct AnimationCompressionBufferBitsRead {
+ uint32_t buffer = 0;
+ uint32_t used = 0;
+ const uint8_t *src_data = nullptr;
+
+ _FORCE_INLINE_ uint32_t read(uint32_t p_bits) {
+ uint32_t output = 0;
+ uint32_t written = 0;
+ while (p_bits > 0) {
+ if (used == 0) {
+ used = 8;
+ buffer = *src_data;
+ src_data++;
+ }
+ uint32_t to_write = MIN(used, p_bits);
+ output |= (buffer & ((1 << to_write) - 1)) << written;
+ buffer >>= to_write;
+ used -= to_write;
+ p_bits -= to_write;
+ written += to_write;
+ }
+ return output;
+ }
+};
+
+void Animation::compress(uint32_t p_page_size, uint32_t p_fps, float p_split_tolerance) {
+ ERR_FAIL_COND_MSG(compression.enabled, "This animation is already compressed");
+
+ p_split_tolerance = CLAMP(p_split_tolerance, 1.1, 8.0);
+ compression.pages.clear();
+
+ uint32_t base_page_size = 0; // Before compressing pages, compute how large the "end page" datablock is.
+ LocalVector<uint32_t> tracks_to_compress;
+ LocalVector<AABB> track_bounds;
+ const uint32_t time_packet_size = 4;
+
+ const uint32_t track_header_size = 4 + 4 + 4; // pointer to time (4 bytes), amount of time keys (4 bytes) pointer to track data (4 bytes)
+
+ for (int i = 0; i < get_track_count(); i++) {
+ TrackType type = track_get_type(i);
+ if (type != TYPE_POSITION_3D && type != TYPE_ROTATION_3D && type != TYPE_SCALE_3D && type != TYPE_BLEND_SHAPE) {
+ continue;
+ }
+ if (track_get_key_count(i) == 0) {
+ continue; //do not compress, no keys
+ }
+ base_page_size += track_header_size; //pointer to beginning of each track timeline and amount of time keys
+ base_page_size += time_packet_size; //for end of track time marker
+ base_page_size += (type == TYPE_BLEND_SHAPE) ? 4 : 8; // at least the end of track packet (at much 8 bytes). This could be less, but have to be pessimistic.
+ tracks_to_compress.push_back(i);
+
+ AABB bounds;
+
+ if (type == TYPE_POSITION_3D) {
+ AABB aabb;
+ int kcount = track_get_key_count(i);
+ for (int j = 0; j < kcount; j++) {
+ Vector3 pos;
+ position_track_get_key(i, j, &pos);
+ if (j == 0) {
+ aabb.position = pos;
+ } else {
+ aabb.expand_to(pos);
+ }
+ }
+ for (int j = 0; j < 3; j++) {
+ //cant have zero
+ if (aabb.size[j] < CMP_EPSILON) {
+ aabb.size[j] = CMP_EPSILON;
+ }
+ }
+ bounds = aabb;
+ }
+ if (type == TYPE_SCALE_3D) {
+ AABB aabb;
+ int kcount = track_get_key_count(i);
+ for (int j = 0; j < kcount; j++) {
+ Vector3 scale;
+ scale_track_get_key(i, j, &scale);
+ if (j == 0) {
+ aabb.position = scale;
+ } else {
+ aabb.expand_to(scale);
+ }
+ }
+ for (int j = 0; j < 3; j++) {
+ //cant have zero
+ if (aabb.size[j] < CMP_EPSILON) {
+ aabb.size[j] = CMP_EPSILON;
+ }
+ }
+ bounds = aabb;
+ }
+
+ track_bounds.push_back(bounds);
+ }
+
+ if (tracks_to_compress.size() == 0) {
+ return; //nothing to compress
+ }
+
+ print_animc("Anim Compression:");
+ print_animc("-----------------");
+ print_animc("Tracks to compress: " + itos(tracks_to_compress.size()));
+
+ uint32_t current_frame = 0;
+ uint32_t base_page_frame = 0;
+ double frame_len = 1.0 / double(p_fps);
+ const uint32_t max_frames_per_page = 65536;
+
+ print_animc("Frame Len: " + rtos(frame_len));
+
+ LocalVector<AnimationCompressionDataState> data_tracks;
+ LocalVector<AnimationCompressionTimeState> time_tracks;
+
+ data_tracks.resize(tracks_to_compress.size());
+ time_tracks.resize(tracks_to_compress.size());
+
+ for (uint32_t i = 0; i < data_tracks.size(); i++) {
+ data_tracks[i].split_tolerance = p_split_tolerance;
+ if (track_get_type(tracks_to_compress[i]) == TYPE_BLEND_SHAPE) {
+ data_tracks[i].components = 1;
+ } else {
+ data_tracks[i].components = 3;
+ }
+ }
+
+ while (true) {
+ // Begin by finding the keyframe in all tracks with the time closest to the current time
+ const uint32_t FRAME_MAX = 0xFFFFFFFF;
+ const int32_t NO_TRACK_FOUND = -1;
+ uint32_t best_frame = FRAME_MAX;
+ uint32_t best_invalid_frame = FRAME_MAX;
+ int32_t best_frame_track = NO_TRACK_FOUND; // Default is -1, which means all keyframes for this page are exhausted.
+ bool start_frame = false;
+
+ for (uint32_t i = 0; i < tracks_to_compress.size(); i++) {
+ uint32_t uncomp_track = tracks_to_compress[i];
+
+ if (time_tracks[i].key_index == track_get_key_count(uncomp_track)) {
+ if (time_tracks[i].needs_start_frame) {
+ start_frame = true;
+ best_frame = base_page_frame;
+ best_frame_track = i;
+ time_tracks[i].needs_start_frame = false;
+ break;
+ } else {
+ continue; // This track is exhausted (all keys were added already), don't consider.
+ }
+ }
+
+ uint32_t key_frame = double(track_get_key_time(uncomp_track, time_tracks[i].key_index)) / frame_len;
+
+ if (time_tracks[i].needs_start_frame && key_frame > base_page_frame) {
+ start_frame = true;
+ best_frame = base_page_frame;
+ best_frame_track = i;
+ time_tracks[i].needs_start_frame = false;
+ break;
+ }
+
+ ERR_FAIL_COND(key_frame < base_page_frame); // Sanity check, should never happen
+
+ if (key_frame - base_page_frame >= max_frames_per_page) {
+ // Invalid because beyond the max frames allowed per page
+ best_invalid_frame = MIN(best_invalid_frame, key_frame);
+ } else if (key_frame < best_frame) {
+ best_frame = key_frame;
+ best_frame_track = i;
+ }
+ }
+
+ print_animc("*KEY*: Current Frame: " + itos(current_frame) + " Best Frame: " + rtos(best_frame) + " Best Track: " + itos(best_frame_track) + " Start: " + String(start_frame ? "true" : "false"));
+
+ if (!start_frame && best_frame > current_frame) {
+ // Any case where the current frame advanced, either because nothing was found or because something was found greater than the current one.
+ print_animc("\tAdvance Condition.");
+ bool rollback = false;
+
+ // The frame has advanced, time to validate the previous frame
+ uint32_t current_page_size = base_page_size;
+ for (uint32_t i = 0; i < data_tracks.size(); i++) {
+ uint32_t track_size = data_tracks[i].data.size(); // track size
+ track_size += data_tracks[i].get_temp_packet_size(); // Add the temporary data
+ if (track_size > Compression::MAX_DATA_TRACK_SIZE) {
+ rollback = true; //track to large, time track can't point to keys any longer, because key offset is 12 bits
+ break;
+ }
+ current_page_size += track_size;
+ }
+ for (uint32_t i = 0; i < time_tracks.size(); i++) {
+ current_page_size += time_tracks[i].packets.size() * 4; // time packet is 32 bits
+ }
+
+ if (!rollback && current_page_size > p_page_size) {
+ rollback = true;
+ }
+
+ print_animc("\tCurrent Page Size: " + itos(current_page_size) + "/" + itos(p_page_size) + " Rollback? " + String(rollback ? "YES!" : "no"));
+
+ if (rollback) {
+ // Not valid any longer, so rollback and commit page
+
+ for (uint32_t i = 0; i < data_tracks.size(); i++) {
+ data_tracks[i].temp_packets.resize(data_tracks[i].validated_packet_count);
+ }
+ for (uint32_t i = 0; i < time_tracks.size(); i++) {
+ time_tracks[i].key_index = time_tracks[i].validated_key_index; //rollback key
+ time_tracks[i].packets.resize(time_tracks[i].validated_packet_count);
+ }
+
+ } else {
+ // All valid, so save rollback information
+ for (uint32_t i = 0; i < data_tracks.size(); i++) {
+ data_tracks[i].validated_packet_count = data_tracks[i].temp_packets.size();
+ }
+ for (uint32_t i = 0; i < time_tracks.size(); i++) {
+ time_tracks[i].validated_key_index = time_tracks[i].key_index;
+ time_tracks[i].validated_packet_count = time_tracks[i].packets.size();
+ }
+
+ // Accept this frame as the frame being processed (as long as it exists)
+ if (best_frame != FRAME_MAX) {
+ current_frame = best_frame;
+ print_animc("\tValidated, New Current Frame: " + itos(current_frame));
+ }
+ }
+
+ if (rollback || best_frame == FRAME_MAX) {
+ // Commit the page if had to rollback or if no track was found
+ print_animc("\tCommiting page..");
+
+ // The end frame for the page depends entirely on whether its valid or
+ // no more keys were found.
+ // If not valid, then the end frame is the current frame (as this means the current frame is being rolled back
+ // If valid, then the end frame is the next invalid one (in case more frames exist), or the current frame in case no more frames exist.
+ uint32_t page_end_frame = (rollback || best_frame == FRAME_MAX) ? current_frame : best_invalid_frame;
+
+ print_animc("\tEnd Frame: " + itos(page_end_frame) + ", " + rtos(page_end_frame * frame_len) + "s");
+
+ // Add finalizer frames and commit pending tracks
+ uint32_t finalizer_local_frame = page_end_frame - base_page_frame;
+
+ uint32_t total_page_size = 0;
+
+ for (uint32_t i = 0; i < data_tracks.size(); i++) {
+ if (data_tracks[i].temp_packets.size() == 0 || (data_tracks[i].temp_packets[data_tracks[i].temp_packets.size() - 1].frame) < finalizer_local_frame) {
+ // Add finalizer frame if it makes sense
+ Vector3i values = _compress_key(tracks_to_compress[i], track_bounds[i], -1, page_end_frame * frame_len);
+
+ bool first_key = data_tracks[i].insert_key(finalizer_local_frame, values);
+ if (first_key) {
+ AnimationCompressionTimeState::Packet p;
+ p.count = 1;
+ p.frame = finalizer_local_frame;
+ p.offset = data_tracks[i].data.size();
+ time_tracks[i].packets.push_back(p);
+ } else {
+ ERR_FAIL_COND(time_tracks[i].packets.size() == 0);
+ time_tracks[i].packets[time_tracks[i].packets.size() - 1].count++;
+ }
+ }
+
+ data_tracks[i].commit_temp_packets();
+ total_page_size += data_tracks[i].data.size();
+ total_page_size += time_tracks[i].packets.size() * 4;
+ total_page_size += track_header_size;
+
+ print_animc("\tTrack " + itos(i) + " time packets: " + itos(time_tracks[i].packets.size()) + " Packet data: " + itos(data_tracks[i].data.size()));
+ }
+
+ print_animc("\tTotal page Size: " + itos(total_page_size) + "/" + itos(p_page_size));
+
+ // Create Page
+ Vector<uint8_t> page_data;
+ page_data.resize(total_page_size);
+ {
+ uint8_t *page_ptr = page_data.ptrw();
+ uint32_t base_offset = data_tracks.size() * track_header_size;
+
+ for (uint32_t i = 0; i < data_tracks.size(); i++) {
+ encode_uint32(base_offset, page_ptr + (track_header_size * i + 0));
+ uint16_t *key_time_ptr = (uint16_t *)(page_ptr + base_offset);
+ for (uint32_t j = 0; j < time_tracks[i].packets.size(); j++) {
+ key_time_ptr[j * 2 + 0] = uint16_t(time_tracks[i].packets[j].frame);
+ uint16_t ptr = time_tracks[i].packets[j].offset / 4;
+ ptr |= (time_tracks[i].packets[j].count - 1) << 12;
+ key_time_ptr[j * 2 + 1] = ptr;
+ base_offset += 4;
+ }
+ encode_uint32(time_tracks[i].packets.size(), page_ptr + (track_header_size * i + 4));
+ encode_uint32(base_offset, page_ptr + (track_header_size * i + 8));
+ memcpy(page_ptr + base_offset, data_tracks[i].data.ptr(), data_tracks[i].data.size());
+ base_offset += data_tracks[i].data.size();
+
+ //reset track
+ data_tracks[i].data.clear();
+ data_tracks[i].temp_packets.clear();
+ data_tracks[i].validated_packet_count = -1;
+
+ time_tracks[i].needs_start_frame = true; //Not required the first time, but from now on it is.
+ time_tracks[i].packets.clear();
+ time_tracks[i].validated_key_index = -1;
+ time_tracks[i].validated_packet_count = 0;
+ }
+ }
+
+ Compression::Page page;
+ page.data = page_data;
+ page.time_offset = base_page_frame * frame_len;
+ compression.pages.push_back(page);
+
+ if (!rollback && best_invalid_frame == FRAME_MAX) {
+ break; // No more pages to add.
+ }
+
+ current_frame = page_end_frame;
+ base_page_frame = page_end_frame;
+
+ continue; // Start over
+ }
+ }
+
+ // A key was found for the current frame and all is ok
+
+ uint32_t comp_track = best_frame_track;
+ Vector3i values;
+
+ if (start_frame) {
+ // Interpolate
+ values = _compress_key(tracks_to_compress[comp_track], track_bounds[comp_track], -1, base_page_frame * frame_len);
+ } else {
+ uint32_t key = time_tracks[comp_track].key_index;
+ values = _compress_key(tracks_to_compress[comp_track], track_bounds[comp_track], key);
+ time_tracks[comp_track].key_index++; //goto next key (but could be rolled back if beyond page size).
+ }
+
+ bool first_key = data_tracks[comp_track].insert_key(best_frame - base_page_frame, values);
+ if (first_key) {
+ AnimationCompressionTimeState::Packet p;
+ p.count = 1;
+ p.frame = best_frame - base_page_frame;
+ p.offset = data_tracks[comp_track].data.size();
+ time_tracks[comp_track].packets.push_back(p);
+ } else {
+ ERR_CONTINUE(time_tracks[comp_track].packets.size() == 0);
+ time_tracks[comp_track].packets[time_tracks[comp_track].packets.size() - 1].count++;
+ }
+ }
+
+ compression.bounds = track_bounds;
+ compression.fps = p_fps;
+ compression.enabled = true;
+
+ for (uint32_t i = 0; i < tracks_to_compress.size(); i++) {
+ Track *t = tracks[tracks_to_compress[i]];
+ t->interpolation = INTERPOLATION_LINEAR; //only linear supported
+ switch (t->type) {
+ case TYPE_POSITION_3D: {
+ PositionTrack *tt = static_cast<PositionTrack *>(t);
+ tt->positions.clear();
+ tt->compressed_track = i;
+ } break;
+ case TYPE_ROTATION_3D: {
+ RotationTrack *rt = static_cast<RotationTrack *>(t);
+ rt->rotations.clear();
+ rt->compressed_track = i;
+ } break;
+ case TYPE_SCALE_3D: {
+ ScaleTrack *st = static_cast<ScaleTrack *>(t);
+ st->scales.clear();
+ st->compressed_track = i;
+ print_line("Scale Bounds " + itos(i) + ": " + track_bounds[i]);
+ } break;
+ case TYPE_BLEND_SHAPE: {
+ BlendShapeTrack *bst = static_cast<BlendShapeTrack *>(t);
+ bst->blend_shapes.clear();
+ bst->compressed_track = i;
+ } break;
+ default: {
+ }
+ }
+ }
+#if 1
+ uint32_t orig_size = 0;
+ for (int i = 0; i < get_track_count(); i++) {
+ switch (track_get_type(i)) {
+ case TYPE_SCALE_3D:
+ case TYPE_POSITION_3D: {
+ orig_size += sizeof(TKey<Vector3>) * track_get_key_count(i);
+ } break;
+ case TYPE_ROTATION_3D: {
+ orig_size += sizeof(TKey<Quaternion>) * track_get_key_count(i);
+ } break;
+ case TYPE_BLEND_SHAPE: {
+ orig_size += sizeof(TKey<float>) * track_get_key_count(i);
+ } break;
+ default: {
+ }
+ }
+ }
+
+ uint32_t new_size = 0;
+ for (uint32_t i = 0; i < compression.pages.size(); i++) {
+ new_size += compression.pages[i].data.size();
+ }
+
+ print_line("Original size: " + itos(orig_size) + " - Compressed size: " + itos(new_size) + " " + String::num(float(new_size) / float(orig_size) * 100, 2) + "% pages: " + itos(compression.pages.size()));
+#endif
+}
+
+bool Animation::_rotation_interpolate_compressed(uint32_t p_compressed_track, double p_time, Quaternion &r_ret) const {
+ Vector3i current;
+ Vector3i next;
+ double time_current;
+ double time_next;
+
+ if (!_fetch_compressed<3>(p_compressed_track, p_time, current, time_current, next, time_next)) {
+ return false; //some sort of problem
+ }
+
+ if (time_current >= p_time || time_current == time_next) {
+ r_ret = _uncompress_quaternion(current);
+ } else if (p_time >= time_next) {
+ r_ret = _uncompress_quaternion(next);
+ } else {
+ double c = (p_time - time_current) / (time_next - time_current);
+ Quaternion from = _uncompress_quaternion(current);
+ Quaternion to = _uncompress_quaternion(next);
+ r_ret = from.slerp(to, c);
+ }
+
+ return true;
+}
+
+bool Animation::_pos_scale_interpolate_compressed(uint32_t p_compressed_track, double p_time, Vector3 &r_ret) const {
+ Vector3i current;
+ Vector3i next;
+ double time_current;
+ double time_next;
+
+ if (!_fetch_compressed<3>(p_compressed_track, p_time, current, time_current, next, time_next)) {
+ return false; //some sort of problem
+ }
+
+ if (time_current >= p_time || time_current == time_next) {
+ r_ret = _uncompress_pos_scale(p_compressed_track, current);
+ } else if (p_time >= time_next) {
+ r_ret = _uncompress_pos_scale(p_compressed_track, next);
+ } else {
+ double c = (p_time - time_current) / (time_next - time_current);
+ Vector3 from = _uncompress_pos_scale(p_compressed_track, current);
+ Vector3 to = _uncompress_pos_scale(p_compressed_track, next);
+ r_ret = from.lerp(to, c);
+ }
+
+ return true;
+}
+bool Animation::_blend_shape_interpolate_compressed(uint32_t p_compressed_track, double p_time, float &r_ret) const {
+ Vector3i current;
+ Vector3i next;
+ double time_current;
+ double time_next;
+
+ if (!_fetch_compressed<1>(p_compressed_track, p_time, current, time_current, next, time_next)) {
+ return false; //some sort of problem
+ }
+
+ if (time_current >= p_time || time_current == time_next) {
+ r_ret = _uncompress_blend_shape(current);
+ } else if (p_time >= time_next) {
+ r_ret = _uncompress_blend_shape(next);
+ } else {
+ float c = (p_time - time_current) / (time_next - time_current);
+ float from = _uncompress_blend_shape(current);
+ float to = _uncompress_blend_shape(next);
+ r_ret = Math::lerp(from, to, c);
+ }
+
+ return true;
+}
+
+template <uint32_t COMPONENTS>
+bool Animation::_fetch_compressed(uint32_t p_compressed_track, double p_time, Vector3i &r_current_value, double &r_current_time, Vector3i &r_next_value, double &r_next_time, uint32_t *key_index) const {
+ ERR_FAIL_COND_V(!compression.enabled, false);
+ ERR_FAIL_UNSIGNED_INDEX_V(p_compressed_track, compression.bounds.size(), false);
+ p_time = CLAMP(p_time, 0, length);
+ if (key_index) {
+ *key_index = 0;
+ }
+
+ double frame_to_sec = 1.0 / double(compression.fps);
+
+ int32_t page_index = -1;
+ for (uint32_t i = 0; i < compression.pages.size(); i++) {
+ if (compression.pages[i].time_offset > p_time) {
+ break;
+ }
+ page_index = i;
+ }
+
+ ERR_FAIL_COND_V(page_index == -1, false); //should not happen
+
+ double page_base_time = compression.pages[page_index].time_offset;
+ const uint8_t *page_data = compression.pages[page_index].data.ptr();
+#ifndef _MSC_VER
+#warning Little endian assumed. No major big endian hardware exists any longer, but in case it does it will need to be supported
+#endif
+ const uint32_t *indices = (const uint32_t *)page_data;
+ const uint16_t *time_keys = (const uint16_t *)&page_data[indices[p_compressed_track * 3 + 0]];
+ uint32_t time_key_count = indices[p_compressed_track * 3 + 1];
+
+ int32_t packet_idx = 0;
+ double packet_time = double(time_keys[0]) * frame_to_sec + page_base_time;
+ uint32_t base_frame = time_keys[0];
+
+ for (uint32_t i = 1; i < time_key_count; i++) {
+ uint32_t f = time_keys[i * 2 + 0];
+ double frame_time = double(f) * frame_to_sec + page_base_time;
+
+ if (frame_time > p_time) {
+ break;
+ }
+
+ if (key_index) {
+ (*key_index) += (time_keys[(i - 1) * 2 + 1] >> 12) + 1;
+ }
+
+ packet_idx = i;
+ packet_time = frame_time;
+ base_frame = f;
+ }
+
+ const uint8_t *data_keys_base = (const uint8_t *)&page_data[indices[p_compressed_track * 3 + 2]];
+
+ uint16_t time_key_data = time_keys[packet_idx * 2 + 1];
+ uint32_t data_offset = (time_key_data & 0xFFF) * 4; // lower 12 bits
+ uint32_t data_count = (time_key_data >> 12) + 1;
+
+ const uint16_t *data_key = (const uint16_t *)(data_keys_base + data_offset);
+
+ uint16_t decode[COMPONENTS];
+ uint16_t decode_next[COMPONENTS];
+
+ for (uint32_t i = 0; i < COMPONENTS; i++) {
+ decode[i] = data_key[i];
+ decode_next[i] = data_key[i];
+ }
+
+ double next_time = packet_time;
+
+ if (p_time > packet_time) { // If its equal or less, then don't bother
+ if (data_count > 1) {
+ //decode forward
+ uint32_t bit_width[COMPONENTS];
+ for (uint32_t i = 0; i < COMPONENTS; i++) {
+ bit_width[i] = (data_key[COMPONENTS] >> (i * 4)) & 0xF;
+ }
+
+ uint32_t frame_bit_width = (data_key[COMPONENTS] >> 12) + 1;
+
+ AnimationCompressionBufferBitsRead buffer;
+
+ buffer.src_data = (const uint8_t *)&data_key[COMPONENTS + 1];
+
+ for (uint32_t i = 1; i < data_count; i++) {
+ uint32_t frame_delta = buffer.read(frame_bit_width);
+ base_frame += frame_delta;
+
+ for (uint32_t j = 0; j < COMPONENTS; j++) {
+ if (bit_width[j] == 0) {
+ continue; // do none
+ }
+ uint32_t valueu = buffer.read(bit_width[j] + 1);
+ bool sign = valueu & (1 << bit_width[j]);
+ int16_t value = valueu & ((1 << bit_width[j]) - 1);
+ if (sign) {
+ value = -value - 1;
+ }
+
+ decode_next[j] += value;
+ }
+
+ next_time = double(base_frame) * frame_to_sec + page_base_time;
+ if (p_time < next_time) {
+ break;
+ }
+
+ packet_time = next_time;
+
+ for (uint32_t j = 0; j < COMPONENTS; j++) {
+ decode[j] = decode_next[j];
+ }
+
+ if (key_index) {
+ (*key_index)++;
+ }
+ }
+ }
+
+ if (p_time > next_time) { // > instead of >= because if its equal, then it will be properly interpolated anyway
+ // So, the last frame found still has a time that is less than the required frame,
+ // will have to interpolate with the first frame of the next timekey.
+
+ if ((uint32_t)packet_idx < time_key_count - 1) { // Sanity check but should not matter much, otherwise current next packet is last packet
+
+ uint16_t time_key_data_next = time_keys[(packet_idx + 1) * 2 + 1];
+ uint32_t data_offset_next = (time_key_data_next & 0xFFF) * 4; // Lower 12 bits
+
+ const uint16_t *data_key_next = (const uint16_t *)(data_keys_base + data_offset_next);
+ base_frame = time_keys[(packet_idx + 1) * 2 + 0];
+ next_time = double(base_frame) * frame_to_sec + page_base_time;
+ for (uint32_t i = 0; i < COMPONENTS; i++) {
+ decode_next[i] = data_key_next[i];
+ }
+ }
+ }
+ }
+
+ r_current_time = packet_time;
+ r_next_time = next_time;
+
+ for (uint32_t i = 0; i < COMPONENTS; i++) {
+ r_current_value[i] = decode[i];
+ r_next_value[i] = decode_next[i];
+ }
+
+ return true;
+}
+
+template <uint32_t COMPONENTS>
+void Animation::_get_compressed_key_indices_in_range(uint32_t p_compressed_track, double p_time, double p_delta, List<int> *r_indices) const {
+ ERR_FAIL_COND(!compression.enabled);
+ ERR_FAIL_UNSIGNED_INDEX(p_compressed_track, compression.bounds.size());
+
+ double frame_to_sec = 1.0 / double(compression.fps);
+ uint32_t key_index = 0;
+
+ for (uint32_t p = 0; p < compression.pages.size(); p++) {
+ if (compression.pages[p].time_offset >= p_time + p_delta) {
+ // Page beyond range
+ return;
+ }
+
+ // Page within range
+
+ uint32_t page_index = p;
+
+ double page_base_time = compression.pages[page_index].time_offset;
+ const uint8_t *page_data = compression.pages[page_index].data.ptr();
+#ifndef _MSC_VER
+#warning Little endian assumed. No major big endian hardware exists any longer, but in case it does it will need to be supported
+#endif
+ const uint32_t *indices = (const uint32_t *)page_data;
+ const uint16_t *time_keys = (const uint16_t *)&page_data[indices[p_compressed_track * 3 + 0]];
+ uint32_t time_key_count = indices[p_compressed_track * 3 + 1];
+
+ for (uint32_t i = 0; i < time_key_count; i++) {
+ uint32_t f = time_keys[i * 2 + 0];
+ double frame_time = f * frame_to_sec + page_base_time;
+ if (frame_time >= p_time + p_delta) {
+ return;
+ } else if (frame_time >= p_time) {
+ r_indices->push_back(key_index);
+ }
+
+ key_index++;
+
+ const uint8_t *data_keys_base = (const uint8_t *)&page_data[indices[p_compressed_track * 3 + 2]];
+
+ uint16_t time_key_data = time_keys[i * 2 + 1];
+ uint32_t data_offset = (time_key_data & 0xFFF) * 4; // lower 12 bits
+ uint32_t data_count = (time_key_data >> 12) + 1;
+
+ const uint16_t *data_key = (const uint16_t *)(data_keys_base + data_offset);
+
+ if (data_count > 1) {
+ //decode forward
+ uint32_t bit_width[COMPONENTS];
+ for (uint32_t j = 0; j < COMPONENTS; j++) {
+ bit_width[j] = (data_key[COMPONENTS] >> (j * 4)) & 0xF;
+ }
+
+ uint32_t frame_bit_width = (data_key[COMPONENTS] >> 12) + 1;
+
+ AnimationCompressionBufferBitsRead buffer;
+
+ buffer.src_data = (const uint8_t *)&data_key[COMPONENTS + 1];
+
+ for (uint32_t j = 1; j < data_count; j++) {
+ uint32_t frame_delta = buffer.read(frame_bit_width);
+ f += frame_delta;
+
+ frame_time = f * frame_to_sec + page_base_time;
+ if (frame_time >= p_time + p_delta) {
+ return;
+ } else if (frame_time >= p_time) {
+ r_indices->push_back(key_index);
+ }
+
+ for (uint32_t k = 0; k < COMPONENTS; k++) {
+ if (bit_width[k] == 0) {
+ continue; // do none
+ }
+ buffer.read(bit_width[k] + 1); // skip
+ }
+
+ key_index++;
+ }
+ }
+ }
+ }
+}
+
+int Animation::_get_compressed_key_count(uint32_t p_compressed_track) const {
+ ERR_FAIL_COND_V(!compression.enabled, -1);
+ ERR_FAIL_UNSIGNED_INDEX_V(p_compressed_track, compression.bounds.size(), -1);
+
+ int key_count = 0;
+
+ for (uint32_t i = 0; i < compression.pages.size(); i++) {
+ const uint8_t *page_data = compression.pages[i].data.ptr();
+#ifndef _MSC_VER
+#warning Little endian assumed. No major big endian hardware exists any longer, but in case it does it will need to be supported
+#endif
+ const uint32_t *indices = (const uint32_t *)page_data;
+ const uint16_t *time_keys = (const uint16_t *)&page_data[indices[p_compressed_track * 3 + 0]];
+ uint32_t time_key_count = indices[p_compressed_track * 3 + 1];
+
+ for (uint32_t j = 0; j < time_key_count; j++) {
+ key_count += (time_keys[j * 2 + 1] >> 12) + 1;
+ }
+ }
+
+ return key_count;
+}
+
+Quaternion Animation::_uncompress_quaternion(const Vector3i &p_value) const {
+ Vector3 axis = Vector3::octahedron_decode(Vector2(float(p_value.x) / 65535.0, float(p_value.y) / 65535.0));
+ float angle = (float(p_value.z) / 65535.0) * 2.0 * Math_PI;
+ return Quaternion(axis, angle);
+}
+Vector3 Animation::_uncompress_pos_scale(uint32_t p_compressed_track, const Vector3i &p_value) const {
+ Vector3 pos_norm(float(p_value.x) / 65535.0, float(p_value.y) / 65535.0, float(p_value.z) / 65535.0);
+ return compression.bounds[p_compressed_track].position + pos_norm * compression.bounds[p_compressed_track].size;
+}
+float Animation::_uncompress_blend_shape(const Vector3i &p_value) const {
+ float bsn = float(p_value.x) / 65535.0;
+ return (bsn * 2.0 - 1.0) * float(Compression::BLEND_SHAPE_RANGE);
+}
+
+template <uint32_t COMPONENTS>
+bool Animation::_fetch_compressed_by_index(uint32_t p_compressed_track, int p_index, Vector3i &r_value, double &r_time) const {
+ ERR_FAIL_COND_V(!compression.enabled, false);
+ ERR_FAIL_UNSIGNED_INDEX_V(p_compressed_track, compression.bounds.size(), false);
+
+ for (uint32_t i = 0; i < compression.pages.size(); i++) {
+ const uint8_t *page_data = compression.pages[i].data.ptr();
+#ifndef _MSC_VER
+#warning Little endian assumed. No major big endian hardware exists any longer, but in case it does it will need to be supported
+#endif
+ const uint32_t *indices = (const uint32_t *)page_data;
+ const uint16_t *time_keys = (const uint16_t *)&page_data[indices[p_compressed_track * 3 + 0]];
+ uint32_t time_key_count = indices[p_compressed_track * 3 + 1];
+ const uint8_t *data_keys_base = (const uint8_t *)&page_data[indices[p_compressed_track * 3 + 2]];
+
+ for (uint32_t j = 0; j < time_key_count; j++) {
+ uint32_t subkeys = (time_keys[j * 2 + 1] >> 12) + 1;
+ if ((uint32_t)p_index < subkeys) {
+ uint16_t data_offset = (time_keys[j * 2 + 1] & 0xFFF) * 4;
+
+ const uint16_t *data_key = (const uint16_t *)(data_keys_base + data_offset);
+
+ uint16_t frame = time_keys[j * 2 + 0];
+ uint16_t decode[COMPONENTS];
+
+ for (uint32_t k = 0; k < COMPONENTS; k++) {
+ decode[k] = data_key[k];
+ }
+
+ if (p_index > 0) {
+ uint32_t bit_width[COMPONENTS];
+ for (uint32_t k = 0; k < COMPONENTS; k++) {
+ bit_width[k] = (data_key[COMPONENTS] >> (k * 4)) & 0xF;
+ }
+ uint32_t frame_bit_width = (data_key[COMPONENTS] >> 12) + 1;
+
+ AnimationCompressionBufferBitsRead buffer;
+ buffer.src_data = (const uint8_t *)&data_key[COMPONENTS + 1];
+
+ for (int k = 0; k < p_index; k++) {
+ uint32_t frame_delta = buffer.read(frame_bit_width);
+ frame += frame_delta;
+ for (uint32_t l = 0; l < COMPONENTS; l++) {
+ if (bit_width[l] == 0) {
+ continue; // do none
+ }
+ uint32_t valueu = buffer.read(bit_width[l] + 1);
+ bool sign = valueu & (1 << bit_width[l]);
+ int16_t value = valueu & ((1 << bit_width[l]) - 1);
+ if (sign) {
+ value = -value - 1;
+ }
+
+ decode[l] += value;
+ }
+ }
+ }
+
+ r_time = compression.pages[i].time_offset + double(frame) / double(compression.fps);
+ for (uint32_t l = 0; l < COMPONENTS; l++) {
+ r_value[l] = decode[l];
+ }
+
+ return true;
+
+ } else {
+ p_index -= subkeys;
+ }
+ }
+ }
+
+ return false;
+}
+
Animation::Animation() {}
Animation::~Animation() {
diff --git a/scene/resources/animation.h b/scene/resources/animation.h
index 4ee0741d87..ee07fb19d3 100644
--- a/scene/resources/animation.h
+++ b/scene/resources/animation.h
@@ -32,6 +32,7 @@
#define ANIMATION_H
#include "core/io/resource.h"
+#include "core/templates/local_vector.h"
#define ANIM_MIN_LENGTH 0.001
@@ -98,7 +99,7 @@ private:
struct PositionTrack : public Track {
Vector<TKey<Vector3>> positions;
-
+ int32_t compressed_track = -1;
PositionTrack() { type = TYPE_POSITION_3D; }
};
@@ -106,7 +107,7 @@ private:
struct RotationTrack : public Track {
Vector<TKey<Quaternion>> rotations;
-
+ int32_t compressed_track = -1;
RotationTrack() { type = TYPE_ROTATION_3D; }
};
@@ -114,6 +115,7 @@ private:
struct ScaleTrack : public Track {
Vector<TKey<Vector3>> scales;
+ int32_t compressed_track = -1;
ScaleTrack() { type = TYPE_SCALE_3D; }
};
@@ -121,6 +123,7 @@ private:
struct BlendShapeTrack : public Track {
Vector<TKey<float>> blend_shapes;
+ int32_t compressed_track = -1;
BlendShapeTrack() { type = TYPE_BLEND_SHAPE; }
};
@@ -230,6 +233,89 @@ private:
real_t step = 0.1;
bool loop = false;
+ /* Animation compression page format (version 1):
+ *
+ * Animation uses bitwidth based compression separated into small pages. The intention is that pages fit easily in the cache, so decoding is cache efficient.
+ * The page-based nature also makes future animation streaming from disk possible.
+ *
+ * Actual format:
+ *
+ * num_compressed_tracks = bounds.size()
+ * header : (x num_compressed_tracks)
+ * -------
+ * timeline_keys_offset : uint32_t - offset to time keys
+ * timeline_size : uint32_t - amount of time keys
+ * data_keys_offset : uint32_t offset to key data
+ *
+ * time key (uint32_t):
+ * ------------------
+ * frame : bits 0-15 - time offset of key, computed as: page.time_offset + frame * (1.0/fps)
+ * data_key_offset : bits 16-27 - offset to key data, computed as: data_keys_offset * 4 + data_key_offset
+ * data_key_count : bits 28-31 - amount of data keys pointed to, computed as: data_key_count+1 (max 16)
+ *
+ * data key:
+ * ---------
+ * X / Blend Shape : uint16_t - X coordinate of XYZ vector key, or Blend Shape value. If Blend shape, Y and Z are not present and can be ignored.
+ * Y : uint16_t
+ * Z : uint16_t
+ * If data_key_count+1 > 1 (if more than 1 key is stored):
+ * data_bitwidth : uint16_t - This is only present if data_key_count > 1. Contains delta bitwidth information.
+ * X / Blend Shape delta bitwidth: bits 0-3 -
+ * if 0, nothing is present for X (use the first key-value for subsequent keys),
+ * else assume the number of bits present for each element (+ 1 for sign). Assumed always 16 bits, delta max signed 15 bits, with underflow and overflow supported.
+ * Y delta bitwidth : bits 4-7
+ * Z delta bitwidth : bits 8-11
+ * FRAME delta bitwidth : 12-15 bits - always present (obviously), actual bitwidth is FRAME+1
+ * Data key is 4 bytes long for Blend Shapes, 8 bytes long for pos/rot/scale.
+ *
+ * delta keys:
+ * -----------
+ * Compressed format is packed in the following format after the data key, containing delta keys one after the next in a tightly bit packed fashion.
+ * FRAME bits -> X / Blend Shape Bits (if bitwidth > 0) -> Y Bits (if not Blend Shape and Y Bitwidth > 0) -> Z Bits (if not Blend Shape and Z Bitwidth > 0)
+ *
+ * data key format:
+ * ----------------
+ * Decoding keys means starting from the base key and going key by key applying deltas until the proper position is reached needed for interpolation.
+ * Resulting values are uint32_t
+ * data for X / Blend Shape, Y and Z must be normalized first: unorm = float(data) / 65535.0
+ * **Blend Shape**: (unorm * 2.0 - 1.0) * Compression::BLEND_SHAPE_RANGE
+ * **Pos/Scale**: unorm_vec3 * bounds[track].size + bounds[track].position
+ * **Rotation**: Quaternion(Vector3::octahedron_decode(unorm_vec3.xy),unorm_vec3.z * Math_PI * 2.0)
+ * **Frame**: page.time_offset + frame * (1.0/fps)
+ */
+
+ struct Compression {
+ enum {
+ MAX_DATA_TRACK_SIZE = 16384,
+ BLEND_SHAPE_RANGE = 8, // - 8.0 to 8.0
+ FORMAT_VERSION = 1
+ };
+ struct Page {
+ Vector<uint8_t> data;
+ double time_offset;
+ };
+
+ uint32_t fps = 120;
+ LocalVector<Page> pages;
+ LocalVector<AABB> bounds; //used by position and scale tracks (which contain index to track and index to bounds).
+ bool enabled = false;
+ } compression;
+
+ Vector3i _compress_key(uint32_t p_track, const AABB &p_bounds, int32_t p_key = -1, float p_time = 0.0);
+ bool _rotation_interpolate_compressed(uint32_t p_compressed_track, double p_time, Quaternion &r_ret) const;
+ bool _pos_scale_interpolate_compressed(uint32_t p_compressed_track, double p_time, Vector3 &r_ret) const;
+ bool _blend_shape_interpolate_compressed(uint32_t p_compressed_track, double p_time, float &r_ret) const;
+ template <uint32_t COMPONENTS>
+ bool _fetch_compressed(uint32_t p_compressed_track, double p_time, Vector3i &r_current_value, double &r_current_time, Vector3i &r_next_value, double &r_next_time, uint32_t *key_index = nullptr) const;
+ template <uint32_t COMPONENTS>
+ bool _fetch_compressed_by_index(uint32_t p_compressed_track, int p_index, Vector3i &r_value, double &r_time) const;
+ int _get_compressed_key_count(uint32_t p_compressed_track) const;
+ template <uint32_t COMPONENTS>
+ void _get_compressed_key_indices_in_range(uint32_t p_compressed_track, double p_time, double p_delta, List<int> *r_indices) const;
+ _FORCE_INLINE_ Quaternion _uncompress_quaternion(const Vector3i &p_value) const;
+ _FORCE_INLINE_ Vector3 _uncompress_pos_scale(uint32_t p_compressed_track, const Vector3i &p_value) const;
+ _FORCE_INLINE_ float _uncompress_blend_shape(const Vector3i &p_value) const;
+
// bind helpers
private:
Vector<int> _value_track_get_key_indices(int p_track, double p_time, double p_delta) const {
@@ -281,8 +367,7 @@ public:
void track_set_path(int p_track, const NodePath &p_path);
NodePath track_get_path(int p_track) const;
- int find_track(const NodePath &p_path) const;
- // transform
+ int find_track(const NodePath &p_path, const TrackType p_type) const;
void track_move_up(int p_track);
void track_move_down(int p_track);
@@ -306,6 +391,7 @@ public:
Variant track_get_key_value(int p_track, int p_key_idx) const;
double track_get_key_time(int p_track, int p_key_idx) const;
real_t track_get_key_transition(int p_track, int p_key_idx) const;
+ bool track_is_compressed(int p_track) const;
int position_track_insert_key(int p_track, double p_time, const Vector3 &p_position);
Error position_track_get_key(int p_track, int p_key, Vector3 *r_position) const;
@@ -376,6 +462,7 @@ public:
void clear();
void optimize(real_t p_allowed_linear_err = 0.05, real_t p_allowed_angular_err = 0.01, real_t p_max_optimizable_angle = Math_PI * 0.125);
+ void compress(uint32_t p_page_size = 8192, uint32_t p_fps = 120, float p_split_tolerance = 4.0); // 4.0 seems to be the split tolerance sweet spot from many tests
Animation();
~Animation();
diff --git a/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp
index 9fdfd493c1..ed093d7ea3 100644
--- a/scene/resources/default_theme/default_theme.cpp
+++ b/scene/resources/default_theme/default_theme.cpp
@@ -1030,7 +1030,6 @@ void make_default_theme(bool p_hidpi, Ref<Font> p_font) {
dynamic_font_data.instantiate();
dynamic_font_data->set_data_ptr(_font_OpenSans_SemiBold, _font_OpenSans_SemiBold_size);
dynamic_font->add_data(dynamic_font_data);
- dynamic_font->set_base_size(default_font_size);
default_font = dynamic_font;
}
diff --git a/scene/resources/font.cpp b/scene/resources/font.cpp
index 04e2b0dc70..819ae95715 100644
--- a/scene/resources/font.cpp
+++ b/scene/resources/font.cpp
@@ -1072,10 +1072,6 @@ void Font::_bind_methods() {
ClassDB::bind_method(D_METHOD("clear_data"), &Font::clear_data);
ClassDB::bind_method(D_METHOD("remove_data", "idx"), &Font::remove_data);
- ClassDB::bind_method(D_METHOD("set_base_size", "size"), &Font::set_base_size);
- ClassDB::bind_method(D_METHOD("get_base_size"), &Font::get_base_size);
- ADD_PROPERTY(PropertyInfo(Variant::INT, "base_size"), "set_base_size", "get_base_size");
-
ClassDB::bind_method(D_METHOD("set_variation_coordinates", "variation_coordinates"), &Font::set_variation_coordinates);
ClassDB::bind_method(D_METHOD("get_variation_coordinates"), &Font::get_variation_coordinates);
ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "variation_coordinates"), "set_variation_coordinates", "get_variation_coordinates");
@@ -1087,20 +1083,20 @@ void Font::_bind_methods() {
ADD_PROPERTYI(PropertyInfo(Variant::INT, "spacing_top"), "set_spacing", "get_spacing", TextServer::SPACING_TOP);
ADD_PROPERTYI(PropertyInfo(Variant::INT, "spacing_bottom"), "set_spacing", "get_spacing", TextServer::SPACING_BOTTOM);
- ClassDB::bind_method(D_METHOD("get_height", "size"), &Font::get_height, DEFVAL(-1));
- ClassDB::bind_method(D_METHOD("get_ascent", "size"), &Font::get_ascent, DEFVAL(-1));
- ClassDB::bind_method(D_METHOD("get_descent", "size"), &Font::get_descent, DEFVAL(-1));
- ClassDB::bind_method(D_METHOD("get_underline_position", "size"), &Font::get_underline_position, DEFVAL(-1));
- ClassDB::bind_method(D_METHOD("get_underline_thickness", "size"), &Font::get_underline_thickness, DEFVAL(-1));
+ ClassDB::bind_method(D_METHOD("get_height", "size"), &Font::get_height, DEFVAL(DEFAULT_FONT_SIZE));
+ ClassDB::bind_method(D_METHOD("get_ascent", "size"), &Font::get_ascent, DEFVAL(DEFAULT_FONT_SIZE));
+ ClassDB::bind_method(D_METHOD("get_descent", "size"), &Font::get_descent, DEFVAL(DEFAULT_FONT_SIZE));
+ ClassDB::bind_method(D_METHOD("get_underline_position", "size"), &Font::get_underline_position, DEFVAL(DEFAULT_FONT_SIZE));
+ ClassDB::bind_method(D_METHOD("get_underline_thickness", "size"), &Font::get_underline_thickness, DEFVAL(DEFAULT_FONT_SIZE));
- ClassDB::bind_method(D_METHOD("get_string_size", "text", "size", "align", "width", "flags"), &Font::get_string_size, DEFVAL(-1), DEFVAL(HALIGN_LEFT), DEFVAL(-1), DEFVAL(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND));
- ClassDB::bind_method(D_METHOD("get_multiline_string_size", "text", "width", "size", "flags"), &Font::get_multiline_string_size, DEFVAL(-1), DEFVAL(-1), DEFVAL(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND));
+ ClassDB::bind_method(D_METHOD("get_string_size", "text", "size", "align", "width", "flags"), &Font::get_string_size, DEFVAL(DEFAULT_FONT_SIZE), DEFVAL(HALIGN_LEFT), DEFVAL(-1), DEFVAL(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND));
+ ClassDB::bind_method(D_METHOD("get_multiline_string_size", "text", "width", "size", "flags"), &Font::get_multiline_string_size, DEFVAL(-1), DEFVAL(DEFAULT_FONT_SIZE), DEFVAL(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND));
- ClassDB::bind_method(D_METHOD("draw_string", "canvas_item", "pos", "text", "align", "width", "size", "modulate", "outline_size", "outline_modulate", "flags"), &Font::draw_string, DEFVAL(HALIGN_LEFT), DEFVAL(-1), DEFVAL(-1), DEFVAL(Color(1, 1, 1)), DEFVAL(0), DEFVAL(Color(1, 1, 1, 0)), DEFVAL(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND));
- ClassDB::bind_method(D_METHOD("draw_multiline_string", "canvas_item", "pos", "text", "align", "width", "max_lines", "size", "modulate", "outline_size", "outline_modulate", "flags"), &Font::draw_multiline_string, DEFVAL(HALIGN_LEFT), DEFVAL(-1), DEFVAL(-1), DEFVAL(-1), DEFVAL(Color(1, 1, 1)), DEFVAL(0), DEFVAL(Color(1, 1, 1, 0)), DEFVAL(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND));
+ ClassDB::bind_method(D_METHOD("draw_string", "canvas_item", "pos", "text", "align", "width", "size", "modulate", "outline_size", "outline_modulate", "flags"), &Font::draw_string, DEFVAL(HALIGN_LEFT), DEFVAL(-1), DEFVAL(DEFAULT_FONT_SIZE), DEFVAL(Color(1, 1, 1)), DEFVAL(0), DEFVAL(Color(1, 1, 1, 0)), DEFVAL(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND));
+ ClassDB::bind_method(D_METHOD("draw_multiline_string", "canvas_item", "pos", "text", "align", "width", "max_lines", "size", "modulate", "outline_size", "outline_modulate", "flags"), &Font::draw_multiline_string, DEFVAL(HALIGN_LEFT), DEFVAL(-1), DEFVAL(-1), DEFVAL(DEFAULT_FONT_SIZE), DEFVAL(Color(1, 1, 1)), DEFVAL(0), DEFVAL(Color(1, 1, 1, 0)), DEFVAL(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND));
- ClassDB::bind_method(D_METHOD("get_char_size", "char", "next", "size"), &Font::get_char_size, DEFVAL(0), DEFVAL(-1));
- ClassDB::bind_method(D_METHOD("draw_char", "canvas_item", "pos", "char", "next", "size", "modulate", "outline_size", "outline_modulate"), &Font::draw_char, DEFVAL(0), DEFVAL(-1), DEFVAL(Color(1, 1, 1)), DEFVAL(0), DEFVAL(Color(1, 1, 1, 0)));
+ ClassDB::bind_method(D_METHOD("get_char_size", "char", "next", "size"), &Font::get_char_size, DEFVAL(0), DEFVAL(DEFAULT_FONT_SIZE));
+ ClassDB::bind_method(D_METHOD("draw_char", "canvas_item", "pos", "char", "next", "size", "modulate", "outline_size", "outline_modulate"), &Font::draw_char, DEFVAL(0), DEFVAL(DEFAULT_FONT_SIZE), DEFVAL(Color(1, 1, 1)), DEFVAL(0), DEFVAL(Color(1, 1, 1, 0)));
ClassDB::bind_method(D_METHOD("has_char", "char"), &Font::has_char);
ClassDB::bind_method(D_METHOD("get_supported_chars"), &Font::get_supported_chars);
@@ -1195,7 +1191,6 @@ void Font::reset_state() {
data.clear();
rids.clear();
- base_size = 16;
variation_coordinates.clear();
spacing_bottom = 0;
spacing_top = 0;
@@ -1308,14 +1303,6 @@ void Font::remove_data(int p_idx) {
notify_property_list_changed();
}
-void Font::set_base_size(int p_size) {
- base_size = p_size;
-}
-
-int Font::get_base_size() const {
- return base_size;
-}
-
void Font::set_variation_coordinates(const Dictionary &p_variation_coordinates) {
_data_changed();
variation_coordinates = p_variation_coordinates;
@@ -1355,51 +1342,46 @@ int Font::get_spacing(TextServer::SpacingType p_spacing) const {
}
real_t Font::get_height(int p_size) const {
- int size = (p_size <= 0) ? base_size : p_size;
real_t ret = 0.f;
for (int i = 0; i < data.size(); i++) {
_ensure_rid(i);
- ret = MAX(ret, TS->font_get_ascent(rids[i], size) + TS->font_get_descent(rids[i], size));
+ ret = MAX(ret, TS->font_get_ascent(rids[i], p_size) + TS->font_get_descent(rids[i], p_size));
}
return ret + spacing_bottom + spacing_top;
}
real_t Font::get_ascent(int p_size) const {
- int size = (p_size <= 0) ? base_size : p_size;
real_t ret = 0.f;
for (int i = 0; i < data.size(); i++) {
_ensure_rid(i);
- ret = MAX(ret, TS->font_get_ascent(rids[i], size));
+ ret = MAX(ret, TS->font_get_ascent(rids[i], p_size));
}
return ret + spacing_top;
}
real_t Font::get_descent(int p_size) const {
- int size = (p_size <= 0) ? base_size : p_size;
real_t ret = 0.f;
for (int i = 0; i < data.size(); i++) {
_ensure_rid(i);
- ret = MAX(ret, TS->font_get_descent(rids[i], size));
+ ret = MAX(ret, TS->font_get_descent(rids[i], p_size));
}
return ret + spacing_bottom;
}
real_t Font::get_underline_position(int p_size) const {
- int size = (p_size <= 0) ? base_size : p_size;
real_t ret = 0.f;
for (int i = 0; i < data.size(); i++) {
_ensure_rid(i);
- ret = MAX(ret, TS->font_get_underline_position(rids[i], size));
+ ret = MAX(ret, TS->font_get_underline_position(rids[i], p_size));
}
return ret + spacing_top;
}
real_t Font::get_underline_thickness(int p_size) const {
- int size = (p_size <= 0) ? base_size : p_size;
real_t ret = 0.f;
for (int i = 0; i < data.size(); i++) {
_ensure_rid(i);
- ret = MAX(ret, TS->font_get_underline_thickness(rids[i], size));
+ ret = MAX(ret, TS->font_get_underline_thickness(rids[i], p_size));
}
return ret;
}
@@ -1407,8 +1389,6 @@ real_t Font::get_underline_thickness(int p_size) const {
Size2 Font::get_string_size(const String &p_text, int p_size, HAlign p_align, real_t p_width, uint16_t p_flags) const {
ERR_FAIL_COND_V(data.is_empty(), Size2());
- int size = (p_size <= 0) ? base_size : p_size;
-
for (int i = 0; i < data.size(); i++) {
_ensure_rid(i);
}
@@ -1418,14 +1398,14 @@ Size2 Font::get_string_size(const String &p_text, int p_size, HAlign p_align, re
hash = hash_djb2_one_64(hash_djb2_one_float(p_width), hash);
hash = hash_djb2_one_64(p_flags, hash);
}
- hash = hash_djb2_one_64(size, hash);
+ hash = hash_djb2_one_64(p_size, hash);
Ref<TextLine> buffer;
if (cache.has(hash)) {
buffer = cache.get(hash);
} else {
buffer.instantiate();
- buffer->add_string(p_text, Ref<Font>(this), size, Dictionary(), TranslationServer::get_singleton()->get_tool_locale());
+ buffer->add_string(p_text, Ref<Font>(this), p_size, Dictionary(), TranslationServer::get_singleton()->get_tool_locale());
cache.insert(hash, buffer);
}
return buffer->get_size();
@@ -1434,8 +1414,6 @@ Size2 Font::get_string_size(const String &p_text, int p_size, HAlign p_align, re
Size2 Font::get_multiline_string_size(const String &p_text, real_t p_width, int p_size, uint16_t p_flags) const {
ERR_FAIL_COND_V(data.is_empty(), Size2());
- int size = (p_size <= 0) ? base_size : p_size;
-
for (int i = 0; i < data.size(); i++) {
_ensure_rid(i);
}
@@ -1443,14 +1421,14 @@ Size2 Font::get_multiline_string_size(const String &p_text, real_t p_width, int
uint64_t hash = p_text.hash64();
uint64_t wrp_hash = hash_djb2_one_64(hash_djb2_one_float(p_width), hash);
wrp_hash = hash_djb2_one_64(p_flags, wrp_hash);
- wrp_hash = hash_djb2_one_64(size, wrp_hash);
+ wrp_hash = hash_djb2_one_64(p_size, wrp_hash);
Ref<TextParagraph> lines_buffer;
if (cache_wrap.has(wrp_hash)) {
lines_buffer = cache_wrap.get(wrp_hash);
} else {
lines_buffer.instantiate();
- lines_buffer->add_string(p_text, Ref<Font>(this), size, Dictionary(), TranslationServer::get_singleton()->get_tool_locale());
+ lines_buffer->add_string(p_text, Ref<Font>(this), p_size, Dictionary(), TranslationServer::get_singleton()->get_tool_locale());
lines_buffer->set_width(p_width);
lines_buffer->set_flags(p_flags);
cache_wrap.insert(wrp_hash, lines_buffer);
@@ -1473,8 +1451,6 @@ Size2 Font::get_multiline_string_size(const String &p_text, real_t p_width, int
void Font::draw_string(RID p_canvas_item, const Point2 &p_pos, const String &p_text, HAlign p_align, real_t p_width, int p_size, const Color &p_modulate, int p_outline_size, const Color &p_outline_modulate, uint16_t p_flags) const {
ERR_FAIL_COND(data.is_empty());
- int size = (p_size <= 0) ? base_size : p_size;
-
for (int i = 0; i < data.size(); i++) {
_ensure_rid(i);
}
@@ -1484,14 +1460,14 @@ void Font::draw_string(RID p_canvas_item, const Point2 &p_pos, const String &p_t
hash = hash_djb2_one_64(hash_djb2_one_float(p_width), hash);
hash = hash_djb2_one_64(p_flags, hash);
}
- hash = hash_djb2_one_64(size, hash);
+ hash = hash_djb2_one_64(p_size, hash);
Ref<TextLine> buffer;
if (cache.has(hash)) {
buffer = cache.get(hash);
} else {
buffer.instantiate();
- buffer->add_string(p_text, Ref<Font>(this), size, Dictionary(), TranslationServer::get_singleton()->get_tool_locale());
+ buffer->add_string(p_text, Ref<Font>(this), p_size, Dictionary(), TranslationServer::get_singleton()->get_tool_locale());
cache.insert(hash, buffer);
}
@@ -1515,8 +1491,6 @@ void Font::draw_string(RID p_canvas_item, const Point2 &p_pos, const String &p_t
void Font::draw_multiline_string(RID p_canvas_item, const Point2 &p_pos, const String &p_text, HAlign p_align, float p_width, int p_max_lines, int p_size, const Color &p_modulate, int p_outline_size, const Color &p_outline_modulate, uint16_t p_flags) const {
ERR_FAIL_COND(data.is_empty());
- int size = (p_size <= 0) ? base_size : p_size;
-
for (int i = 0; i < data.size(); i++) {
_ensure_rid(i);
}
@@ -1524,14 +1498,14 @@ void Font::draw_multiline_string(RID p_canvas_item, const Point2 &p_pos, const S
uint64_t hash = p_text.hash64();
uint64_t wrp_hash = hash_djb2_one_64(hash_djb2_one_float(p_width), hash);
wrp_hash = hash_djb2_one_64(p_flags, wrp_hash);
- wrp_hash = hash_djb2_one_64(size, wrp_hash);
+ wrp_hash = hash_djb2_one_64(p_size, wrp_hash);
Ref<TextParagraph> lines_buffer;
if (cache_wrap.has(wrp_hash)) {
lines_buffer = cache_wrap.get(wrp_hash);
} else {
lines_buffer.instantiate();
- lines_buffer->add_string(p_text, Ref<Font>(this), size, Dictionary(), TranslationServer::get_singleton()->get_tool_locale());
+ lines_buffer->add_string(p_text, Ref<Font>(this), p_size, Dictionary(), TranslationServer::get_singleton()->get_tool_locale());
lines_buffer->set_width(p_width);
lines_buffer->set_flags(p_flags);
cache_wrap.insert(wrp_hash, lines_buffer);
@@ -1573,16 +1547,14 @@ void Font::draw_multiline_string(RID p_canvas_item, const Point2 &p_pos, const S
}
Size2 Font::get_char_size(char32_t p_char, char32_t p_next, int p_size) const {
- int size = (p_size <= 0) ? base_size : p_size;
-
for (int i = 0; i < data.size(); i++) {
_ensure_rid(i);
if (data[i]->has_char(p_char)) {
- int32_t glyph_a = TS->font_get_glyph_index(rids[i], size, p_char, 0);
- Size2 ret = Size2(TS->font_get_glyph_advance(rids[i], size, glyph_a).x, TS->font_get_ascent(rids[i], size) + TS->font_get_descent(rids[i], size));
+ int32_t glyph_a = TS->font_get_glyph_index(rids[i], p_size, p_char, 0);
+ Size2 ret = Size2(TS->font_get_glyph_advance(rids[i], p_size, glyph_a).x, TS->font_get_ascent(rids[i], p_size) + TS->font_get_descent(rids[i], p_size));
if ((p_next != 0) && data[i]->has_char(p_next)) {
- int32_t glyph_b = TS->font_get_glyph_index(rids[i], size, p_next, 0);
- ret.x -= TS->font_get_kerning(rids[i], size, Vector2i(glyph_a, glyph_b)).x;
+ int32_t glyph_b = TS->font_get_glyph_index(rids[i], p_size, p_next, 0);
+ ret.x -= TS->font_get_kerning(rids[i], p_size, Vector2i(glyph_a, glyph_b)).x;
}
return ret;
}
@@ -1591,22 +1563,20 @@ Size2 Font::get_char_size(char32_t p_char, char32_t p_next, int p_size) const {
}
real_t Font::draw_char(RID p_canvas_item, const Point2 &p_pos, char32_t p_char, char32_t p_next, int p_size, const Color &p_modulate, int p_outline_size, const Color &p_outline_modulate) const {
- int size = (p_size <= 0) ? base_size : p_size;
-
for (int i = 0; i < data.size(); i++) {
_ensure_rid(i);
if (data[i]->has_char(p_char)) {
- int32_t glyph_a = TS->font_get_glyph_index(rids[i], size, p_char, 0);
- real_t ret = TS->font_get_glyph_advance(rids[i], size, glyph_a).x;
+ int32_t glyph_a = TS->font_get_glyph_index(rids[i], p_size, p_char, 0);
+ real_t ret = TS->font_get_glyph_advance(rids[i], p_size, glyph_a).x;
if ((p_next != 0) && data[i]->has_char(p_next)) {
- int32_t glyph_b = TS->font_get_glyph_index(rids[i], size, p_next, 0);
- ret -= TS->font_get_kerning(rids[i], size, Vector2i(glyph_a, glyph_b)).x;
+ int32_t glyph_b = TS->font_get_glyph_index(rids[i], p_size, p_next, 0);
+ ret -= TS->font_get_kerning(rids[i], p_size, Vector2i(glyph_a, glyph_b)).x;
}
if (p_outline_size > 0 && p_outline_modulate.a != 0.0f) {
- TS->font_draw_glyph_outline(rids[i], p_canvas_item, size, p_outline_size, p_pos, glyph_a, p_outline_modulate);
+ TS->font_draw_glyph_outline(rids[i], p_canvas_item, p_size, p_outline_size, p_pos, glyph_a, p_outline_modulate);
}
- TS->font_draw_glyph(rids[i], p_canvas_item, size, p_pos, glyph_a, p_modulate);
+ TS->font_draw_glyph(rids[i], p_canvas_item, p_size, p_pos, glyph_a, p_modulate);
return ret;
}
}
diff --git a/scene/resources/font.h b/scene/resources/font.h
index e65fdb0751..e1f1f6d742 100644
--- a/scene/resources/font.h
+++ b/scene/resources/font.h
@@ -219,7 +219,6 @@ class Font : public Resource {
mutable Vector<RID> rids;
// Font config.
- int base_size = 16;
Dictionary variation_coordinates;
int spacing_bottom = 0;
int spacing_top = 0;
@@ -237,6 +236,8 @@ protected:
virtual void reset_state() override;
public:
+ static const int DEFAULT_FONT_SIZE = 16;
+
Dictionary get_feature_list() const;
// Font data.
@@ -249,9 +250,6 @@ public:
virtual void remove_data(int p_idx);
// Font configuration.
- virtual void set_base_size(int p_size);
- virtual int get_base_size() const;
-
virtual void set_variation_coordinates(const Dictionary &p_variation_coordinates);
virtual Dictionary get_variation_coordinates() const;
@@ -259,26 +257,26 @@ public:
virtual int get_spacing(TextServer::SpacingType p_spacing) const;
// Font metrics.
- virtual real_t get_height(int p_size = -1) const;
- virtual real_t get_ascent(int p_size = -1) const;
- virtual real_t get_descent(int p_size = -1) const;
- virtual real_t get_underline_position(int p_size = -1) const;
- virtual real_t get_underline_thickness(int p_size = -1) const;
+ virtual real_t get_height(int p_size = DEFAULT_FONT_SIZE) const;
+ virtual real_t get_ascent(int p_size = DEFAULT_FONT_SIZE) const;
+ virtual real_t get_descent(int p_size = DEFAULT_FONT_SIZE) const;
+ virtual real_t get_underline_position(int p_size = DEFAULT_FONT_SIZE) const;
+ virtual real_t get_underline_thickness(int p_size = DEFAULT_FONT_SIZE) const;
// Drawing string.
- virtual Size2 get_string_size(const String &p_text, int p_size = -1, HAlign p_align = HALIGN_LEFT, real_t p_width = -1, uint16_t p_flags = TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND) const;
- virtual Size2 get_multiline_string_size(const String &p_text, real_t p_width = -1, int p_size = -1, uint16_t p_flags = TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND) const;
+ virtual Size2 get_string_size(const String &p_text, int p_size = DEFAULT_FONT_SIZE, HAlign p_align = HALIGN_LEFT, real_t p_width = -1, uint16_t p_flags = TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND) const;
+ virtual Size2 get_multiline_string_size(const String &p_text, real_t p_width = -1, int p_size = DEFAULT_FONT_SIZE, uint16_t p_flags = TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND) const;
- virtual void draw_string(RID p_canvas_item, const Point2 &p_pos, const String &p_text, HAlign p_align = HALIGN_LEFT, real_t p_width = -1, int p_size = -1, const Color &p_modulate = Color(1, 1, 1), int p_outline_size = 0, const Color &p_outline_modulate = Color(1, 1, 1, 0), uint16_t p_flags = TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND) const;
- virtual void draw_multiline_string(RID p_canvas_item, const Point2 &p_pos, const String &p_text, HAlign p_align = HALIGN_LEFT, real_t p_width = -1, int p_max_lines = -1, int p_size = -1, const Color &p_modulate = Color(1, 1, 1), int p_outline_size = 0, const Color &p_outline_modulate = Color(1, 1, 1, 0), uint16_t p_flags = TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND) const;
+ virtual void draw_string(RID p_canvas_item, const Point2 &p_pos, const String &p_text, HAlign p_align = HALIGN_LEFT, real_t p_width = -1, int p_size = DEFAULT_FONT_SIZE, const Color &p_modulate = Color(1, 1, 1), int p_outline_size = 0, const Color &p_outline_modulate = Color(1, 1, 1, 0), uint16_t p_flags = TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND) const;
+ virtual void draw_multiline_string(RID p_canvas_item, const Point2 &p_pos, const String &p_text, HAlign p_align = HALIGN_LEFT, real_t p_width = -1, int p_max_lines = -1, int p_size = DEFAULT_FONT_SIZE, const Color &p_modulate = Color(1, 1, 1), int p_outline_size = 0, const Color &p_outline_modulate = Color(1, 1, 1, 0), uint16_t p_flags = TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND) const;
// Helper functions.
virtual bool has_char(char32_t p_char) const;
virtual String get_supported_chars() const;
// Drawing char.
- virtual Size2 get_char_size(char32_t p_char, char32_t p_next = 0, int p_size = -1) const;
- virtual real_t draw_char(RID p_canvas_item, const Point2 &p_pos, char32_t p_char, char32_t p_next = 0, int p_size = -1, const Color &p_modulate = Color(1, 1, 1), int p_outline_size = 0, const Color &p_outline_modulate = Color(1, 1, 1, 0)) const;
+ virtual Size2 get_char_size(char32_t p_char, char32_t p_next = 0, int p_size = DEFAULT_FONT_SIZE) const;
+ virtual real_t draw_char(RID p_canvas_item, const Point2 &p_pos, char32_t p_char, char32_t p_next = 0, int p_size = DEFAULT_FONT_SIZE, const Color &p_modulate = Color(1, 1, 1), int p_outline_size = 0, const Color &p_outline_modulate = Color(1, 1, 1, 0)) const;
Vector<RID> get_rids() const;
diff --git a/scene/resources/skeleton_modification_stack_3d.cpp b/scene/resources/skeleton_modification_stack_3d.cpp
index a724b732b9..c03210cf48 100644
--- a/scene/resources/skeleton_modification_stack_3d.cpp
+++ b/scene/resources/skeleton_modification_stack_3d.cpp
@@ -125,6 +125,7 @@ Ref<SkeletonModification3D> SkeletonModificationStack3D::get_modification(int p_
}
void SkeletonModificationStack3D::add_modification(Ref<SkeletonModification3D> p_mod) {
+ ERR_FAIL_NULL(p_mod);
p_mod->_setup_modification(this);
modifications.push_back(p_mod);
}
diff --git a/scene/resources/theme.cpp b/scene/resources/theme.cpp
index 65cdc1e24e..99977a20f2 100644
--- a/scene/resources/theme.cpp
+++ b/scene/resources/theme.cpp
@@ -169,7 +169,7 @@ void Theme::_get_property_list(List<PropertyInfo> *p_list) const {
const StringName *key2 = nullptr;
while ((key2 = font_size_map[*key].next(key2))) {
- list.push_back(PropertyInfo(Variant::INT, String() + *key + "/font_sizes/" + *key2));
+ list.push_back(PropertyInfo(Variant::INT, String() + *key + "/font_sizes/" + *key2, PROPERTY_HINT_RANGE, "0,256,1,or_greater"));
}
}
@@ -1655,7 +1655,7 @@ void Theme::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "default_base_scale", PROPERTY_HINT_RANGE, "0.0,2.0,0.01,or_greater"), "set_default_base_scale", "get_default_base_scale");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "default_font", PROPERTY_HINT_RESOURCE_TYPE, "Font"), "set_default_font", "get_default_font");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "default_font_size"), "set_default_font_size", "get_default_font_size");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "default_font_size", PROPERTY_HINT_RANGE, "0,256,1,or_greater"), "set_default_font_size", "get_default_font_size");
BIND_ENUM_CONSTANT(DATA_TYPE_COLOR);
BIND_ENUM_CONSTANT(DATA_TYPE_CONSTANT);
diff --git a/scene/resources/tile_set.cpp b/scene/resources/tile_set.cpp
index a45b6b8eb6..6411a6d233 100644
--- a/scene/resources/tile_set.cpp
+++ b/scene/resources/tile_set.cpp
@@ -301,6 +301,66 @@ int TileSet::get_next_source_id() const {
return next_source_id;
}
+void TileSet::_update_terrains_cache() {
+ if (terrains_cache_dirty) {
+ // Organizes tiles into structures.
+ per_terrain_pattern_tiles.resize(terrain_sets.size());
+ for (int i = 0; i < (int)per_terrain_pattern_tiles.size(); i++) {
+ per_terrain_pattern_tiles[i].clear();
+ }
+
+ for (const KeyValue<int, Ref<TileSetSource>> &kv : sources) {
+ Ref<TileSetSource> source = kv.value;
+ Ref<TileSetAtlasSource> atlas_source = source;
+ if (atlas_source.is_valid()) {
+ for (int tile_index = 0; tile_index < source->get_tiles_count(); tile_index++) {
+ Vector2i tile_id = source->get_tile_id(tile_index);
+ for (int alternative_index = 0; alternative_index < source->get_alternative_tiles_count(tile_id); alternative_index++) {
+ int alternative_id = source->get_alternative_tile_id(tile_id, alternative_index);
+
+ // Executed for each tile_data.
+ TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(tile_id, alternative_id));
+ int terrain_set = tile_data->get_terrain_set();
+ if (terrain_set >= 0) {
+ TileMapCell cell;
+ cell.source_id = kv.key;
+ cell.set_atlas_coords(tile_id);
+ cell.alternative_tile = alternative_id;
+
+ TileSet::TerrainsPattern terrains_pattern = tile_data->get_terrains_pattern();
+
+ // Terrain bits.
+ for (int i = 0; i < terrains_pattern.size(); i++) {
+ int terrain = terrains_pattern[i];
+ if (terrain >= 0) {
+ per_terrain_pattern_tiles[terrain_set][terrains_pattern].insert(cell);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Add the empty cell in the possible patterns and cells.
+ for (int i = 0; i < terrain_sets.size(); i++) {
+ TileSet::TerrainsPattern empty_pattern;
+ for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) {
+ if (is_valid_peering_bit_terrain(i, TileSet::CellNeighbor(j))) {
+ empty_pattern.push_back(-1);
+ }
+ }
+
+ TileMapCell empty_cell;
+ empty_cell.source_id = TileSet::INVALID_SOURCE;
+ empty_cell.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS);
+ empty_cell.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE;
+ per_terrain_pattern_tiles[i][empty_pattern].insert(empty_cell);
+ }
+ terrains_cache_dirty = false;
+ }
+}
+
void TileSet::_compute_next_source_id() {
while (sources.has(next_source_id)) {
next_source_id = (next_source_id + 1) % 1073741824; // 2 ** 30
@@ -321,6 +381,7 @@ int TileSet::add_source(Ref<TileSetSource> p_tile_set_source, int p_atlas_source
sources[new_source_id]->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &TileSet::_source_changed));
+ terrains_cache_dirty = true;
emit_changed();
return new_source_id;
@@ -336,6 +397,7 @@ void TileSet::remove_source(int p_source_id) {
source_ids.erase(p_source_id);
source_ids.sort();
+ terrains_cache_dirty = true;
emit_changed();
}
@@ -357,6 +419,7 @@ void TileSet::set_source_id(int p_source_id, int p_new_source_id) {
_compute_next_source_id();
+ terrains_cache_dirty = true;
emit_changed();
}
@@ -545,6 +608,7 @@ void TileSet::add_terrain_set(int p_index) {
}
notify_property_list_changed();
+ terrains_cache_dirty = true;
emit_changed();
}
@@ -557,6 +621,7 @@ void TileSet::move_terrain_set(int p_from_index, int p_to_pos) {
source.value->move_terrain_set(p_from_index, p_to_pos);
}
notify_property_list_changed();
+ terrains_cache_dirty = true;
emit_changed();
}
@@ -567,6 +632,7 @@ void TileSet::remove_terrain_set(int p_index) {
source.value->remove_terrain_set(p_index);
}
notify_property_list_changed();
+ terrains_cache_dirty = true;
emit_changed();
}
@@ -578,6 +644,7 @@ void TileSet::set_terrain_set_mode(int p_terrain_set, TerrainMode p_terrain_mode
}
notify_property_list_changed();
+ terrains_cache_dirty = true;
emit_changed();
}
@@ -612,6 +679,7 @@ void TileSet::add_terrain(int p_terrain_set, int p_index) {
}
notify_property_list_changed();
+ terrains_cache_dirty = true;
emit_changed();
}
@@ -627,6 +695,7 @@ void TileSet::move_terrain(int p_terrain_set, int p_from_index, int p_to_pos) {
source.value->move_terrain(p_terrain_set, p_from_index, p_to_pos);
}
notify_property_list_changed();
+ terrains_cache_dirty = true;
emit_changed();
}
@@ -640,6 +709,7 @@ void TileSet::remove_terrain(int p_terrain_set, int p_index) {
source.value->remove_terrain(p_terrain_set, p_index);
}
notify_property_list_changed();
+ terrains_cache_dirty = true;
emit_changed();
}
@@ -1196,6 +1266,73 @@ int TileSet::get_patterns_count() {
return patterns.size();
}
+Set<TileSet::TerrainsPattern> TileSet::get_terrains_pattern_set(int p_terrain_set) {
+ ERR_FAIL_INDEX_V(p_terrain_set, terrain_sets.size(), Set<TileSet::TerrainsPattern>());
+ _update_terrains_cache();
+
+ Set<TileSet::TerrainsPattern> output;
+ for (KeyValue<TileSet::TerrainsPattern, Set<TileMapCell>> kv : per_terrain_pattern_tiles[p_terrain_set]) {
+ output.insert(kv.key);
+ }
+ return output;
+}
+
+Set<TileMapCell> TileSet::get_tiles_for_terrains_pattern(int p_terrain_set, TerrainsPattern p_terrain_tile_pattern) {
+ ERR_FAIL_INDEX_V(p_terrain_set, terrain_sets.size(), Set<TileMapCell>());
+ _update_terrains_cache();
+ return per_terrain_pattern_tiles[p_terrain_set][p_terrain_tile_pattern];
+}
+
+TileMapCell TileSet::get_random_tile_from_pattern(int p_terrain_set, TileSet::TerrainsPattern p_terrain_tile_pattern) {
+ ERR_FAIL_INDEX_V(p_terrain_set, terrain_sets.size(), TileMapCell());
+ _update_terrains_cache();
+
+ // Count the sum of probabilities.
+ double sum = 0.0;
+ Set<TileMapCell> set = per_terrain_pattern_tiles[p_terrain_set][p_terrain_tile_pattern];
+ for (Set<TileMapCell>::Element *E = set.front(); E; E = E->next()) {
+ if (E->get().source_id >= 0) {
+ Ref<TileSetSource> source = sources[E->get().source_id];
+ Ref<TileSetAtlasSource> atlas_source = source;
+ if (atlas_source.is_valid()) {
+ TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(E->get().get_atlas_coords(), E->get().alternative_tile));
+ sum += tile_data->get_probability();
+ } else {
+ sum += 1.0;
+ }
+ } else {
+ sum += 1.0;
+ }
+ }
+
+ // Generate a random number.
+ double count = 0.0;
+ double picked = Math::random(0.0, sum);
+
+ // Pick the tile.
+ for (Set<TileMapCell>::Element *E = set.front(); E; E = E->next()) {
+ if (E->get().source_id >= 0) {
+ Ref<TileSetSource> source = sources[E->get().source_id];
+
+ Ref<TileSetAtlasSource> atlas_source = source;
+ if (atlas_source.is_valid()) {
+ TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(E->get().get_atlas_coords(), E->get().alternative_tile));
+ count += tile_data->get_probability();
+ } else {
+ count += 1.0;
+ }
+ } else {
+ count += 1.0;
+ }
+
+ if (count >= picked) {
+ return E->get();
+ }
+ }
+
+ ERR_FAIL_V(TileMapCell());
+}
+
Vector<Vector2> TileSet::get_tile_shape_polygon() {
Vector<Vector2> points;
if (tile_shape == TileSet::TILE_SHAPE_SQUARE) {
@@ -1510,6 +1647,7 @@ Vector<Vector<Ref<Texture2D>>> TileSet::generate_terrains_icons(Size2i p_size) {
}
void TileSet::_source_changed() {
+ terrains_cache_dirty = true;
emit_changed();
}
@@ -4743,6 +4881,18 @@ bool TileData::is_valid_peering_bit_terrain(TileSet::CellNeighbor p_peering_bit)
return tile_set->is_valid_peering_bit_terrain(terrain_set, p_peering_bit);
}
+TileSet::TerrainsPattern TileData::get_terrains_pattern() const {
+ ERR_FAIL_COND_V(!tile_set, TileSet::TerrainsPattern());
+
+ TileSet::TerrainsPattern output;
+ for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) {
+ if (tile_set->is_valid_peering_bit_terrain(terrain_set, TileSet::CellNeighbor(i))) {
+ output.push_back(get_peering_bit_terrain(TileSet::CellNeighbor(i)));
+ }
+ }
+ return output;
+}
+
// Navigation
void TileData::set_navigation_polygon(int p_layer_id, Ref<NavigationPolygon> p_navigation_polygon) {
ERR_FAIL_INDEX(p_layer_id, navigation.size());
diff --git a/scene/resources/tile_set.h b/scene/resources/tile_set.h
index 530c90920f..52446fd680 100644
--- a/scene/resources/tile_set.h
+++ b/scene/resources/tile_set.h
@@ -253,6 +253,7 @@ public:
Ref<PackedScene> scene;
Vector2 offset;
};
+ typedef Array TerrainsPattern;
protected:
bool _set(const StringName &p_name, const Variant &p_value);
@@ -303,6 +304,10 @@ private:
Map<TerrainMode, Map<CellNeighbor, Ref<ArrayMesh>>> terrain_bits_meshes;
bool terrain_bits_meshes_dirty = true;
+ LocalVector<Map<TileSet::TerrainsPattern, Set<TileMapCell>>> per_terrain_pattern_tiles; // Cached data.
+ bool terrains_cache_dirty = true;
+ void _update_terrains_cache();
+
// Navigation
struct NavigationLayer {
uint32_t layers = 1;
@@ -470,6 +475,11 @@ public:
void remove_pattern(int p_index);
int get_patterns_count();
+ // Terrains.
+ Set<TerrainsPattern> get_terrains_pattern_set(int p_terrain_set);
+ Set<TileMapCell> get_tiles_for_terrains_pattern(int p_terrain_set, TerrainsPattern p_terrain_tile_pattern);
+ TileMapCell get_random_tile_from_pattern(int p_terrain_set, TerrainsPattern p_terrain_tile_pattern);
+
// Helpers
Vector<Vector2> get_tile_shape_polygon();
void draw_tile_shape(CanvasItem *p_canvas_item, Transform2D p_transform, Color p_color, bool p_filled = false, Ref<Texture2D> p_texture = Ref<Texture2D>());
@@ -831,6 +841,8 @@ public:
int get_peering_bit_terrain(TileSet::CellNeighbor p_peering_bit) const;
bool is_valid_peering_bit_terrain(TileSet::CellNeighbor p_peering_bit) const;
+ TileSet::TerrainsPattern get_terrains_pattern() const; // Not exposed.
+
// Navigation
void set_navigation_polygon(int p_layer_id, Ref<NavigationPolygon> p_navigation_polygon);
Ref<NavigationPolygon> get_navigation_polygon(int p_layer_id) const;
diff --git a/servers/physics_3d/joints/godot_generic_6dof_joint_3d.cpp b/servers/physics_3d/joints/godot_generic_6dof_joint_3d.cpp
index d7e0537439..b88e2d1190 100644
--- a/servers/physics_3d/joints/godot_generic_6dof_joint_3d.cpp
+++ b/servers/physics_3d/joints/godot_generic_6dof_joint_3d.cpp
@@ -232,7 +232,7 @@ GodotGeneric6DOFJoint3D::GodotGeneric6DOFJoint3D(GodotBody3D *rbA, GodotBody3D *
void GodotGeneric6DOFJoint3D::calculateAngleInfo() {
Basis relative_frame = m_calculatedTransformB.basis.inverse() * m_calculatedTransformA.basis;
- m_calculatedAxisAngleDiff = relative_frame.get_euler_xyz();
+ m_calculatedAxisAngleDiff = relative_frame.get_euler(Basis::EULER_ORDER_XYZ);
// in euler angle mode we do not actually constrain the angular velocity
// along the axes axis[0] and axis[2] (although we do use axis[1]) :
diff --git a/servers/rendering/renderer_rd/shaders/scene_forward_clustered.glsl b/servers/rendering/renderer_rd/shaders/scene_forward_clustered.glsl
index 987960069b..5bb8fea89a 100644
--- a/servers/rendering/renderer_rd/shaders/scene_forward_clustered.glsl
+++ b/servers/rendering/renderer_rd/shaders/scene_forward_clustered.glsl
@@ -1251,6 +1251,7 @@ void main() {
{ //directional light
+#ifndef SHADOWS_DISABLED
// Do shadow and lighting in two passes to reduce register pressure
uint shadow0 = 0;
uint shadow1 = 0;
@@ -1449,6 +1450,7 @@ void main() {
shadow1 |= uint(clamp(shadow * 255.0, 0.0, 255.0)) << ((i - 4) * 8);
}
}
+#endif // SHADOWS_DISABLED
for (uint i = 0; i < 8; i++) {
if (i >= scene_data.directional_light_count) {
@@ -1511,12 +1513,13 @@ void main() {
#endif
float shadow = 1.0;
-
+#ifndef SHADOWS_DISABLED
if (i < 4) {
shadow = float(shadow0 >> (i * 8) & 0xFF) / 255.0;
} else {
shadow = float(shadow1 >> ((i - 4) * 8) & 0xFF) / 255.0;
}
+#endif
blur_shadow(shadow);
diff --git a/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl b/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl
index 72872c9c08..61559fe809 100644
--- a/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl
+++ b/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl
@@ -287,7 +287,7 @@ void light_compute(vec3 N, vec3 L, vec3 V, float A, vec3 light_color, float atte
#endif //defined(LIGHT_CODE_USED)
}
-#ifndef USE_NO_SHADOWS
+#ifndef SHADOWS_DISABLED
// Interleaved Gradient Noise
// https://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare
@@ -433,7 +433,7 @@ float sample_directional_soft_shadow(texture2D shadow, vec3 pssm_coord, vec2 tex
}
}
-#endif //USE_NO_SHADOWS
+#endif // SHADOWS_DISABLED
float get_omni_attenuation(float distance, float inv_range, float decay) {
float nd = distance * inv_range;
@@ -445,7 +445,7 @@ float get_omni_attenuation(float distance, float inv_range, float decay) {
}
float light_process_omni_shadow(uint idx, vec3 vertex, vec3 normal) {
-#ifndef USE_NO_SHADOWS
+#ifndef SHADOWS_DISABLED
if (omni_lights.data[idx].shadow_enabled) {
// there is a shadowmap
vec2 texel_size = scene_data.shadow_atlas_pixel_size;
@@ -730,7 +730,7 @@ void light_process_omni(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 v
}
float light_process_spot_shadow(uint idx, vec3 vertex, vec3 normal) {
-#ifndef USE_NO_SHADOWS
+#ifndef SHADOWS_DISABLED
if (spot_lights.data[idx].shadow_enabled) {
vec3 light_rel_vec = spot_lights.data[idx].position - vertex;
float light_length = length(light_rel_vec);
@@ -806,7 +806,7 @@ float light_process_spot_shadow(uint idx, vec3 vertex, vec3 normal) {
return shadow;
}
-#endif //USE_NO_SHADOWS
+#endif // SHADOWS_DISABLED
return 1.0;
}
diff --git a/servers/rendering/renderer_scene_cull.cpp b/servers/rendering/renderer_scene_cull.cpp
index a7886bb6b1..cf422962f0 100644
--- a/servers/rendering/renderer_scene_cull.cpp
+++ b/servers/rendering/renderer_scene_cull.cpp
@@ -1139,7 +1139,7 @@ void RendererSceneCull::instance_geometry_set_cast_shadows_setting(RID p_instanc
if (instance->scenario && instance->array_index >= 0) {
InstanceData &idata = instance->scenario->instance_data[instance->array_index];
- if (instance->cast_shadows != RS::SHADOW_CASTING_SETTING_SHADOWS_ONLY) {
+ if (instance->cast_shadows != RS::SHADOW_CASTING_SETTING_OFF) {
idata.flags |= InstanceData::FLAG_CAST_SHADOWS;
} else {
idata.flags &= ~uint32_t(InstanceData::FLAG_CAST_SHADOWS);
@@ -1603,7 +1603,7 @@ void RendererSceneCull::_update_instance(Instance *p_instance) {
//always dirty when added
idata.flags |= InstanceData::FLAG_REFLECTION_PROBE_DIRTY;
}
- if (p_instance->cast_shadows != RS::SHADOW_CASTING_SETTING_SHADOWS_ONLY) {
+ if (p_instance->cast_shadows != RS::SHADOW_CASTING_SETTING_OFF) {
idata.flags |= InstanceData::FLAG_CAST_SHADOWS;
}
if (p_instance->cast_shadows == RS::SHADOW_CASTING_SETTING_SHADOWS_ONLY) {
diff --git a/tests/test_basis.h b/tests/test_basis.h
index 11c68f9eb7..b254d9fb7f 100644
--- a/tests/test_basis.h
+++ b/tests/test_basis.h
@@ -60,27 +60,27 @@ Basis EulerToBasis(RotOrder mode, const Vector3 &p_rotation) {
Basis ret;
switch (mode) {
case EulerXYZ:
- ret.set_euler_xyz(p_rotation);
+ ret.set_euler(p_rotation, Basis::EULER_ORDER_XYZ);
break;
case EulerXZY:
- ret.set_euler_xzy(p_rotation);
+ ret.set_euler(p_rotation, Basis::EULER_ORDER_XZY);
break;
case EulerYZX:
- ret.set_euler_yzx(p_rotation);
+ ret.set_euler(p_rotation, Basis::EULER_ORDER_YZX);
break;
case EulerYXZ:
- ret.set_euler_yxz(p_rotation);
+ ret.set_euler(p_rotation, Basis::EULER_ORDER_YXZ);
break;
case EulerZXY:
- ret.set_euler_zxy(p_rotation);
+ ret.set_euler(p_rotation, Basis::EULER_ORDER_ZXY);
break;
case EulerZYX:
- ret.set_euler_zyx(p_rotation);
+ ret.set_euler(p_rotation, Basis::EULER_ORDER_ZYX);
break;
default:
@@ -94,22 +94,22 @@ Basis EulerToBasis(RotOrder mode, const Vector3 &p_rotation) {
Vector3 BasisToEuler(RotOrder mode, const Basis &p_rotation) {
switch (mode) {
case EulerXYZ:
- return p_rotation.get_euler_xyz();
+ return p_rotation.get_euler(Basis::EULER_ORDER_XYZ);
case EulerXZY:
- return p_rotation.get_euler_xzy();
+ return p_rotation.get_euler(Basis::EULER_ORDER_XZY);
case EulerYZX:
- return p_rotation.get_euler_yzx();
+ return p_rotation.get_euler(Basis::EULER_ORDER_YZX);
case EulerYXZ:
- return p_rotation.get_euler_yxz();
+ return p_rotation.get_euler(Basis::EULER_ORDER_YXZ);
case EulerZXY:
- return p_rotation.get_euler_zxy();
+ return p_rotation.get_euler(Basis::EULER_ORDER_ZXY);
case EulerZYX:
- return p_rotation.get_euler_zyx();
+ return p_rotation.get_euler(Basis::EULER_ORDER_ZYX);
default:
// If you land here, Please integrate all rotation orders.
@@ -170,9 +170,9 @@ void test_rotation(Vector3 deg_original_euler, RotOrder rot_order) {
CHECK_MESSAGE((res.get_axis(2) - Vector3(0.0, 0.0, 1.0)).length() <= 0.1, vformat("Fail due to Z %s\n", String(res.get_axis(2))).utf8().ptr());
// Double check `to_rotation` decomposing with XYZ rotation order.
- const Vector3 euler_xyz_from_rotation = to_rotation.get_euler_xyz();
+ const Vector3 euler_xyz_from_rotation = to_rotation.get_euler(Basis::EULER_ORDER_XYZ);
Basis rotation_from_xyz_computed_euler;
- rotation_from_xyz_computed_euler.set_euler_xyz(euler_xyz_from_rotation);
+ rotation_from_xyz_computed_euler.set_euler(euler_xyz_from_rotation, Basis::EULER_ORDER_XYZ);
res = to_rotation.inverse() * rotation_from_xyz_computed_euler;
diff --git a/thirdparty/README.md b/thirdparty/README.md
index 964ac6246e..319e4d1ee0 100644
--- a/thirdparty/README.md
+++ b/thirdparty/README.md
@@ -318,6 +318,9 @@ File extracted from upstream release tarball:
pre-defined symbol.
- Applied the patch in `patches/pr4948-fix-clang12-opt.patch`. Upstream bugfix
from PR 4948 to fix a bug caused by Clang 12 optimizations.
+- Applied the patch in `patches/pr4819-faster-base64.patch`. This fixes a certs
+ parsing speed regression since 2.16.10 (upstream PR:
+ https://github.com/ARMmbed/mbedtls/pull/4819).
- Added 2 files `godot_core_mbedtls_platform.c` and `godot_core_mbedtls_config.h`
providing configuration for light bundling with core.
diff --git a/thirdparty/mbedtls/library/base64.c b/thirdparty/mbedtls/library/base64.c
index 692e11e3fa..b89313062b 100644
--- a/thirdparty/mbedtls/library/base64.c
+++ b/thirdparty/mbedtls/library/base64.c
@@ -66,127 +66,38 @@
#endif /* MBEDTLS_PLATFORM_C */
#endif /* MBEDTLS_SELF_TEST */
-static const unsigned char base64_enc_map[64] =
-{
- 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
- 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
- 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
- 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
- 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
- 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7',
- '8', '9', '+', '/'
-};
-
-static const unsigned char base64_dec_map[128] =
-{
- 127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
- 127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
- 127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
- 127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
- 127, 127, 127, 62, 127, 127, 127, 63, 52, 53,
- 54, 55, 56, 57, 58, 59, 60, 61, 127, 127,
- 127, 64, 127, 127, 127, 0, 1, 2, 3, 4,
- 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
- 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
- 25, 127, 127, 127, 127, 127, 127, 26, 27, 28,
- 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
- 39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
- 49, 50, 51, 127, 127, 127, 127, 127
-};
-
#define BASE64_SIZE_T_MAX ( (size_t) -1 ) /* SIZE_T_MAX is not standard */
-/*
- * Constant flow conditional assignment to unsigned char
- */
-static void mbedtls_base64_cond_assign_uchar( unsigned char * dest, const unsigned char * const src,
- unsigned char condition )
-{
- /* MSVC has a warning about unary minus on unsigned integer types,
- * but this is well-defined and precisely what we want to do here. */
-#if defined(_MSC_VER)
-#pragma warning( push )
-#pragma warning( disable : 4146 )
-#endif
-
- /* Generate bitmask from condition, mask will either be 0xFF or 0 */
- unsigned char mask = ( condition | -condition );
- mask >>= 7;
- mask = -mask;
-
-#if defined(_MSC_VER)
-#pragma warning( pop )
-#endif
-
- *dest = ( ( *src ) & mask ) | ( ( *dest ) & ~mask );
-}
-
-/*
- * Constant flow conditional assignment to uint_32
- */
-static void mbedtls_base64_cond_assign_uint32( uint32_t * dest, const uint32_t src,
- uint32_t condition )
-{
- /* MSVC has a warning about unary minus on unsigned integer types,
- * but this is well-defined and precisely what we want to do here. */
-#if defined(_MSC_VER)
-#pragma warning( push )
-#pragma warning( disable : 4146 )
-#endif
-
- /* Generate bitmask from condition, mask will either be 0xFFFFFFFF or 0 */
- uint32_t mask = ( condition | -condition );
- mask >>= 31;
- mask = -mask;
-
-#if defined(_MSC_VER)
-#pragma warning( pop )
-#endif
-
- *dest = ( src & mask ) | ( ( *dest ) & ~mask );
-}
-
-/*
- * Constant flow check for equality
+/* Return 0xff if low <= c <= high, 0 otherwise.
+ *
+ * Constant flow with respect to c.
*/
-static unsigned char mbedtls_base64_eq( size_t in_a, size_t in_b )
+static unsigned char mask_of_range( unsigned char low, unsigned char high,
+ unsigned char c )
{
- size_t difference = in_a ^ in_b;
-
- /* MSVC has a warning about unary minus on unsigned integer types,
- * but this is well-defined and precisely what we want to do here. */
-#if defined(_MSC_VER)
-#pragma warning( push )
-#pragma warning( disable : 4146 )
-#endif
-
- difference |= -difference;
-
-#if defined(_MSC_VER)
-#pragma warning( pop )
-#endif
-
- /* cope with the varying size of size_t per platform */
- difference >>= ( sizeof( difference ) * 8 - 1 );
-
- return (unsigned char) ( 1 ^ difference );
+ /* low_mask is: 0 if low <= c, 0x...ff if low > c */
+ unsigned low_mask = ( (unsigned) c - low ) >> 8;
+ /* high_mask is: 0 if c <= high, 0x...ff if high > c */
+ unsigned high_mask = ( (unsigned) high - c ) >> 8;
+ return( ~( low_mask | high_mask ) & 0xff );
}
-/*
- * Constant flow lookup into table.
+/* Given a value in the range 0..63, return the corresponding Base64 digit.
+ * The implementation assumes that letters are consecutive (e.g. ASCII
+ * but not EBCDIC).
*/
-static unsigned char mbedtls_base64_table_lookup( const unsigned char * const table,
- const size_t table_size, const size_t table_index )
+static unsigned char enc_char( unsigned char val )
{
- size_t i;
- unsigned char result = 0;
-
- for( i = 0; i < table_size; ++i )
- {
- mbedtls_base64_cond_assign_uchar( &result, &table[i], mbedtls_base64_eq( i, table_index ) );
- }
-
- return result;
+ unsigned char digit = 0;
+ /* For each range of values, if val is in that range, mask digit with
+ * the corresponding value. Since val can only be in a single range,
+ * only at most one masking will change digit. */
+ digit |= mask_of_range( 0, 25, val ) & ( 'A' + val );
+ digit |= mask_of_range( 26, 51, val ) & ( 'a' + val - 26 );
+ digit |= mask_of_range( 52, 61, val ) & ( '0' + val - 52 );
+ digit |= mask_of_range( 62, 62, val ) & '+';
+ digit |= mask_of_range( 63, 63, val ) & '/';
+ return( digit );
}
/*
@@ -229,17 +140,10 @@ int mbedtls_base64_encode( unsigned char *dst, size_t dlen, size_t *olen,
C2 = *src++;
C3 = *src++;
- *p++ = mbedtls_base64_table_lookup( base64_enc_map, sizeof( base64_enc_map ),
- ( ( C1 >> 2 ) & 0x3F ) );
-
- *p++ = mbedtls_base64_table_lookup( base64_enc_map, sizeof( base64_enc_map ),
- ( ( ( ( C1 & 3 ) << 4 ) + ( C2 >> 4 ) ) & 0x3F ) );
-
- *p++ = mbedtls_base64_table_lookup( base64_enc_map, sizeof( base64_enc_map ),
- ( ( ( ( C2 & 15 ) << 2 ) + ( C3 >> 6 ) ) & 0x3F ) );
-
- *p++ = mbedtls_base64_table_lookup( base64_enc_map, sizeof( base64_enc_map ),
- ( C3 & 0x3F ) );
+ *p++ = enc_char( ( C1 >> 2 ) & 0x3F );
+ *p++ = enc_char( ( ( ( C1 & 3 ) << 4 ) + ( C2 >> 4 ) ) & 0x3F );
+ *p++ = enc_char( ( ( ( C2 & 15 ) << 2 ) + ( C3 >> 6 ) ) & 0x3F );
+ *p++ = enc_char( C3 & 0x3F );
}
if( i < slen )
@@ -247,15 +151,11 @@ int mbedtls_base64_encode( unsigned char *dst, size_t dlen, size_t *olen,
C1 = *src++;
C2 = ( ( i + 1 ) < slen ) ? *src++ : 0;
- *p++ = mbedtls_base64_table_lookup( base64_enc_map, sizeof( base64_enc_map ),
- ( ( C1 >> 2 ) & 0x3F ) );
-
- *p++ = mbedtls_base64_table_lookup( base64_enc_map, sizeof( base64_enc_map ),
- ( ( ( ( C1 & 3 ) << 4 ) + ( C2 >> 4 ) ) & 0x3F ) );
+ *p++ = enc_char( ( C1 >> 2 ) & 0x3F );
+ *p++ = enc_char( ( ( ( C1 & 3 ) << 4 ) + ( C2 >> 4 ) ) & 0x3F );
if( ( i + 1 ) < slen )
- *p++ = mbedtls_base64_table_lookup( base64_enc_map, sizeof( base64_enc_map ),
- ( ( ( C2 & 15 ) << 2 ) & 0x3F ) );
+ *p++ = enc_char( ( ( C2 & 15 ) << 2 ) & 0x3F );
else *p++ = '=';
*p++ = '=';
@@ -267,26 +167,57 @@ int mbedtls_base64_encode( unsigned char *dst, size_t dlen, size_t *olen,
return( 0 );
}
+/* Given a Base64 digit, return its value.
+ * If c is not a Base64 digit ('A'..'Z', 'a'..'z', '0'..'9', '+' or '/'),
+ * return -1.
+ *
+ * The implementation assumes that letters are consecutive (e.g. ASCII
+ * but not EBCDIC).
+ *
+ * The implementation is constant-flow (no branch or memory access depending
+ * on the value of c) unless the compiler inlines and optimizes a specific
+ * access.
+ */
+static signed char dec_value( unsigned char c )
+{
+ unsigned char val = 0;
+ /* For each range of digits, if c is in that range, mask val with
+ * the corresponding value. Since c can only be in a single range,
+ * only at most one masking will change val. Set val to one plus
+ * the desired value so that it stays 0 if c is in none of the ranges. */
+ val |= mask_of_range( 'A', 'Z', c ) & ( c - 'A' + 0 + 1 );
+ val |= mask_of_range( 'a', 'z', c ) & ( c - 'a' + 26 + 1 );
+ val |= mask_of_range( '0', '9', c ) & ( c - '0' + 52 + 1 );
+ val |= mask_of_range( '+', '+', c ) & ( c - '+' + 62 + 1 );
+ val |= mask_of_range( '/', '/', c ) & ( c - '/' + 63 + 1 );
+ /* At this point, val is 0 if c is an invalid digit and v+1 if c is
+ * a digit with the value v. */
+ return( val - 1 );
+}
+
/*
* Decode a base64-formatted buffer
*/
int mbedtls_base64_decode( unsigned char *dst, size_t dlen, size_t *olen,
const unsigned char *src, size_t slen )
{
- size_t i, n;
- uint32_t j, x;
+ size_t i; /* index in source */
+ size_t n; /* number of digits or trailing = in source */
+ uint32_t x; /* value accumulator */
+ unsigned accumulated_digits = 0;
+ unsigned equals = 0;
+ int spaces_present = 0;
unsigned char *p;
- unsigned char dec_map_lookup;
/* First pass: check for validity and get output length */
- for( i = n = j = 0; i < slen; i++ )
+ for( i = n = 0; i < slen; i++ )
{
/* Skip spaces before checking for EOL */
- x = 0;
+ spaces_present = 0;
while( i < slen && src[i] == ' ' )
{
++i;
- ++x;
+ spaces_present = 1;
}
/* Spaces at end of buffer are OK */
@@ -301,20 +232,24 @@ int mbedtls_base64_decode( unsigned char *dst, size_t dlen, size_t *olen,
continue;
/* Space inside a line is an error */
- if( x != 0 )
+ if( spaces_present )
return( MBEDTLS_ERR_BASE64_INVALID_CHARACTER );
- if( src[i] == '=' && ++j > 2 )
- return( MBEDTLS_ERR_BASE64_INVALID_CHARACTER );
-
- dec_map_lookup = mbedtls_base64_table_lookup( base64_dec_map, sizeof( base64_dec_map ), src[i] );
-
- if( src[i] > 127 || dec_map_lookup == 127 )
- return( MBEDTLS_ERR_BASE64_INVALID_CHARACTER );
-
- if( dec_map_lookup < 64 && j != 0 )
+ if( src[i] > 127 )
return( MBEDTLS_ERR_BASE64_INVALID_CHARACTER );
+ if( src[i] == '=' )
+ {
+ if( ++equals > 2 )
+ return( MBEDTLS_ERR_BASE64_INVALID_CHARACTER );
+ }
+ else
+ {
+ if( equals != 0 )
+ return( MBEDTLS_ERR_BASE64_INVALID_CHARACTER );
+ if( dec_value( src[i] ) < 0 )
+ return( MBEDTLS_ERR_BASE64_INVALID_CHARACTER );
+ }
n++;
}
@@ -329,7 +264,7 @@ int mbedtls_base64_decode( unsigned char *dst, size_t dlen, size_t *olen,
* n = ( ( n * 6 ) + 7 ) >> 3;
*/
n = ( 6 * ( n >> 3 ) ) + ( ( 6 * ( n & 0x7 ) + 7 ) >> 3 );
- n -= j;
+ n -= equals;
if( dst == NULL || dlen < n )
{
@@ -337,22 +272,24 @@ int mbedtls_base64_decode( unsigned char *dst, size_t dlen, size_t *olen,
return( MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL );
}
- for( j = 3, n = x = 0, p = dst; i > 0; i--, src++ )
- {
+ equals = 0;
+ for( x = 0, p = dst; i > 0; i--, src++ )
+ {
if( *src == '\r' || *src == '\n' || *src == ' ' )
continue;
- dec_map_lookup = mbedtls_base64_table_lookup( base64_dec_map, sizeof( base64_dec_map ), *src );
-
- mbedtls_base64_cond_assign_uint32( &j, j - 1, mbedtls_base64_eq( dec_map_lookup, 64 ) );
- x = ( x << 6 ) | ( dec_map_lookup & 0x3F );
+ x = x << 6;
+ if( *src == '=' )
+ ++equals;
+ else
+ x |= dec_value( *src );
- if( ++n == 4 )
+ if( ++accumulated_digits == 4 )
{
- n = 0;
- if( j > 0 ) *p++ = (unsigned char)( x >> 16 );
- if( j > 1 ) *p++ = (unsigned char)( x >> 8 );
- if( j > 2 ) *p++ = (unsigned char)( x );
+ accumulated_digits = 0;
+ *p++ = (unsigned char)( x >> 16 );
+ if( equals <= 1 ) *p++ = (unsigned char)( x >> 8 );
+ if( equals <= 0 ) *p++ = (unsigned char)( x );
}
}
diff --git a/thirdparty/mbedtls/patches/pr4819-faster-base64.patch b/thirdparty/mbedtls/patches/pr4819-faster-base64.patch
new file mode 100644
index 0000000000..2f25b7b54c
--- /dev/null
+++ b/thirdparty/mbedtls/patches/pr4819-faster-base64.patch
@@ -0,0 +1,341 @@
+diff --git a/library/base64.c b/library/base64.c
+index 692e11e3fae..b89313062b6 100644
+--- a/library/base64.c
++++ b/library/base64.c
+@@ -66,127 +66,38 @@
+ #endif /* MBEDTLS_PLATFORM_C */
+ #endif /* MBEDTLS_SELF_TEST */
+
+-static const unsigned char base64_enc_map[64] =
+-{
+- 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
+- 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
+- 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
+- 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
+- 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
+- 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7',
+- '8', '9', '+', '/'
+-};
+-
+-static const unsigned char base64_dec_map[128] =
+-{
+- 127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
+- 127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
+- 127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
+- 127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
+- 127, 127, 127, 62, 127, 127, 127, 63, 52, 53,
+- 54, 55, 56, 57, 58, 59, 60, 61, 127, 127,
+- 127, 64, 127, 127, 127, 0, 1, 2, 3, 4,
+- 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+- 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
+- 25, 127, 127, 127, 127, 127, 127, 26, 27, 28,
+- 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
+- 39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
+- 49, 50, 51, 127, 127, 127, 127, 127
+-};
+-
+ #define BASE64_SIZE_T_MAX ( (size_t) -1 ) /* SIZE_T_MAX is not standard */
+
+-/*
+- * Constant flow conditional assignment to unsigned char
+- */
+-static void mbedtls_base64_cond_assign_uchar( unsigned char * dest, const unsigned char * const src,
+- unsigned char condition )
+-{
+- /* MSVC has a warning about unary minus on unsigned integer types,
+- * but this is well-defined and precisely what we want to do here. */
+-#if defined(_MSC_VER)
+-#pragma warning( push )
+-#pragma warning( disable : 4146 )
+-#endif
+-
+- /* Generate bitmask from condition, mask will either be 0xFF or 0 */
+- unsigned char mask = ( condition | -condition );
+- mask >>= 7;
+- mask = -mask;
+-
+-#if defined(_MSC_VER)
+-#pragma warning( pop )
+-#endif
+-
+- *dest = ( ( *src ) & mask ) | ( ( *dest ) & ~mask );
+-}
+-
+-/*
+- * Constant flow conditional assignment to uint_32
+- */
+-static void mbedtls_base64_cond_assign_uint32( uint32_t * dest, const uint32_t src,
+- uint32_t condition )
+-{
+- /* MSVC has a warning about unary minus on unsigned integer types,
+- * but this is well-defined and precisely what we want to do here. */
+-#if defined(_MSC_VER)
+-#pragma warning( push )
+-#pragma warning( disable : 4146 )
+-#endif
+-
+- /* Generate bitmask from condition, mask will either be 0xFFFFFFFF or 0 */
+- uint32_t mask = ( condition | -condition );
+- mask >>= 31;
+- mask = -mask;
+-
+-#if defined(_MSC_VER)
+-#pragma warning( pop )
+-#endif
+-
+- *dest = ( src & mask ) | ( ( *dest ) & ~mask );
+-}
+-
+-/*
+- * Constant flow check for equality
++/* Return 0xff if low <= c <= high, 0 otherwise.
++ *
++ * Constant flow with respect to c.
+ */
+-static unsigned char mbedtls_base64_eq( size_t in_a, size_t in_b )
++static unsigned char mask_of_range( unsigned char low, unsigned char high,
++ unsigned char c )
+ {
+- size_t difference = in_a ^ in_b;
+-
+- /* MSVC has a warning about unary minus on unsigned integer types,
+- * but this is well-defined and precisely what we want to do here. */
+-#if defined(_MSC_VER)
+-#pragma warning( push )
+-#pragma warning( disable : 4146 )
+-#endif
+-
+- difference |= -difference;
+-
+-#if defined(_MSC_VER)
+-#pragma warning( pop )
+-#endif
+-
+- /* cope with the varying size of size_t per platform */
+- difference >>= ( sizeof( difference ) * 8 - 1 );
+-
+- return (unsigned char) ( 1 ^ difference );
++ /* low_mask is: 0 if low <= c, 0x...ff if low > c */
++ unsigned low_mask = ( (unsigned) c - low ) >> 8;
++ /* high_mask is: 0 if c <= high, 0x...ff if high > c */
++ unsigned high_mask = ( (unsigned) high - c ) >> 8;
++ return( ~( low_mask | high_mask ) & 0xff );
+ }
+
+-/*
+- * Constant flow lookup into table.
++/* Given a value in the range 0..63, return the corresponding Base64 digit.
++ * The implementation assumes that letters are consecutive (e.g. ASCII
++ * but not EBCDIC).
+ */
+-static unsigned char mbedtls_base64_table_lookup( const unsigned char * const table,
+- const size_t table_size, const size_t table_index )
++static unsigned char enc_char( unsigned char val )
+ {
+- size_t i;
+- unsigned char result = 0;
+-
+- for( i = 0; i < table_size; ++i )
+- {
+- mbedtls_base64_cond_assign_uchar( &result, &table[i], mbedtls_base64_eq( i, table_index ) );
+- }
+-
+- return result;
++ unsigned char digit = 0;
++ /* For each range of values, if val is in that range, mask digit with
++ * the corresponding value. Since val can only be in a single range,
++ * only at most one masking will change digit. */
++ digit |= mask_of_range( 0, 25, val ) & ( 'A' + val );
++ digit |= mask_of_range( 26, 51, val ) & ( 'a' + val - 26 );
++ digit |= mask_of_range( 52, 61, val ) & ( '0' + val - 52 );
++ digit |= mask_of_range( 62, 62, val ) & '+';
++ digit |= mask_of_range( 63, 63, val ) & '/';
++ return( digit );
+ }
+
+ /*
+@@ -229,17 +140,10 @@ int mbedtls_base64_encode( unsigned char *dst, size_t dlen, size_t *olen,
+ C2 = *src++;
+ C3 = *src++;
+
+- *p++ = mbedtls_base64_table_lookup( base64_enc_map, sizeof( base64_enc_map ),
+- ( ( C1 >> 2 ) & 0x3F ) );
+-
+- *p++ = mbedtls_base64_table_lookup( base64_enc_map, sizeof( base64_enc_map ),
+- ( ( ( ( C1 & 3 ) << 4 ) + ( C2 >> 4 ) ) & 0x3F ) );
+-
+- *p++ = mbedtls_base64_table_lookup( base64_enc_map, sizeof( base64_enc_map ),
+- ( ( ( ( C2 & 15 ) << 2 ) + ( C3 >> 6 ) ) & 0x3F ) );
+-
+- *p++ = mbedtls_base64_table_lookup( base64_enc_map, sizeof( base64_enc_map ),
+- ( C3 & 0x3F ) );
++ *p++ = enc_char( ( C1 >> 2 ) & 0x3F );
++ *p++ = enc_char( ( ( ( C1 & 3 ) << 4 ) + ( C2 >> 4 ) ) & 0x3F );
++ *p++ = enc_char( ( ( ( C2 & 15 ) << 2 ) + ( C3 >> 6 ) ) & 0x3F );
++ *p++ = enc_char( C3 & 0x3F );
+ }
+
+ if( i < slen )
+@@ -247,15 +151,11 @@ int mbedtls_base64_encode( unsigned char *dst, size_t dlen, size_t *olen,
+ C1 = *src++;
+ C2 = ( ( i + 1 ) < slen ) ? *src++ : 0;
+
+- *p++ = mbedtls_base64_table_lookup( base64_enc_map, sizeof( base64_enc_map ),
+- ( ( C1 >> 2 ) & 0x3F ) );
+-
+- *p++ = mbedtls_base64_table_lookup( base64_enc_map, sizeof( base64_enc_map ),
+- ( ( ( ( C1 & 3 ) << 4 ) + ( C2 >> 4 ) ) & 0x3F ) );
++ *p++ = enc_char( ( C1 >> 2 ) & 0x3F );
++ *p++ = enc_char( ( ( ( C1 & 3 ) << 4 ) + ( C2 >> 4 ) ) & 0x3F );
+
+ if( ( i + 1 ) < slen )
+- *p++ = mbedtls_base64_table_lookup( base64_enc_map, sizeof( base64_enc_map ),
+- ( ( ( C2 & 15 ) << 2 ) & 0x3F ) );
++ *p++ = enc_char( ( ( C2 & 15 ) << 2 ) & 0x3F );
+ else *p++ = '=';
+
+ *p++ = '=';
+@@ -267,26 +167,57 @@ int mbedtls_base64_encode( unsigned char *dst, size_t dlen, size_t *olen,
+ return( 0 );
+ }
+
++/* Given a Base64 digit, return its value.
++ * If c is not a Base64 digit ('A'..'Z', 'a'..'z', '0'..'9', '+' or '/'),
++ * return -1.
++ *
++ * The implementation assumes that letters are consecutive (e.g. ASCII
++ * but not EBCDIC).
++ *
++ * The implementation is constant-flow (no branch or memory access depending
++ * on the value of c) unless the compiler inlines and optimizes a specific
++ * access.
++ */
++static signed char dec_value( unsigned char c )
++{
++ unsigned char val = 0;
++ /* For each range of digits, if c is in that range, mask val with
++ * the corresponding value. Since c can only be in a single range,
++ * only at most one masking will change val. Set val to one plus
++ * the desired value so that it stays 0 if c is in none of the ranges. */
++ val |= mask_of_range( 'A', 'Z', c ) & ( c - 'A' + 0 + 1 );
++ val |= mask_of_range( 'a', 'z', c ) & ( c - 'a' + 26 + 1 );
++ val |= mask_of_range( '0', '9', c ) & ( c - '0' + 52 + 1 );
++ val |= mask_of_range( '+', '+', c ) & ( c - '+' + 62 + 1 );
++ val |= mask_of_range( '/', '/', c ) & ( c - '/' + 63 + 1 );
++ /* At this point, val is 0 if c is an invalid digit and v+1 if c is
++ * a digit with the value v. */
++ return( val - 1 );
++}
++
+ /*
+ * Decode a base64-formatted buffer
+ */
+ int mbedtls_base64_decode( unsigned char *dst, size_t dlen, size_t *olen,
+ const unsigned char *src, size_t slen )
+ {
+- size_t i, n;
+- uint32_t j, x;
++ size_t i; /* index in source */
++ size_t n; /* number of digits or trailing = in source */
++ uint32_t x; /* value accumulator */
++ unsigned accumulated_digits = 0;
++ unsigned equals = 0;
++ int spaces_present = 0;
+ unsigned char *p;
+- unsigned char dec_map_lookup;
+
+ /* First pass: check for validity and get output length */
+- for( i = n = j = 0; i < slen; i++ )
++ for( i = n = 0; i < slen; i++ )
+ {
+ /* Skip spaces before checking for EOL */
+- x = 0;
++ spaces_present = 0;
+ while( i < slen && src[i] == ' ' )
+ {
+ ++i;
+- ++x;
++ spaces_present = 1;
+ }
+
+ /* Spaces at end of buffer are OK */
+@@ -301,20 +232,24 @@ int mbedtls_base64_decode( unsigned char *dst, size_t dlen, size_t *olen,
+ continue;
+
+ /* Space inside a line is an error */
+- if( x != 0 )
++ if( spaces_present )
+ return( MBEDTLS_ERR_BASE64_INVALID_CHARACTER );
+
+- if( src[i] == '=' && ++j > 2 )
+- return( MBEDTLS_ERR_BASE64_INVALID_CHARACTER );
+-
+- dec_map_lookup = mbedtls_base64_table_lookup( base64_dec_map, sizeof( base64_dec_map ), src[i] );
+-
+- if( src[i] > 127 || dec_map_lookup == 127 )
+- return( MBEDTLS_ERR_BASE64_INVALID_CHARACTER );
+-
+- if( dec_map_lookup < 64 && j != 0 )
++ if( src[i] > 127 )
+ return( MBEDTLS_ERR_BASE64_INVALID_CHARACTER );
+
++ if( src[i] == '=' )
++ {
++ if( ++equals > 2 )
++ return( MBEDTLS_ERR_BASE64_INVALID_CHARACTER );
++ }
++ else
++ {
++ if( equals != 0 )
++ return( MBEDTLS_ERR_BASE64_INVALID_CHARACTER );
++ if( dec_value( src[i] ) < 0 )
++ return( MBEDTLS_ERR_BASE64_INVALID_CHARACTER );
++ }
+ n++;
+ }
+
+@@ -329,7 +264,7 @@ int mbedtls_base64_decode( unsigned char *dst, size_t dlen, size_t *olen,
+ * n = ( ( n * 6 ) + 7 ) >> 3;
+ */
+ n = ( 6 * ( n >> 3 ) ) + ( ( 6 * ( n & 0x7 ) + 7 ) >> 3 );
+- n -= j;
++ n -= equals;
+
+ if( dst == NULL || dlen < n )
+ {
+@@ -337,22 +272,24 @@ int mbedtls_base64_decode( unsigned char *dst, size_t dlen, size_t *olen,
+ return( MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL );
+ }
+
+- for( j = 3, n = x = 0, p = dst; i > 0; i--, src++ )
+- {
++ equals = 0;
++ for( x = 0, p = dst; i > 0; i--, src++ )
++ {
+ if( *src == '\r' || *src == '\n' || *src == ' ' )
+ continue;
+
+- dec_map_lookup = mbedtls_base64_table_lookup( base64_dec_map, sizeof( base64_dec_map ), *src );
+-
+- mbedtls_base64_cond_assign_uint32( &j, j - 1, mbedtls_base64_eq( dec_map_lookup, 64 ) );
+- x = ( x << 6 ) | ( dec_map_lookup & 0x3F );
++ x = x << 6;
++ if( *src == '=' )
++ ++equals;
++ else
++ x |= dec_value( *src );
+
+- if( ++n == 4 )
++ if( ++accumulated_digits == 4 )
+ {
+- n = 0;
+- if( j > 0 ) *p++ = (unsigned char)( x >> 16 );
+- if( j > 1 ) *p++ = (unsigned char)( x >> 8 );
+- if( j > 2 ) *p++ = (unsigned char)( x );
++ accumulated_digits = 0;
++ *p++ = (unsigned char)( x >> 16 );
++ if( equals <= 1 ) *p++ = (unsigned char)( x >> 8 );
++ if( equals <= 0 ) *p++ = (unsigned char)( x );
+ }
+ }
+