Browse Source

Avoid jittering behavior of elements when using subpixel offsets, by no longer rounding dimensions in the layout engine. Translation and vertices that need sharpness (fonts and images) are rounded as late in the pipeline as possible.
The drawback of this approach is that subelements don't necessarily move on the pixel grid when a parent moves, but this is much less jarring than animation jittering.
Possible future change is to have a bool that tells whether to pixel-align offsets. Could be user-controlled, and/or turned off during animations etc.
To change this behavior, see calls to Geometry::Render and GeometryUtilities::GenerateQuad in particular.

Michael 7 years ago
parent
commit
51efaa7569

+ 7 - 3
Include/Rocket/Core/Math.h

@@ -127,18 +127,22 @@ ROCKETCORE_API float NormaliseAngle(float angle);
 /// @return The square root of the value.
 ROCKETCORE_API float SquareRoot(float value);
 
+/// Rounds a floating-point value to the nearest integer.
+/// @param[in] value The value to round.
+/// @return The rounded integer as float.
+ROCKETCORE_API float RoundFloat(float value);
 /// Rounds a floating-point value to the nearest integer.
 /// @param[in] value The value to round.
 /// @return The rounded integer.
-ROCKETCORE_API int Round(float value);
+ROCKETCORE_API int RoundToInteger(float value);
 /// Rounds a floating-point value up to the nearest integer.
 /// @param[in] value The value to round.
 /// @return The rounded integer.
-ROCKETCORE_API int RoundUp(float value);
+ROCKETCORE_API int RoundUpToInteger(float value);
 /// Rounds a floating-point value down to the nearest integer.
 /// @param[in] value The value to round.
 /// @return The rounded integer.
-ROCKETCORE_API int RoundDown(float value);
+ROCKETCORE_API int RoundDownToInteger(float value);
 
 /// Efficiently truncates a floating-point value into an integer.
 /// @param[in] value The value to truncate.

+ 3 - 0
Include/Rocket/Core/Vector2.h

@@ -59,6 +59,9 @@ class Vector2
 		/// Generates a normalised vector from this vector.
 		/// @return The normalised vector.
 		inline Vector2 Normalise() const;
+		/// Generates a vector with values rounded to their nearest integer.
+		/// @return The rounded vector
+		inline Vector2 Round() const;
 
 		/// Computes the dot-product between this vector and another.
 		/// @param[in] rhs The other vector to use in the dot-product.

+ 15 - 0
Include/Rocket/Core/Vector2.inl

@@ -69,6 +69,21 @@ Vector2< Type > Vector2< Type >::Normalise() const
 	return *this / magnitude;
 }
 
+// Generates a rounded vector from this vector.
+Vector2< float > Vector2< float >::Round() const
+{
+	Vector2 < float > result;
+	result.x = std::roundf(x);
+	result.y = std::roundf(y);
+	return result;
+}
+
+// Generates a rounded vector from this vector.
+Vector2< int > Vector2< int >::Round() const
+{
+	return *this;
+}
+
 // Computes the dot-product between this vector and another.
 template < typename Type >
 Type Vector2< Type >::DotProduct(const Vector2< Type >& rhs) const

+ 1 - 0
Samples/basic/animation/data/animation.rml

@@ -39,5 +39,6 @@
 	<button id="options">Options</button><br />
 	<button id="help">Help</button><br />
 	<button id="exit" onclick="exit">Exit</button>
+	<div style="position: absolute; right: 15.4px; bottom: 20.5px; height: 30px; width: 20px; border: 3px #000; background-color: #c66;">A</div>
 </body>
 </rml>

+ 28 - 5
Samples/basic/animation/src/main.cpp

@@ -37,7 +37,6 @@
 
 
 // Animations TODO:
-//  - Jittering on slow margin-left updates
 //  - Proper interpolation of full transform matrices (split into translate/rotate/skew/scale).
 //  - Support interpolation of primitive derivatives without going to full matrices.
 //  - Better error reporting when submitting invalid animations, check validity on add. Remove animation if invalid.
@@ -45,6 +44,7 @@
 //  - RCSS support? Both @keyframes and transition, maybe.
 //  - Profiling
 //  - [offtopic] Improve performance of transform parser (hashtable)
