Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/terrain_3d_collision.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ Dictionary Terrain3DCollision::_get_shape_data(const Vector2i &p_position, const

real_t blend_factor = 2.0f + 126.0f * (1.0f - region_blend);
Vector2 f = Vector2(uv2.x - Math::floor(uv2.x), uv2.y - Math::floor(uv2.y));
f.x = Math::clamp(f.x, 1e-8f, 1.f - 1e-8f);
f.y = Math::clamp(f.y, 1e-8f, 1.f - 1e-8f);
f.x = Math::clamp(f.x, real_t(1e-8), real_t(1.0 - 1e-8));
f.y = Math::clamp(f.y, real_t(1e-8), real_t(1.0 - 1e-8));
Copy link
Copy Markdown
Owner

@TokisanGames TokisanGames Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1e-8f is a float literal that can be converted to a double for free.
1e-8 is a double that has a potential loss of precision and a cost to convert to float.
All single and double float literals should have f.
If a Godot macro or template complains about mixing types during a double precision build, then that literal can be wrapped in real_t() but still needs the f.

Vector2 w = Vector2(1.f / (1.f + Math::exp(blend_factor * Math::log((1.f - f.x) / f.x))),
1.f / (1.f + Math::exp(blend_factor * Math::log((1.f - f.y) / f.y))));
real_t blend = Math::lerp(Math::lerp(d, c, w.x), Math::lerp(a, b, w.x), w.y);
Expand Down
14 changes: 12 additions & 2 deletions src/terrain_3d_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <godot_cpp/classes/node.hpp>
#include <godot_cpp/classes/resource_loader.hpp>
#include <godot_cpp/variant/utility_functions.hpp>
#include <type_traits>

#include "constants.h"
#include "generated_texture.h"
Expand Down Expand Up @@ -231,46 +232,54 @@ inline uint32_t as_uint(const float p_value) { return *(uint32_t *)&p_value; }

inline uint8_t get_base(const uint32_t p_pixel) { return p_pixel >> 27 & 0x1F; }
inline uint8_t get_base(const float p_pixel) { return get_base(as_uint(p_pixel)); }
inline uint8_t get_base(const double p_pixel) { return get_base(static_cast<float>(p_pixel)); }
Copy link
Copy Markdown
Owner

@TokisanGames TokisanGames Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When would a pixel be a double?
Image.get_pixel() returns a Color
Color is exactly 4x 32-bit floats, not real_t, never doubles.
https://github.com/godotengine/godot/blob/master/core/math/color.h#L38-L49

Our control map data format is FORMAT_RF: exactly 1x 32-bit float per pixel, never a double. Those 32-bits are only a container for int data, not floats. It should never be converted or treated as a float. This function assumes the source integer data was converted from an invalid float to an invalid double, then converts it back to an invalid float in order to retrieve the integer data out of it. I would be amazed if that worked without data loss, but regardless this is not safe.

If you have a compiling typing error because of this, let's look at that separately, but this is not the solution.

inline uint32_t enc_base(const uint8_t p_base) { return (p_base & 0x1F) << 27; }
inline uint32_t gd_get_base(const uint32_t p_pixel) { return get_base(p_pixel); }
inline uint32_t gd_enc_base(const uint32_t p_base) { return enc_base(p_base); }

inline uint8_t get_overlay(const uint32_t p_pixel) { return p_pixel >> 22 & 0x1F; }
inline uint8_t get_overlay(const float p_pixel) { return get_overlay(as_uint(p_pixel)); }
inline uint8_t get_overlay(const double p_pixel) { return get_overlay(static_cast<float>(p_pixel)); }
inline uint32_t enc_overlay(const uint8_t p_over) { return (p_over & 0x1F) << 22; }
inline uint32_t gd_get_overlay(const uint32_t p_pixel) { return get_overlay(p_pixel); }
inline uint32_t gd_enc_overlay(const uint32_t p_over) { return enc_overlay(p_over); }

inline uint8_t get_blend(const uint32_t p_pixel) { return p_pixel >> 14 & 0xFF; }
inline uint8_t get_blend(const float p_pixel) { return get_blend(as_uint(p_pixel)); }
inline uint8_t get_blend(const double p_pixel) { return get_blend(static_cast<float>(p_pixel)); }
inline uint32_t enc_blend(const uint8_t p_blend) { return (p_blend & 0xFF) << 14; }
inline uint32_t gd_get_blend(const uint32_t p_pixel) { return get_blend(p_pixel); }
inline uint32_t gd_enc_blend(const uint32_t p_blend) { return enc_blend(p_blend); }

inline uint8_t get_uv_rotation(const uint32_t p_pixel) { return p_pixel >> 10 & 0xF; }
inline uint8_t get_uv_rotation(const float p_pixel) { return get_uv_rotation(as_uint(p_pixel)); }
inline uint8_t get_uv_rotation(const double p_pixel) { return get_uv_rotation(static_cast<float>(p_pixel)); }
inline uint32_t enc_uv_rotation(const uint8_t p_rotation) { return (p_rotation & 0xF) << 10; }
inline uint32_t gd_get_uv_rotation(const uint32_t p_pixel) { return get_uv_rotation(p_pixel); }
inline uint32_t gd_enc_uv_rotation(const uint32_t p_rotation) { return enc_uv_rotation(p_rotation); }

inline uint8_t get_uv_scale(const uint32_t p_pixel) { return p_pixel >> 7 & 0x7; }
inline uint8_t get_uv_scale(const float p_pixel) { return get_uv_scale(as_uint(p_pixel)); }
inline uint8_t get_uv_scale(const double p_pixel) { return get_uv_scale(static_cast<float>(p_pixel)); }
inline uint32_t enc_uv_scale(const uint8_t p_scale) { return (p_scale & 0x7) << 7; }
inline uint32_t gd_get_uv_scale(const uint32_t p_pixel) { return get_uv_scale(p_pixel); }
inline uint32_t gd_enc_uv_scale(const uint32_t p_scale) { return enc_uv_scale(p_scale); }

inline bool is_hole(const uint32_t p_pixel) { return (p_pixel >> 2 & 0x1) == 1; }
inline bool is_hole(const float p_pixel) { return is_hole(as_uint(p_pixel)); }
inline bool is_hole(const double p_pixel) { return is_hole(static_cast<float>(p_pixel)); }
inline uint32_t enc_hole(const bool p_hole) { return (p_hole & 0x1) << 2; }
inline bool gd_is_hole(const uint32_t p_pixel) { return is_hole(p_pixel); }

inline bool is_nav(const uint32_t p_pixel) { return (p_pixel >> 1 & 0x1) == 1; }
inline bool is_nav(const float p_pixel) { return is_nav(as_uint(p_pixel)); }
inline bool is_nav(const double p_pixel) { return is_nav(static_cast<float>(p_pixel)); }
inline uint32_t enc_nav(const bool p_nav) { return (p_nav & 0x1) << 1; }
inline bool gd_is_nav(const uint32_t p_pixel) { return is_nav(p_pixel); }

inline bool is_auto(const uint32_t p_pixel) { return (p_pixel & 0x1) == 1; }
inline bool is_auto(const float p_pixel) { return is_auto(as_uint(p_pixel)); }
inline bool is_auto(const double p_pixel) { return is_auto(static_cast<float>(p_pixel)); }
inline uint32_t enc_auto(const bool p_auto) { return p_auto & 0x1; }
inline bool gd_is_auto(const uint32_t p_pixel) { return is_auto(p_pixel); }

Expand Down Expand Up @@ -336,9 +345,10 @@ _FORCE_INLINE_ bool differs(const T &a, const T &b) {
}

// Sets A if different from B, otherwise returns
// Cast b to a's type for precision=double builds.
#define SET_IF_DIFF(a, b) \
if (differs(a, b)) { \
a = b; \
if (differs(a, static_cast<std::remove_reference_t<decltype(a)>>(b))) { \
a = static_cast<std::remove_reference_t<decltype(a)>>(b); \
Copy link
Copy Markdown
Owner

@TokisanGames TokisanGames Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change allows us to compare mixed types. When was that occurring? In the repo code we only use this macro on setters, where the incoming parameter and the member variable already have the exact same type. So what prompted you to make this change?

Double precision builds were working before, but occasionally we add code that mistakenly breaks it. Usually that just means wrapping something in real_t. If you ran into compiler errors that prompted you to do this, I'd like to know what they are.

As explained above sometimes a float isn't actually used as a float and shouldn't be converted. If we have an explicit float compared against a double, or an int compared with a float, this will now pass it when it should have broken the compile so we could fix it.

} else { \
return; \
}
Expand Down
Loading