+//  - [offtopic] Use double for absolute time, get and cache time for each render/update loop
 
 class DemoWindow
 {
@@ -64,8 +64,8 @@ public:
 			el->Animate("margin-left", Property(0.f, Property::PX), 0.3f, 10, true);
 			el->Animate("margin-left", Property(100.f, Property::PX), 0.6f);
 
-			el = document->GetElementById("exit");
-			el->Animate("margin-left", Property(100.f, Property::PX), 8.0f, -1, true);
+			//el = document->GetElementById("exit");
+			//el->Animate("margin-left", Property(100.f, Property::PX), 8.0f, -1, true);
 
 			el = document->GetElementById("start_game");
 			auto t0 = TransformRef{ new Transform };
@@ -115,6 +115,7 @@ DemoWindow* window = NULL;
 
 bool pause_loop = false;
 bool single_loop = false;
+int nudge = 0;
 
 void GameLoop()
 {
@@ -129,8 +130,22 @@ void GameLoop()
 		single_loop = false;
 	}
 
-	//auto el = window->GetDocument()->GetElementById("exit");
-	//auto f = el->GetProperty<int>("margin-left");
+	static float t_prev = 0.0f;
+	float t = Shell::GetElapsedTime();
+	float dt = t - t_prev;
+	//if(dt > 1.0f)
+	if(nudge)
+	{
+		t_prev = t;
+		static float ff = 0.0f;
+		ff += float(nudge)*0.3f;
+		auto el = window->GetDocument()->GetElementById("exit");
+		auto f = el->GetProperty<float>("margin-left");
+		el->SetProperty("margin-left", Rocket::Core::Property(ff, Rocket::Core::Property::PX));
+		float f_left = el->GetAbsoluteLeft();
+		Rocket::Core::Log::Message(Rocket::Core::Log::LT_INFO, "margin-left: '%f'   abs: %f.", ff, f_left);
+		nudge = 0;
+	}
 	//static int f_prev = 0.0f;
 	//int df = f - f_prev;
 	//f_prev = f;
@@ -165,6 +180,14 @@ public:
 				pause_loop = true;
 				single_loop = true;
 			}
+			else if (key_identifier == Rocket::Core::Input::KI_OEM_PLUS && key_down)
+			{
+				nudge = 1;
+			}
+			else if (key_identifier == Rocket::Core::Input::KI_OEM_MINUS && key_down)
+			{
+				nudge = -1;
+			}
 			else if (key_identifier == Rocket::Core::Input::KI_ESCAPE)
 			{
 				Shell::RequestExit();

+ 2 - 2
Source/Controls/WidgetSliderInput.cpp

@@ -47,7 +47,7 @@ WidgetSliderInput::~WidgetSliderInput()
 void WidgetSliderInput::SetValue(float value)
 {
 	float num_steps = (value - min_value) / step;
-	float new_value = min_value + Rocket::Core::Math::Round(num_steps) * step;
+	float new_value = min_value + Rocket::Core::Math::RoundFloat(num_steps) * step;
 
 	SetBarPosition(SetValueInternal(new_value));
 }
@@ -90,7 +90,7 @@ void WidgetSliderInput::FormatElements()
 float WidgetSliderInput::OnBarChange(float bar_position)
 {
 	float new_value = min_value + bar_position * (max_value - min_value);
-	int num_steps = Rocket::Core::Math::Round((new_value - value) / step);
+	int num_steps = Rocket::Core::Math::RoundToInteger((new_value - value) / step);
 
 	return SetValueInternal(value + num_steps * step);
 }

+ 3 - 0
Source/Core/DecoratorTiled.cpp

@@ -252,6 +252,9 @@ void DecoratorTiled::Tile::GenerateGeometry(std::vector< Vertex >& vertices, std
 			tile_position.x = surface_origin.x + (float) tile_dimensions.x * x;
 			tile_size.x = (float) (x < num_tiles[0] - 1 ? tile_dimensions.x : final_tile_dimensions.x);
 
+			tile_position = tile_position.Round();
+			tile_size = tile_size.Round();
+
 			GeometryUtilities::GenerateQuad(new_vertices, new_indices, tile_position, tile_size, quad_colour, tile_texcoords[0], tile_texcoords[1], index_offset);
 			new_vertices += 4;
 			new_indices += 6;

+ 1 - 1
Source/Core/DecoratorTiledBox.cpp

@@ -290,7 +290,7 @@ void DecoratorTiledBox::ReleaseElementData(DecoratorDataHandle element_data)
 // Called to render the decorator on an element.
 void DecoratorTiledBox::RenderElement(Element* element, DecoratorDataHandle element_data)
 {
-	Vector2f translation = element->GetAbsoluteOffset(Box::PADDING);
+	Vector2f translation = element->GetAbsoluteOffset(Box::PADDING).Round();
 	DecoratorTiledBoxData* data = reinterpret_cast< DecoratorTiledBoxData* >(element_data);
 
 	for (int i = 0; i < 9; i++)

+ 1 - 1
Source/Core/DecoratorTiledHorizontal.cpp

@@ -149,7 +149,7 @@ void DecoratorTiledHorizontal::ReleaseElementData(DecoratorDataHandle element_da
 // Called to render the decorator on an element.
 void DecoratorTiledHorizontal::RenderElement(Element* element, DecoratorDataHandle element_data)
 {
-	Vector2f translation = element->GetAbsoluteOffset(Box::PADDING);
+	Vector2f translation = element->GetAbsoluteOffset(Box::PADDING).Round();
 	DecoratorTiledHorizontalData* data = reinterpret_cast< DecoratorTiledHorizontalData* >(element_data);
 
 	for (int i = 0; i < 3; i++)

+ 1 - 1
Source/Core/DecoratorTiledImage.cpp

@@ -79,7 +79,7 @@ void DecoratorTiledImage::ReleaseElementData(DecoratorDataHandle element_data)
 void DecoratorTiledImage::RenderElement(Element* element, DecoratorDataHandle element_data)
 {
 	Geometry* data = reinterpret_cast< Geometry* >(element_data);
-	data->Render(element->GetAbsoluteOffset(Box::PADDING));
+	data->Render(element->GetAbsoluteOffset(Box::PADDING).Round());
 }
 
 }

+ 1 - 1
Source/Core/DecoratorTiledVertical.cpp

@@ -154,7 +154,7 @@ void DecoratorTiledVertical::RenderElement(Element* element, DecoratorDataHandle
 	DecoratorTiledVerticalData* data = reinterpret_cast< DecoratorTiledVerticalData* >(element_data);
 
 	for (int i = 0; i < 3; i++)
-		data->geometry[i]->Render(translation);
+		data->geometry[i]->Render(translation.Round());
 }
 
 }

+ 2 - 5
Source/Core/Element.cpp

@@ -1142,7 +1142,7 @@ float Element::GetScrollLeft()
 // Sets the left scroll offset of the element.
 void Element::SetScrollLeft(float scroll_left)
 {
-	scroll_offset.x = LayoutEngine::Round(Math::Clamp(scroll_left, 0.0f, GetScrollWidth() - GetClientWidth()));
+	scroll_offset.x = Math::Clamp(scroll_left, 0.0f, GetScrollWidth() - GetClientWidth());
 	scroll->UpdateScrollbar(ElementScroll::HORIZONTAL);
 	DirtyOffset();
 
@@ -1159,7 +1159,7 @@ float Element::GetScrollTop()
 // Sets the top scroll offset of the element.
 void Element::SetScrollTop(float scroll_top)
 {
-	scroll_offset.y = LayoutEngine::Round(Math::Clamp(scroll_top, 0.0f, GetScrollHeight() - GetClientHeight()));
+	scroll_offset.y = Math::Clamp(scroll_top, 0.0f, GetScrollHeight() - GetClientHeight());
 	scroll->UpdateScrollbar(ElementScroll::VERTICAL);
 	DirtyOffset();
 
@@ -2225,9 +2225,6 @@ void Element::UpdateOffset()
 		relative_offset_position.x = 0;
 		relative_offset_position.y = 0;
 	}
-
-	LayoutEngine::Round(relative_offset_base);
-	LayoutEngine::Round(relative_offset_position);
 }
 
 void Element::BuildLocalStackingContext()

+ 4 - 10
Source/Core/ElementImage.cpp

@@ -84,7 +84,7 @@ void ElementImage::OnRender()
 		GenerateGeometry();
 
 	// Render the geometry beginning at this element's content region.
-	geometry.Render(GetAbsoluteOffset(Rocket::Core::Box::CONTENT));
+	geometry.Render(GetAbsoluteOffset(Rocket::Core::Box::CONTENT).Round());
 }
 
 // Called when attributes on the element are changed.
@@ -213,17 +213,11 @@ void ElementImage::GenerateGeometry()
 
     float opacity = GetProperty<float>(OPACITY);
 	Colourb quad_colour = GetProperty<Colourb>(IMAGE_COLOR);
-
-    // Apply opacity
     quad_colour.alpha = (byte)(opacity * (float)quad_colour.alpha);
+	
+	Vector2f quad_size = GetBox().GetSize(Rocket::Core::Box::CONTENT).Round();
 
-	Rocket::Core::GeometryUtilities::GenerateQuad(&vertices[0],									// vertices to write to
-												  &indices[0],									// indices to write to
-												  Vector2f(0, 0),					            // origin of the quad
-												  GetBox().GetSize(Rocket::Core::Box::CONTENT),	// size of the quad
-												  quad_colour,		                            // colour of the vertices
-												  texcoords[0],									// top-left texture coordinate
-												  texcoords[1]);								// top-right texture coordinate
+	Rocket::Core::GeometryUtilities::GenerateQuad(&vertices[0], &indices[0], Vector2f(0, 0), quad_size, quad_colour,  texcoords[0], texcoords[1]);
 
 	geometry_dirty = false;
 }

+ 2 - 0
Source/Core/ElementTextDefault.cpp

@@ -128,6 +128,8 @@ void ElementTextDefault::OnRender()
 			}
 		}
 	}
+
+	translation = translation.Round();
 	
 	if (render)
 	{

+ 9 - 9
Source/Core/ElementUtilities.cpp

@@ -173,27 +173,27 @@ int ElementUtilities::GetLineHeight(Element* element)
 	case Property::EM:
 	case Property::REM:
 		// If the property is a straight number or an em measurement, then it scales the line height.
-		return Math::Round(line_height_property->value.Get< float >() * line_height);
+		return Math::RoundToInteger(line_height_property->value.Get< float >() * line_height);
 	case Property::PERCENT:
 		// If the property is a percentage, then it scales the line height.
-		return Math::Round(line_height_property->value.Get< float >() * line_height * 0.01f);
+		return Math::RoundToInteger(line_height_property->value.Get< float >() * line_height * 0.01f);
 	case Property::PX:
 		// A px measurement.
-		return Math::Round(line_height_property->value.Get< float >());
+		return Math::RoundToInteger(line_height_property->value.Get< float >());
 	case Property::DP:
 		// A density-independent pixel measurement.
-		return Math::Round(line_height_property->value.Get< float >() * ElementUtilities::GetDensityIndependentPixelRatio(element));
+		return Math::RoundToInteger(line_height_property->value.Get< float >() * ElementUtilities::GetDensityIndependentPixelRatio(element));
 	case Property::INCH:
 		// Values based on pixels-per-inch.
-		return Math::Round(line_height_property->value.Get< float >() * inch);
+		return Math::RoundToInteger(line_height_property->value.Get< float >() * inch);
 	case Property::CM:
-		return Math::Round(line_height_property->value.Get< float >() * inch * (1.0f / 2.54f));
+		return Math::RoundToInteger(line_height_property->value.Get< float >() * inch * (1.0f / 2.54f));
 	case Property::MM:
-		return Math::Round(line_height_property->value.Get< float >() * inch * (1.0f / 25.4f));
+		return Math::RoundToInteger(line_height_property->value.Get< float >() * inch * (1.0f / 25.4f));
 	case Property::PT:
-		return Math::Round(line_height_property->value.Get< float >() * inch * (1.0f / 72.0f));
+		return Math::RoundToInteger(line_height_property->value.Get< float >() * inch * (1.0f / 72.0f));
 	case Property::PC:
-		return Math::Round(line_height_property->value.Get< float >() * inch * (1.0f / 6.0f));
+		return Math::RoundToInteger(line_height_property->value.Get< float >() * inch * (1.0f / 6.0f));
 	}
 
 	return 0;

+ 1 - 1
Source/Core/FontFaceHandle.cpp

@@ -286,7 +286,7 @@ void FontFaceHandle::GenerateLine(Geometry* geometry, const Vector2f& position,
 
 	line_vertices.resize(line_vertices.size() + 4);
 	line_indices.resize(line_indices.size() + 6);
-	GeometryUtilities::GenerateQuad(&line_vertices[0] + (line_vertices.size() - 4), &line_indices[0] + (line_indices.size() - 6), Vector2f(position.x, position.y + offset), Vector2f((float) width, underline_thickness), colour, (int)line_vertices.size() - 4);
+	GeometryUtilities::GenerateQuad(&line_vertices[0] + (line_vertices.size() - 4), &line_indices[0] + (line_indices.size() - 6), Vector2f(position.x, position.y + offset).Round(), Vector2f((float) width, underline_thickness), colour, (int)line_vertices.size() - 4);
 }
 
 // Returns the font face's raw charset (the charset range as a string).

+ 10 - 1
Source/Core/FontFaceLayer.h

@@ -88,7 +88,16 @@ public:
 
 		character_vertices.resize(character_vertices.size() + 4);
 		character_indices.resize(character_indices.size() + 6);
-		GeometryUtilities::GenerateQuad(&character_vertices[0] + (character_vertices.size() - 4), &character_indices[0] + (character_indices.size() - 6), Vector2f(position.x + character.origin.x, position.y + character.origin.y), character.dimensions, colour, character.texcoords[0], character.texcoords[1], (int)character_vertices.size() - 4);
+		GeometryUtilities::GenerateQuad(
+			&character_vertices[0] + (character_vertices.size() - 4),
+			&character_indices[0] + (character_indices.size() - 6),
+			Vector2f(position.x + character.origin.x, position.y + character.origin.y).Round(),
+			character.dimensions,
+			colour, 
+			character.texcoords[0],
+			character.texcoords[1],
+			(int)character_vertices.size() - 4
+		);
 	}
 
 	/// Returns the effect used to generate the layer.

+ 1 - 1
Source/Core/FreeType/FontFaceHandle.cpp

@@ -281,7 +281,7 @@ void FontFaceHandle::GenerateLine(Geometry* geometry, const Vector2f& position,
 
 	line_vertices.resize(line_vertices.size() + 4);
 	line_indices.resize(line_indices.size() + 6);
-	GeometryUtilities::GenerateQuad(&line_vertices[0] + (line_vertices.size() - 4), &line_indices[0] + (line_indices.size() - 6), Vector2f(position.x, position.y + offset), Vector2f((float) width, underline_thickness), colour, (int)line_vertices.size() - 4);
+	GeometryUtilities::GenerateQuad(&line_vertices[0] + (line_vertices.size() - 4), &line_indices[0] + (line_indices.size() - 6), Vector2f(position.x, position.y + offset).Round(), Vector2f((float) width, underline_thickness), colour, (int)line_vertices.size() - 4);
 }
 
 // Destroys the handle.

+ 0 - 2
Source/Core/LayoutBlockBox.cpp

@@ -511,8 +511,6 @@ void LayoutBlockBox::PositionBlockBox(Vector2f& box_position, const Box& box, in
 	PositionBox(box_position, box.GetEdge(Box::MARGIN, Box::TOP), clear_property);
 	box_position.x += box.GetEdge(Box::MARGIN, Box::LEFT);
 	box_position.y += box.GetEdge(Box::MARGIN, Box::TOP);
-
-	LayoutEngine::Round(box_position);
 }
 
 // Returns the offset from the top-left corner of this box for the next line.

+ 0 - 20
Source/Core/LayoutEngine.cpp

@@ -294,26 +294,6 @@ float LayoutEngine::ClampHeight(float height, Element* element, float containing
 	return Math::Clamp(height, min_height, max_height);
 }
 
-// Rounds a vector of two floating-point values to integral values.
-Vector2f& LayoutEngine::Round(Vector2f& value)
-{
-	value.x = Round(value.x);
-	value.y = Round(value.y);
-
-	return value;
-}
-
-// Rounds a floating-point value to an integral value.
-float LayoutEngine::Round(float value)
-{
-#if defined(_MSC_VER) && _MSC_VER < 1800
-	// Before Visual Studio 2013, roundf did not exist
-	return value >= 0.0f ? floorf(value + 0.5f) : ceilf(value - 0.5f);
-#else
-	return roundf(value);
-#endif
-}
-
 void* LayoutEngine::AllocateLayoutChunk(size_t ROCKET_UNUSED_ASSERT_PARAMETER(size))
 {
 	ROCKET_UNUSED_ASSERT(size);

+ 0 - 9
Source/Core/LayoutEngine.h

@@ -79,15 +79,6 @@ public:
 	/// @return The clamped height.
 	static float ClampHeight(float height, Element* element, float containing_block_height);
 
-	/// Rounds a vector of two floating-point values to integral values.
-	/// @param[inout] value The vector to round.
-	/// @return The rounded vector.
-	static Vector2f& Round(Vector2f& value);
-	/// Rounds a floating-point value to an integral value.
-	/// @param[in] value The value to round.
-	/// @return The rounded value.
-	static float Round(float value);
-
 	static void* AllocateLayoutChunk(size_t size);
 	static void DeallocateLayoutChunk(void* chunk);
 

+ 2 - 2
Source/Core/LayoutInlineBox.cpp

@@ -198,7 +198,7 @@ void LayoutInlineBox::CalculateBaseline(float& ascender, float& descender)
 			if (parent_font == NULL)
 				SetVerticalPosition(0);
 			else
-				SetVerticalPosition(LayoutEngine::Round(parent_font->GetLineHeight() * 0.2f));
+				SetVerticalPosition(float(parent_font->GetLineHeight()) * 0.2f);
 		}
 		break;
 
@@ -209,7 +209,7 @@ void LayoutInlineBox::CalculateBaseline(float& ascender, float& descender)
 			if (parent_font == NULL)
 				SetVerticalPosition(0);
 			else
-				SetVerticalPosition(-1 * LayoutEngine::Round(parent_font->GetLineHeight() * 0.4f));
+				SetVerticalPosition(float(-1 * parent_font->GetLineHeight()) * 0.4f);
 		}
 		break;
 

+ 0 - 1
Source/Core/LayoutInlineBoxText.cpp

@@ -102,7 +102,6 @@ void LayoutInlineBoxText::OffsetBaseline(float ascender)
 
 	// Offset by the half-leading.
 	position.y += leading * 0.5f;
-	position.y = LayoutEngine::Round(position.y);
 }
 
 // Positions the inline box's element.

+ 1 - 3
Source/Core/LayoutLineBox.cpp

@@ -153,8 +153,6 @@ LayoutInlineBox* LayoutLineBox::Close(LayoutInlineBox* overflow)
 
 		if (element_offset != 0)
 		{
-			element_offset = LayoutEngine::Round(element_offset);
-
 			for (size_t i = 0; i < inline_boxes.size(); i++)
 				inline_boxes[i]->SetHorizontalPosition(inline_boxes[i]->GetPosition().x + element_offset);
 		}
@@ -380,7 +378,7 @@ void LayoutLineBox::AppendBox(LayoutInlineBox* box)
 
 	box->SetParent(open_inline_box);
 	box->SetLine(this);
-	box->SetHorizontalPosition(LayoutEngine::Round(box_cursor + box->GetBox().GetEdge(Box::MARGIN, Box::LEFT)));
+	box->SetHorizontalPosition(box_cursor + box->GetBox().GetEdge(Box::MARGIN, Box::LEFT));
 	box_cursor += GetSpacing(box->GetBox(), Box::LEFT);
 
 	open_inline_box = box;

+ 9 - 3
Source/Core/Math.cpp

@@ -118,7 +118,13 @@ ROCKETCORE_API float SquareRoot(float value)
 }
 
 // Rounds a floating-point value to the nearest integer.
-ROCKETCORE_API int Round(float value)
+ROCKETCORE_API float RoundFloat(float value)
+{
+	return roundf(value);
+}
+
+// Rounds a floating-point value to the nearest integer.
+ROCKETCORE_API int RoundToInteger(float value)
 {
 	if (value > 0.0f)
 		return RealToInteger(value + 0.5f);
@@ -127,13 +133,13 @@ ROCKETCORE_API int Round(float value)
 }
 
 // Rounds a floating-point value up to the nearest integer.
-ROCKETCORE_API int RoundUp(float value)
+ROCKETCORE_API int RoundUpToInteger(float value)
 {
 	return RealToInteger(ceilf(value));
 }
 
 // Rounds a floating-point value down to the nearest integer.
-ROCKETCORE_API int RoundDown(float value)
+ROCKETCORE_API int RoundDownToInteger(float value)
 {
 	return RealToInteger(floorf(value));
 }