From 3bfa37019e2be364d5eff59ba891135832ca8726 Mon Sep 17 00:00:00 2001 From: NoriteSC <53096989+NoriteSC@users.noreply.github.com> Date: Tue, 12 Sep 2023 10:59:07 +0200 Subject: [PATCH 001/139] DebugDraw extra fixes ray direction was dictating the length of ray now ray is normalized and multiplied by length added default vaule for Color& color to be Color::White [info] c# because Color color = Color.White is not valid think to do on c# side the default value is not genereted [to do] generate extra overload binding in c# --- Source/Engine/Debug/DebugDraw.cpp | 9 +++++++-- Source/Engine/Debug/DebugDraw.h | 13 ++++++++++++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Debug/DebugDraw.cpp b/Source/Engine/Debug/DebugDraw.cpp index e8489f5b5..ccd247674 100644 --- a/Source/Engine/Debug/DebugDraw.cpp +++ b/Source/Engine/Debug/DebugDraw.cpp @@ -920,9 +920,14 @@ void DebugDraw::DrawActors(Actor** selectedActors, int32 selectedActorsCount, bo } } -void DebugDraw::DrawRay(const Vector3& origin, const Vector3& direction, const Color& color, float duration, bool depthTest) +void DebugDraw::DrawRay(const Vector3& origin, const Vector3& direction, Color& color, float length, float duration, bool depthTest) { - DrawLine(origin, origin + direction, color, duration, depthTest); + DrawLine(origin, origin + (direction.GetNormalized() * length), color, duration, depthTest); +} + +void DebugDraw::DrawRay(const Ray& ray, Color& color, float length, float duration, bool depthTest) +{ + DrawLine(ray.Position, ray.Position + (ray.Direction.GetNormalized() * length), color, duration, depthTest); } void DebugDraw::DrawLine(const Vector3& start, const Vector3& end, const Color& color, float duration, bool depthTest) diff --git a/Source/Engine/Debug/DebugDraw.h b/Source/Engine/Debug/DebugDraw.h index a4f69678a..5a811be7e 100644 --- a/Source/Engine/Debug/DebugDraw.h +++ b/Source/Engine/Debug/DebugDraw.h @@ -75,9 +75,20 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The origin of the line. /// The direction of the line. /// The color. + /// The length of the ray. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawRay(const Vector3& origin, const Vector3& direction, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawRay(const Vector3& origin, const Vector3& direction, Color& color = Color::White, float length = 3.402823466e+38f, float duration = 0.0f, bool depthTest = true); + + /// + /// Draws the line in a direction. + /// + /// The ray. + /// The color. + /// The length of the ray. + /// The duration (in seconds). Use 0 to draw it only once. + /// If set to true depth test will be performed, otherwise depth will be ignored. + API_FUNCTION() static void DrawRay(const Ray& ray, Color& color = Color::White, float length = 3.402823466e+38f, float duration = 0.0f, bool depthTest = true); /// /// Draws the line. From 78ffcc8ae5781d1b6171fbfd2921dc8a844f8a0f Mon Sep 17 00:00:00 2001 From: NoriteSC <53096989+NoriteSC@users.noreply.github.com> Date: Tue, 12 Sep 2023 11:06:46 +0200 Subject: [PATCH 002/139] added other defaults --- Source/Engine/Debug/DebugDraw.h | 104 ++++++++++++++++---------------- 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/Source/Engine/Debug/DebugDraw.h b/Source/Engine/Debug/DebugDraw.h index 5a811be7e..5bd51a578 100644 --- a/Source/Engine/Debug/DebugDraw.h +++ b/Source/Engine/Debug/DebugDraw.h @@ -98,7 +98,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawLine(const Vector3& start, const Vector3& end, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawLine(const Vector3& start, const Vector3& end, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the lines. Line positions are located one after another (e.g. l0.start, l0.end, l1.start, l1.end,...). @@ -108,7 +108,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawLines(const Span& lines, const Matrix& transform, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawLines(const Span& lines, const Matrix& transform, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the lines. Line positions are located one after another (e.g. l0.start, l0.end, l1.start, l1.end,...). @@ -118,7 +118,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - static void DrawLines(const Array& lines, const Matrix& transform, const Color& color, float duration = 0.0f, bool depthTest = true); + static void DrawLines(const Array& lines, const Matrix& transform, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the lines. Line positions are located one after another (e.g. l0.start, l0.end, l1.start, l1.end,...). @@ -128,7 +128,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawLines(const Span& lines, const Matrix& transform, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawLines(const Span& lines, const Matrix& transform, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the lines. Line positions are located one after another (e.g. l0.start, l0.end, l1.start, l1.end,...). @@ -138,7 +138,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - static void DrawLines(const Array& lines, const Matrix& transform, const Color& color, float duration = 0.0f, bool depthTest = true); + static void DrawLines(const Array& lines, const Matrix& transform, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws a Bezier curve. @@ -150,7 +150,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The line color /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawBezier(const Vector3& p1, const Vector3& p2, const Vector3& p3, const Vector3& p4, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawBezier(const Vector3& p1, const Vector3& p2, const Vector3& p3, const Vector3& p4, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the circle. @@ -161,7 +161,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawCircle(const Vector3& position, const Float3& normal, float radius, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawCircle(const Vector3& position, const Float3& normal, float radius, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe triangle. @@ -172,7 +172,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawWireTriangle(const Vector3& v0, const Vector3& v1, const Vector3& v2, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawWireTriangle(const Vector3& v0, const Vector3& v1, const Vector3& v2, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the triangle. @@ -183,7 +183,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawTriangle(const Vector3& v0, const Vector3& v1, const Vector3& v2, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawTriangle(const Vector3& v0, const Vector3& v1, const Vector3& v2, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the triangles. @@ -192,7 +192,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawTriangles(const Span& vertices, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawTriangles(const Span& vertices, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the triangles. @@ -202,7 +202,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawTriangles(const Span& vertices, const Matrix& transform, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawTriangles(const Span& vertices, const Matrix& transform, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the triangles. @@ -211,7 +211,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - static void DrawTriangles(const Array& vertices, const Color& color, float duration = 0.0f, bool depthTest = true); + static void DrawTriangles(const Array& vertices, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the triangles. @@ -221,7 +221,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - static void DrawTriangles(const Array& vertices, const Matrix& transform, const Color& color, float duration = 0.0f, bool depthTest = true); + static void DrawTriangles(const Array& vertices, const Matrix& transform, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the triangles using the given index buffer. @@ -231,7 +231,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawTriangles(const Span& vertices, const Span& indices, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawTriangles(const Span& vertices, const Span& indices, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the triangles using the given index buffer. @@ -242,7 +242,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawTriangles(const Span& vertices, const Span& indices, const Matrix& transform, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawTriangles(const Span& vertices, const Span& indices, const Matrix& transform, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the triangles using the given index buffer. @@ -252,7 +252,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - static void DrawTriangles(const Array& vertices, const Array& indices, const Color& color, float duration = 0.0f, bool depthTest = true); + static void DrawTriangles(const Array& vertices, const Array& indices, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the triangles using the given index buffer. @@ -263,7 +263,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - static void DrawTriangles(const Array& vertices, const Array& indices, const Matrix& transform, const Color& color, float duration = 0.0f, bool depthTest = true); + static void DrawTriangles(const Array& vertices, const Array& indices, const Matrix& transform, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the triangles. @@ -272,7 +272,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawTriangles(const Span& vertices, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawTriangles(const Span& vertices, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the triangles. @@ -282,7 +282,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawTriangles(const Span& vertices, const Matrix& transform, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawTriangles(const Span& vertices, const Matrix& transform, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the triangles. @@ -291,7 +291,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - static void DrawTriangles(const Array& vertices, const Color& color, float duration = 0.0f, bool depthTest = true); + static void DrawTriangles(const Array& vertices, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the triangles. @@ -301,7 +301,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - static void DrawTriangles(const Array& vertices, const Matrix& transform, const Color& color, float duration = 0.0f, bool depthTest = true); + static void DrawTriangles(const Array& vertices, const Matrix& transform, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the triangles using the given index buffer. @@ -311,7 +311,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawTriangles(const Span& vertices, const Span& indices, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawTriangles(const Span& vertices, const Span& indices, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the triangles using the given index buffer. @@ -322,7 +322,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawTriangles(const Span& vertices, const Span& indices, const Matrix& transform, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawTriangles(const Span& vertices, const Span& indices, const Matrix& transform, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the triangles using the given index buffer. @@ -332,7 +332,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - static void DrawTriangles(const Array& vertices, const Array& indices, const Color& color, float duration = 0.0f, bool depthTest = true); + static void DrawTriangles(const Array& vertices, const Array& indices, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the triangles using the given index buffer. @@ -343,7 +343,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - static void DrawTriangles(const Array& vertices, const Array& indices, const Matrix& transform, const Color& color, float duration = 0.0f, bool depthTest = true); + static void DrawTriangles(const Array& vertices, const Array& indices, const Matrix& transform, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe triangles. @@ -352,7 +352,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawWireTriangles(const Span& vertices, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawWireTriangles(const Span& vertices, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe triangles. @@ -361,7 +361,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - static void DrawWireTriangles(const Array& vertices, const Color& color, float duration = 0.0f, bool depthTest = true); + static void DrawWireTriangles(const Array& vertices, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe triangles using the given index buffer. @@ -371,7 +371,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawWireTriangles(const Span& vertices, const Span& indices, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawWireTriangles(const Span& vertices, const Span& indices, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe triangles using the given index buffer. @@ -381,7 +381,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - static void DrawWireTriangles(const Array& vertices, const Array& indices, const Color& color, float duration = 0.0f, bool depthTest = true); + static void DrawWireTriangles(const Array& vertices, const Array& indices, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe triangles. @@ -390,7 +390,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawWireTriangles(const Span& vertices, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawWireTriangles(const Span& vertices, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe triangles. @@ -399,7 +399,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - static void DrawWireTriangles(const Array& vertices, const Color& color, float duration = 0.0f, bool depthTest = true); + static void DrawWireTriangles(const Array& vertices, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe triangles using the given index buffer. @@ -409,7 +409,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawWireTriangles(const Span& vertices, const Span& indices, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawWireTriangles(const Span& vertices, const Span& indices, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe triangles using the given index buffer. @@ -419,7 +419,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - static void DrawWireTriangles(const Array& vertices, const Array& indices, const Color& color, float duration = 0.0f, bool depthTest = true); + static void DrawWireTriangles(const Array& vertices, const Array& indices, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe box. @@ -428,7 +428,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawWireBox(const BoundingBox& box, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawWireBox(const BoundingBox& box, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe frustum. @@ -437,7 +437,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawWireFrustum(const BoundingFrustum& frustum, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawWireFrustum(const BoundingFrustum& frustum, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe box. @@ -446,7 +446,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawWireBox(const OrientedBoundingBox& box, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawWireBox(const OrientedBoundingBox& box, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe sphere. @@ -455,7 +455,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawWireSphere(const BoundingSphere& sphere, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawWireSphere(const BoundingSphere& sphere, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the sphere. @@ -464,7 +464,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawSphere(const BoundingSphere& sphere, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawSphere(const BoundingSphere& sphere, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the tube. @@ -476,7 +476,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawTube(const Vector3& position, const Quaternion& orientation, float radius, float length, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawTube(const Vector3& position, const Quaternion& orientation, float radius, float length, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe tube. @@ -488,7 +488,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawWireTube(const Vector3& position, const Quaternion& orientation, float radius, float length, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawWireTube(const Vector3& position, const Quaternion& orientation, float radius, float length, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the cylinder. @@ -500,7 +500,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawCylinder(const Vector3& position, const Quaternion& orientation, float radius, float height, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawCylinder(const Vector3& position, const Quaternion& orientation, float radius, float height, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe cylinder. @@ -512,7 +512,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawWireCylinder(const Vector3& position, const Quaternion& orientation, float radius, float height, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawWireCylinder(const Vector3& position, const Quaternion& orientation, float radius, float height, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the cone. @@ -525,7 +525,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawCone(const Vector3& position, const Quaternion& orientation, float radius, float angleXY, float angleXZ, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawCone(const Vector3& position, const Quaternion& orientation, float radius, float angleXY, float angleXZ, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe cone. @@ -538,7 +538,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawWireCone(const Vector3& position, const Quaternion& orientation, float radius, float angleXY, float angleXZ, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawWireCone(const Vector3& position, const Quaternion& orientation, float radius, float angleXY, float angleXZ, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the arc. @@ -550,7 +550,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawArc(const Vector3& position, const Quaternion& orientation, float radius, float angle, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawArc(const Vector3& position, const Quaternion& orientation, float radius, float angle, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe arc. @@ -562,7 +562,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawWireArc(const Vector3& position, const Quaternion& orientation, float radius, float angle, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawWireArc(const Vector3& position, const Quaternion& orientation, float radius, float angle, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe arrow. @@ -573,7 +573,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawWireArrow(const Vector3& position, const Quaternion& orientation, float scale, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawWireArrow(const Vector3& position, const Quaternion& orientation, float scale, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the box. @@ -582,7 +582,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawBox(const BoundingBox& box, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawBox(const BoundingBox& box, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the box. @@ -591,7 +591,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawBox(const OrientedBoundingBox& box, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawBox(const OrientedBoundingBox& box, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the text on a screen (2D). @@ -601,7 +601,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The font size. /// The duration (in seconds). Use 0 to draw it only once. - API_FUNCTION() static void DrawText(const StringView& text, const Float2& position, const Color& color, int32 size = 20, float duration = 0.0f); + API_FUNCTION() static void DrawText(const StringView& text, const Float2& position, Color& color = Color::White, int32 size = 20, float duration = 0.0f); /// /// Draws the text (3D) that automatically faces the camera. @@ -612,7 +612,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The font size. /// The duration (in seconds). Use 0 to draw it only once. /// The text scale. - API_FUNCTION() static void DrawText(const StringView& text, const Vector3& position, const Color& color, int32 size = 32, float duration = 0.0f, float scale = 1.0f); + API_FUNCTION() static void DrawText(const StringView& text, const Vector3& position, Color& color = Color::White, int32 size = 32, float duration = 0.0f, float scale = 1.0f); /// /// Draws the text (3D). @@ -622,7 +622,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The font size. /// The duration (in seconds). Use 0 to draw it only once. - API_FUNCTION() static void DrawText(const StringView& text, const Transform& transform, const Color& color, int32 size = 32, float duration = 0.0f); + API_FUNCTION() static void DrawText(const StringView& text, const Transform& transform, Color& color = Color::White, int32 size = 32, float duration = 0.0f); }; #define DEBUG_DRAW_RAY(origin, direction, color, duration, depthTest) DebugDraw::DrawRay(origin, direction, color, duration, depthTest) From f11ea025506967f0dacd05e9ded8daac16a39ba6 Mon Sep 17 00:00:00 2001 From: NoriteSC <53096989+NoriteSC@users.noreply.github.com> Date: Tue, 12 Sep 2023 11:08:47 +0200 Subject: [PATCH 003/139] forgot about const :sweat_smile: --- Source/Engine/Debug/DebugDraw.h | 108 ++++++++++++++++---------------- 1 file changed, 54 insertions(+), 54 deletions(-) diff --git a/Source/Engine/Debug/DebugDraw.h b/Source/Engine/Debug/DebugDraw.h index 5bd51a578..b5af321c7 100644 --- a/Source/Engine/Debug/DebugDraw.h +++ b/Source/Engine/Debug/DebugDraw.h @@ -78,7 +78,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The length of the ray. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawRay(const Vector3& origin, const Vector3& direction, Color& color = Color::White, float length = 3.402823466e+38f, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawRay(const Vector3& origin, const Vector3& direction, const Color& color = Color::White, float length = 3.402823466e+38f, float duration = 0.0f, bool depthTest = true); /// /// Draws the line in a direction. @@ -88,7 +88,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The length of the ray. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawRay(const Ray& ray, Color& color = Color::White, float length = 3.402823466e+38f, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawRay(const Ray& ray,const Color& color = Color::White, float length = 3.402823466e+38f, float duration = 0.0f, bool depthTest = true); /// /// Draws the line. @@ -98,7 +98,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawLine(const Vector3& start, const Vector3& end, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawLine(const Vector3& start, const Vector3& end, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the lines. Line positions are located one after another (e.g. l0.start, l0.end, l1.start, l1.end,...). @@ -108,7 +108,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawLines(const Span& lines, const Matrix& transform, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawLines(const Span& lines, const Matrix& transform, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the lines. Line positions are located one after another (e.g. l0.start, l0.end, l1.start, l1.end,...). @@ -118,7 +118,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - static void DrawLines(const Array& lines, const Matrix& transform, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + static void DrawLines(const Array& lines, const Matrix& transform, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the lines. Line positions are located one after another (e.g. l0.start, l0.end, l1.start, l1.end,...). @@ -128,7 +128,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawLines(const Span& lines, const Matrix& transform, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawLines(const Span& lines, const Matrix& transform, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the lines. Line positions are located one after another (e.g. l0.start, l0.end, l1.start, l1.end,...). @@ -138,7 +138,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - static void DrawLines(const Array& lines, const Matrix& transform, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + static void DrawLines(const Array& lines, const Matrix& transform, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws a Bezier curve. @@ -150,7 +150,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The line color /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawBezier(const Vector3& p1, const Vector3& p2, const Vector3& p3, const Vector3& p4, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawBezier(const Vector3& p1, const Vector3& p2, const Vector3& p3, const Vector3& p4, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the circle. @@ -161,7 +161,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawCircle(const Vector3& position, const Float3& normal, float radius, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawCircle(const Vector3& position, const Float3& normal, float radius, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe triangle. @@ -172,7 +172,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawWireTriangle(const Vector3& v0, const Vector3& v1, const Vector3& v2, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawWireTriangle(const Vector3& v0, const Vector3& v1, const Vector3& v2, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the triangle. @@ -183,7 +183,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawTriangle(const Vector3& v0, const Vector3& v1, const Vector3& v2, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawTriangle(const Vector3& v0, const Vector3& v1, const Vector3& v2, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the triangles. @@ -192,7 +192,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawTriangles(const Span& vertices, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawTriangles(const Span& vertices, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the triangles. @@ -202,7 +202,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawTriangles(const Span& vertices, const Matrix& transform, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawTriangles(const Span& vertices, const Matrix& transform, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the triangles. @@ -211,7 +211,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - static void DrawTriangles(const Array& vertices, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + static void DrawTriangles(const Array& vertices, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the triangles. @@ -221,7 +221,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - static void DrawTriangles(const Array& vertices, const Matrix& transform, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + static void DrawTriangles(const Array& vertices, const Matrix& transform, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the triangles using the given index buffer. @@ -231,7 +231,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawTriangles(const Span& vertices, const Span& indices, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawTriangles(const Span& vertices, const Span& indices, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the triangles using the given index buffer. @@ -242,7 +242,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawTriangles(const Span& vertices, const Span& indices, const Matrix& transform, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawTriangles(const Span& vertices, const Span& indices, const Matrix& transform, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the triangles using the given index buffer. @@ -252,7 +252,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - static void DrawTriangles(const Array& vertices, const Array& indices, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + static void DrawTriangles(const Array& vertices, const Array& indices, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the triangles using the given index buffer. @@ -263,7 +263,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - static void DrawTriangles(const Array& vertices, const Array& indices, const Matrix& transform, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + static void DrawTriangles(const Array& vertices, const Array& indices, const Matrix& transform, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the triangles. @@ -272,7 +272,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawTriangles(const Span& vertices, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawTriangles(const Span& vertices, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the triangles. @@ -282,7 +282,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawTriangles(const Span& vertices, const Matrix& transform, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawTriangles(const Span& vertices, const Matrix& transform, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the triangles. @@ -291,7 +291,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - static void DrawTriangles(const Array& vertices, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + static void DrawTriangles(const Array& vertices, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the triangles. @@ -301,7 +301,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - static void DrawTriangles(const Array& vertices, const Matrix& transform, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + static void DrawTriangles(const Array& vertices, const Matrix& transform, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the triangles using the given index buffer. @@ -311,7 +311,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawTriangles(const Span& vertices, const Span& indices, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawTriangles(const Span& vertices, const Span& indices, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the triangles using the given index buffer. @@ -322,7 +322,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawTriangles(const Span& vertices, const Span& indices, const Matrix& transform, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawTriangles(const Span& vertices, const Span& indices, const Matrix& transform, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the triangles using the given index buffer. @@ -332,7 +332,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - static void DrawTriangles(const Array& vertices, const Array& indices, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + static void DrawTriangles(const Array& vertices, const Array& indices, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the triangles using the given index buffer. @@ -343,7 +343,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - static void DrawTriangles(const Array& vertices, const Array& indices, const Matrix& transform, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + static void DrawTriangles(const Array& vertices, const Array& indices, const Matrix& transform, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe triangles. @@ -352,7 +352,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawWireTriangles(const Span& vertices, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawWireTriangles(const Span& vertices, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe triangles. @@ -361,7 +361,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - static void DrawWireTriangles(const Array& vertices, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + static void DrawWireTriangles(const Array& vertices, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe triangles using the given index buffer. @@ -371,7 +371,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawWireTriangles(const Span& vertices, const Span& indices, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawWireTriangles(const Span& vertices, const Span& indices, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe triangles using the given index buffer. @@ -381,7 +381,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - static void DrawWireTriangles(const Array& vertices, const Array& indices, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + static void DrawWireTriangles(const Array& vertices, const Array& indices, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe triangles. @@ -390,7 +390,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawWireTriangles(const Span& vertices, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawWireTriangles(const Span& vertices, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe triangles. @@ -399,7 +399,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - static void DrawWireTriangles(const Array& vertices, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + static void DrawWireTriangles(const Array& vertices, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe triangles using the given index buffer. @@ -409,7 +409,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawWireTriangles(const Span& vertices, const Span& indices, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawWireTriangles(const Span& vertices, const Span& indices, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe triangles using the given index buffer. @@ -419,7 +419,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - static void DrawWireTriangles(const Array& vertices, const Array& indices, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + static void DrawWireTriangles(const Array& vertices, const Array& indices, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe box. @@ -428,7 +428,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawWireBox(const BoundingBox& box, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawWireBox(const BoundingBox& box, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe frustum. @@ -437,7 +437,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawWireFrustum(const BoundingFrustum& frustum, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawWireFrustum(const BoundingFrustum& frustum, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe box. @@ -446,7 +446,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawWireBox(const OrientedBoundingBox& box, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawWireBox(const OrientedBoundingBox& box, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe sphere. @@ -455,7 +455,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawWireSphere(const BoundingSphere& sphere, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawWireSphere(const BoundingSphere& sphere, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the sphere. @@ -464,7 +464,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawSphere(const BoundingSphere& sphere, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawSphere(const BoundingSphere& sphere, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the tube. @@ -476,7 +476,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawTube(const Vector3& position, const Quaternion& orientation, float radius, float length, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawTube(const Vector3& position, const Quaternion& orientation, float radius, float length, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe tube. @@ -488,7 +488,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawWireTube(const Vector3& position, const Quaternion& orientation, float radius, float length, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawWireTube(const Vector3& position, const Quaternion& orientation, float radius, float length, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the cylinder. @@ -500,7 +500,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawCylinder(const Vector3& position, const Quaternion& orientation, float radius, float height, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawCylinder(const Vector3& position, const Quaternion& orientation, float radius, float height, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe cylinder. @@ -512,7 +512,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawWireCylinder(const Vector3& position, const Quaternion& orientation, float radius, float height, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawWireCylinder(const Vector3& position, const Quaternion& orientation, float radius, float height, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the cone. @@ -525,7 +525,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawCone(const Vector3& position, const Quaternion& orientation, float radius, float angleXY, float angleXZ, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawCone(const Vector3& position, const Quaternion& orientation, float radius, float angleXY, float angleXZ, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe cone. @@ -538,7 +538,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawWireCone(const Vector3& position, const Quaternion& orientation, float radius, float angleXY, float angleXZ, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawWireCone(const Vector3& position, const Quaternion& orientation, float radius, float angleXY, float angleXZ, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the arc. @@ -550,7 +550,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawArc(const Vector3& position, const Quaternion& orientation, float radius, float angle, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawArc(const Vector3& position, const Quaternion& orientation, float radius, float angle, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe arc. @@ -562,7 +562,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawWireArc(const Vector3& position, const Quaternion& orientation, float radius, float angle, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawWireArc(const Vector3& position, const Quaternion& orientation, float radius, float angle, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe arrow. @@ -573,7 +573,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawWireArrow(const Vector3& position, const Quaternion& orientation, float scale, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawWireArrow(const Vector3& position, const Quaternion& orientation, float scale, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the box. @@ -582,7 +582,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawBox(const BoundingBox& box, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawBox(const BoundingBox& box, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the box. @@ -591,7 +591,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawBox(const OrientedBoundingBox& box, Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawBox(const OrientedBoundingBox& box, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the text on a screen (2D). @@ -601,7 +601,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The font size. /// The duration (in seconds). Use 0 to draw it only once. - API_FUNCTION() static void DrawText(const StringView& text, const Float2& position, Color& color = Color::White, int32 size = 20, float duration = 0.0f); + API_FUNCTION() static void DrawText(const StringView& text, const Float2& position, const Color& color = Color::White, int32 size = 20, float duration = 0.0f); /// /// Draws the text (3D) that automatically faces the camera. @@ -612,7 +612,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The font size. /// The duration (in seconds). Use 0 to draw it only once. /// The text scale. - API_FUNCTION() static void DrawText(const StringView& text, const Vector3& position, Color& color = Color::White, int32 size = 32, float duration = 0.0f, float scale = 1.0f); + API_FUNCTION() static void DrawText(const StringView& text, const Vector3& position, const Color& color = Color::White, int32 size = 32, float duration = 0.0f, float scale = 1.0f); /// /// Draws the text (3D). @@ -622,7 +622,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The font size. /// The duration (in seconds). Use 0 to draw it only once. - API_FUNCTION() static void DrawText(const StringView& text, const Transform& transform, Color& color = Color::White, int32 size = 32, float duration = 0.0f); + API_FUNCTION() static void DrawText(const StringView& text, const Transform& transform, const Color& color = Color::White, int32 size = 32, float duration = 0.0f); }; #define DEBUG_DRAW_RAY(origin, direction, color, duration, depthTest) DebugDraw::DrawRay(origin, direction, color, duration, depthTest) From d107947ba9c40a851756db8ac6ca1085aea0d171 Mon Sep 17 00:00:00 2001 From: NoriteSC <53096989+NoriteSC@users.noreply.github.com> Date: Tue, 12 Sep 2023 11:19:19 +0200 Subject: [PATCH 004/139] Update DebugDraw.cpp --- Source/Engine/Debug/DebugDraw.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Debug/DebugDraw.cpp b/Source/Engine/Debug/DebugDraw.cpp index ccd247674..2afc9d047 100644 --- a/Source/Engine/Debug/DebugDraw.cpp +++ b/Source/Engine/Debug/DebugDraw.cpp @@ -920,12 +920,12 @@ void DebugDraw::DrawActors(Actor** selectedActors, int32 selectedActorsCount, bo } } -void DebugDraw::DrawRay(const Vector3& origin, const Vector3& direction, Color& color, float length, float duration, bool depthTest) +void DebugDraw::DrawRay(const Vector3& origin, const Vector3& direction,const Color& color, float length, float duration, bool depthTest) { DrawLine(origin, origin + (direction.GetNormalized() * length), color, duration, depthTest); } -void DebugDraw::DrawRay(const Ray& ray, Color& color, float length, float duration, bool depthTest) +void DebugDraw::DrawRay(const Ray& ray,const Color& color, float length, float duration, bool depthTest) { DrawLine(ray.Position, ray.Position + (ray.Direction.GetNormalized() * length), color, duration, depthTest); } From f71f214f36c0960a820c2be35d0e547ed9a52d54 Mon Sep 17 00:00:00 2001 From: NoriteSC <53096989+NoriteSC@users.noreply.github.com> Date: Wed, 13 Sep 2023 02:53:20 +0200 Subject: [PATCH 005/139] bug fix macro added extra macros --- Source/Engine/Debug/DebugDraw.cpp | 16 ++++ Source/Engine/Debug/DebugDraw.cs | 36 +++++++- Source/Engine/Debug/DebugDraw.h | 135 ++++++++++++++++++------------ 3 files changed, 132 insertions(+), 55 deletions(-) diff --git a/Source/Engine/Debug/DebugDraw.cpp b/Source/Engine/Debug/DebugDraw.cpp index 2afc9d047..733c4a643 100644 --- a/Source/Engine/Debug/DebugDraw.cpp +++ b/Source/Engine/Debug/DebugDraw.cpp @@ -920,6 +920,22 @@ void DebugDraw::DrawActors(Actor** selectedActors, int32 selectedActorsCount, bo } } +void DebugDraw::DrawAxisFromDirection(const Vector3& origin, const Vector3& direction, const Color& color , float Size, float duration, bool depthTest) +{ + auto rot = Quaternion::FromDirection(direction.GetNormalized()); + Vector3 Up = (rot * Vector3::Up ); + Vector3 Forward = (rot * Vector3::Forward); + Vector3 Right = (rot * Vector3::Right ); + + DrawLine(origin, origin + (Up * (Size * 0.5f)) + Up , Color::Green ,duration, depthTest); + DrawLine(origin, origin + (Forward * (Size * 0.5f)) + Forward, Color::Blue ,duration, depthTest); + DrawLine(origin, origin + (Right * (Size * 0.5f)) + Right , Color::Red ,duration, depthTest); +} + +void DebugDraw::DrawDirection(const Vector3& origin, const Vector3& direction, const Color& color, float duration, bool depthTest) +{ + DrawLine(origin, origin + direction, color, duration, depthTest); +} void DebugDraw::DrawRay(const Vector3& origin, const Vector3& direction,const Color& color, float length, float duration, bool depthTest) { DrawLine(origin, origin + (direction.GetNormalized() * length), color, duration, depthTest); diff --git a/Source/Engine/Debug/DebugDraw.cs b/Source/Engine/Debug/DebugDraw.cs index bc000e03d..f6a8a19f4 100644 --- a/Source/Engine/Debug/DebugDraw.cs +++ b/Source/Engine/Debug/DebugDraw.cs @@ -31,6 +31,17 @@ namespace FlaxEngine { } + /// + /// Draws the lines axis from direction. + /// + /// The origin of the line. + /// The direction of the line. + /// The color. + /// The size of the axis. + /// The duration (in seconds). Use 0 to draw it only once. + /// If set to true depth test will be performed, otherwise depth will be ignored. + static void DrawAxisFromDirection(Vector3 origin, Vector3 direction, Color color ,float Size = 100.0f, float duration = 0.0f, bool depthTest = true); + /// /// Draws the line in a direction. /// @@ -39,9 +50,28 @@ namespace FlaxEngine /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - public static void DrawRay(Vector3 origin, Vector3 direction, Color color, float duration = 0.0f, bool depthTest = true) - { - } + static void DrawDirection(Vector3 origin, Vector3 direction, Color color, float duration = 0.0f, bool depthTest = true); + + /// + /// Draws the line in a direction. + /// + /// The origin of the line. + /// The direction of the line. + /// The color. + /// The length of the ray. + /// The duration (in seconds). Use 0 to draw it only once. + /// If set to true depth test will be performed, otherwise depth will be ignored. + static void DrawRay(Vector3 origin, Vector3 direction, Color color, float length = 3.402823466e+38f, float duration = 0.0f, bool depthTest = true); + + /// + /// Draws the line in a direction. + /// + /// The ray. + /// The color. + /// The length of the ray. + /// The duration (in seconds). Use 0 to draw it only once. + /// If set to true depth test will be performed, otherwise depth will be ignored. + static void DrawRay(Ray ray,Color color, float length = 3.402823466e+38f, float duration = 0.0f, bool depthTest = true); /// /// Draws the line. diff --git a/Source/Engine/Debug/DebugDraw.h b/Source/Engine/Debug/DebugDraw.h index b5af321c7..5df31d831 100644 --- a/Source/Engine/Debug/DebugDraw.h +++ b/Source/Engine/Debug/DebugDraw.h @@ -69,6 +69,27 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// True if draw all debug shapes from scenes too or false if draw just from specified actor list. API_FUNCTION() static void DrawActors(Actor** selectedActors, int32 selectedActorsCount, bool drawScenes); + /// + /// Draws the lines axis from direction. + /// + /// The origin of the line. + /// The direction of the line. + /// The color. + /// The size of the axis. + /// The duration (in seconds). Use 0 to draw it only once. + /// If set to true depth test will be performed, otherwise depth will be ignored. + API_FUNCTION() static void DrawAxisFromDirection(const Vector3& origin, const Vector3& direction, const Color& color = Color::White ,float Size = 100.0f, float duration = 0.0f, bool depthTest = true); + + /// + /// Draws the line in a direction. + /// + /// The origin of the line. + /// The direction of the line. + /// The color. + /// The duration (in seconds). Use 0 to draw it only once. + /// If set to true depth test will be performed, otherwise depth will be ignored. + API_FUNCTION() static void DrawDirection(const Vector3& origin, const Vector3& direction, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + /// /// Draws the line in a direction. /// @@ -625,61 +646,71 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw API_FUNCTION() static void DrawText(const StringView& text, const Transform& transform, const Color& color = Color::White, int32 size = 32, float duration = 0.0f); }; -#define DEBUG_DRAW_RAY(origin, direction, color, duration, depthTest) DebugDraw::DrawRay(origin, direction, color, duration, depthTest) -#define DEBUG_DRAW_LINE(start, end, color, duration, depthTest) DebugDraw::DrawLine(start, end, color, duration, depthTest) -#define DEBUG_DRAW_LINES(lines, transform, color, duration, depthTest) DebugDraw::DrawLines(lines, transform, color, duration, depthTest) -#define DEBUG_DRAW_BEZIER(p1, p2, p3, p4, color, duration, depthTest) DebugDraw::DrawBezier(p1, p2, p3, p4, color, duration, depthTest) -#define DEBUG_DRAW_CIRCLE(position, normal, radius, color, duration, depthTest) DebugDraw::DrawCircle(position, normal, radius, color, duration, depthTest) -#define DEBUG_DRAW_TRIANGLE(v0, v1, v2, color, duration, depthTest) DebugDraw::DrawTriangle(v0, v1, v2, color, duration, depthTest) -#define DEBUG_DRAW_TRIANGLES(vertices, color, duration, depthTest) DebugDraw::DrawTriangles(vertices, color, duration, depthTest) -#define DEBUG_DRAW_TRIANGLES_EX(vertices, indices, color, duration, depthTest) DebugDraw::DrawTriangles(vertices, indices, color, duration, depthTest) -#define DEBUG_DRAW_TRIANGLES_EX2(vertices, indices, transform, color, duration, depthTest) DebugDraw::DrawTriangles(vertices, indices, transform, color, duration, depthTest) -#define DEBUG_DRAW_SPHERE(sphere, color, duration, depthTest) DebugDraw::DrawSphere(sphere, color, duration, depthTest) -#define DEBUG_DRAW_TUBE(position, orientation, radius, length, color, duration, depthTest) DebugDraw::DrawTube(position, orientation, radius, length, color, duration, depthTest) -#define DEBUG_DRAW_BOX(box, color, duration, depthTest) DebugDraw::DrawBox(box, color, duration, depthTest) -#define DEBUG_DRAW_CYLINDER(position, orientation, radius, height, color, duration, depthTest) DebugDraw::DrawCylinder(position, orientation, radius, height, color, duration, depthTest) -#define DEBUG_DRAW_CONE(position, orientation, radius, angleXY, angleXZ, color, duration, depthTest) DebugDraw::DrawCone(position, orientation, radius, angleXY, angleXZ, color, duration, depthTest) -#define DEBUG_DRAW_ARC(position, orientation, radius, angle, color, duration, depthTest) DebugDraw::DrawArc(position, orientation, radius, angle, color, duration, depthTest) -#define DEBUG_DRAW_WIRE_TRIANGLE(v0, v1, v2, color, duration, depthTest) DebugDraw::DrawWireTriangle(v0, v1, v2, color, duration, depthTest) -#define DEBUG_DRAW_WIRE_TRIANGLES(vertices, color, duration, depthTest) DebugDraw::DrawWireTriangles(vertices, color, duration, depthTest) -#define DEBUG_DRAW_WIRE_TRIANGLES_EX(vertices, indices, color, duration, depthTest) DebugDraw::DrawWireTriangles(vertices, indices, color, duration, depthTest) -#define DEBUG_DRAW_WIRE_BOX(box, color, duration, depthTest) DebugDraw::DrawWireBox(box, color, duration, depthTest) -#define DEBUG_DRAW_WIRE_FRUSTUM(frustum, color, duration, depthTest) DebugDraw::DrawWireFrustum(frustum, color, duration, depthTest) -#define DEBUG_DRAW_WIRE_SPHERE(sphere, color, duration, depthTest) DebugDraw::DrawWireSphere(sphere, color, duration, depthTest) -#define DEBUG_DRAW_WIRE_TUBE(position, orientation, radius, length, color, duration, depthTest) DebugDraw::DrawWireTube(position, orientation, radius, length, color, duration, depthTest) -#define DEBUG_DRAW_WIRE_CYLINDER(position, orientation, radius, height, color, duration, depthTest) DebugDraw::DrawWireCylinder(position, orientation, radius, height, color, duration, depthTest) -#define DEBUG_DRAW_WIRE_CONE(position, orientation, radius, angleXY, angleXZ, color, duration, depthTest) DebugDraw::DrawWireCone(position, orientation, radius, angleXY, angleXZ, color, duration, depthTest) -#define DEBUG_DRAW_WIRE_ARC(position, orientation, radius, angle, color, duration, depthTest) DebugDraw::DrawWireArc(position, orientation, radius, angle, color, duration, depthTest) -#define DEBUG_DRAW_WIRE_ARROW(position, orientation, scale, color, duration, depthTest) DebugDraw::DrawWireArrow(position, orientation, scale, color, duration, depthTest) -#define DEBUG_DRAW_TEXT(text, position, color, size, duration) DebugDraw::DrawText(text, position, color, size, duration) + +#define DEBUG_DRAW_AXIS_FROM_DIRECTION(origin,direction,color,Size,duration,depthTest) DebugDraw::DrawAxisFromDirection(origin,direction,color,Size,duration,depthTest); +#define DEBUG_DRAW_DIRECTION (origin,direction,color,duration,depthTest) DebugDraw::DrawDirection(origin,direction,color,duration,depthTest); +#define DEBUG_DRAW_RAY (origin,direction,color,length,duration,depthTest) DebugDraw::DrawRay(origin,direction,color,length,duration,depthTest); +#define DEBUG_DRAW_RAY (ray,color,length,duration,depthTest) DebugDraw::DrawRay(ray,color,length,duration,depthTest); + +#define DEBUG_DRAW_LINE(start, end, color, duration, depthTest) DebugDraw::DrawLine(start, end, color, duration, depthTest) +#define DEBUG_DRAW_LINES(lines, transform, color, duration, depthTest) DebugDraw::DrawLines(lines, transform, color, duration, depthTest) +#define DEBUG_DRAW_BEZIER(p1, p2, p3, p4, color, duration, depthTest) DebugDraw::DrawBezier(p1, p2, p3, p4, color, duration, depthTest) +#define DEBUG_DRAW_CIRCLE(position, normal, radius, color, duration, depthTest) DebugDraw::DrawCircle(position, normal, radius, color, duration, depthTest) +#define DEBUG_DRAW_TRIANGLE(v0, v1, v2, color, duration, depthTest) DebugDraw::DrawTriangle(v0, v1, v2, color, duration, depthTest) +#define DEBUG_DRAW_TRIANGLES(vertices, color, duration, depthTest) DebugDraw::DrawTriangles(vertices, color, duration, depthTest) +#define DEBUG_DRAW_TRIANGLES_EX(vertices, indices, color, duration, depthTest) DebugDraw::DrawTriangles(vertices, indices, color, duration, depthTest) +#define DEBUG_DRAW_TRIANGLES_EX2(vertices, indices, transform, color, duration, depthTest) DebugDraw::DrawTriangles(vertices, indices, transform, color, duration, depthTest) +#define DEBUG_DRAW_SPHERE(sphere, color, duration, depthTest) DebugDraw::DrawSphere(sphere, color, duration, depthTest) +#define DEBUG_DRAW_TUBE(position, orientation, radius, length, color, duration, depthTest) DebugDraw::DrawTube(position, orientation, radius, length, color, duration, depthTest) +#define DEBUG_DRAW_BOX(box, color, duration, depthTest) DebugDraw::DrawBox(box, color, duration, depthTest) +#define DEBUG_DRAW_CYLINDER(position, orientation, radius, height, color, duration, depthTest) DebugDraw::DrawCylinder(position, orientation, radius, height, color, duration, depthTest) +#define DEBUG_DRAW_CONE(position, orientation, radius, angleXY, angleXZ, color, duration, depthTest) DebugDraw::DrawCone(position, orientation, radius, angleXY, angleXZ, color, duration, depthTest) +#define DEBUG_DRAW_ARC(position, orientation, radius, angle, color, duration, depthTest) DebugDraw::DrawArc(position, orientation, radius, angle, color, duration, depthTest) +#define DEBUG_DRAW_WIRE_TRIANGLE(v0, v1, v2, color, duration, depthTest) DebugDraw::DrawWireTriangle(v0, v1, v2, color, duration, depthTest) +#define DEBUG_DRAW_WIRE_TRIANGLES(vertices, color, duration, depthTest) DebugDraw::DrawWireTriangles(vertices, color, duration, depthTest) +#define DEBUG_DRAW_WIRE_TRIANGLES_EX(vertices, indices, color, duration, depthTest) DebugDraw::DrawWireTriangles(vertices, indices, color, duration, depthTest) +#define DEBUG_DRAW_WIRE_BOX(box, color, duration, depthTest) DebugDraw::DrawWireBox(box, color, duration, depthTest) +#define DEBUG_DRAW_WIRE_FRUSTUM(frustum, color, duration, depthTest) DebugDraw::DrawWireFrustum(frustum, color, duration, depthTest) +#define DEBUG_DRAW_WIRE_SPHERE(sphere, color, duration, depthTest) DebugDraw::DrawWireSphere(sphere, color, duration, depthTest) +#define DEBUG_DRAW_WIRE_TUBE(position, orientation, radius, length, color, duration, depthTest) DebugDraw::DrawWireTube(position, orientation, radius, length, color, duration, depthTest) +#define DEBUG_DRAW_WIRE_CYLINDER(position, orientation, radius, height, color, duration, depthTest) DebugDraw::DrawWireCylinder(position, orientation, radius, height, color, duration, depthTest) +#define DEBUG_DRAW_WIRE_CONE(position, orientation, radius, angleXY, angleXZ, color, duration, depthTest) DebugDraw::DrawWireCone(position, orientation, radius, angleXY, angleXZ, color, duration, depthTest) +#define DEBUG_DRAW_WIRE_ARC(position, orientation, radius, angle, color, duration, depthTest) DebugDraw::DrawWireArc(position, orientation, radius, angle, color, duration, depthTest) +#define DEBUG_DRAW_WIRE_ARROW(position, orientation, scale, color, duration, depthTest) DebugDraw::DrawWireArrow(position, orientation, scale, color, duration, depthTest) +#define DEBUG_DRAW_TEXT(text, position, color, size, duration) DebugDraw::DrawText(text, position, color, size, duration) #else +#define DEBUG_DRAW_AXIS_FROM_DIRECTION(origin,direction,color,Size,duration,depthTest) +#define DEBUG_DRAW_DIRECTION (origin,direction,color,duration,depthTest) +#define DEBUG_DRAW_RAY (origin,direction,color,length,duration,depthTest) +#define DEBUG_DRAW_RAY (ray,color,length,duration,depthTest) -#define DEBUG_DRAW_LINE(start, end, color, duration, depthTest) -#define DEBUG_DRAW_LINES(lines, transform, color, duration, depthTest) -#define DEBUG_DRAW_BEZIER(p1, p2, p3, p4, color, duration, depthTest) -#define DEBUG_DRAW_CIRCLE(position, normal, radius, color, duration, depthTest) -#define DEBUG_DRAW_TRIANGLE(v0, v1, v2, color, duration, depthTest) -#define DEBUG_DRAW_TRIANGLES(vertices, color, duration, depthTest) -#define DEBUG_DRAW_TRIANGLES_EX(vertices, indices, color, duration, depthTest) -#define DEBUG_DRAW_TRIANGLES_EX2(vertices, indices, transform, color, duration, depthTest) -#define DEBUG_DRAW_SPHERE(sphere, color, duration, depthTest) -#define DEBUG_DRAW_TUBE(position, orientation, radius, length, color, duration, depthTest) -#define DEBUG_DRAW_BOX(box, color, duration, depthTest) -#define DEBUG_DRAW_CYLINDER(position, orientation, radius, height, color, duration, depthTest) -#define DEBUG_DRAW_CONE(position, orientation, radius, angleXY, angleXZ, color, duration, depthTest) -#define DEBUG_DRAW_ARC(position, orientation, radius, angle, color, duration, depthTest) -#define DEBUG_DRAW_WIRE_TRIANGLE(v0, v1, v2, color, duration, depthTest) -#define DEBUG_DRAW_WIRE_TRIANGLES(vertices, color, duration, depthTest) -#define DEBUG_DRAW_WIRE_TRIANGLES_EX(vertices, indices, color, duration, depthTest) -#define DEBUG_DRAW_WIRE_BOX(box, color, duration, depthTest) -#define DEBUG_DRAW_WIRE_FRUSTUM(frustum, color, duration, depthTest) -#define DEBUG_DRAW_WIRE_SPHERE(sphere, color, duration, depthTest) -#define DEBUG_DRAW_WIRE_TUBE(position, orientation, radius, length, color, duration, depthTest) -#define DEBUG_DRAW_WIRE_CYLINDER(position, orientation, radius, height, color, duration, depthTest) +#define DEBUG_DRAW_RAY(origin, direction, color, duration, depthTest) +#define DEBUG_DRAW_LINE(start, end, color, duration, depthTest) +#define DEBUG_DRAW_LINES(lines, transform, color, duration, depthTest) +#define DEBUG_DRAW_BEZIER(p1, p2, p3, p4, color, duration, depthTest) +#define DEBUG_DRAW_CIRCLE(position, normal, radius, color, duration, depthTest) +#define DEBUG_DRAW_TRIANGLE(v0, v1, v2, color, duration, depthTest) +#define DEBUG_DRAW_TRIANGLES(vertices, color, duration, depthTest) +#define DEBUG_DRAW_TRIANGLES_EX(vertices, indices, color, duration, depthTest) +#define DEBUG_DRAW_TRIANGLES_EX2(vertices, indices, transform, color, duration, depthTest) +#define DEBUG_DRAW_SPHERE(sphere, color, duration, depthTest) +#define DEBUG_DRAW_TUBE(position, orientation, radius, length, color, duration, depthTest) +#define DEBUG_DRAW_BOX(box, color, duration, depthTest) +#define DEBUG_DRAW_CYLINDER(position, orientation, radius, height, color, duration, depthTest) +#define DEBUG_DRAW_CONE(position, orientation, radius, angleXY, angleXZ, color, duration, depthTest) +#define DEBUG_DRAW_ARC(position, orientation, radius, angle, color, duration, depthTest) +#define DEBUG_DRAW_WIRE_TRIANGLE(v0, v1, v2, color, duration, depthTest) +#define DEBUG_DRAW_WIRE_TRIANGLES(vertices, color, duration, depthTest) +#define DEBUG_DRAW_WIRE_TRIANGLES_EX(vertices, indices, color, duration, depthTest) +#define DEBUG_DRAW_WIRE_BOX(box, color, duration, depthTest) +#define DEBUG_DRAW_WIRE_FRUSTUM(frustum, color, duration, depthTest) +#define DEBUG_DRAW_WIRE_SPHERE(sphere, color, duration, depthTest) +#define DEBUG_DRAW_WIRE_TUBE(position, orientation, radius, length, color, duration, depthTest) +#define DEBUG_DRAW_WIRE_CYLINDER(position, orientation, radius, height, color, duration, depthTest) #define DEBUG_DRAW_WIRE_CONE(position, orientation, radius, angleXY, angleXZ, color, duration, depthTest) -#define DEBUG_DRAW_WIRE_ARC(position, orientation, radius, angle, color, duration, depthTest) -#define DEBUG_DRAW_WIRE_ARROW(position, orientation, scale, color, duration, depthTest) -#define DEBUG_DRAW_TEXT(text, position, color, size, duration) +#define DEBUG_DRAW_WIRE_ARC(position, orientation, radius, angle, color, duration, depthTest) +#define DEBUG_DRAW_WIRE_ARROW(position, orientation, scale, color, duration, depthTest) +#define DEBUG_DRAW_TEXT(text, position, color, size, duration) #endif From 394a3696af4d4b040296b94de783791564faf77e Mon Sep 17 00:00:00 2001 From: NoriteSC <53096989+NoriteSC@users.noreply.github.com> Date: Wed, 13 Sep 2023 02:54:17 +0200 Subject: [PATCH 006/139] removed coppy --- Source/Engine/Debug/DebugDraw.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Source/Engine/Debug/DebugDraw.h b/Source/Engine/Debug/DebugDraw.h index 5df31d831..c557fa57c 100644 --- a/Source/Engine/Debug/DebugDraw.h +++ b/Source/Engine/Debug/DebugDraw.h @@ -684,8 +684,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw #define DEBUG_DRAW_DIRECTION (origin,direction,color,duration,depthTest) #define DEBUG_DRAW_RAY (origin,direction,color,length,duration,depthTest) #define DEBUG_DRAW_RAY (ray,color,length,duration,depthTest) - -#define DEBUG_DRAW_RAY(origin, direction, color, duration, depthTest) + #define DEBUG_DRAW_LINE(start, end, color, duration, depthTest) #define DEBUG_DRAW_LINES(lines, transform, color, duration, depthTest) #define DEBUG_DRAW_BEZIER(p1, p2, p3, p4, color, duration, depthTest) From 0b3b32195abc889a334eca24cc9abf106f194126 Mon Sep 17 00:00:00 2001 From: NoriteSC <53096989+NoriteSC@users.noreply.github.com> Date: Wed, 13 Sep 2023 12:17:17 +0200 Subject: [PATCH 007/139] Update DebugDraw.cs --- Source/Engine/Debug/DebugDraw.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Source/Engine/Debug/DebugDraw.cs b/Source/Engine/Debug/DebugDraw.cs index f6a8a19f4..5884fc4fa 100644 --- a/Source/Engine/Debug/DebugDraw.cs +++ b/Source/Engine/Debug/DebugDraw.cs @@ -40,7 +40,7 @@ namespace FlaxEngine /// The size of the axis. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - static void DrawAxisFromDirection(Vector3 origin, Vector3 direction, Color color ,float Size = 100.0f, float duration = 0.0f, bool depthTest = true); + static void DrawAxisFromDirection(Vector3 origin, Vector3 direction, Color color ,float Size = 100.0f, float duration = 0.0f, bool depthTest = true){} /// /// Draws the line in a direction. @@ -50,7 +50,7 @@ namespace FlaxEngine /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - static void DrawDirection(Vector3 origin, Vector3 direction, Color color, float duration = 0.0f, bool depthTest = true); + static void DrawDirection(Vector3 origin, Vector3 direction, Color color, float duration = 0.0f, bool depthTest = true){} /// /// Draws the line in a direction. @@ -61,7 +61,7 @@ namespace FlaxEngine /// The length of the ray. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - static void DrawRay(Vector3 origin, Vector3 direction, Color color, float length = 3.402823466e+38f, float duration = 0.0f, bool depthTest = true); + static void DrawRay(Vector3 origin, Vector3 direction, Color color, float length = 3.402823466e+38f, float duration = 0.0f, bool depthTest = true){} /// /// Draws the line in a direction. @@ -71,7 +71,9 @@ namespace FlaxEngine /// The length of the ray. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - static void DrawRay(Ray ray,Color color, float length = 3.402823466e+38f, float duration = 0.0f, bool depthTest = true); + static void DrawRay(Ray ray,Color color, float length = 3.402823466e+38f, float duration = 0.0f, bool depthTest = true) + { + } /// /// Draws the line. From 4451f5feb280165bd06caeeff37cdcd3925787a6 Mon Sep 17 00:00:00 2001 From: Ruan Lucas <79365912+RuanLucasGD@users.noreply.github.com> Date: Thu, 21 Sep 2023 12:44:47 -0400 Subject: [PATCH 008/139] add spline snap --- Source/Editor/SceneGraph/Actors/SplineNode.cs | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/Source/Editor/SceneGraph/Actors/SplineNode.cs b/Source/Editor/SceneGraph/Actors/SplineNode.cs index 1b35cdc35..a6d07fc2a 100644 --- a/Source/Editor/SceneGraph/Actors/SplineNode.cs +++ b/Source/Editor/SceneGraph/Actors/SplineNode.cs @@ -7,6 +7,7 @@ using Real = System.Single; #endif using System; +using System.Collections.Generic; using FlaxEditor.GUI.ContextMenu; using FlaxEditor.Modules; using FlaxEngine; @@ -287,6 +288,10 @@ namespace FlaxEditor.SceneGraph.Actors private const Real PointNodeSize = 1.5f; private const Real TangentNodeSize = 1.0f; + private const Real SnapIndicatorSize = 1.7f; + private const Real SnapPointIndicatorSize = 2f; + + private static Spline _currentEditSpline; /// public SplineNode(Actor actor) @@ -298,6 +303,11 @@ namespace FlaxEditor.SceneGraph.Actors private unsafe void OnUpdate() { + if (Input.Keyboard.GetKey(KeyboardKeys.Shift)) + { + EditSplineWithSnap(); + } + // Sync spline points with gizmo handles var actor = (Spline)Actor; var dstCount = actor.SplinePointsCount; @@ -328,6 +338,56 @@ namespace FlaxEditor.SceneGraph.Actors } } + private void EditSplineWithSnap() + { + if (_currentEditSpline == null || _currentEditSpline != Actor) + return; + + var selectedNode = SelectedSplineNode(); + if (selectedNode == null) + return; + + var selectedNodeBounds = new BoundingSphere(selectedNode.Transform.Translation, 1f); + var allSplinesInView = GetSplinesOnView(); + allSplinesInView.Remove(_currentEditSpline); + + if (allSplinesInView.Count == 0 || selectedNode == null) + return; + + var snappedOnSplinePoint = false; + for (int i = 0; i < allSplinesInView.Count; i++) + { + for (int x = 0; x < allSplinesInView[i].SplineKeyframes.Length; x++) + { + var keyframePosition = allSplinesInView[i].GetSplinePoint(x); + var pointIndicatorSize = NodeSizeByDistance(keyframePosition, SnapPointIndicatorSize); + var keyframeBounds = new BoundingSphere(keyframePosition, pointIndicatorSize); + DebugDraw.DrawSphere(keyframeBounds, Color.Red, 0, false); + + if (keyframeBounds.Intersects(selectedNodeBounds)) + { + _currentEditSpline.SetSplinePoint(selectedNode.Index, keyframeBounds.Center); + snappedOnSplinePoint = true; + break; + } + } + } + + if (!snappedOnSplinePoint) + { + var nearSplineSnapPoint = GetNearSplineSnapPosition(selectedNode.Transform.Translation, allSplinesInView); + var snapIndicatorSize = NodeSizeByDistance(nearSplineSnapPoint, SnapIndicatorSize); + var snapBounds = new BoundingSphere(nearSplineSnapPoint, snapIndicatorSize); + + if (snapBounds.Intersects(selectedNodeBounds)) + { + _currentEditSpline.SetSplinePoint(selectedNode.Index, snapBounds.Center); + } + + DebugDraw.DrawSphere(snapBounds, Color.Yellow, 0, true); + } + } + /// public override void PostSpawn() { @@ -396,6 +456,7 @@ namespace FlaxEditor.SceneGraph.Actors internal static void OnSplineEdited(Spline spline) { + _currentEditSpline = spline; var collider = spline.GetChild(); if (collider && collider.Scene && collider.IsActiveInHierarchy && collider.HasStaticFlag(StaticFlags.Navigation) && !Editor.IsPlayMode) { @@ -407,6 +468,60 @@ namespace FlaxEditor.SceneGraph.Actors } } + private static SplinePointNode SelectedSplineNode() + { + var selection = Editor.Instance.SceneEditing.Selection; + if (selection.Count != 1) + return null; + if (selection[0] is not SplineNode.SplinePointNode) + return null; + + return (SplinePointNode)selection[0]; + } + + private static List GetSplinesOnView() + { + var splines = Level.GetActors(); + var splinesOnView = new List(); + + var viewTransform = Editor.Instance.Windows.EditWin.Viewport.ViewTransform; + var viewFov = Editor.Instance.Windows.EditWin.Viewport.FieldOfView; + var viewNear = Editor.Instance.Windows.EditWin.Viewport.NearPlane; + var viewFar = Editor.Instance.Windows.EditWin.Viewport.FarPlane; + var viewAspect = Editor.Instance.Windows.EditWin.Width / Editor.Instance.Windows.EditWin.Height; + var viewBounds = BoundingFrustum.FromCamera(viewTransform.Translation, viewTransform.Forward, viewTransform.Up, viewFov, viewNear, viewFar, viewAspect); + + foreach (var s in splines) + { + var contains = viewBounds.Contains(s.EditorBox); + if (contains == ContainmentType.Contains || contains == ContainmentType.Intersects) + { + splinesOnView.Add(s); + } + } + + return splinesOnView; + } + + private static Vector3 GetNearSplineSnapPosition(Vector3 position, List splines) + { + var nearPoint = splines[0].GetSplinePointClosestToPoint(position); + var nearDistance = Vector3.Distance(nearPoint, position); + + for (int i = 1; i < splines.Count; i++) + { + var point = splines[i].GetSplinePointClosestToPoint(position); + var distance = Vector3.Distance(point, position); + if (distance < nearDistance) + { + nearPoint = point; + nearDistance = distance; + } + } + + return nearPoint; + } + internal static Real NodeSizeByDistance(Vector3 nodePosition, Real nodeSize) { var cameraTransform = Editor.Instance.Windows.EditWin.Viewport.ViewportCamera.Viewport.ViewTransform; From 29e1d9855a6b52d3ffb00cec3f6062860d1a6d5a Mon Sep 17 00:00:00 2001 From: Ruan Lucas <79365912+RuanLucasGD@users.noreply.github.com> Date: Fri, 22 Sep 2023 15:32:07 -0400 Subject: [PATCH 009/139] add hability to create spline points with mouse click --- Source/Editor/SceneGraph/Actors/SplineNode.cs | 101 +++++++++++++++++- 1 file changed, 96 insertions(+), 5 deletions(-) diff --git a/Source/Editor/SceneGraph/Actors/SplineNode.cs b/Source/Editor/SceneGraph/Actors/SplineNode.cs index a6d07fc2a..d752073d1 100644 --- a/Source/Editor/SceneGraph/Actors/SplineNode.cs +++ b/Source/Editor/SceneGraph/Actors/SplineNode.cs @@ -8,10 +8,12 @@ using Real = System.Single; using System; using System.Collections.Generic; -using FlaxEditor.GUI.ContextMenu; +using FlaxEditor.Actions; using FlaxEditor.Modules; +using FlaxEditor.GUI.ContextMenu; using FlaxEngine; using FlaxEngine.Json; +using FlaxEngine.Utilities; using Object = FlaxEngine.Object; namespace FlaxEditor.SceneGraph.Actors @@ -301,14 +303,24 @@ namespace FlaxEditor.SceneGraph.Actors FlaxEngine.Scripting.Update += OnUpdate; } - private unsafe void OnUpdate() + private void OnUpdate() { if (Input.Keyboard.GetKey(KeyboardKeys.Shift)) { EditSplineWithSnap(); } - // Sync spline points with gizmo handles + var canAddSplinePoint = Input.Mouse.PositionDelta == Float2.Zero && Input.Mouse.Position != Float2.Zero; + var requestAddSplinePoint = Input.Keyboard.GetKey(KeyboardKeys.Control) && Input.Mouse.GetButtonDown(MouseButton.Right); + + if (requestAddSplinePoint && canAddSplinePoint) + AddSplinePoint(); + + SyncSplineKeyframeWithNodes(); + } + + private unsafe void SyncSplineKeyframeWithNodes() + { var actor = (Spline)Actor; var dstCount = actor.SplinePointsCount; if (dstCount > 1 && actor.IsLoop) @@ -338,12 +350,91 @@ namespace FlaxEditor.SceneGraph.Actors } } + private unsafe void AddSplinePoint() + { + var selectedPoint = GetSelectedSplineNode(); + if (selectedPoint == null) + return; + + // checking mouse hit on scene + var spline = (Spline)Actor; + var viewport = Editor.Instance.Windows.EditWin.Viewport; + var mouseRay = viewport.MouseRay; + var viewRay = new Ray(viewport.ViewPosition, viewport.ViewDirection); + var flags = RayCastData.FlagTypes.SkipColliders | RayCastData.FlagTypes.SkipEditorPrimitives; + var hit = Editor.Instance.Scene.Root.RayCast(ref mouseRay, ref viewRay, out var closest, out var normal, flags); + + if (hit == null) + return; + + // Undo data + var oldSpline = spline.SplineKeyframes; + var editAction = new EditSplineAction(spline, oldSpline); + Root.Undo.AddAction(editAction); + + // Getting spline point to duplicate + var hitPoint = mouseRay.Position + mouseRay.Direction * closest; + var lastPointIndex = selectedPoint.Index; + var newPointIndex = lastPointIndex > 0 ? lastPointIndex + 1 : 0; + var lastKeyframe = spline.GetSplineKeyframe(lastPointIndex); + var isLastPoint = lastPointIndex == spline.SplinePointsCount - 1; + var isFirstPoint = lastPointIndex == 0; + + // Getting data to create new point + + var lastPointTime = spline.GetSplineTime(lastPointIndex); + var nextPointTime = isLastPoint ? lastPointTime : spline.GetSplineTime(newPointIndex); + var newTime = isLastPoint ? lastPointTime + 1.0f : (lastPointTime + nextPointTime) * 0.5f; + var distanceFromLastPoint = Vector3.Distance(hitPoint, spline.GetSplinePoint(lastPointIndex)); + var newPointDirection = spline.GetSplineTangent(lastPointIndex, false).Translation - hitPoint; + + // set correctly keyframe direction on spawn point + if (isFirstPoint) + newPointDirection = hitPoint - spline.GetSplineTangent(lastPointIndex, true).Translation; + else if (isLastPoint) + newPointDirection = spline.GetSplineTangent(lastPointIndex, false).Translation - hitPoint; + + var newPointLocalPosition = spline.Transform.WorldToLocal(hitPoint); + var newPointLocalOrientation = Quaternion.LookRotation(newPointDirection); + + // Adding new point + spline.InsertSplinePoint(newPointIndex, newTime, Transform.Identity, false); + var newKeyframe = lastKeyframe.DeepClone(); + var newKeyframeTransform = newKeyframe.Value; + newKeyframeTransform.Translation = newPointLocalPosition; + newKeyframeTransform.Orientation = newPointLocalOrientation; + newKeyframe.Value = newKeyframeTransform; + + // Setting new point keyframe + var newkeyframeTangentIn = Transform.Identity; + var newkeyframeTangentOut = Transform.Identity; + newkeyframeTangentIn.Translation = (Vector3.Forward * newPointLocalOrientation) * distanceFromLastPoint; + newkeyframeTangentOut.Translation = (Vector3.Backward * newPointLocalOrientation) * distanceFromLastPoint; + newKeyframe.TangentIn = newkeyframeTangentIn; + newKeyframe.TangentOut = newkeyframeTangentOut; + spline.SetSplineKeyframe(newPointIndex, newKeyframe); + + for (int i = 1; i < spline.SplinePointsCount; i++) + { + // check all elements to don't left keyframe has invalid time + // because points can be added on start or on middle of spline + // conflicting with time of another keyframes + spline.SetSplinePointTime(i, i, false); + } + + // Select new point node + SyncSplineKeyframeWithNodes(); + Editor.Instance.SceneEditing.Select(ChildNodes[newPointIndex]); + + spline.UpdateSpline(); + } + private void EditSplineWithSnap() { if (_currentEditSpline == null || _currentEditSpline != Actor) return; - var selectedNode = SelectedSplineNode(); + var selectedNode = GetSelectedSplineNode(); if (selectedNode == null) return; @@ -468,7 +559,7 @@ namespace FlaxEditor.SceneGraph.Actors } } - private static SplinePointNode SelectedSplineNode() + private static SplinePointNode GetSelectedSplineNode() { var selection = Editor.Instance.SceneEditing.Selection; if (selection.Count != 1) From 809b9e3a7ae5aef0d4a196a482cec3e641429836 Mon Sep 17 00:00:00 2001 From: NoriteSC <53096989+NoriteSC@users.noreply.github.com> Date: Sun, 1 Oct 2023 16:47:33 +0200 Subject: [PATCH 010/139] added safeguards --- Source/Engine/Debug/DebugDraw.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Source/Engine/Debug/DebugDraw.cpp b/Source/Engine/Debug/DebugDraw.cpp index 733c4a643..1e50a74be 100644 --- a/Source/Engine/Debug/DebugDraw.cpp +++ b/Source/Engine/Debug/DebugDraw.cpp @@ -934,15 +934,22 @@ void DebugDraw::DrawAxisFromDirection(const Vector3& origin, const Vector3& dire void DebugDraw::DrawDirection(const Vector3& origin, const Vector3& direction, const Color& color, float duration, bool depthTest) { + auto dir = origin + direction; + if (dir.IsNanOrInfinity()) + return; DrawLine(origin, origin + direction, color, duration, depthTest); } void DebugDraw::DrawRay(const Vector3& origin, const Vector3& direction,const Color& color, float length, float duration, bool depthTest) { + if (isnan(length) || isinf(length)) + return; DrawLine(origin, origin + (direction.GetNormalized() * length), color, duration, depthTest); } void DebugDraw::DrawRay(const Ray& ray,const Color& color, float length, float duration, bool depthTest) { + if (isnan(length) || isinf(length)) + return; DrawLine(ray.Position, ray.Position + (ray.Direction.GetNormalized() * length), color, duration, depthTest); } From 0a127249743917f5a15b86142cc0a4469fea8532 Mon Sep 17 00:00:00 2001 From: NoriteSC <53096989+NoriteSC@users.noreply.github.com> Date: Tue, 10 Oct 2023 22:04:30 +0000 Subject: [PATCH 011/139] changed the 3.402823466e+38f to MAX_float --- Source/Engine/Debug/DebugDraw.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Debug/DebugDraw.h b/Source/Engine/Debug/DebugDraw.h index c557fa57c..140a11013 100644 --- a/Source/Engine/Debug/DebugDraw.h +++ b/Source/Engine/Debug/DebugDraw.h @@ -99,7 +99,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The length of the ray. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawRay(const Vector3& origin, const Vector3& direction, const Color& color = Color::White, float length = 3.402823466e+38f, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawRay(const Vector3& origin, const Vector3& direction, const Color& color = Color::White, float length = MAX_float, float duration = 0.0f, bool depthTest = true); /// /// Draws the line in a direction. @@ -109,7 +109,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The length of the ray. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawRay(const Ray& ray,const Color& color = Color::White, float length = 3.402823466e+38f, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawRay(const Ray& ray,const Color& color = Color::White, float length = MAX_float, float duration = 0.0f, bool depthTest = true); /// /// Draws the line. From 4f67d142612410fc57a01e99835774bf93eb0c25 Mon Sep 17 00:00:00 2001 From: NoriteSC <53096989+NoriteSC@users.noreply.github.com> Date: Tue, 10 Oct 2023 22:15:31 +0000 Subject: [PATCH 012/139] forgotten the public keyword in engine/debug/debugdraw.cs --- Source/Engine/Debug/DebugDraw.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/Engine/Debug/DebugDraw.cs b/Source/Engine/Debug/DebugDraw.cs index 5884fc4fa..a6fc7b3b2 100644 --- a/Source/Engine/Debug/DebugDraw.cs +++ b/Source/Engine/Debug/DebugDraw.cs @@ -40,7 +40,7 @@ namespace FlaxEngine /// The size of the axis. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - static void DrawAxisFromDirection(Vector3 origin, Vector3 direction, Color color ,float Size = 100.0f, float duration = 0.0f, bool depthTest = true){} + public static void DrawAxisFromDirection(Vector3 origin, Vector3 direction, Color color ,float Size = 100.0f, float duration = 0.0f, bool depthTest = true){} /// /// Draws the line in a direction. @@ -50,7 +50,7 @@ namespace FlaxEngine /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - static void DrawDirection(Vector3 origin, Vector3 direction, Color color, float duration = 0.0f, bool depthTest = true){} + public static void DrawDirection(Vector3 origin, Vector3 direction, Color color, float duration = 0.0f, bool depthTest = true){} /// /// Draws the line in a direction. @@ -61,7 +61,7 @@ namespace FlaxEngine /// The length of the ray. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - static void DrawRay(Vector3 origin, Vector3 direction, Color color, float length = 3.402823466e+38f, float duration = 0.0f, bool depthTest = true){} + public static void DrawRay(Vector3 origin, Vector3 direction, Color color, float length = 3.402823466e+38f, float duration = 0.0f, bool depthTest = true){} /// /// Draws the line in a direction. @@ -71,7 +71,7 @@ namespace FlaxEngine /// The length of the ray. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - static void DrawRay(Ray ray,Color color, float length = 3.402823466e+38f, float duration = 0.0f, bool depthTest = true) + public static void DrawRay(Ray ray,Color color, float length = 3.402823466e+38f, float duration = 0.0f, bool depthTest = true) { } From 6fb411cee32acb8cb1f1ce5a66af58afb9d9ab1e Mon Sep 17 00:00:00 2001 From: MineBill Date: Thu, 19 Oct 2023 01:05:34 +0300 Subject: [PATCH 013/139] Fix terrain painting. --- .../Tools/Terrain/Paint/SingleLayerMode.cs | 34 +++++-------------- 1 file changed, 8 insertions(+), 26 deletions(-) diff --git a/Source/Editor/Tools/Terrain/Paint/SingleLayerMode.cs b/Source/Editor/Tools/Terrain/Paint/SingleLayerMode.cs index ec41b5286..3bb014e24 100644 --- a/Source/Editor/Tools/Terrain/Paint/SingleLayerMode.cs +++ b/Source/Editor/Tools/Terrain/Paint/SingleLayerMode.cs @@ -1,6 +1,5 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. -using System; using FlaxEngine; namespace FlaxEditor.Tools.Terrain.Paint @@ -87,32 +86,15 @@ namespace FlaxEditor.Tools.Terrain.Paint var samplePositionLocal = p.PatchPositionLocal + new Vector3(xx * FlaxEngine.Terrain.UnitsPerVertex, 0, zz * FlaxEngine.Terrain.UnitsPerVertex); Vector3.Transform(ref samplePositionLocal, ref p.TerrainWorld, out Vector3 samplePositionWorld); + + var sample = Mathf.Clamp(p.Brush.Sample(ref brushPosition, ref samplePositionWorld), 0f, 1f); + var paintAmount = sample * strength * (1f - src[layerComponent] / 255f); - var paintAmount = p.Brush.Sample(ref brushPosition, ref samplePositionWorld) * strength; - - // Extract layer weight - byte* srcPtr = &src.R; - var srcWeight = *(srcPtr + layerComponent) / 255.0f; - - // Accumulate weight - float dstWeight = srcWeight + paintAmount; - - // Check for solid layer case - if (dstWeight >= 1.0f) - { - // Erase other layers - // TODO: maybe erase only the higher layers? - // TODO: need to erase also weights form the other splatmaps - src = Color32.Transparent; - - // Use limit value - dstWeight = 1.0f; - } - - // Modify packed weight - *(srcPtr + layerComponent) = (byte)(dstWeight * 255.0f); - - // Write back + src[layerComponent] = (byte)Mathf.Clamp(src[layerComponent] + paintAmount * 255, 0, 255); + src[(layerComponent + 1) % 4] = (byte)Mathf.Clamp(src[(layerComponent + 1) % 4] - paintAmount * 255, 0, 255); + src[(layerComponent + 2) % 4] = (byte)Mathf.Clamp(src[(layerComponent + 2) % 4] - paintAmount * 255, 0, 255); + src[(layerComponent + 3) % 4] = (byte)Mathf.Clamp(src[(layerComponent + 3) % 4] - paintAmount * 255, 0, 255); + p.TempBuffer[z * p.ModifiedSize.X + x] = src; } } From fff8a1e8a6b78de58c0e29287656eaaf3bb09b64 Mon Sep 17 00:00:00 2001 From: "Mr. Capybara" Date: Sun, 22 Oct 2023 13:26:07 -0400 Subject: [PATCH 014/139] Optimize actor search --- Source/Engine/Level/Actor.cpp | 12 ++++++++---- Source/Engine/Level/Actor.cs | 12 +++++++----- Source/Engine/Level/Actor.h | 6 ++++-- Source/Engine/Level/Level.cpp | 28 ++++++++++++++++------------ Source/Engine/Level/Level.h | 9 ++++++--- 5 files changed, 41 insertions(+), 26 deletions(-) diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp index 75e3c12d4..06ddd8d8d 100644 --- a/Source/Engine/Level/Actor.cpp +++ b/Source/Engine/Level/Actor.cpp @@ -1359,14 +1359,16 @@ Actor* Actor::FindActor(const StringView& name) const return result; } -Actor* Actor::FindActor(const MClass* type) const +Actor* Actor::FindActor(const MClass* type, bool activeOnly) const { CHECK_RETURN(type, nullptr); + if (activeOnly && !_isActive) + return nullptr; if (GetClass()->IsSubClassOf(type)) return const_cast(this); for (auto child : Children) { - const auto actor = child->FindActor(type); + const auto actor = child->FindActor(type, activeOnly); if (actor) return actor; } @@ -1387,14 +1389,16 @@ Actor* Actor::FindActor(const MClass* type, const StringView& name) const return nullptr; } -Actor* Actor::FindActor(const MClass* type, const Tag& tag) const +Actor* Actor::FindActor(const MClass* type, const Tag& tag, bool activeOnly) const { CHECK_RETURN(type, nullptr); + if (activeOnly && !_isActive) + return nullptr; if (GetClass()->IsSubClassOf(type) && HasTag(tag)) return const_cast(this); for (auto child : Children) { - const auto actor = child->FindActor(type, tag); + const auto actor = child->FindActor(type, tag, activeOnly); if (actor) return actor; } diff --git a/Source/Engine/Level/Actor.cs b/Source/Engine/Level/Actor.cs index dbe8a89b5..f1fc09bfd 100644 --- a/Source/Engine/Level/Actor.cs +++ b/Source/Engine/Level/Actor.cs @@ -253,10 +253,11 @@ namespace FlaxEngine /// Tries to find the actor of the given type in this actor hierarchy (checks this actor and all children hierarchy). /// /// Type of the object. + /// Finds only a active actor. /// Actor instance if found, null otherwise. - public T FindActor() where T : Actor + public T FindActor(bool activeOnly = false) where T : Actor { - return FindActor(typeof(T)) as T; + return FindActor(typeof(T), activeOnly) as T; } /// @@ -269,16 +270,17 @@ namespace FlaxEngine { return FindActor(typeof(T), name) as T; } - + /// /// Tries to find actor of the given type and tag in this actor hierarchy (checks this actor and all children hierarchy). /// /// A tag on the object. /// Type of the object. + /// Finds only an active actor. /// Actor instance if found, null otherwise. - public T FindActor(Tag tag) where T : Actor + public T FindActor(Tag tag, bool activeOnly = false) where T : Actor { - return FindActor(typeof(T), tag) as T; + return FindActor(typeof(T), tag, activeOnly) as T; } /// diff --git a/Source/Engine/Level/Actor.h b/Source/Engine/Level/Actor.h index a1f2dc78f..ded07fce5 100644 --- a/Source/Engine/Level/Actor.h +++ b/Source/Engine/Level/Actor.h @@ -751,8 +751,9 @@ public: /// Tries to find the actor of the given type in this actor hierarchy (checks this actor and all children hierarchy). /// /// Type of the actor to search for. Includes any actors derived from the type. + /// Finds only a active actor. /// Actor instance if found, null otherwise. - API_FUNCTION() Actor* FindActor(API_PARAM(Attributes="TypeReference(typeof(Actor))") const MClass* type) const; + API_FUNCTION() Actor* FindActor(API_PARAM(Attributes="TypeReference(typeof(Actor))") const MClass* type, bool activeOnly = false) const; /// /// Tries to find the actor of the given type and name in this actor hierarchy (checks this actor and all children hierarchy). @@ -767,8 +768,9 @@ public: /// /// Type of the actor to search for. Includes any actors derived from the type. /// The tag of the actor to search for. + /// Finds only an active actor. /// Actor instance if found, null otherwise. - API_FUNCTION() Actor* FindActor(API_PARAM(Attributes="TypeReference(typeof(Actor))") const MClass* type, const Tag& tag) const; + API_FUNCTION() Actor* FindActor(API_PARAM(Attributes="TypeReference(typeof(Actor))") const MClass* type, const Tag& tag, bool activeOnly = false) const; /// /// Tries to find the actor of the given type in this actor hierarchy (checks this actor and all children hierarchy). diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index dfd5786c6..19d0313bb 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -1421,13 +1421,13 @@ Actor* Level::FindActor(const StringView& name) return result; } -Actor* Level::FindActor(const MClass* type) +Actor* Level::FindActor(const MClass* type, bool activeOnly) { CHECK_RETURN(type, nullptr); Actor* result = nullptr; ScopeLock lock(ScenesLock); for (int32 i = 0; result == nullptr && i < Scenes.Count(); i++) - result = Scenes[i]->FindActor(type); + result = Scenes[i]->FindActor(type, activeOnly); return result; } @@ -1441,29 +1441,33 @@ Actor* Level::FindActor(const MClass* type, const StringView& name) return result; } -Actor* FindActorRecursive(Actor* node, const Tag& tag) +Actor* FindActorRecursive(Actor* node, const Tag& tag, bool activeOnly) { + if (activeOnly && !node->GetIsActive()) + return nullptr; if (node->HasTag(tag)) return node; Actor* result = nullptr; for (Actor* child : node->Children) { - result = FindActorRecursive(child, tag); + result = FindActorRecursive(child, tag, activeOnly); if (result) break; } return result; } -Actor* FindActorRecursiveByType(Actor* node, const MClass* type, const Tag& tag) +Actor* FindActorRecursiveByType(Actor* node, const MClass* type, const Tag& tag, bool activeOnly) { CHECK_RETURN(type, nullptr); + if (activeOnly && !node->GetIsActive()) + return nullptr; if (node->HasTag(tag) && node->GetClass()->IsSubClassOf(type)) return node; Actor* result = nullptr; for (Actor* child : node->Children) { - result = FindActorRecursiveByType(child, type, tag); + result = FindActorRecursiveByType(child, type, tag, activeOnly); if (result) break; } @@ -1496,30 +1500,30 @@ void FindActorsRecursiveByParentTags(Actor* node, const Array& tags, const FindActorsRecursiveByParentTags(child, tags, activeOnly, result); } -Actor* Level::FindActor(const Tag& tag, Actor* root) +Actor* Level::FindActor(const Tag& tag, bool activeOnly, Actor* root) { PROFILE_CPU(); if (root) - return FindActorRecursive(root, tag); + return FindActorRecursive(root, tag, activeOnly); Actor* result = nullptr; for (Scene* scene : Scenes) { - result = FindActorRecursive(scene, tag); + result = FindActorRecursive(scene, tag, activeOnly); if (result) break; } return result; } -Actor* Level::FindActor(const MClass* type, const Tag& tag, Actor* root) +Actor* Level::FindActor(const MClass* type, const Tag& tag, bool activeOnly, Actor* root) { CHECK_RETURN(type, nullptr); if (root) - return FindActorRecursiveByType(root, type, tag); + return FindActorRecursiveByType(root, type, tag, activeOnly); Actor* result = nullptr; ScopeLock lock(ScenesLock); for (int32 i = 0; result == nullptr && i < Scenes.Count(); i++) - result = Scenes[i]->FindActor(type, tag); + result = Scenes[i]->FindActor(type, tag, activeOnly); return result; } diff --git a/Source/Engine/Level/Level.h b/Source/Engine/Level/Level.h index 1f0acda2d..bb97b8908 100644 --- a/Source/Engine/Level/Level.h +++ b/Source/Engine/Level/Level.h @@ -360,8 +360,9 @@ public: /// Tries to find the actor of the given type in all the loaded scenes. /// /// Type of the actor to search for. Includes any actors derived from the type. + /// Finds only an active actor. /// Found actor or null. - API_FUNCTION() static Actor* FindActor(API_PARAM(Attributes="TypeReference(typeof(Actor))") const MClass* type); + API_FUNCTION() static Actor* FindActor(API_PARAM(Attributes="TypeReference(typeof(Actor))") const MClass* type, bool activeOnly = false); /// /// Tries to find the actor of the given type and name in all the loaded scenes. @@ -375,18 +376,20 @@ public: /// Tries to find the actor with the given tag (returns the first one found). /// /// The tag of the actor to search for. + /// Finds only an active actor. /// The custom root actor to start searching from (hierarchical), otherwise null to search all loaded scenes. /// Found actor or null. - API_FUNCTION() static Actor* FindActor(const Tag& tag, Actor* root = nullptr); + API_FUNCTION() static Actor* FindActor(const Tag& tag, bool activeOnly = false, Actor* root = nullptr); /// /// Tries to find the actor of the given type and tag in all the loaded scenes. /// /// Type of the actor to search for. Includes any actors derived from the type. /// The tag of the actor to search for. + /// Finds only an active actor. /// The custom root actor to start searching from (hierarchical), otherwise null to search all loaded scenes. /// Actor instance if found, null otherwise. - API_FUNCTION() static Actor* FindActor(API_PARAM(Attributes="TypeReference(typeof(Actor))") const MClass* type, const Tag& tag, Actor* root = nullptr); + API_FUNCTION() static Actor* FindActor(API_PARAM(Attributes="TypeReference(typeof(Actor))") const MClass* type, const Tag& tag, bool activeOnly = false, Actor* root = nullptr); /// /// Tries to find the actors with the given tag (returns all found). From 9d1b287cd7792a09e202a2bae6584cf5369bf48e Mon Sep 17 00:00:00 2001 From: Wiktor Kocielski Date: Wed, 18 Oct 2023 13:12:10 +0300 Subject: [PATCH 015/139] Terrain-related classes exposure to scripting API --- .../Engine/ShadowsOfMordor/Builder.Jobs.cpp | 2 +- Source/Engine/Terrain/Terrain.h | 18 +-- Source/Engine/Terrain/TerrainChunk.cpp | 6 + Source/Engine/Terrain/TerrainChunk.h | 38 +++--- Source/Engine/Terrain/TerrainPatch.cpp | 5 + Source/Engine/Terrain/TerrainPatch.h | 109 ++++++++++++------ 6 files changed, 111 insertions(+), 67 deletions(-) diff --git a/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp b/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp index 11d136d62..8aed89eb3 100644 --- a/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp +++ b/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp @@ -165,7 +165,7 @@ void ShadowsOfMordor::Builder::onJobRender(GPUContext* context) Matrix::Transpose(world, shaderData.WorldMatrix); shaderData.LightmapArea = chunk->Lightmap.UVsArea; shaderData.TerrainChunkSizeLOD0 = TERRAIN_UNITS_PER_VERTEX * chunkSize; - chunk->GetHeightmapUVScaleBias(&shaderData.HeightmapUVScaleBias); + shaderData.HeightmapUVScaleBias = chunk->GetHeightmapUVScaleBias(); // Extract per axis scales from LocalToWorld transform const float scaleX = Float3(world.M11, world.M12, world.M13).Length(); diff --git a/Source/Engine/Terrain/Terrain.h b/Source/Engine/Terrain/Terrain.h index 636f30206..5b8465cd0 100644 --- a/Source/Engine/Terrain/Terrain.h +++ b/Source/Engine/Terrain/Terrain.h @@ -219,7 +219,7 @@ public: /// /// The patch location (x and z). /// The patch. - TerrainPatch* GetPatch(const Int2& patchCoord) const; + API_FUNCTION() TerrainPatch* GetPatch(API_PARAM(Ref) const Int2& patchCoord) const; /// /// Gets the patch at the given location. @@ -227,7 +227,7 @@ public: /// The patch location x. /// The patch location z. /// The patch. - TerrainPatch* GetPatch(int32 x, int32 z) const; + API_FUNCTION() TerrainPatch* GetPatch(int32 x, int32 z) const; /// /// Gets the zero-based index of the terrain patch in the terrain patches collection. @@ -241,7 +241,7 @@ public: /// /// The index. /// The patch. - FORCE_INLINE TerrainPatch* GetPatch(int32 index) const + API_FUNCTION() FORCE_INLINE TerrainPatch* GetPatch(int32 index) const { return _patches[index]; } @@ -344,22 +344,22 @@ public: /// /// Updates the cached bounds of the actor. Updates the cached world bounds for every patch and chunk. /// - void UpdateBounds(); + API_FUNCTION() void UpdateBounds(); /// /// Caches the neighbor chunks of this terrain. /// - void CacheNeighbors(); + API_FUNCTION() void CacheNeighbors(); /// /// Updates the collider shapes collisions/queries layer mask bits. /// - void UpdateLayerBits(); + API_FUNCTION() void UpdateLayerBits(); /// /// Removes the lightmap data from the terrain. /// - void RemoveLightmap(); + API_FUNCTION() void RemoveLightmap(); public: @@ -371,7 +371,7 @@ public: /// The raycast result hit position distance from the ray origin. Valid only if raycast hits anything. /// The maximum distance the ray should check for collisions. /// True if ray hits an object, otherwise false. - API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, float& resultHitDistance, float maxDistance = MAX_float) const; + API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) float& resultHitDistance, float maxDistance = MAX_float) const; /// /// Performs a raycast against this terrain collision shape. Returns the hit chunk. @@ -382,7 +382,7 @@ public: /// The raycast result hit chunk. Valid only if raycast hits anything. /// The maximum distance the ray should check for collisions. /// True if ray hits an object, otherwise false. - bool RayCast(const Vector3& origin, const Vector3& direction, float& resultHitDistance, TerrainChunk*& resultChunk, float maxDistance = MAX_float) const; + API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) float& resultHitDistance, API_PARAM(Out) TerrainChunk*& resultChunk, float maxDistance = MAX_float) const; /// /// Performs a raycast against this terrain collision shape. Returns the hit chunk. diff --git a/Source/Engine/Terrain/TerrainChunk.cpp b/Source/Engine/Terrain/TerrainChunk.cpp index 737ce08f8..5088357fe 100644 --- a/Source/Engine/Terrain/TerrainChunk.cpp +++ b/Source/Engine/Terrain/TerrainChunk.cpp @@ -13,6 +13,12 @@ #include "Engine/Level/Scene/Scene.h" #include "Engine/Level/Prefabs/PrefabManager.h" + +TerrainChunk::TerrainChunk(const SpawnParams& params) + : ScriptingObject(params) +{ +} + void TerrainChunk::Init(TerrainPatch* patch, uint16 x, uint16 z) { // Initialize chunk properties diff --git a/Source/Engine/Terrain/TerrainChunk.h b/Source/Engine/Terrain/TerrainChunk.h index 884160b45..15de44a9c 100644 --- a/Source/Engine/Terrain/TerrainChunk.h +++ b/Source/Engine/Terrain/TerrainChunk.h @@ -17,8 +17,9 @@ struct RenderContext; /// /// Represents a single terrain chunk. /// -class FLAXENGINE_API TerrainChunk : public ISerializable +API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API TerrainChunk : public ScriptingObject, public ISerializable { + DECLARE_SCRIPTING_TYPE(TerrainChunk); friend Terrain; friend TerrainPatch; friend TerrainChunk; @@ -45,7 +46,7 @@ public: /// /// The material to override the terrain default one for this chunk. /// - AssetReference OverrideMaterial; + API_FIELD() AssetReference OverrideMaterial; /// /// The baked lightmap entry info for this chunk. @@ -57,7 +58,7 @@ public: /// /// Gets the x coordinate. /// - FORCE_INLINE int32 GetX() const + API_FUNCTION() FORCE_INLINE int32 GetX() const { return _x; } @@ -65,7 +66,7 @@ public: /// /// Gets the z coordinate. /// - FORCE_INLINE int32 GetZ() const + API_FUNCTION() FORCE_INLINE int32 GetZ() const { return _z; } @@ -73,7 +74,7 @@ public: /// /// Gets the patch. /// - FORCE_INLINE TerrainPatch* GetPatch() const + API_FUNCTION() FORCE_INLINE TerrainPatch* GetPatch() const { return _patch; } @@ -81,7 +82,7 @@ public: /// /// Gets the chunk world bounds. /// - FORCE_INLINE const BoundingBox& GetBounds() const + API_FUNCTION() FORCE_INLINE const BoundingBox& GetBounds() const { return _bounds; } @@ -89,7 +90,7 @@ public: /// /// Gets the chunk transformation (world to local). /// - FORCE_INLINE const Transform& GetTransform() const + API_FUNCTION() FORCE_INLINE const Transform& GetTransform() const { return _transform; } @@ -97,16 +98,15 @@ public: /// /// Gets the scale (in XY) and bias (in ZW) applied to the vertex UVs to get the chunk coordinates. /// - /// The result. - FORCE_INLINE void GetHeightmapUVScaleBias(Float4* result) const + API_FUNCTION() FORCE_INLINE const Float4& GetHeightmapUVScaleBias() const { - *result = _heightmapUVScaleBias; + return _heightmapUVScaleBias; } /// /// Determines whether this chunk has valid lightmap data. /// - FORCE_INLINE bool HasLightmap() const + API_FUNCTION() FORCE_INLINE bool HasLightmap() const { return Lightmap.TextureIndex != INVALID_INDEX; } @@ -114,7 +114,7 @@ public: /// /// Removes the lightmap data from the chunk. /// - FORCE_INLINE void RemoveLightmap() + API_FUNCTION() FORCE_INLINE void RemoveLightmap() { Lightmap.TextureIndex = INVALID_INDEX; } @@ -126,13 +126,13 @@ public: /// /// The rendering context. /// True if draw chunk, otherwise false. - bool PrepareDraw(const RenderContext& renderContext); + API_FUNCTION() bool PrepareDraw(API_PARAM(Ref) const RenderContext& renderContext); /// /// Draws the chunk (adds the draw call). Must be called after PrepareDraw. /// /// The rendering context. - void Draw(const RenderContext& renderContext) const; + API_FUNCTION() void Draw(API_PARAM(Ref) const RenderContext& renderContext) const; /// /// Draws the terrain chunk. @@ -140,7 +140,7 @@ public: /// The rendering context. /// The material to use for rendering. /// The LOD index. - void Draw(const RenderContext& renderContext, MaterialBase* material, int32 lodIndex = 0) const; + API_FUNCTION() void Draw(API_PARAM(Ref) const RenderContext& renderContext, MaterialBase* material, int32 lodIndex = 0) const; /// /// Determines if there is an intersection between the terrain chunk and a point @@ -148,22 +148,22 @@ public: /// The ray. /// The output distance. /// True if chunk intersects with the ray, otherwise false. - bool Intersects(const Ray& ray, Real& distance); + API_FUNCTION() bool Intersects(const Ray& ray, API_PARAM(Out) Real& distance); /// /// Updates the cached bounds of the chunk. /// - void UpdateBounds(); + API_FUNCTION() void UpdateBounds(); /// /// Updates the cached transform of the chunk. /// - void UpdateTransform(); + API_FUNCTION() void UpdateTransform(); /// /// Caches the neighbor chunks of this chunk. /// - void CacheNeighbors(); + API_FUNCTION() void CacheNeighbors(); public: diff --git a/Source/Engine/Terrain/TerrainPatch.cpp b/Source/Engine/Terrain/TerrainPatch.cpp index 8e05062c8..f6421ff97 100644 --- a/Source/Engine/Terrain/TerrainPatch.cpp +++ b/Source/Engine/Terrain/TerrainPatch.cpp @@ -41,6 +41,11 @@ struct TerrainCollisionDataHeader float ScaleXZ; }; +TerrainPatch::TerrainPatch(const SpawnParams& params) + : ScriptingObject(params) +{ +} + void TerrainPatch::Init(Terrain* terrain, int16 x, int16 z) { ScopeLock lock(_collisionLocker); diff --git a/Source/Engine/Terrain/TerrainPatch.h b/Source/Engine/Terrain/TerrainPatch.h index 689c629c5..103ee028b 100644 --- a/Source/Engine/Terrain/TerrainPatch.h +++ b/Source/Engine/Terrain/TerrainPatch.h @@ -15,17 +15,27 @@ class TerrainMaterialShader; /// /// Represents single terrain patch made of 16 terrain chunks. /// -class FLAXENGINE_API TerrainPatch : public ISerializable +API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API TerrainPatch : public ScriptingObject, public ISerializable { + DECLARE_SCRIPTING_TYPE(TerrainPatch); friend Terrain; friend TerrainPatch; friend TerrainChunk; public: - enum + /// + /// Various defines regarding maximum chunk counts + /// + API_ENUM() enum ChunksCount { + /// + /// The maximum allowed amount of chunks per patch + /// CHUNKS_COUNT = 16, + /// + /// The maximum allowed amount of chunks per chunk + /// CHUNKS_COUNT_EDGE = 4, }; @@ -73,12 +83,12 @@ public: /// /// The chunks contained within the patch. Organized in 4x4 square. /// - TerrainChunk Chunks[CHUNKS_COUNT]; + TerrainChunk Chunks[ChunksCount::CHUNKS_COUNT]; /// /// The heightmap texture. /// - AssetReference Heightmap; + API_FIELD() AssetReference Heightmap; /// /// The splatmap textures. @@ -91,7 +101,7 @@ public: /// Gets the Y axis heightmap offset from terrain origin. /// /// The offset. - FORCE_INLINE float GetOffsetY() const + API_FUNCTION() FORCE_INLINE float GetOffsetY() const { return _yOffset; } @@ -100,7 +110,7 @@ public: /// Gets the Y axis heightmap height. /// /// The height. - FORCE_INLINE float GetHeightY() const + API_FUNCTION() FORCE_INLINE float GetHeightY() const { return _yHeight; } @@ -109,7 +119,7 @@ public: /// Gets the x coordinate. /// /// The x position. - FORCE_INLINE int32 GetX() const + API_FUNCTION() FORCE_INLINE int32 GetX() const { return _x; } @@ -118,7 +128,7 @@ public: /// Gets the z coordinate. /// /// The z position. - FORCE_INLINE int32 GetZ() const + API_FUNCTION() FORCE_INLINE int32 GetZ() const { return _z; } @@ -127,7 +137,7 @@ public: /// Gets the terrain. /// /// The terrain, - FORCE_INLINE Terrain* GetTerrain() const + API_FUNCTION() FORCE_INLINE Terrain* GetTerrain() const { return _terrain; } @@ -137,7 +147,7 @@ public: /// /// The chunk zero-based index. /// The chunk. - TerrainChunk* GetChunk(int32 index) + API_FUNCTION() TerrainChunk* GetChunk(int32 index) { if (index < 0 || index >= CHUNKS_COUNT) return nullptr; @@ -149,7 +159,7 @@ public: /// /// The chunk location (x and z). /// The chunk. - TerrainChunk* GetChunk(const Int2& chunkCoord) + API_FUNCTION() TerrainChunk* GetChunk(API_PARAM(Ref) const Int2& chunkCoord) { return GetChunk(chunkCoord.Y * CHUNKS_COUNT_EDGE + chunkCoord.X); } @@ -160,16 +170,28 @@ public: /// The chunk location x. /// The chunk location z. /// The chunk. - TerrainChunk* GetChunk(int32 x, int32 z) + API_FUNCTION() TerrainChunk* GetChunk(int32 x, int32 z) { return GetChunk(z * CHUNKS_COUNT_EDGE + x); } + /// + /// Gets the splatmap assigned to this patch. + /// + /// Splatmap index. + /// Splatmap texture. + API_FUNCTION() AssetReference GetSplatmap(int32 index) + { + if (index < 0 || index >= TERRAIN_MAX_SPLATMAPS_COUNT) + return nullptr; + return Splatmap[index]; + } + /// /// Gets the patch world bounds. /// /// The bounding box. - FORCE_INLINE const BoundingBox& GetBounds() const + API_FUNCTION() FORCE_INLINE const BoundingBox& GetBounds() const { return _bounds; } @@ -179,17 +201,28 @@ public: /// /// Removes the lightmap data from the terrain patch. /// - void RemoveLightmap(); + API_FUNCTION() void RemoveLightmap(); /// /// Updates the cached bounds of the patch and child chunks. /// - void UpdateBounds(); + API_FUNCTION() void UpdateBounds(); /// /// Updates the cached transform of the patch and child chunks. /// - void UpdateTransform(); + API_FUNCTION() void UpdateTransform(); + + /// + /// Assigns a splatmap to this patch. + /// + /// Splatmap index. + /// Splatmap texture. + API_FUNCTION() void SetSplatmap(int32 index, const AssetReference& splatMap) + { + if (index >= 0 && index < TERRAIN_MAX_SPLATMAPS_COUNT) + Splatmap[index] = splatMap; + } #if TERRAIN_EDITING @@ -197,7 +230,7 @@ public: /// Initializes the patch heightmap and collision to the default flat level. /// /// True if failed, otherwise false. - bool InitializeHeightMap(); + API_FUNCTION() bool InitializeHeightMap(); /// /// Setups the terrain patch using the specified heightmap data. @@ -207,7 +240,7 @@ public: /// The holes mask (optional). Normalized to 0-1 range values with holes mask per-vertex. Must match the heightmap dimensions. /// If set to true patch will use virtual storage by force. Otherwise it can use normal texture asset storage on drive (valid only during Editor). Runtime-created terrain can only use virtual storage (in RAM). /// True if failed, otherwise false. - bool SetupHeightMap(int32 heightMapLength, const float* heightMap, const byte* holesMask = nullptr, bool forceUseVirtualStorage = false); + API_FUNCTION() bool SetupHeightMap(int32 heightMapLength, API_PARAM(Ref) const float* heightMap, API_PARAM(Ref) const byte* holesMask = nullptr, bool forceUseVirtualStorage = false); /// /// Setups the terrain patch layer weights using the specified splatmaps data. @@ -217,7 +250,7 @@ public: /// The splat map. Each array item contains 4 layer weights. /// If set to true patch will use virtual storage by force. Otherwise it can use normal texture asset storage on drive (valid only during Editor). Runtime-created terrain can only use virtual storage (in RAM). /// True if failed, otherwise false. - bool SetupSplatMap(int32 index, int32 splatMapLength, const Color32* splatMap, bool forceUseVirtualStorage = false); + API_FUNCTION() bool SetupSplatMap(int32 index, int32 splatMapLength, API_PARAM(Ref) const Color32* splatMap, bool forceUseVirtualStorage = false); #endif @@ -227,40 +260,40 @@ public: /// Gets the raw pointer to the heightmap data. /// /// The heightmap data. - float* GetHeightmapData(); + API_FUNCTION() float* GetHeightmapData(); /// /// Clears cache of the heightmap data. /// - void ClearHeightmapCache(); + API_FUNCTION() void ClearHeightmapCache(); /// /// Gets the raw pointer to the holes mask data. /// /// The holes mask data. - byte* GetHolesMaskData(); + API_FUNCTION() byte* GetHolesMaskData(); /// /// Clears cache of the holes mask data. /// - void ClearHolesMaskCache(); + API_FUNCTION() void ClearHolesMaskCache(); /// /// Gets the raw pointer to the splat map data. /// /// The zero-based index of the splatmap texture. /// The splat map data. - Color32* GetSplatMapData(int32 index); + API_FUNCTION() Color32* GetSplatMapData(int32 index); /// /// Clears cache of the splat map data. /// - void ClearSplatMapCache(); + API_FUNCTION() void ClearSplatMapCache(); /// /// Clears all caches. /// - void ClearCache(); + API_FUNCTION() void ClearCache(); /// /// Modifies the terrain patch heightmap with the given samples. @@ -269,7 +302,7 @@ public: /// The offset from the first row and column of the heightmap data (offset destination x and z start position). /// The size of the heightmap to modify (x and z). Amount of samples in each direction. /// True if failed, otherwise false. - bool ModifyHeightMap(const float* samples, const Int2& modifiedOffset, const Int2& modifiedSize); + API_FUNCTION() bool ModifyHeightMap(API_PARAM(Ref) const float* samples, API_PARAM(Ref) const Int2& modifiedOffset, API_PARAM(Ref) const Int2& modifiedSize); /// /// Modifies the terrain patch holes mask with the given samples. @@ -278,7 +311,7 @@ public: /// The offset from the first row and column of the holes map data (offset destination x and z start position). /// The size of the holes map to modify (x and z). Amount of samples in each direction. /// True if failed, otherwise false. - bool ModifyHolesMask(const byte* samples, const Int2& modifiedOffset, const Int2& modifiedSize); + API_FUNCTION() bool ModifyHolesMask(API_PARAM(Ref) const byte* samples, API_PARAM(Ref) const Int2& modifiedOffset, API_PARAM(Ref) const Int2& modifiedSize); /// /// Modifies the terrain patch splat map (layers mask) with the given samples. @@ -288,7 +321,7 @@ public: /// The offset from the first row and column of the splat map data (offset destination x and z start position). /// The size of the splat map to modify (x and z). Amount of samples in each direction. /// True if failed, otherwise false. - bool ModifySplatMap(int32 index, const Color32* samples, const Int2& modifiedOffset, const Int2& modifiedSize); + API_FUNCTION() bool ModifySplatMap(int32 index, API_PARAM(Ref) const Color32* samples, API_PARAM(Ref) const Int2& modifiedOffset, API_PARAM(Ref) const Int2& modifiedSize); private: @@ -311,7 +344,7 @@ public: /// The raycast result hit position distance from the ray origin. Valid only if raycast hits anything. /// The maximum distance the ray should check for collisions. /// True if ray hits an object, otherwise false. - bool RayCast(const Vector3& origin, const Vector3& direction, float& resultHitDistance, float maxDistance = MAX_float) const; + API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) float& resultHitDistance, float maxDistance = MAX_float) const; /// /// Performs a raycast against this terrain collision shape. @@ -322,7 +355,7 @@ public: /// The raycast result hit position normal vector. Valid only if raycast hits anything. /// The maximum distance the ray should check for collisions. /// True if ray hits an object, otherwise false. - bool RayCast(const Vector3& origin, const Vector3& direction, float& resultHitDistance, Vector3& resultHitNormal, float maxDistance = MAX_float) const; + API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) float& resultHitDistance, API_PARAM(Out) Vector3& resultHitNormal, float maxDistance = MAX_float) const; /// /// Performs a raycast against this terrain collision shape. Returns the hit chunk. @@ -333,7 +366,7 @@ public: /// The raycast result hit chunk. Valid only if raycast hits anything. /// The maximum distance the ray should check for collisions. /// True if ray hits an object, otherwise false. - bool RayCast(const Vector3& origin, const Vector3& direction, float& resultHitDistance, TerrainChunk*& resultChunk, float maxDistance = MAX_float) const; + API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) float& resultHitDistance, API_PARAM(Out) TerrainChunk*& resultChunk, float maxDistance = MAX_float) const; /// /// Performs a raycast against terrain collision, returns results in a RaycastHit structure. @@ -343,21 +376,21 @@ public: /// The result hit information. Valid only when method returns true. /// The maximum distance the ray should check for collisions. /// True if ray hits an object, otherwise false. - bool RayCast(const Vector3& origin, const Vector3& direction, RayCastHit& hitInfo, float maxDistance = MAX_float) const; + API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) RayCastHit& hitInfo, float maxDistance = MAX_float) const; /// /// Gets a point on the terrain collider that is closest to a given location. Can be used to find a hit location or position to apply explosion force or any other special effects. /// /// The position to find the closest point to it. /// The result point on the collider that is closest to the specified location. - void ClosestPoint(const Vector3& position, Vector3& result) const; + API_FUNCTION() void ClosestPoint(API_PARAM(Ref) const Vector3& position, API_PARAM(Out) Vector3& result) const; #if USE_EDITOR /// /// Updates the patch data after manual deserialization called at runtime (eg. by editor undo). /// - void UpdatePostManualDeserialization(); + API_FUNCTION() void UpdatePostManualDeserialization(); #endif @@ -369,14 +402,14 @@ public: /// Gets the collision mesh triangles array (3 vertices per triangle in linear list). Cached internally to reuse data. /// /// The collision triangles vertices list (in world-space). - const Array& GetCollisionTriangles(); + API_FUNCTION() const Array& GetCollisionTriangles(); /// /// Gets the collision mesh triangles array (3 vertices per triangle in linear list) that intersect with the given bounds. /// /// The world-space bounds to find terrain triangles that intersect with it. /// The result triangles that intersect with the given bounds (in world-space). - void GetCollisionTriangles(const BoundingSphere& bounds, Array& result); + API_FUNCTION() void GetCollisionTriangles(API_PARAM(Ref) const BoundingSphere& bounds, API_PARAM(Out) Array& result); #endif @@ -385,7 +418,7 @@ public: /// /// The output vertex buffer. /// The output index buffer. - void ExtractCollisionGeometry(Array& vertexBuffer, Array& indexBuffer); + API_FUNCTION() void ExtractCollisionGeometry(API_PARAM(Out) Array& vertexBuffer, API_PARAM(Out) Array& indexBuffer); private: From cf5f501ab7edca8ca4cc6ba541f2f1996a9d3db3 Mon Sep 17 00:00:00 2001 From: MineBill Date: Tue, 24 Oct 2023 14:31:54 +0300 Subject: [PATCH 016/139] Allow erasing layers/painting over layers from the other splatmap. Since Layers are hardcoded to 8, this commit also assumes that there will always be 2 splatmaps. --- Source/Editor/Tools/Terrain/Paint/Mode.cs | 30 +++++++++- .../Tools/Terrain/Paint/SingleLayerMode.cs | 30 +++++++--- .../Tools/Terrain/PaintTerrainGizmoMode.cs | 60 ++++++++++++++----- 3 files changed, 95 insertions(+), 25 deletions(-) diff --git a/Source/Editor/Tools/Terrain/Paint/Mode.cs b/Source/Editor/Tools/Terrain/Paint/Mode.cs index 3647691b3..bf9857bea 100644 --- a/Source/Editor/Tools/Terrain/Paint/Mode.cs +++ b/Source/Editor/Tools/Terrain/Paint/Mode.cs @@ -67,11 +67,13 @@ namespace FlaxEditor.Tools.Terrain.Paint // Prepare var splatmapIndex = ActiveSplatmapIndex; + var splatmapIndexOther = (splatmapIndex + 1) % 2; var chunkSize = terrain.ChunkSize; var heightmapSize = chunkSize * FlaxEngine.Terrain.PatchEdgeChunksCount + 1; var heightmapLength = heightmapSize * heightmapSize; var patchSize = chunkSize * FlaxEngine.Terrain.UnitsPerVertex * FlaxEngine.Terrain.PatchEdgeChunksCount; - var tempBuffer = (Color32*)gizmo.GetSplatmapTempBuffer(heightmapLength * Color32.SizeInBytes).ToPointer(); + var tempBuffer = (Color32*)gizmo.GetSplatmapTempBuffer(heightmapLength * Color32.SizeInBytes, splatmapIndex).ToPointer(); + var tempBufferOther = (Color32*)gizmo.GetSplatmapTempBuffer(heightmapLength * Color32.SizeInBytes, (splatmapIndex + 1) % 2).ToPointer(); var unitsPerVertexInv = 1.0f / FlaxEngine.Terrain.UnitsPerVertex; ApplyParams p = new ApplyParams { @@ -81,8 +83,10 @@ namespace FlaxEditor.Tools.Terrain.Paint Options = options, Strength = strength, SplatmapIndex = splatmapIndex, + SplatmapIndexOther = splatmapIndexOther, HeightmapSize = heightmapSize, TempBuffer = tempBuffer, + TempBufferOther = tempBufferOther, }; // Get brush bounds in terrain local space @@ -131,11 +135,16 @@ namespace FlaxEditor.Tools.Terrain.Paint var sourceData = TerrainTools.GetSplatMapData(terrain, ref patch.PatchCoord, splatmapIndex); if (sourceData == null) throw new Exception("Cannot modify terrain. Loading splatmap failed. See log for more info."); + + var sourceDataOther = TerrainTools.GetSplatMapData(terrain, ref patch.PatchCoord, splatmapIndexOther); + if (sourceDataOther == null) + throw new Exception("Cannot modify terrain. Loading splatmap failed. See log for more info."); // Record patch data before editing it if (!gizmo.CurrentEditUndoAction.HashPatch(ref patch.PatchCoord)) { gizmo.CurrentEditUndoAction.AddPatch(ref patch.PatchCoord, splatmapIndex); + gizmo.CurrentEditUndoAction.AddPatch(ref patch.PatchCoord, splatmapIndexOther); } // Apply modification @@ -144,6 +153,7 @@ namespace FlaxEditor.Tools.Terrain.Paint p.PatchCoord = patch.PatchCoord; p.PatchPositionLocal = patchPositionLocal; p.SourceData = sourceData; + p.SourceDataOther = sourceDataOther; Apply(ref p); } } @@ -197,16 +207,34 @@ namespace FlaxEditor.Tools.Terrain.Paint /// The splatmap texture index. /// public int SplatmapIndex; + + /// + /// The splatmap texture index. If is 0, this will be 1. + /// If is 1, this will be 0. + /// + public int SplatmapIndexOther; /// /// The temporary data buffer (for modified data). /// public Color32* TempBuffer; + + /// + /// The 'other" temporary data buffer (for modified data). If refers + /// to the splatmap with index 0, this one will refer to the one with index 1. + /// + public Color32* TempBufferOther; /// /// The source data buffer. /// public Color32* SourceData; + + /// + /// The 'other' source data buffer. If refers + /// to the splatmap with index 0, this one will refer to the one with index 1. + /// + public Color32* SourceDataOther; /// /// The heightmap size (edge). diff --git a/Source/Editor/Tools/Terrain/Paint/SingleLayerMode.cs b/Source/Editor/Tools/Terrain/Paint/SingleLayerMode.cs index 3bb014e24..d0c76a830 100644 --- a/Source/Editor/Tools/Terrain/Paint/SingleLayerMode.cs +++ b/Source/Editor/Tools/Terrain/Paint/SingleLayerMode.cs @@ -72,7 +72,7 @@ namespace FlaxEditor.Tools.Terrain.Paint var strength = p.Strength; var layer = (int)Layer; var brushPosition = p.Gizmo.CursorPosition; - var layerComponent = layer % 4; + var c = layer % 4; // Apply brush modification Profiler.BeginEvent("Apply Brush"); @@ -82,26 +82,38 @@ namespace FlaxEditor.Tools.Terrain.Paint for (int x = 0; x < p.ModifiedSize.X; x++) { var xx = x + p.ModifiedOffset.X; - var src = p.SourceData[zz * p.HeightmapSize + xx]; + var src = (Color)p.SourceData[zz * p.HeightmapSize + xx]; var samplePositionLocal = p.PatchPositionLocal + new Vector3(xx * FlaxEngine.Terrain.UnitsPerVertex, 0, zz * FlaxEngine.Terrain.UnitsPerVertex); Vector3.Transform(ref samplePositionLocal, ref p.TerrainWorld, out Vector3 samplePositionWorld); - - var sample = Mathf.Clamp(p.Brush.Sample(ref brushPosition, ref samplePositionWorld), 0f, 1f); - var paintAmount = sample * strength * (1f - src[layerComponent] / 255f); - src[layerComponent] = (byte)Mathf.Clamp(src[layerComponent] + paintAmount * 255, 0, 255); - src[(layerComponent + 1) % 4] = (byte)Mathf.Clamp(src[(layerComponent + 1) % 4] - paintAmount * 255, 0, 255); - src[(layerComponent + 2) % 4] = (byte)Mathf.Clamp(src[(layerComponent + 2) % 4] - paintAmount * 255, 0, 255); - src[(layerComponent + 3) % 4] = (byte)Mathf.Clamp(src[(layerComponent + 3) % 4] - paintAmount * 255, 0, 255); + var sample = Mathf.Clamp(p.Brush.Sample(ref brushPosition, ref samplePositionWorld), 0f, 1f); + var paintAmount = sample * strength * (1f - src[c]); + // Paint on the active splatmap texture + src[c] = Mathf.Clamp(src[c] + paintAmount, 0, 1f); + src[(c + 1) % 4] = Mathf.Clamp(src[(c + 1) % 4] - paintAmount, 0, 1f); + src[(c + 2) % 4] = Mathf.Clamp(src[(c + 2) % 4] - paintAmount, 0, 1f); + src[(c + 3) % 4] = Mathf.Clamp(src[(c + 3) % 4] - paintAmount, 0, 1f); + p.TempBuffer[z * p.ModifiedSize.X + x] = src; + + // Remove 'paint' from the other splatmap texture + var other = (Color)p.SourceDataOther[zz * p.HeightmapSize + xx]; + + other[c] = Mathf.Clamp(other[c] - paintAmount, 0, 1f); + other[(c + 1) % 4] = Mathf.Clamp(other[(c + 1) % 4] - paintAmount, 0, 1f); + other[(c + 2) % 4] = Mathf.Clamp(other[(c + 2) % 4] - paintAmount, 0, 1f); + other[(c + 3) % 4] = Mathf.Clamp(other[(c + 3) % 4] - paintAmount, 0, 1f); + + p.TempBufferOther[z * p.ModifiedSize.X + x] = other; } } Profiler.EndEvent(); // Update terrain patch TerrainTools.ModifySplatMap(p.Terrain, ref p.PatchCoord, p.SplatmapIndex, p.TempBuffer, ref p.ModifiedOffset, ref p.ModifiedSize); + TerrainTools.ModifySplatMap(p.Terrain, ref p.PatchCoord, p.SplatmapIndexOther, p.TempBufferOther, ref p.ModifiedOffset, ref p.ModifiedSize); } } } diff --git a/Source/Editor/Tools/Terrain/PaintTerrainGizmoMode.cs b/Source/Editor/Tools/Terrain/PaintTerrainGizmoMode.cs index 1d1bf87ca..05aaa48b5 100644 --- a/Source/Editor/Tools/Terrain/PaintTerrainGizmoMode.cs +++ b/Source/Editor/Tools/Terrain/PaintTerrainGizmoMode.cs @@ -36,9 +36,43 @@ namespace FlaxEditor.Tools.Terrain "Layer 7", }; - private IntPtr _cachedSplatmapData; - private int _cachedSplatmapDataSize; + private class SplatmapData + { + public IntPtr DataPtr { get; set; } = IntPtr.Zero; + public int Size { get; set; } = 0; + + public SplatmapData(int size) + { + EnsureCapacity(size); + } + + public void EnsureCapacity(int size) + { + if (Size < size) + { + if (DataPtr != IntPtr.Zero) + { + Marshal.FreeHGlobal(DataPtr); + } + DataPtr = Marshal.AllocHGlobal(size); + Size = size; + } + } + + /// + public void Free() + { + if (DataPtr == IntPtr.Zero) + return; + + Marshal.FreeHGlobal(DataPtr); + DataPtr = IntPtr.Zero; + Size = 0; + } + } + private EditTerrainMapAction _activeAction; + private List _cachedSplatmapData = new(); /// /// The terrain painting gizmo. @@ -230,20 +264,18 @@ namespace FlaxEditor.Tools.Terrain /// Gets the splatmap temporary scratch memory buffer used to modify terrain samples. Allocated memory is unmanaged by GC. /// /// The minimum buffer size (in bytes). + /// The splatmap index for which to return/create the temp buffer. /// The allocated memory using interface. - public IntPtr GetSplatmapTempBuffer(int size) + public IntPtr GetSplatmapTempBuffer(int size, int splatmapIndex) { - if (_cachedSplatmapDataSize < size) + if (_cachedSplatmapData.Count <= splatmapIndex) { - if (_cachedSplatmapData != IntPtr.Zero) - { - Marshal.FreeHGlobal(_cachedSplatmapData); - } - _cachedSplatmapData = Marshal.AllocHGlobal(size); - _cachedSplatmapDataSize = size; + _cachedSplatmapData.Add(new SplatmapData(size)); + return _cachedSplatmapData[splatmapIndex].DataPtr; } - return _cachedSplatmapData; + _cachedSplatmapData[splatmapIndex].EnsureCapacity(size); + return _cachedSplatmapData[splatmapIndex].DataPtr; } /// @@ -276,11 +308,9 @@ namespace FlaxEditor.Tools.Terrain base.OnDeactivated(); // Free temporary memory buffer - if (_cachedSplatmapData != IntPtr.Zero) + foreach (var splatmapData in _cachedSplatmapData) { - Marshal.FreeHGlobal(_cachedSplatmapData); - _cachedSplatmapData = IntPtr.Zero; - _cachedSplatmapDataSize = 0; + splatmapData.Free(); } } From ff64fdb503e7d27a91da8d41628f83c2ca3f0621 Mon Sep 17 00:00:00 2001 From: Nils Hausfeld Date: Thu, 26 Oct 2023 20:33:07 +0200 Subject: [PATCH 017/139] - Test environment and first tests --- Source/Editor/Surface/Archetypes/Math.cs | 14 +++++++++++--- Source/Editor/Surface/ContextMenu/VisjectCMItem.cs | 4 +--- Source/Editor/Utilities/QueryFilterHelper.cs | 9 ++++----- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/Source/Editor/Surface/Archetypes/Math.cs b/Source/Editor/Surface/Archetypes/Math.cs index fe2f1e044..7f4afa6a6 100644 --- a/Source/Editor/Surface/Archetypes/Math.cs +++ b/Source/Editor/Surface/Archetypes/Math.cs @@ -13,6 +13,11 @@ namespace FlaxEditor.Surface.Archetypes public static class Math { private static NodeArchetype Op1(ushort id, string title, string desc, ConnectionsHint hints = ConnectionsHint.Numeric, Type type = null) + { + return Op1(id, title, desc, null, hints, type); + } + + private static NodeArchetype Op1(ushort id, string title, string desc, string[] altTitles, ConnectionsHint hints = ConnectionsHint.Numeric, Type type = null) { return new NodeArchetype { @@ -20,6 +25,7 @@ namespace FlaxEditor.Surface.Archetypes Title = title, Description = desc, Flags = NodeFlags.AllGraphs, + AlternativeTitles = altTitles, Size = new Float2(110, 20), DefaultType = new ScriptType(type), ConnectionsHints = hints, @@ -92,6 +98,7 @@ namespace FlaxEditor.Surface.Archetypes { TypeID = 11, Title = "Length", + AlternativeTitles = new[] { "Magnitude", "Mag" }, Description = "Returns the length of A vector", Flags = NodeFlags.AllGraphs, Size = new Float2(110, 20), @@ -107,10 +114,10 @@ namespace FlaxEditor.Surface.Archetypes Op1(13, "Round", "Rounds A to the nearest integer"), Op1(14, "Saturate", "Clamps A to the range [0, 1]"), Op1(15, "Sine", "Returns sine of A"), - Op1(16, "Sqrt", "Returns square root of A"), + Op1(16, "Sqrt", "Returns square root of A", new [] { "Square Root", "Square", "Root" }), Op1(17, "Tangent", "Returns tangent of A"), Op2(18, "Cross", "Returns the cross product of A and B", ConnectionsHint.None, typeof(Float3)), - Op2(19, "Distance", "Returns a distance scalar between A and B", ConnectionsHint.Vector, null, typeof(float), false), + Op2(19, "Distance", "Returns a distance scalar between A and B", new [] { "Magnitude", "Mag", "Length" }, ConnectionsHint.Vector, null, typeof(float), false), Op2(20, "Dot", "Returns the dot product of A and B", ConnectionsHint.Vector, null, typeof(float), false), Op2(21, "Max", "Selects the greater of A and B"), Op2(22, "Min", "Selects the lesser of A and B"), @@ -185,7 +192,7 @@ namespace FlaxEditor.Surface.Archetypes } }, // - Op1(27, "Negate", "Returns opposite value"), + Op1(27, "Negate", "Returns opposite value", new [] { "Invert" }), Op1(28, "One Minus", "Returns 1 - value"), // new NodeArchetype @@ -225,6 +232,7 @@ namespace FlaxEditor.Surface.Archetypes { TypeID = 31, Title = "Mad", + AlternativeTitles = new [] { "Multiply", "Add", "*+" }, Description = "Performs value multiplication and addition at once", Flags = NodeFlags.AllGraphs, Size = new Float2(160, 60), diff --git a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs index 37da7d74d..2694d7e53 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs @@ -72,9 +72,7 @@ namespace FlaxEditor.Surface.ContextMenu public void UpdateScore(Box selectedBox) { SortScore = 0; - - if (!(_highlights?.Count > 0)) - return; + if (!Visible) return; diff --git a/Source/Editor/Utilities/QueryFilterHelper.cs b/Source/Editor/Utilities/QueryFilterHelper.cs index e9e5e9ccb..f2db81009 100644 --- a/Source/Editor/Utilities/QueryFilterHelper.cs +++ b/Source/Editor/Utilities/QueryFilterHelper.cs @@ -140,8 +140,7 @@ namespace FlaxEditor.Utilities // Check if start the matching sequence if (matchStartPos == -1) { - if (ranges == null) - ranges = new List(); + ranges ??= new List(); matchStartPos = textPos; } } @@ -152,7 +151,7 @@ namespace FlaxEditor.Utilities { var length = textPos - matchStartPos; if (length >= MinLength) - ranges.Add(new Range(matchStartPos, length)); + ranges!.Add(new Range(matchStartPos, length)); textPos = matchStartPos + length; matchStartPos = -1; } @@ -165,13 +164,13 @@ namespace FlaxEditor.Utilities { var length = endPos - matchStartPos; if (length >= MinLength) - ranges.Add(new Range(matchStartPos, length)); + ranges!.Add(new Range(matchStartPos, length)); textPos = matchStartPos + length; } } // Check if has any range - if (ranges != null && ranges.Count > 0) + if (ranges is { Count: > 0 }) { matches = ranges.ToArray(); return true; From 0acf352d40ae4eb767359adcba5d506a4adebecb Mon Sep 17 00:00:00 2001 From: Nils Hausfeld Date: Thu, 26 Oct 2023 21:13:06 +0200 Subject: [PATCH 018/139] - Alternative titles now use QueryFilterHelper Match function as well - Cleaned up some code and using early outs now - Added some comments --- .../Surface/ContextMenu/VisjectCMItem.cs | 136 ++++++++++-------- 1 file changed, 76 insertions(+), 60 deletions(-) diff --git a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs index 2694d7e53..41821c2aa 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs @@ -166,6 +166,7 @@ namespace FlaxEditor.Surface.ContextMenu /// True if item's group header got a filter match and item should stay visible. public void UpdateFilter(string filterText, Box selectedBox, bool groupHeaderMatches = false) { + // When dragging connection out of a box, validate if the box is compatible with this item's type if (selectedBox != null) { Visible = CanConnectTo(selectedBox); @@ -182,72 +183,87 @@ namespace FlaxEditor.Surface.ContextMenu // Clear filter _highlights?.Clear(); Visible = true; + return; } - else + + GetTextRectangle(out var textRect); + + // Check archetype title + if (QueryFilterHelper.Match(filterText, _archetype.Title, out var ranges)) { - GetTextRectangle(out var textRect); - if (QueryFilterHelper.Match(filterText, _archetype.Title, out var ranges)) + // Update highlights + if (_highlights == null) + _highlights = new List(ranges.Length); + else + _highlights.Clear(); + var style = Style.Current; + var font = style.FontSmall; + for (int i = 0; i < ranges.Length; i++) { - // Update highlights - if (_highlights == null) - _highlights = new List(ranges.Length); - else - _highlights.Clear(); - var style = Style.Current; - var font = style.FontSmall; - for (int i = 0; i < ranges.Length; i++) + var start = font.GetCharPosition(_archetype.Title, ranges[i].StartIndex); + var end = font.GetCharPosition(_archetype.Title, ranges[i].EndIndex); + _highlights.Add(new Rectangle(start.X + textRect.X, 0, end.X - start.X, Height)); + + if (ranges[i].StartIndex <= 0) { - var start = font.GetCharPosition(_archetype.Title, ranges[i].StartIndex); - var end = font.GetCharPosition(_archetype.Title, ranges[i].EndIndex); - _highlights.Add(new Rectangle(start.X + textRect.X, 0, end.X - start.X, Height)); - - if (ranges[i].StartIndex <= 0) - { - _isStartsWithMatch = true; - if (ranges[i].Length == _archetype.Title.Length) - _isFullMatch = true; - } + _isStartsWithMatch = true; + if (ranges[i].Length == _archetype.Title.Length) + _isFullMatch = true; } - Visible = true; - } - else if (_archetype.AlternativeTitles?.Any(altTitle => string.Equals(filterText, altTitle, StringComparison.CurrentCultureIgnoreCase)) == true) - { - // Update highlights - if (_highlights == null) - _highlights = new List(1); - else - _highlights.Clear(); - var style = Style.Current; - var font = style.FontSmall; - var start = font.GetCharPosition(_archetype.Title, 0); - var end = font.GetCharPosition(_archetype.Title, _archetype.Title.Length - 1); - _highlights.Add(new Rectangle(start.X + textRect.X, 0, end.X - start.X, Height)); - _isFullMatch = true; - Visible = true; - } - else if (NodeArchetype.TryParseText != null && NodeArchetype.TryParseText(filterText, out var data)) - { - // Update highlights - if (_highlights == null) - _highlights = new List(1); - else - _highlights.Clear(); - var style = Style.Current; - var font = style.FontSmall; - var start = font.GetCharPosition(_archetype.Title, 0); - var end = font.GetCharPosition(_archetype.Title, _archetype.Title.Length - 1); - _highlights.Add(new Rectangle(start.X + textRect.X, 0, end.X - start.X, Height)); - Visible = true; - - Data = data; - } - else if (!groupHeaderMatches) - { - // Hide - _highlights?.Clear(); - Visible = false; } + Visible = true; + return; } + + // Check archetype synonyms + if (_archetype.AlternativeTitles!= null && _archetype.AlternativeTitles.Any(altTitle => QueryFilterHelper.Match(filterText, altTitle, out ranges))) + { + // Update highlights + if (_highlights == null) + _highlights = new List(1); + else + _highlights.Clear(); + var style = Style.Current; + var font = style.FontSmall; + var start = font.GetCharPosition(_archetype.Title, 0); + var end = font.GetCharPosition(_archetype.Title, _archetype.Title.Length - 1); + _highlights.Add(new Rectangle(start.X + textRect.X, 0, end.X - start.X, Height)); + for (int i = 0; i < ranges.Length; i++) + { + if (ranges[i].StartIndex <= 0) + { + _isStartsWithMatch = true; + } + } + Visible = true; + return; + } + + // Check archetype data (if it exists) + if (NodeArchetype.TryParseText != null && NodeArchetype.TryParseText(filterText, out var data)) + { + // Update highlights + if (_highlights == null) + _highlights = new List(1); + else + _highlights.Clear(); + var style = Style.Current; + var font = style.FontSmall; + var start = font.GetCharPosition(_archetype.Title, 0); + var end = font.GetCharPosition(_archetype.Title, _archetype.Title.Length - 1); + _highlights.Add(new Rectangle(start.X + textRect.X, 0, end.X - start.X, Height)); + Visible = true; + + Data = data; + return; + } + + if (groupHeaderMatches) + return; + + // Hide + _highlights?.Clear(); + Visible = false; } /// @@ -280,7 +296,7 @@ namespace FlaxEditor.Surface.ContextMenu } // Draw name - Render2D.DrawText(style.FontSmall, _archetype.Title, textRect, Enabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center); + Render2D.DrawText(style.FontSmall, _archetype.Title + "(" + SortScore + ")", textRect, Enabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center); if (_archetype.SubTitle != null) { var titleLength = style.FontSmall.MeasureText(_archetype.Title).X; From 8cc7c5d3d905270308e58ce0da2bfa0db979a5ee Mon Sep 17 00:00:00 2001 From: Nils Hausfeld Date: Thu, 26 Oct 2023 21:47:32 +0200 Subject: [PATCH 019/139] - Highlights get cleared now when there is no match - Highlights now give points, since it means there is a substring match - Cleanup --- .../Surface/ContextMenu/VisjectCMItem.cs | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs index 41821c2aa..c6288f327 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs @@ -80,6 +80,8 @@ namespace FlaxEditor.Surface.ContextMenu SortScore += 1; if (Data != null) SortScore += 1; + if (_highlights is { Count: > 0 }) + SortScore += 1; if (_isStartsWithMatch) SortScore += 2; if (_isFullMatch) @@ -185,9 +187,9 @@ namespace FlaxEditor.Surface.ContextMenu Visible = true; return; } - + GetTextRectangle(out var textRect); - + // Check archetype title if (QueryFilterHelper.Match(filterText, _archetype.Title, out var ranges)) { @@ -214,7 +216,7 @@ namespace FlaxEditor.Surface.ContextMenu Visible = true; return; } - + // Check archetype synonyms if (_archetype.AlternativeTitles!= null && _archetype.AlternativeTitles.Any(altTitle => QueryFilterHelper.Match(filterText, altTitle, out ranges))) { @@ -238,7 +240,7 @@ namespace FlaxEditor.Surface.ContextMenu Visible = true; return; } - + // Check archetype data (if it exists) if (NodeArchetype.TryParseText != null && NodeArchetype.TryParseText(filterText, out var data)) { @@ -257,13 +259,12 @@ namespace FlaxEditor.Surface.ContextMenu Data = data; return; } - - if (groupHeaderMatches) - return; - - // Hide + _highlights?.Clear(); - Visible = false; + + // Hide + if (!groupHeaderMatches) + Visible = false; } /// @@ -296,7 +297,7 @@ namespace FlaxEditor.Surface.ContextMenu } // Draw name - Render2D.DrawText(style.FontSmall, _archetype.Title + "(" + SortScore + ")", textRect, Enabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center); + Render2D.DrawText(style.FontSmall, _archetype.Title, textRect, Enabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center); if (_archetype.SubTitle != null) { var titleLength = style.FontSmall.MeasureText(_archetype.Title).X; From e55cdd91d5c11052c9e54e3c4867230e4979b4a8 Mon Sep 17 00:00:00 2001 From: Nils Hausfeld Date: Thu, 26 Oct 2023 22:23:55 +0200 Subject: [PATCH 020/139] - Reverted match class from more alternative titles PR --- Source/Editor/Surface/Archetypes/Math.cs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/Source/Editor/Surface/Archetypes/Math.cs b/Source/Editor/Surface/Archetypes/Math.cs index 7f4afa6a6..fe2f1e044 100644 --- a/Source/Editor/Surface/Archetypes/Math.cs +++ b/Source/Editor/Surface/Archetypes/Math.cs @@ -13,11 +13,6 @@ namespace FlaxEditor.Surface.Archetypes public static class Math { private static NodeArchetype Op1(ushort id, string title, string desc, ConnectionsHint hints = ConnectionsHint.Numeric, Type type = null) - { - return Op1(id, title, desc, null, hints, type); - } - - private static NodeArchetype Op1(ushort id, string title, string desc, string[] altTitles, ConnectionsHint hints = ConnectionsHint.Numeric, Type type = null) { return new NodeArchetype { @@ -25,7 +20,6 @@ namespace FlaxEditor.Surface.Archetypes Title = title, Description = desc, Flags = NodeFlags.AllGraphs, - AlternativeTitles = altTitles, Size = new Float2(110, 20), DefaultType = new ScriptType(type), ConnectionsHints = hints, @@ -98,7 +92,6 @@ namespace FlaxEditor.Surface.Archetypes { TypeID = 11, Title = "Length", - AlternativeTitles = new[] { "Magnitude", "Mag" }, Description = "Returns the length of A vector", Flags = NodeFlags.AllGraphs, Size = new Float2(110, 20), @@ -114,10 +107,10 @@ namespace FlaxEditor.Surface.Archetypes Op1(13, "Round", "Rounds A to the nearest integer"), Op1(14, "Saturate", "Clamps A to the range [0, 1]"), Op1(15, "Sine", "Returns sine of A"), - Op1(16, "Sqrt", "Returns square root of A", new [] { "Square Root", "Square", "Root" }), + Op1(16, "Sqrt", "Returns square root of A"), Op1(17, "Tangent", "Returns tangent of A"), Op2(18, "Cross", "Returns the cross product of A and B", ConnectionsHint.None, typeof(Float3)), - Op2(19, "Distance", "Returns a distance scalar between A and B", new [] { "Magnitude", "Mag", "Length" }, ConnectionsHint.Vector, null, typeof(float), false), + Op2(19, "Distance", "Returns a distance scalar between A and B", ConnectionsHint.Vector, null, typeof(float), false), Op2(20, "Dot", "Returns the dot product of A and B", ConnectionsHint.Vector, null, typeof(float), false), Op2(21, "Max", "Selects the greater of A and B"), Op2(22, "Min", "Selects the lesser of A and B"), @@ -192,7 +185,7 @@ namespace FlaxEditor.Surface.Archetypes } }, // - Op1(27, "Negate", "Returns opposite value", new [] { "Invert" }), + Op1(27, "Negate", "Returns opposite value"), Op1(28, "One Minus", "Returns 1 - value"), // new NodeArchetype @@ -232,7 +225,6 @@ namespace FlaxEditor.Surface.Archetypes { TypeID = 31, Title = "Mad", - AlternativeTitles = new [] { "Multiply", "Add", "*+" }, Description = "Performs value multiplication and addition at once", Flags = NodeFlags.AllGraphs, Size = new Float2(160, 60), From 95735035f3989a6a4c40b67ffe8586fa44735ff6 Mon Sep 17 00:00:00 2001 From: Nils Hausfeld Date: Sat, 28 Oct 2023 16:51:56 +0200 Subject: [PATCH 021/139] - Tab key now also spawns node --- Source/Editor/Surface/ContextMenu/VisjectCM.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/Surface/ContextMenu/VisjectCM.cs b/Source/Editor/Surface/ContextMenu/VisjectCM.cs index 930741807..8e5e4defd 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCM.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCM.cs @@ -704,7 +704,7 @@ namespace FlaxEditor.Surface.ContextMenu Hide(); return true; } - else if (key == KeyboardKeys.Return) + else if (key == KeyboardKeys.Return || key == KeyboardKeys.Tab) { if (SelectedItem != null) OnClickItem(SelectedItem); From b88233a65d012903faff04bd776324d41cfc01c6 Mon Sep 17 00:00:00 2001 From: Nils Hausfeld Date: Sat, 28 Oct 2023 16:58:37 +0200 Subject: [PATCH 022/139] - Always select first valid item when updating filters --- Source/Editor/Surface/ContextMenu/VisjectCM.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Source/Editor/Surface/ContextMenu/VisjectCM.cs b/Source/Editor/Surface/ContextMenu/VisjectCM.cs index 8e5e4defd..ad4bf8372 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCM.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCM.cs @@ -492,8 +492,7 @@ namespace FlaxEditor.Surface.ContextMenu // If no item is selected (or it's not visible anymore), select the top one Profiler.BeginEvent("VisjectCM.Layout"); - if (SelectedItem == null || !SelectedItem.VisibleInHierarchy) - SelectedItem = _groups.Find(g => g.Visible)?.Children.Find(c => c.Visible && c is VisjectCMItem) as VisjectCMItem; + SelectedItem = _groups.Find(g => g.Visible)?.Children.Find(c => c.Visible && c is VisjectCMItem) as VisjectCMItem; PerformLayout(); if (SelectedItem != null) _panel1.ScrollViewTo(SelectedItem); From 0fea7aad9295755113f817c95e6556e208d49b88 Mon Sep 17 00:00:00 2001 From: Nils Hausfeld Date: Sat, 28 Oct 2023 17:11:03 +0200 Subject: [PATCH 023/139] - Minor cleanup --- Source/Editor/Surface/ContextMenu/VisjectCMItem.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs index c6288f327..d1bcec377 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs @@ -72,7 +72,7 @@ namespace FlaxEditor.Surface.ContextMenu public void UpdateScore(Box selectedBox) { SortScore = 0; - + if (!Visible) return; @@ -230,6 +230,7 @@ namespace FlaxEditor.Surface.ContextMenu var start = font.GetCharPosition(_archetype.Title, 0); var end = font.GetCharPosition(_archetype.Title, _archetype.Title.Length - 1); _highlights.Add(new Rectangle(start.X + textRect.X, 0, end.X - start.X, Height)); + for (int i = 0; i < ranges.Length; i++) { if (ranges[i].StartIndex <= 0) @@ -237,6 +238,7 @@ namespace FlaxEditor.Surface.ContextMenu _isStartsWithMatch = true; } } + Visible = true; return; } @@ -255,7 +257,6 @@ namespace FlaxEditor.Surface.ContextMenu var end = font.GetCharPosition(_archetype.Title, _archetype.Title.Length - 1); _highlights.Add(new Rectangle(start.X + textRect.X, 0, end.X - start.X, Height)); Visible = true; - Data = data; return; } From 5afb00e2c6444caa53c53b314e9c53eeab585304 Mon Sep 17 00:00:00 2001 From: MineBill Date: Sat, 28 Oct 2023 20:21:43 +0300 Subject: [PATCH 024/139] Allow quick creation of scripts. --- .../CustomEditors/Dedicated/ScriptsEditor.cs | 138 +++++++++++++++++- Source/Editor/GUI/ItemsListContextMenu.cs | 3 + 2 files changed, 139 insertions(+), 2 deletions(-) diff --git a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs index d7bfbbad7..7e2b84fea 100644 --- a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs @@ -3,6 +3,8 @@ using System; using System.Collections; using System.Collections.Generic; +using System.IO; +using System.Linq; using FlaxEditor.Actions; using FlaxEditor.Content; using FlaxEditor.GUI; @@ -16,6 +18,25 @@ using Object = FlaxEngine.Object; namespace FlaxEditor.CustomEditors.Dedicated { + internal class NewScriptItem : ItemsListContextMenu.Item + { + private string _scriptName; + public string ScriptName + { + get => _scriptName; + set + { + _scriptName = value; + Name = $"Create script '{value}'"; + } + } + + public NewScriptItem(string scriptName) + { + ScriptName = scriptName; + TooltipText = "Create a new script"; + } + } /// /// Drag and drop scripts area control. /// @@ -74,7 +95,48 @@ namespace FlaxEditor.CustomEditors.Dedicated { cm.AddItem(new TypeSearchPopup.TypeItemView(scripts[i])); } - cm.ItemClicked += item => AddScript((ScriptType)item.Tag); + cm.TextChanged += text => + { + if (!IsValidScriptName(text)) + return; + var items = cm.ItemsPanel.Children.Count(x => x.Visible && x is not NewScriptItem); + if (items == 0) + { + // If there are no visible items, that means the search failed so we can find the create script + // button or create one if it's the first time. + + var createScriptItem = cm.ItemsPanel.Children.FirstOrDefault(x => x is NewScriptItem); + if (createScriptItem != null) + { + var item = createScriptItem as NewScriptItem; + item.Visible = true; + item.ScriptName = text; + } + else + { + cm.AddItem(new NewScriptItem(text)); + } + } + else + { + // Make sure to hide the create script button if there + var createScriptButton = cm.ItemsPanel.Children.FirstOrDefault(x => x is NewScriptItem); + if (createScriptButton != null) + createScriptButton.Visible = false; + } + }; + cm.ItemClicked += item => + { + if (item.Tag is ScriptType script) + { + AddScript(script); + } + else if (item is NewScriptItem newScriptItem) + { + CreateScript(newScriptItem); + } + + }; cm.SortItems(); cm.Show(this, button.BottomLeft - new Float2((cm.Width - button.Width) / 2, 0)); } @@ -112,6 +174,17 @@ namespace FlaxEditor.CustomEditors.Dedicated return scriptItem.ScriptType != ScriptType.Null; return false; } + + private static bool IsValidScriptName(string text) + { + if (text.Contains(' ')) + return false; + if (char.IsDigit(text[0])) + return false; + if (text.Any(c => !char.IsLetterOrDigit(c) && c != '_')) + return false; + return true; + } /// public override DragDropEffect OnDragEnter(ref Float2 location, DragData data) @@ -177,7 +250,46 @@ namespace FlaxEditor.CustomEditors.Dedicated return result; } - private void AddScript(ScriptType item) + private void CreateScript(NewScriptItem item) + { + ScriptsEditor.NewScriptItem = item; + var paths = Directory.GetFiles(Globals.ProjectSourceFolder, "*.Build.cs"); + + string moduleName = null; + foreach (var p in paths) + { + var file = File.ReadAllText(p); + // Skip + if (!file.Contains("GameProjectTarget")) + continue; + + if (file.Contains("Modules.Add(\"Game\")")) + { + // Assume Game represents the main game module + moduleName = "Game"; + break; + } + } + + // Ensure the path slashes are correct for the OS + var correctedPath = Path.GetFullPath(Globals.ProjectSourceFolder); + + if (string.IsNullOrEmpty(moduleName)) + { + var error = FileSystem.ShowBrowseFolderDialog(Editor.Instance.Windows.MainWindow, correctedPath, "Select a module folder to put the new script in", out moduleName); + if (error) + return; + } + + var path = Path.Combine(Globals.ProjectSourceFolder, moduleName, item.ScriptName + ".cs"); + new CSharpScriptProxy().Create(path, null); + } + + /// + /// Attach a script to the actor. + /// + /// The script. + public void AddScript(ScriptType item) { var list = new List(1) { item }; AddScripts(list); @@ -439,6 +551,11 @@ namespace FlaxEditor.CustomEditors.Dedicated /// public override IEnumerable UndoObjects => _scripts; + // We need somewhere to store the newly created script name. + // The problem is that the ScriptsEditor gets destroyed after scripts compilation + // so we must make it static to store this information. + internal static NewScriptItem NewScriptItem { get; set; } + private void AddMissingScript(int index, LayoutElementsContainer layout) { var group = layout.Group("Missing script"); @@ -546,6 +663,23 @@ namespace FlaxEditor.CustomEditors.Dedicated // Area for drag&drop scripts var dragArea = layout.CustomContainer(); dragArea.CustomControl.ScriptsEditor = this; + + // If the initialization is triggered by an editor recompilation, check if it + // was due to script generation from DragAreaControl. + if (NewScriptItem != null) + { + var script = Editor.Instance.CodeEditing.Scripts.Get() + .FirstOrDefault(x => x.Name == NewScriptItem.ScriptName); + if (script != null) + { + dragArea.CustomControl.AddScript(script); + NewScriptItem = null; + } + else + { + Editor.LogWarning("Failed to find newly created script."); + } + } // No support for showing scripts from multiple actors that have different set of scripts var scripts = (Script[])Values[0]; diff --git a/Source/Editor/GUI/ItemsListContextMenu.cs b/Source/Editor/GUI/ItemsListContextMenu.cs index 42d236991..a0351bf0f 100644 --- a/Source/Editor/GUI/ItemsListContextMenu.cs +++ b/Source/Editor/GUI/ItemsListContextMenu.cs @@ -189,6 +189,8 @@ namespace FlaxEditor.GUI /// public event Action ItemClicked; + public event Action TextChanged; + /// /// The panel control where you should add your items. /// @@ -263,6 +265,7 @@ namespace FlaxEditor.GUI UnlockChildrenRecursive(); PerformLayout(true); _searchBox.Focus(); + TextChanged?.Invoke(_searchBox.Text); } /// From 1c23f0f5b49a11beee76032329b268cffd87986f Mon Sep 17 00:00:00 2001 From: "Mr. Capybara" Date: Fri, 3 Nov 2023 11:30:24 -0400 Subject: [PATCH 025/139] Add ActiveOnly parameter to Level::GetActors --- Source/Engine/Level/Level.cpp | 10 ++++++---- Source/Engine/Level/Level.cs | 5 +++-- Source/Engine/Level/Level.h | 3 ++- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index cfdf11a6f..6770961fb 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -1564,12 +1564,14 @@ Script* Level::FindScript(const MClass* type) namespace { - void GetActors(const MClass* type, Actor* actor, Array& result) + void GetActors(const MClass* type, Actor* actor, bool activeOnly, Array& result) { + if (activeOnly && !actor->GetIsActive()) + return; if (actor->GetClass()->IsSubClassOf(type)) result.Add(actor); for (auto child : actor->Children) - GetActors(type, child, result); + GetActors(type, child, activeOnly, result); } void GetScripts(const MClass* type, Actor* actor, Array& result) @@ -1582,13 +1584,13 @@ namespace } } -Array Level::GetActors(const MClass* type) +Array Level::GetActors(const MClass* type, bool activeOnly) { Array result; CHECK_RETURN(type, result); ScopeLock lock(ScenesLock); for (int32 i = 0; i < Scenes.Count(); i++) - ::GetActors(type, Scenes[i], result); + ::GetActors(type, Scenes[i], activeOnly, result); return result; } diff --git a/Source/Engine/Level/Level.cs b/Source/Engine/Level/Level.cs index 1e8524f2c..b43d9e91a 100644 --- a/Source/Engine/Level/Level.cs +++ b/Source/Engine/Level/Level.cs @@ -119,10 +119,11 @@ namespace FlaxEngine /// Finds all the actors of the given type in all the loaded scenes. /// /// Type of the object. + /// Finds only active actors. /// Found actors list. - public static T[] GetActors() where T : Actor + public static T[] GetActors(bool activeOnly = false) where T : Actor { - var actors = GetActors(typeof(T)); + var actors = GetActors(typeof(T), activeOnly); var result = new T[actors.Length]; for (int i = 0; i < actors.Length; i++) result[i] = actors[i] as T; diff --git a/Source/Engine/Level/Level.h b/Source/Engine/Level/Level.h index e09f8e376..0e7ff04c9 100644 --- a/Source/Engine/Level/Level.h +++ b/Source/Engine/Level/Level.h @@ -460,8 +460,9 @@ public: /// Finds all the actors of the given type in all the loaded scenes. /// /// Type of the actor to search for. Includes any actors derived from the type. + /// Finds only active actors in the scene. /// Found actors list. - API_FUNCTION() static Array GetActors(API_PARAM(Attributes="TypeReference(typeof(Actor))") const MClass* type); + API_FUNCTION() static Array GetActors(API_PARAM(Attributes="TypeReference(typeof(Actor))") const MClass* type, bool activeOnly = false); /// /// Finds all the scripts of the given type in all the loaded scenes. From 41d513e5d25a44bf871c5dece69124384bfb534e Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 3 Nov 2023 21:22:00 -0500 Subject: [PATCH 026/139] Add directions to slider control. --- Source/Engine/UI/GUI/Common/Slider.cs | 235 +++++++++++++++++++++----- 1 file changed, 189 insertions(+), 46 deletions(-) diff --git a/Source/Engine/UI/GUI/Common/Slider.cs b/Source/Engine/UI/GUI/Common/Slider.cs index 6dbd5082c..6332088b4 100644 --- a/Source/Engine/UI/GUI/Common/Slider.cs +++ b/Source/Engine/UI/GUI/Common/Slider.cs @@ -7,6 +7,32 @@ namespace FlaxEngine.GUI; /// public class Slider : ContainerControl { + /// + /// The slider direction + /// + public enum SliderDirection + { + /// + /// Slider direction, horizontal right + /// + HorizontalRight, + + /// + /// Slider direction, horizontal left + /// + HorizontalLeft, + + /// + /// Slider direction, vertical up + /// + VerticalUp, + + /// + /// Slider direction, vertical down + /// + VerticalDown, + } + /// /// The minimum value. /// @@ -15,26 +41,7 @@ public class Slider : ContainerControl /// /// The maximum value. /// - protected float _maximum = 100f; - - /// - /// Gets or sets the minimum value. - /// - [EditorOrder(20), Tooltip("The minimum value.")] - public float Minimum - { - get => _minimum; - set - { - if (value > _maximum) - throw new ArgumentOutOfRangeException(); - if (WholeNumbers) - value = Mathf.RoundToInt(value); - _minimum = value; - if (Value < _minimum) - Value = _minimum; - } - } + protected float _maximum = 100; /// /// Gets or sets the maximum value. @@ -45,8 +52,8 @@ public class Slider : ContainerControl get => _maximum; set { - if (value < _minimum || Mathf.IsZero(value)) - throw new ArgumentOutOfRangeException(); + //if (value < _minimum || Mathf.IsZero(value)) + //throw new ArgumentOutOfRangeException(); if (WholeNumbers) value = Mathf.RoundToInt(value); _maximum = value; @@ -55,6 +62,40 @@ public class Slider : ContainerControl } } + /// + /// Gets or sets the minimum value. + /// + [EditorOrder(20), Tooltip("The minimum value.")] + public float Minimum + { + get => _minimum; + set + { + //if (value > _maximum) + //throw new ArgumentOutOfRangeException(); + if (WholeNumbers) + value = Mathf.RoundToInt(value); + _minimum = value; + if (Value < _minimum) + Value = _minimum; + } + } + + /// + /// Gets or sets the slider direction. + /// + [EditorOrder(40), Tooltip("Slider Direction.")] + public SliderDirection Direction + { + get => _direction; + set + { + _direction = value; + UpdateThumb(); + } + } + + private SliderDirection _direction = SliderDirection.HorizontalRight; private float _value = 100f; private Rectangle _thumbRect; private float _thumbCenter; @@ -89,25 +130,61 @@ public class Slider : ContainerControl /// The local position of the thumb center /// [HideInEditor] - public Float2 ThumbCenter => new(_thumbCenter, Height / 2); + public Float2 ThumbCenter => (Direction is SliderDirection.HorizontalLeft or SliderDirection.HorizontalRight) ? new Float2(_thumbCenter, Height / 2) : new Float2(Width / 2, _thumbCenter); /// /// The local position of the beginning of the track. /// [HideInEditor] - public Float2 TrackBeginning => new(_thumbSize.X / 2, Height / 2); + public Float2 TrackBeginning + { + get + { + switch (Direction) + { + case SliderDirection.HorizontalRight: + return new Float2(_thumbSize.X / 2, Height / 2); + case SliderDirection.HorizontalLeft: + return new Float2(Width - _thumbSize.X / 2, Height / 2); + case SliderDirection.VerticalUp: + return new Float2(Width / 2, Height - _thumbSize.Y / 2); + case SliderDirection.VerticalDown: + return new Float2(Width / 2, _thumbSize.Y / 2); + default: break; + } + return Float2.Zero; + } + } /// /// The local position of the end of the track. /// [HideInEditor] - public Float2 TrackEnd => new(Width - _thumbSize.X / 2, Height / 2); - + public Float2 TrackEnd + { + get + { + switch (Direction) + { + case SliderDirection.HorizontalRight: + return new Float2(Width - _thumbSize.X / 2, Height / 2); + case SliderDirection.HorizontalLeft: + return new Float2(_thumbSize.X / 2, Height / 2); + case SliderDirection.VerticalUp: + return new Float2(Width / 2, _thumbSize.Y / 2); + case SliderDirection.VerticalDown: + return new Float2(Width / 2, Height - _thumbSize.Y / 2); + default: break; + } + return Float2.Zero; + } + } + /// /// The height of the track. /// [EditorOrder(40), Tooltip("The track height.")] - public int TrackHeight { get; set; } = 2; + public int TrackThickness { get; set; } = 2; /// /// The thumb size. @@ -147,9 +224,14 @@ public class Slider : ContainerControl public Color TrackFillLineColor { get; set; } /// - /// Gets the size of the track. + /// Gets the width of the track. /// - private float TrackWidth => Width; + private float TrackWidth => (Direction is SliderDirection.HorizontalLeft or SliderDirection.HorizontalRight) ? Width - _thumbSize.X : TrackThickness; + + /// + /// Gets the height of the track. + /// + private float TrackHeight => (Direction is SliderDirection.HorizontalLeft or SliderDirection.HorizontalRight) ? TrackThickness : Height - _thumbSize.Y; /// /// Gets or sets the brush used for slider track drawing. @@ -236,13 +318,32 @@ public class Slider : ContainerControl private void UpdateThumb() { // Cache data - float trackSize = TrackWidth; + var isHorizontal = Direction is SliderDirection.HorizontalRight or SliderDirection.HorizontalLeft; + float trackSize = isHorizontal ? Width : Height; float range = Maximum - Minimum; - float pixelRange = trackSize - _thumbSize.X; + float pixelRange = trackSize - (isHorizontal ? _thumbSize.X : _thumbSize.Y); float perc = (_value - Minimum) / range; float thumbPosition = (int)(perc * pixelRange); - _thumbCenter = thumbPosition + _thumbSize.X / 2; - _thumbRect = new Rectangle(thumbPosition, (Height - _thumbSize.Y) / 2, _thumbSize.X, _thumbSize.Y); + switch (Direction) + { + case SliderDirection.HorizontalRight: + _thumbCenter = thumbPosition + _thumbSize.X / 2; + _thumbRect = new Rectangle(thumbPosition, (Height - _thumbSize.Y) / 2, _thumbSize.X, _thumbSize.Y); + break; + case SliderDirection.VerticalDown: + _thumbCenter = thumbPosition + _thumbSize.Y / 2; + _thumbRect = new Rectangle((Width - _thumbSize.X) / 2, thumbPosition, _thumbSize.X, _thumbSize.Y); + break; + case SliderDirection.HorizontalLeft: + _thumbCenter = Width - thumbPosition - _thumbSize.X / 2; + _thumbRect = new Rectangle(Width - thumbPosition - _thumbSize.X, (Height - _thumbSize.Y) / 2, _thumbSize.X, _thumbSize.Y); + break; + case SliderDirection.VerticalUp: + _thumbCenter = Height - thumbPosition - _thumbSize.Y / 2; + _thumbRect = new Rectangle((Width - _thumbSize.X) / 2, Height - thumbPosition - _thumbSize.Y, _thumbSize.X, _thumbSize.Y); + break; + default: break; + } } private void EndSliding() @@ -256,19 +357,37 @@ public class Slider : ContainerControl public override void Draw() { base.Draw(); - + + // Set rectangles + var lineRect = new Rectangle(_thumbSize.X / 2, (Height - TrackThickness) / 2, Width - _thumbSize.X, TrackThickness); + var fillLineRect = new Rectangle(_thumbSize.X / 2 - 1, (Height - TrackThickness - 2) / 2, Width - (Width - _thumbCenter) - _thumbSize.X / 2 + 1, TrackThickness + 2); + switch (Direction) + { + case SliderDirection.HorizontalRight: + break; + case SliderDirection.VerticalDown: + lineRect = new Rectangle((Width - TrackThickness) / 2 , _thumbSize.Y / 2, TrackThickness, Height - _thumbSize.Y); + fillLineRect = new Rectangle((Width - TrackThickness - 2) / 2, _thumbSize.Y / 2 - 1, TrackThickness + 2, Height - (Height - _thumbCenter) - _thumbSize.Y / 2 + 1); + break; + case SliderDirection.HorizontalLeft: + fillLineRect = new Rectangle(Width - (Width - _thumbCenter) - 1, (Height - TrackThickness - 2) / 2, Width - _thumbCenter + 1, TrackThickness + 2); + break; + case SliderDirection.VerticalUp: + lineRect = new Rectangle((Width - TrackThickness) / 2 , _thumbSize.Y / 2, TrackThickness, Height - _thumbSize.Y); + fillLineRect = new Rectangle((Width - TrackThickness - 2) / 2, Height - (Height - _thumbCenter) - 1, TrackThickness + 2, Height - _thumbCenter + 1); + break; + default: break; + } + // Draw track line - //var lineRect = new Rectangle(4, (Height - TrackHeight) / 2, Width - 8, TrackHeight); - var lineRect = new Rectangle(_thumbSize.X / 2, (Height - TrackHeight) / 2, Width - _thumbSize.X, TrackHeight); if (TrackBrush != null) TrackBrush.Draw(lineRect, TrackLineColor); else - Render2D.FillRectangle(lineRect, TrackLineColor); - + Render2D.FillRectangle(lineRect, TrackLineColor); + // Draw track fill if (FillTrack) { - var fillLineRect = new Rectangle(_thumbSize.X / 2 - 1, (Height - TrackHeight - 2) / 2, Width - (Width - _thumbCenter) - _thumbSize.X / 2, TrackHeight + 2); Render2D.PushClip(ref fillLineRect); if (FillTrackBrush != null) FillTrackBrush.Draw(lineRect, TrackFillLineColor); @@ -276,13 +395,13 @@ public class Slider : ContainerControl Render2D.FillRectangle(lineRect, TrackFillLineColor); Render2D.PopClip(); } - + // Draw thumb - var thumbColor = _isSliding ? ThumbColorSelected : (_mouseOverThumb ? ThumbColorHighlighted : ThumbColor); + var thumbColorV = _isSliding ? ThumbColorSelected : (_mouseOverThumb ? ThumbColorHighlighted : ThumbColor); if (ThumbBrush != null) - ThumbBrush.Draw(_thumbRect, thumbColor); + ThumbBrush.Draw(_thumbRect, thumbColorV); else - Render2D.FillRectangle(_thumbRect, thumbColor); + Render2D.FillRectangle(_thumbRect, thumbColorV); } /// @@ -302,7 +421,7 @@ public class Slider : ContainerControl if (button == MouseButton.Left) { Focus(); - float mousePosition = location.X; + float mousePosition = Direction is SliderDirection.HorizontalRight or SliderDirection.HorizontalLeft ? location.X : location.Y; if (_thumbRect.Contains(ref location)) { @@ -315,7 +434,16 @@ public class Slider : ContainerControl else { // Click change - Value += (mousePosition < _thumbCenter ? -1 : 1) * 10; + switch (Direction) + { + case SliderDirection.HorizontalRight or SliderDirection.VerticalDown: + Value += (mousePosition < _thumbCenter ? -1 : 1) * 10; + break; + case SliderDirection.HorizontalLeft or SliderDirection.VerticalUp: + Value -= (mousePosition < _thumbCenter ? -1 : 1) * 10; + break; + default: break; + } } } @@ -330,7 +458,22 @@ public class Slider : ContainerControl { // Update sliding var slidePosition = location + Root.TrackingMouseOffset; - Value = Mathf.Remap(slidePosition.X, 4, TrackWidth - 4, Minimum, Maximum); + switch (Direction) + { + case SliderDirection.HorizontalRight: + Value = Mathf.Remap(slidePosition.X, 4, Width - 4, Minimum, Maximum); + break; + case SliderDirection.VerticalDown: + Value = Mathf.Remap(slidePosition.Y, 4, Height - 4, Minimum, Maximum); + break; + case SliderDirection.HorizontalLeft: + Value = Mathf.Remap(slidePosition.X, Width - 4, 4, Minimum, Maximum); + break; + case SliderDirection.VerticalUp: + Value = Mathf.Remap(slidePosition.Y, Height - 4, 4, Minimum, Maximum); + break; + default: break; + } } else { From 253c4c27ab6f6901960839ff2131bb6530247c49 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 3 Nov 2023 21:23:09 -0500 Subject: [PATCH 027/139] Remove out of range exceptions due to serialization issues. --- Source/Engine/UI/GUI/Common/Slider.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Source/Engine/UI/GUI/Common/Slider.cs b/Source/Engine/UI/GUI/Common/Slider.cs index 6332088b4..d9d998bc2 100644 --- a/Source/Engine/UI/GUI/Common/Slider.cs +++ b/Source/Engine/UI/GUI/Common/Slider.cs @@ -52,8 +52,6 @@ public class Slider : ContainerControl get => _maximum; set { - //if (value < _minimum || Mathf.IsZero(value)) - //throw new ArgumentOutOfRangeException(); if (WholeNumbers) value = Mathf.RoundToInt(value); _maximum = value; @@ -71,8 +69,6 @@ public class Slider : ContainerControl get => _minimum; set { - //if (value > _maximum) - //throw new ArgumentOutOfRangeException(); if (WholeNumbers) value = Mathf.RoundToInt(value); _minimum = value; From de55ad90b75c23e5ed7c14d96335a3daa829c286 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Thu, 16 Nov 2023 20:57:53 -0600 Subject: [PATCH 028/139] Add require script attribute and functionality. --- .../CustomEditors/Dedicated/ScriptsEditor.cs | 70 ++++++++++++++++++- .../Editor/RequireScriptAttribute.cs | 25 +++++++ 2 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 Source/Engine/Scripting/Attributes/Editor/RequireScriptAttribute.cs diff --git a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs index d7bfbbad7..9da2ab180 100644 --- a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs @@ -224,16 +224,44 @@ namespace FlaxEditor.CustomEditors.Dedicated private void AddScripts(List items) { - var actions = new List(4); + var actions = new List(); for (int i = 0; i < items.Count; i++) { var scriptType = items[i]; + List requiredScripts = new List(); + if (scriptType.HasAttribute(typeof(RequireScriptAttribute), false)) + { + var attributes = new List(); + foreach (var e in scriptType.GetAttributes(false)) + { + if (e is not RequireScriptAttribute requireScriptAttribute) + continue; + attributes.Add(requireScriptAttribute); + } + + if (attributes.Count > 0) + { + foreach (var attribute in attributes) + { + if (!attribute.RequiredType.IsSubclassOf(typeof(Script))) + continue; + requiredScripts.Add(new ScriptType(attribute.RequiredType)); + } + } + } var actors = ScriptsEditor.ParentEditor.Values; for (int j = 0; j < actors.Count; j++) { var actor = (Actor)actors[j]; actions.Add(AddRemoveScript.Add(actor, scriptType)); + // Check if actor has required scripts and add them if the actor does not. + foreach (var type in requiredScripts) + { + if (actor.GetScript(type.Type) != null) + continue; + actions.Add(AddRemoveScript.Add(actor, type)); + } } } @@ -584,10 +612,50 @@ namespace FlaxEditor.CustomEditors.Dedicated var values = new ScriptsContainer(elementType, i, Values); var scriptType = TypeUtils.GetObjectType(script); var editor = CustomEditorsUtil.CreateEditor(scriptType, false); + + // Check if actor has all the required scripts + bool hasAllRequiredScripts = true; + if (scriptType.HasAttribute(typeof(RequireScriptAttribute), false)) + { + var scriptTypesToCheck = new List(); + var attributes = new List(); + foreach (var e in scriptType.GetAttributes(false)) + { + if (e is not RequireScriptAttribute requireScriptAttribute) + continue; + attributes.Add(requireScriptAttribute); + } + + if (attributes.Count > 0) + { + foreach (var attribute in attributes) + { + if (!attribute.RequiredType.IsSubclassOf(typeof(Script))) + continue; + scriptTypesToCheck.Add(new ScriptType(attribute.RequiredType)); + } + } + + if (scriptTypesToCheck.Count > 0) + { + foreach (var type in scriptTypesToCheck) + { + var requiredScript = script.Actor.GetScript(type.Type); + if (requiredScript == null) + { + Editor.LogWarning($"{script} on {script.Actor} is missing required script of type {type}."); + hasAllRequiredScripts = false; + break; + } + } + } + } // Create group var title = Utilities.Utils.GetPropertyNameUI(scriptType.Name); var group = layout.Group(title, editor); + if (!hasAllRequiredScripts) + group.Panel.HeaderTextColor = FlaxEngine.GUI.Style.Current.Statusbar.Failed; if ((Presenter.Features & FeatureFlags.CacheExpandedGroups) != 0) { if (Editor.Instance.ProjectCache.IsCollapsedGroup(title)) diff --git a/Source/Engine/Scripting/Attributes/Editor/RequireScriptAttribute.cs b/Source/Engine/Scripting/Attributes/Editor/RequireScriptAttribute.cs new file mode 100644 index 000000000..eee529c5c --- /dev/null +++ b/Source/Engine/Scripting/Attributes/Editor/RequireScriptAttribute.cs @@ -0,0 +1,25 @@ +using System; + +namespace FlaxEngine; + +/// +/// This attribute is used to check for if a script requires another script type. +/// +[Serializable] +[AttributeUsage(AttributeTargets.Class)] +public class RequireScriptAttribute : Attribute +{ + /// + /// The required type. + /// + public Type RequiredType; + + /// + /// Initializes a new instance of the class. + /// + /// The required type. + public RequireScriptAttribute(Type type) + { + RequiredType = type; + } +} From c4c3a3a5e8f0743cbaca227deda933583d88c9f3 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Thu, 16 Nov 2023 21:01:19 -0600 Subject: [PATCH 029/139] Remove break to show all missing required scripts in logs. --- Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs index 9da2ab180..91a89bba7 100644 --- a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs @@ -643,9 +643,8 @@ namespace FlaxEditor.CustomEditors.Dedicated var requiredScript = script.Actor.GetScript(type.Type); if (requiredScript == null) { - Editor.LogWarning($"{script} on {script.Actor} is missing required script of type {type}."); + Editor.LogWarning($"{Utilities.Utils.GetPropertyNameUI(scriptType.Name)} on {script.Actor} is missing a required script of type {type}."); hasAllRequiredScripts = false; - break; } } } From 44e55cc8b6a3c5fb3cce5f1de828d9489fed7bf3 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Thu, 16 Nov 2023 21:25:40 -0600 Subject: [PATCH 030/139] Add require Actor attribute --- .../CustomEditors/Dedicated/ScriptsEditor.cs | 62 ++++++++++++++++--- .../Editor/RequireActorAttribute.cs | 25 ++++++++ 2 files changed, 80 insertions(+), 7 deletions(-) create mode 100644 Source/Engine/Scripting/Attributes/Editor/RequireActorAttribute.cs diff --git a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs index 91a89bba7..93a8ec6be 100644 --- a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs @@ -242,18 +242,43 @@ namespace FlaxEditor.CustomEditors.Dedicated if (attributes.Count > 0) { - foreach (var attribute in attributes) + foreach (var e in attributes) { - if (!attribute.RequiredType.IsSubclassOf(typeof(Script))) + if (!e.RequiredType.IsSubclassOf(typeof(Script))) continue; - requiredScripts.Add(new ScriptType(attribute.RequiredType)); + requiredScripts.Add(new ScriptType(e.RequiredType)); } } } + + // See if script requires a specific actor type + RequireActorAttribute actorAttribute = null; + if (scriptType.HasAttribute(typeof(RequireActorAttribute), false)) + { + foreach (var e in scriptType.GetAttributes(false)) + { + if (e is not RequireActorAttribute requireActorAttribute) + continue; + actorAttribute = requireActorAttribute; + break; + } + } + var actors = ScriptsEditor.ParentEditor.Values; for (int j = 0; j < actors.Count; j++) { var actor = (Actor)actors[j]; + + // If required actor exists but is not this actor type then skip adding to actor + if (actorAttribute != null) + { + if (actor.GetType() != actorAttribute.RequiredType && !actor.GetType().IsSubclassOf(actorAttribute.RequiredType)) + { + Editor.LogWarning($"{Utilities.Utils.GetPropertyNameUI(scriptType.Name)} not added to {actor} due to script requiring an Actor type of {actorAttribute.RequiredType}."); + continue; + } + } + actions.Add(AddRemoveScript.Add(actor, scriptType)); // Check if actor has required scripts and add them if the actor does not. foreach (var type in requiredScripts) @@ -614,7 +639,7 @@ namespace FlaxEditor.CustomEditors.Dedicated var editor = CustomEditorsUtil.CreateEditor(scriptType, false); // Check if actor has all the required scripts - bool hasAllRequiredScripts = true; + bool hasAllRequirements = true; if (scriptType.HasAttribute(typeof(RequireScriptAttribute), false)) { var scriptTypesToCheck = new List(); @@ -643,17 +668,40 @@ namespace FlaxEditor.CustomEditors.Dedicated var requiredScript = script.Actor.GetScript(type.Type); if (requiredScript == null) { - Editor.LogWarning($"{Utilities.Utils.GetPropertyNameUI(scriptType.Name)} on {script.Actor} is missing a required script of type {type}."); - hasAllRequiredScripts = false; + Editor.LogWarning($"{Utilities.Utils.GetPropertyNameUI(scriptType.Name)} on {script.Actor} is missing a required Script of type {type}."); + hasAllRequirements = false; } } } } + if (scriptType.HasAttribute(typeof(RequireActorAttribute), false)) + { + var scriptTypesToCheck = new List(); + RequireActorAttribute attribute = null; + foreach (var e in scriptType.GetAttributes(false)) + { + if (e is not RequireActorAttribute requireActorAttribute) + continue; + attribute = requireActorAttribute; + break; + } + + if (attribute != null) + { + var actor = script.Actor; + if (actor.GetType() != attribute.RequiredType && !actor.GetType().IsSubclassOf(attribute.RequiredType)) + { + Editor.LogWarning($"{Utilities.Utils.GetPropertyNameUI(scriptType.Name)} on {script.Actor} is missing a required Actor of type {attribute.RequiredType}."); + hasAllRequirements = false; + // Maybe call to remove script here? + } + } + } // Create group var title = Utilities.Utils.GetPropertyNameUI(scriptType.Name); var group = layout.Group(title, editor); - if (!hasAllRequiredScripts) + if (!hasAllRequirements) group.Panel.HeaderTextColor = FlaxEngine.GUI.Style.Current.Statusbar.Failed; if ((Presenter.Features & FeatureFlags.CacheExpandedGroups) != 0) { diff --git a/Source/Engine/Scripting/Attributes/Editor/RequireActorAttribute.cs b/Source/Engine/Scripting/Attributes/Editor/RequireActorAttribute.cs new file mode 100644 index 000000000..6e08217bf --- /dev/null +++ b/Source/Engine/Scripting/Attributes/Editor/RequireActorAttribute.cs @@ -0,0 +1,25 @@ +using System; + +namespace FlaxEngine; + +/// +/// This attribute is used to check for if a script requires an Actor type. +/// +[Serializable] +[AttributeUsage(AttributeTargets.Class)] +public class RequireActorAttribute : Attribute +{ + /// + /// The required type. + /// + public Type RequiredType; + + /// + /// Initializes a new instance of the class. + /// + /// The required type. + public RequireActorAttribute(Type type) + { + RequiredType = type; + } +} From 2ac8480df4c311177efeaced06bcfcd3873c8eb5 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Thu, 16 Nov 2023 21:45:33 -0600 Subject: [PATCH 031/139] Simplify RequireScriptAttribute code. --- .../CustomEditors/Dedicated/ScriptsEditor.cs | 60 ++++++++----------- .../Editor/RequireScriptAttribute.cs | 13 +++- 2 files changed, 35 insertions(+), 38 deletions(-) diff --git a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs index 93a8ec6be..6692eb5f0 100644 --- a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs @@ -229,25 +229,15 @@ namespace FlaxEditor.CustomEditors.Dedicated for (int i = 0; i < items.Count; i++) { var scriptType = items[i]; - List requiredScripts = new List(); + RequireScriptAttribute scriptAttribute = null; if (scriptType.HasAttribute(typeof(RequireScriptAttribute), false)) { - var attributes = new List(); foreach (var e in scriptType.GetAttributes(false)) { if (e is not RequireScriptAttribute requireScriptAttribute) continue; - attributes.Add(requireScriptAttribute); - } - - if (attributes.Count > 0) - { - foreach (var e in attributes) - { - if (!e.RequiredType.IsSubclassOf(typeof(Script))) - continue; - requiredScripts.Add(new ScriptType(e.RequiredType)); - } + scriptAttribute = requireScriptAttribute; + break; } } @@ -274,18 +264,26 @@ namespace FlaxEditor.CustomEditors.Dedicated { if (actor.GetType() != actorAttribute.RequiredType && !actor.GetType().IsSubclassOf(actorAttribute.RequiredType)) { - Editor.LogWarning($"{Utilities.Utils.GetPropertyNameUI(scriptType.Name)} not added to {actor} due to script requiring an Actor type of {actorAttribute.RequiredType}."); + Editor.LogWarning($"`{Utilities.Utils.GetPropertyNameUI(scriptType.Name)}` not added to `{actor}` due to script requiring an Actor type of `{actorAttribute.RequiredType}`."); continue; } } actions.Add(AddRemoveScript.Add(actor, scriptType)); // Check if actor has required scripts and add them if the actor does not. - foreach (var type in requiredScripts) + if (scriptAttribute != null) { - if (actor.GetScript(type.Type) != null) - continue; - actions.Add(AddRemoveScript.Add(actor, type)); + foreach (var type in scriptAttribute.RequiredTypes) + { + if (!type.IsSubclassOf(typeof(Script))) + { + Editor.LogWarning($"`{Utilities.Utils.GetPropertyNameUI(type.Name)}` not added to `{actor}` due to the class not being a subclass of Script."); + continue; + } + if (actor.GetScript(type) != null) + continue; + actions.Add(AddRemoveScript.Add(actor, new ScriptType(type))); + } } } } @@ -642,33 +640,24 @@ namespace FlaxEditor.CustomEditors.Dedicated bool hasAllRequirements = true; if (scriptType.HasAttribute(typeof(RequireScriptAttribute), false)) { - var scriptTypesToCheck = new List(); - var attributes = new List(); + RequireScriptAttribute scriptAttribute = null; foreach (var e in scriptType.GetAttributes(false)) { if (e is not RequireScriptAttribute requireScriptAttribute) continue; - attributes.Add(requireScriptAttribute); + scriptAttribute = requireScriptAttribute; } - if (attributes.Count > 0) + if (scriptAttribute != null) { - foreach (var attribute in attributes) + foreach (var type in scriptAttribute.RequiredTypes) { - if (!attribute.RequiredType.IsSubclassOf(typeof(Script))) + if (!type.IsSubclassOf(typeof(Script))) continue; - scriptTypesToCheck.Add(new ScriptType(attribute.RequiredType)); - } - } - - if (scriptTypesToCheck.Count > 0) - { - foreach (var type in scriptTypesToCheck) - { - var requiredScript = script.Actor.GetScript(type.Type); + var requiredScript = script.Actor.GetScript(type); if (requiredScript == null) { - Editor.LogWarning($"{Utilities.Utils.GetPropertyNameUI(scriptType.Name)} on {script.Actor} is missing a required Script of type {type}."); + Editor.LogWarning($"`{Utilities.Utils.GetPropertyNameUI(scriptType.Name)}` on `{script.Actor}` is missing a required Script of type `{type}`."); hasAllRequirements = false; } } @@ -676,7 +665,6 @@ namespace FlaxEditor.CustomEditors.Dedicated } if (scriptType.HasAttribute(typeof(RequireActorAttribute), false)) { - var scriptTypesToCheck = new List(); RequireActorAttribute attribute = null; foreach (var e in scriptType.GetAttributes(false)) { @@ -691,7 +679,7 @@ namespace FlaxEditor.CustomEditors.Dedicated var actor = script.Actor; if (actor.GetType() != attribute.RequiredType && !actor.GetType().IsSubclassOf(attribute.RequiredType)) { - Editor.LogWarning($"{Utilities.Utils.GetPropertyNameUI(scriptType.Name)} on {script.Actor} is missing a required Actor of type {attribute.RequiredType}."); + Editor.LogWarning($"`{Utilities.Utils.GetPropertyNameUI(scriptType.Name)}` on `{script.Actor}` is missing a required Actor of type `{attribute.RequiredType}`."); hasAllRequirements = false; // Maybe call to remove script here? } diff --git a/Source/Engine/Scripting/Attributes/Editor/RequireScriptAttribute.cs b/Source/Engine/Scripting/Attributes/Editor/RequireScriptAttribute.cs index eee529c5c..9b3c4ccd1 100644 --- a/Source/Engine/Scripting/Attributes/Editor/RequireScriptAttribute.cs +++ b/Source/Engine/Scripting/Attributes/Editor/RequireScriptAttribute.cs @@ -12,7 +12,7 @@ public class RequireScriptAttribute : Attribute /// /// The required type. /// - public Type RequiredType; + public Type[] RequiredTypes; /// /// Initializes a new instance of the class. @@ -20,6 +20,15 @@ public class RequireScriptAttribute : Attribute /// The required type. public RequireScriptAttribute(Type type) { - RequiredType = type; + RequiredTypes = new[] { type }; + } + + /// + /// Initializes a new instance of the <see cref="RequireScriptAttribute"/> class. + /// + /// The required types. + public RequireScriptAttribute(Type[] types) + { + RequiredTypes = types; } } From 9de408e4e886d4455dcff1c6f3ed25cb707d7dfd Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Thu, 16 Nov 2023 21:46:37 -0600 Subject: [PATCH 032/139] Fix comments --- .../Scripting/Attributes/Editor/RequireScriptAttribute.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/Engine/Scripting/Attributes/Editor/RequireScriptAttribute.cs b/Source/Engine/Scripting/Attributes/Editor/RequireScriptAttribute.cs index 9b3c4ccd1..414a13aeb 100644 --- a/Source/Engine/Scripting/Attributes/Editor/RequireScriptAttribute.cs +++ b/Source/Engine/Scripting/Attributes/Editor/RequireScriptAttribute.cs @@ -3,19 +3,19 @@ using System; namespace FlaxEngine; /// -/// This attribute is used to check for if a script requires another script type. +/// This attribute is used to check for if a script requires other script types. /// [Serializable] [AttributeUsage(AttributeTargets.Class)] public class RequireScriptAttribute : Attribute { /// - /// The required type. + /// The required types. /// public Type[] RequiredTypes; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The required type. public RequireScriptAttribute(Type type) @@ -24,7 +24,7 @@ public class RequireScriptAttribute : Attribute } /// - /// Initializes a new instance of the <see cref="RequireScriptAttribute"/> class. + /// Initializes a new instance of the class. /// /// The required types. public RequireScriptAttribute(Type[] types) From 3824b19c8b4aab7ad4ba2e53fc8de8805d6f0e05 Mon Sep 17 00:00:00 2001 From: Menotdan <32620310+Menotdan@users.noreply.github.com> Date: Thu, 16 Nov 2023 23:46:44 -0500 Subject: [PATCH 033/139] Added Actor.GetPrefabRoot() --- Source/Engine/Level/Actor.cpp | 15 +++++++++++++++ Source/Engine/Level/Actor.h | 6 ++++++ 2 files changed, 21 insertions(+) diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp index addb4861e..fa0b10af1 100644 --- a/Source/Engine/Level/Actor.cpp +++ b/Source/Engine/Level/Actor.cpp @@ -1339,6 +1339,21 @@ bool Actor::IsPrefabRoot() const return _isPrefabRoot != 0; } +Actor* Actor::GetPrefabRoot() +{ + if (!this->HasPrefabLink()) + { + return NULL; + } + + Actor* result = this; + while (!result == NULL && !result->IsPrefabRoot()) + { + result = result->GetParent(); + } + return result; +} + Actor* Actor::FindActor(const StringView& name) const { Actor* result = nullptr; diff --git a/Source/Engine/Level/Actor.h b/Source/Engine/Level/Actor.h index 0ce9a0dbc..6aad2f481 100644 --- a/Source/Engine/Level/Actor.h +++ b/Source/Engine/Level/Actor.h @@ -739,6 +739,12 @@ public: /// API_PROPERTY() bool IsPrefabRoot() const; + /// + /// Gets the root of the prefab this actor is attached to. + /// + /// The root prefab object, or null if this actor is not a prefab. + API_FUNCTION() Actor* GetPrefabRoot(); + public: /// /// Tries to find the actor with the given name in this actor hierarchy (checks this actor and all children hierarchy). From e4dc6d729597e4260a6617cbb1550eb5956441db Mon Sep 17 00:00:00 2001 From: "Mr. Capybara" Date: Sat, 18 Nov 2023 14:43:32 -0400 Subject: [PATCH 034/139] add checkbox to start game focused --- Source/Editor/Windows/GameWindow.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Source/Editor/Windows/GameWindow.cs b/Source/Editor/Windows/GameWindow.cs index 8c6f5dc06..0af541713 100644 --- a/Source/Editor/Windows/GameWindow.cs +++ b/Source/Editor/Windows/GameWindow.cs @@ -484,6 +484,16 @@ namespace FlaxEditor.Windows { base.OnShowContextMenu(menu); + // Focus on play + { + var focus = menu.AddButton("Start Focused"); + focus.CloseMenuOnClick = false; + var checkbox = new CheckBox(140, 2, FocusOnPlay) { Parent = focus }; + checkbox.StateChanged += state => FocusOnPlay = state.Checked; + } + + menu.AddSeparator(); + // Viewport Brightness { var brightness = menu.AddButton("Viewport Brightness"); From 03d629f0e1789ef7950219d7a264037b766f5af1 Mon Sep 17 00:00:00 2001 From: nothingTVatYT Date: Mon, 27 Nov 2023 04:19:45 +0100 Subject: [PATCH 035/139] calculate follow selection on FOV and far plane --- Source/Editor/Viewport/Cameras/FPSCamera.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Source/Editor/Viewport/Cameras/FPSCamera.cs b/Source/Editor/Viewport/Cameras/FPSCamera.cs index bf2e840ea..0bbcfd0e8 100644 --- a/Source/Editor/Viewport/Cameras/FPSCamera.cs +++ b/Source/Editor/Viewport/Cameras/FPSCamera.cs @@ -174,7 +174,10 @@ namespace FlaxEditor.Viewport.Cameras } else { - position = sphere.Center - Vector3.Forward * orientation * (sphere.Radius * 2.5f); + // calculate the min. distance so that the sphere fits roughly 70% in FOV + // clip to far plane as a disappearing big object might be confusing + var distance = Mathf.Min(1.4f * sphere.Radius / Mathf.Tan(Mathf.DegreesToRadians * Viewport.FieldOfView / 2), Viewport.FarPlane); + position = sphere.Center - Vector3.Forward * orientation * distance; } TargetPoint = sphere.Center; MoveViewport(position, orientation); From 47a25c7828cb6351f660ca2a6d282957458b8b6d Mon Sep 17 00:00:00 2001 From: ExMatics HydrogenC <33123710+HydrogenC@users.noreply.github.com> Date: Tue, 28 Nov 2023 07:17:46 +0800 Subject: [PATCH 036/139] Add font fallback Note: All the `First()` in the code are temperary workarounds to make it work and require refractoring --- Source/Editor/Content/Tree/ContentTreeNode.cs | 5 +- .../CustomEditors/Dedicated/ScriptsEditor.cs | 3 +- .../Dedicated/UIControlEditor.cs | 4 +- .../Editors/ActorTransformEditor.cs | 3 +- .../Editor/CustomEditors/Editors/TagEditor.cs | 2 +- Source/Editor/EditorAssets.cs | 2 + Source/Editor/GUI/ComboBox.cs | 2 +- .../GUI/ContextMenu/ContextMenuButton.cs | 7 +- Source/Editor/GUI/CurveEditor.cs | 2 +- Source/Editor/GUI/Docking/DockWindow.cs | 3 +- Source/Editor/GUI/ItemsListContextMenu.cs | 5 +- Source/Editor/GUI/MainMenuButton.cs | 5 +- Source/Editor/GUI/NavigationButton.cs | 5 +- Source/Editor/GUI/Row.cs | 5 +- Source/Editor/GUI/Table.cs | 3 +- .../Editor/GUI/Timeline/Tracks/MemberTrack.cs | 2 +- Source/Editor/GUI/ToolStripButton.cs | 5 +- Source/Editor/GUI/Tree/TreeNode.cs | 3 +- Source/Editor/Options/InterfaceOptions.cs | 6 + Source/Editor/Options/OptionsModule.cs | 9 +- Source/Editor/SceneGraph/GUI/ActorTreeNode.cs | 4 +- .../Editor/Surface/Archetypes/BehaviorTree.cs | 3 +- .../Surface/ContextMenu/VisjectCMItem.cs | 14 +- Source/Editor/Surface/Elements/InputBox.cs | 4 +- Source/Editor/Surface/SurfaceNode.cs | 5 +- Source/Editor/Tools/Foliage/FoliageTab.cs | 3 +- Source/Editor/Tools/Terrain/CarveTab.cs | 3 +- Source/Editor/Viewport/EditorViewport.cs | 6 +- .../Viewport/Widgets/ViewportWidgetButton.cs | 5 +- Source/Editor/Windows/AboutDialog.cs | 3 +- Source/Editor/Windows/Profiler/Timeline.cs | 5 +- Source/Editor/Windows/ToolboxWindow.cs | 5 +- Source/Engine/Render2D/Font.cpp | 205 +++++++++++ Source/Engine/Render2D/Font.h | 35 ++ Source/Engine/Render2D/FontManager.cpp | 3 + Source/Engine/Render2D/Render2D.cpp | 332 +++++++++++++++++- Source/Engine/Render2D/Render2D.cs | 53 +++ Source/Engine/Render2D/Render2D.h | 45 ++- Source/Engine/Scripting/Scripting.cs | 5 +- Source/Engine/UI/GUI/Common/Button.cs | 3 +- Source/Engine/UI/GUI/Common/Dropdown.cs | 3 +- Source/Engine/UI/GUI/Common/Label.cs | 8 +- Source/Engine/UI/GUI/Common/RichTextBox.cs | 3 +- Source/Engine/UI/GUI/Common/TextBox.cs | 4 +- Source/Engine/UI/GUI/Panels/DropPanel.cs | 3 +- Source/Engine/UI/GUI/Style.cs | 26 +- Source/Engine/UI/GUI/Tooltip.cs | 5 +- 47 files changed, 787 insertions(+), 87 deletions(-) diff --git a/Source/Editor/Content/Tree/ContentTreeNode.cs b/Source/Editor/Content/Tree/ContentTreeNode.cs index 0c0fc6a51..47159c6c9 100644 --- a/Source/Editor/Content/Tree/ContentTreeNode.cs +++ b/Source/Editor/Content/Tree/ContentTreeNode.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System.Collections.Generic; +using System.Linq; using FlaxEditor.GUI; using FlaxEditor.GUI.Drag; using FlaxEditor.GUI.Tree; @@ -140,8 +141,8 @@ namespace FlaxEditor.Content var textRect = TextRect; for (int i = 0; i < ranges.Length; i++) { - var start = font.GetCharPosition(text, ranges[i].StartIndex); - var end = font.GetCharPosition(text, ranges[i].EndIndex); + var start = font.First().GetCharPosition(text, ranges[i].StartIndex); + var end = font.First().GetCharPosition(text, ranges[i].EndIndex); _highlights.Add(new Rectangle(start.X + textRect.X, textRect.Y, end.X - start.X, textRect.Height)); } isThisVisible = true; diff --git a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs index d7bfbbad7..1f69074ce 100644 --- a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs @@ -3,6 +3,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Linq; using FlaxEditor.Actions; using FlaxEditor.Content; using FlaxEditor.GUI; @@ -42,7 +43,7 @@ namespace FlaxEditor.CustomEditors.Dedicated // Add script button var buttonText = "Add script"; - var textSize = Style.Current.FontMedium.MeasureText(buttonText); + var textSize = Style.Current.FontMedium.First().MeasureText(buttonText); float addScriptButtonWidth = (textSize.X < 60.0f) ? 60.0f : textSize.X + 4; var buttonHeight = (textSize.Y < 18) ? 18 : textSize.Y + 4; _addScriptsButton = new Button diff --git a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs index 296560507..5b5c2f90d 100644 --- a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs @@ -247,7 +247,7 @@ namespace FlaxEditor.CustomEditors.Dedicated // Info var info = new Label(0, title.Bottom, DialogWidth, InfoHeight) { - Font = new FontReference(style.FontSmall), + Font = new FontReference(style.FontSmall.First()), Text = "Shift: also set bounds\nControl: also set pivot", Parent = this }; @@ -423,7 +423,7 @@ namespace FlaxEditor.CustomEditors.Dedicated // Set control type button var space = layout.Space(20); var buttonText = "Set Type"; - var textSize = FlaxEngine.GUI.Style.Current.FontMedium.MeasureText(buttonText); + var textSize = FlaxEngine.GUI.Style.Current.FontMedium.First().MeasureText(buttonText); float setTypeButtonWidth = (textSize.X < 60.0f) ? 60.0f : textSize.X + 4; var setTypeButton = new Button { diff --git a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs index 4c153e759..d7059f712 100644 --- a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs @@ -2,6 +2,7 @@ using FlaxEngine; using FlaxEngine.GUI; +using System.Linq; namespace FlaxEditor.CustomEditors.Editors { @@ -100,7 +101,7 @@ namespace FlaxEditor.CustomEditors.Editors _linkButton.Clicked += ToggleLink; ToggleEnabled(); SetLinkStyle(); - var textSize = FlaxEngine.GUI.Style.Current.FontMedium.MeasureText(LinkedLabel.Text.Value); + var textSize = FlaxEngine.GUI.Style.Current.FontMedium.First().MeasureText(LinkedLabel.Text.Value); _linkButton.LocalX += textSize.X + 10; LinkedLabel.SetupContextMenu += (label, menu, editor) => { diff --git a/Source/Editor/CustomEditors/Editors/TagEditor.cs b/Source/Editor/CustomEditors/Editors/TagEditor.cs index dbd5d124c..a174d02e4 100644 --- a/Source/Editor/CustomEditors/Editors/TagEditor.cs +++ b/Source/Editor/CustomEditors/Editors/TagEditor.cs @@ -631,7 +631,7 @@ namespace FlaxEditor.CustomEditors.Editors TooltipText = "Edit...", Parent = _label, }; - var textSize = FlaxEngine.GUI.Style.Current.FontMedium.MeasureText(buttonText); + var textSize = FlaxEngine.GUI.Style.Current.FontMedium.First().MeasureText(buttonText); if (textSize.Y > button.Width) button.Width = textSize.Y + 2; diff --git a/Source/Editor/EditorAssets.cs b/Source/Editor/EditorAssets.cs index eb2f21356..237908a0c 100644 --- a/Source/Editor/EditorAssets.cs +++ b/Source/Editor/EditorAssets.cs @@ -54,6 +54,8 @@ namespace FlaxEditor /// public static string PrimaryFont = "Editor/Fonts/Roboto-Regular"; + public static string CJKFont = "Editor/Fonts/NotoSansSC-Medium"; + /// /// The Inconsolata Regular font. /// diff --git a/Source/Editor/GUI/ComboBox.cs b/Source/Editor/GUI/ComboBox.cs index 0417cc7e3..7dc407698 100644 --- a/Source/Editor/GUI/ComboBox.cs +++ b/Source/Editor/GUI/ComboBox.cs @@ -273,7 +273,7 @@ namespace FlaxEditor.GUI MaximumItemsInViewCount = 20; var style = Style.Current; - Font = new FontReference(style.FontMedium); + Font = new FontReference(style.FontMedium.First()); TextColor = style.Foreground; BackgroundColor = style.BackgroundNormal; BackgroundColorHighlighted = BackgroundColor; diff --git a/Source/Editor/GUI/ContextMenu/ContextMenuButton.cs b/Source/Editor/GUI/ContextMenu/ContextMenuButton.cs index e371f7c4b..872700a9b 100644 --- a/Source/Editor/GUI/ContextMenu/ContextMenuButton.cs +++ b/Source/Editor/GUI/ContextMenu/ContextMenuButton.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; +using System.Linq; using FlaxEngine; using FlaxEngine.GUI; @@ -233,11 +234,11 @@ namespace FlaxEditor.GUI.ContextMenu { var style = Style.Current; float width = 20; - if (style.FontMedium) + if (style.FontMedium.First()) { - width += style.FontMedium.MeasureText(Text).X; + width += style.FontMedium.First().MeasureText(Text).X; if (!string.IsNullOrEmpty(ShortKeys)) - width += 40 + style.FontMedium.MeasureText(ShortKeys).X; + width += 40 + style.FontMedium.First().MeasureText(ShortKeys).X; } return Mathf.Max(width, base.MinimumWidth); diff --git a/Source/Editor/GUI/CurveEditor.cs b/Source/Editor/GUI/CurveEditor.cs index 22deec120..f71409b20 100644 --- a/Source/Editor/GUI/CurveEditor.cs +++ b/Source/Editor/GUI/CurveEditor.cs @@ -437,7 +437,7 @@ namespace FlaxEditor.GUI _contentsColor = style.Background.RGBMultiplied(0.7f); _linesColor = style.ForegroundDisabled.RGBMultiplied(0.7f); _labelsColor = style.ForegroundDisabled; - _labelsFont = style.FontSmall; + _labelsFont = style.FontSmall.First(); _mainPanel = new Panel(ScrollBars.Both) { diff --git a/Source/Editor/GUI/Docking/DockWindow.cs b/Source/Editor/GUI/Docking/DockWindow.cs index dd4e39ce2..8601b9c53 100644 --- a/Source/Editor/GUI/Docking/DockWindow.cs +++ b/Source/Editor/GUI/Docking/DockWindow.cs @@ -6,6 +6,7 @@ using FlaxEngine; using FlaxEngine.Assertions; using FlaxEngine.GUI; using FlaxEditor.Options; +using System.Linq; namespace FlaxEditor.GUI.Docking { @@ -488,7 +489,7 @@ namespace FlaxEditor.GUI.Docking { var style = Style.Current; if (style?.FontMedium != null) - _titleSize = style.FontMedium.MeasureText(_title); + _titleSize = style.FontMedium.First().MeasureText(_title); } base.PerformLayoutBeforeChildren(); diff --git a/Source/Editor/GUI/ItemsListContextMenu.cs b/Source/Editor/GUI/ItemsListContextMenu.cs index 42d236991..ce69f3544 100644 --- a/Source/Editor/GUI/ItemsListContextMenu.cs +++ b/Source/Editor/GUI/ItemsListContextMenu.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Linq; using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.Input; using FlaxEditor.Utilities; @@ -86,8 +87,8 @@ namespace FlaxEditor.GUI var font = style.FontSmall; for (int i = 0; i < ranges.Length; i++) { - var start = font.GetCharPosition(Name, ranges[i].StartIndex); - var end = font.GetCharPosition(Name, ranges[i].EndIndex); + var start = font.First().GetCharPosition(Name, ranges[i].StartIndex); + var end = font.First().GetCharPosition(Name, ranges[i].EndIndex); _highlights.Add(new Rectangle(start.X + 2, 0, end.X - start.X, Height)); } Visible = true; diff --git a/Source/Editor/GUI/MainMenuButton.cs b/Source/Editor/GUI/MainMenuButton.cs index 117922361..76687f8b9 100644 --- a/Source/Editor/GUI/MainMenuButton.cs +++ b/Source/Editor/GUI/MainMenuButton.cs @@ -2,6 +2,7 @@ using FlaxEngine; using FlaxEngine.GUI; +using System.Linq; namespace FlaxEditor.GUI { @@ -101,8 +102,8 @@ namespace FlaxEditor.GUI var style = Style.Current; float width = 18; - if (style.FontMedium) - width += style.FontMedium.MeasureText(Text).X; + if (style.FontMedium.First()) + width += style.FontMedium.First().MeasureText(Text).X; Width = width; } diff --git a/Source/Editor/GUI/NavigationButton.cs b/Source/Editor/GUI/NavigationButton.cs index 18e862304..77f8a7656 100644 --- a/Source/Editor/GUI/NavigationButton.cs +++ b/Source/Editor/GUI/NavigationButton.cs @@ -2,6 +2,7 @@ using FlaxEngine; using FlaxEngine.GUI; +using System.Linq; namespace FlaxEditor.GUI { @@ -65,9 +66,9 @@ namespace FlaxEditor.GUI { var style = Style.Current; - if (style.FontMedium) + if (style.FontMedium.First()) { - Width = style.FontMedium.MeasureText(Text).X + 2 * DefaultMargin; + Width = style.FontMedium.First().MeasureText(Text).X + 2 * DefaultMargin; } } } diff --git a/Source/Editor/GUI/Row.cs b/Source/Editor/GUI/Row.cs index f6bd5b02a..458138aea 100644 --- a/Source/Editor/GUI/Row.cs +++ b/Source/Editor/GUI/Row.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; +using System.Linq; using FlaxEngine; using FlaxEngine.GUI; @@ -38,8 +39,8 @@ namespace FlaxEditor.GUI { Depth = -1; - if (Height < Style.Current.FontMedium.Height) - Height = Style.Current.FontMedium.Height + 4; + if (Height < Style.Current.FontMedium.First().Height) + Height = Style.Current.FontMedium.First().Height + 4; } /// diff --git a/Source/Editor/GUI/Table.cs b/Source/Editor/GUI/Table.cs index 1d22ddb15..fed33a336 100644 --- a/Source/Editor/GUI/Table.cs +++ b/Source/Editor/GUI/Table.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; +using System.Linq; using System.Runtime.CompilerServices; using FlaxEngine; using FlaxEngine.GUI; @@ -129,7 +130,7 @@ namespace FlaxEditor.GUI Render2D.FillRectangle(rect, column.TitleBackgroundColor); var style = Style.Current; - var font = column.TitleFont ?? style.FontMedium; + var font = column.TitleFont ?? style.FontMedium.First(); Render2D.DrawText(font, column.Title, rect, column.TitleColor, TextAlignment.Center, TextAlignment.Center); if (columnIndex < _columns.Length - 1) diff --git a/Source/Editor/GUI/Timeline/Tracks/MemberTrack.cs b/Source/Editor/GUI/Timeline/Tracks/MemberTrack.cs index 63787df2c..928129917 100644 --- a/Source/Editor/GUI/Timeline/Tracks/MemberTrack.cs +++ b/Source/Editor/GUI/Timeline/Tracks/MemberTrack.cs @@ -345,7 +345,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks if (_previewValue != null) { // Based on Track.Draw for track text placement - var left = _xOffset + 16 + Style.Current.FontSmall.MeasureText(Title ?? Name).X; + var left = _xOffset + 16 + Style.Current.FontSmall.First().MeasureText(Title ?? Name).X; if (Icon.IsValid) left += 18; if (IsExpanded) diff --git a/Source/Editor/GUI/ToolStripButton.cs b/Source/Editor/GUI/ToolStripButton.cs index e839a7356..b74c7c19f 100644 --- a/Source/Editor/GUI/ToolStripButton.cs +++ b/Source/Editor/GUI/ToolStripButton.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; +using System.Linq; using FlaxEngine; using FlaxEngine.GUI; @@ -150,8 +151,8 @@ namespace FlaxEditor.GUI if (hasSprite) width += iconSize; - if (!string.IsNullOrEmpty(_text) && style.FontMedium) - width += style.FontMedium.MeasureText(_text).X + (hasSprite ? DefaultMargin : 0); + if (!string.IsNullOrEmpty(_text) && style.FontMedium.First()) + width += style.FontMedium.First().MeasureText(_text).X + (hasSprite ? DefaultMargin : 0); Width = width; } diff --git a/Source/Editor/GUI/Tree/TreeNode.cs b/Source/Editor/GUI/Tree/TreeNode.cs index 703079469..f26929993 100644 --- a/Source/Editor/GUI/Tree/TreeNode.cs +++ b/Source/Editor/GUI/Tree/TreeNode.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; +using System.Linq; using FlaxEngine; using FlaxEngine.GUI; @@ -317,7 +318,7 @@ namespace FlaxEditor.GUI.Tree BackgroundColorSelected = style.BackgroundSelected; BackgroundColorHighlighted = style.BackgroundHighlighted; BackgroundColorSelectedUnfocused = style.LightBackground; - TextFont = new FontReference(style.FontSmall); + TextFont = new FontReference(style.FontSmall.First()); } /// diff --git a/Source/Editor/Options/InterfaceOptions.cs b/Source/Editor/Options/InterfaceOptions.cs index 95a273f19..c96a52fb1 100644 --- a/Source/Editor/Options/InterfaceOptions.cs +++ b/Source/Editor/Options/InterfaceOptions.cs @@ -237,12 +237,18 @@ namespace FlaxEditor.Options public int NumberOfGameClientsToLaunch = 1; private static FontAsset DefaultFont => FlaxEngine.Content.LoadAsyncInternal(EditorAssets.PrimaryFont); + private static FontAsset _cjkFont => FlaxEngine.Content.LoadAsyncInternal(EditorAssets.CJKFont); private FontReference _titleFont = new FontReference(DefaultFont, 18); private FontReference _largeFont = new FontReference(DefaultFont, 14); private FontReference _mediumFont = new FontReference(DefaultFont, 9); private FontReference _smallFont = new FontReference(DefaultFont, 9); private FontReference _outputLogFont = new FontReference(FlaxEngine.Content.LoadAsyncInternal(EditorAssets.InconsolataRegularFont), 10); + public FontReference CJKFont + { + get => new FontReference(_cjkFont, 9); + } + /// /// Gets or sets the title font for editor UI. /// diff --git a/Source/Editor/Options/OptionsModule.cs b/Source/Editor/Options/OptionsModule.cs index 1137e4c37..15059bd56 100644 --- a/Source/Editor/Options/OptionsModule.cs +++ b/Source/Editor/Options/OptionsModule.cs @@ -261,8 +261,9 @@ namespace FlaxEditor.Options // Fonts FontTitle = options.Interface.TitleFont.GetFont(), FontLarge = options.Interface.LargeFont.GetFont(), - FontMedium = options.Interface.MediumFont.GetFont(), - FontSmall = options.Interface.SmallFont.GetFont(), + FontMedium = new Font[] { options.Interface.MediumFont.GetFont(), options.Interface.CJKFont.GetFont() }, + FontSmall = new Font[] { options.Interface.SmallFont.GetFont(), options.Interface.CJKFont.GetFont() }, + FontCJK = options.Interface.CJKFont.GetFont(), // Icons ArrowDown = Editor.Icons.ArrowDown12, @@ -314,8 +315,8 @@ namespace FlaxEditor.Options // Fonts FontTitle = options.Interface.TitleFont.GetFont(), FontLarge = options.Interface.LargeFont.GetFont(), - FontMedium = options.Interface.MediumFont.GetFont(), - FontSmall = options.Interface.SmallFont.GetFont(), + FontMedium = new Font[] { options.Interface.MediumFont.GetFont(), options.Interface.CJKFont.GetFont() }, + FontSmall = new Font[] { options.Interface.SmallFont.GetFont(), options.Interface.CJKFont.GetFont() }, // Icons ArrowDown = Editor.Icons.ArrowDown12, diff --git a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs index f64e46385..9a6fe4fd2 100644 --- a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs +++ b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs @@ -142,8 +142,8 @@ namespace FlaxEditor.SceneGraph.GUI var textRect = TextRect; for (int i = 0; i < ranges.Length; i++) { - var start = font.GetCharPosition(text, ranges[i].StartIndex); - var end = font.GetCharPosition(text, ranges[i].EndIndex); + var start = font.First().GetCharPosition(text, ranges[i].StartIndex); + var end = font.First().GetCharPosition(text, ranges[i].EndIndex); _highlights.Add(new Rectangle(start.X + textRect.X, textRect.Y, end.X - start.X, textRect.Height)); } isThisVisible = true; diff --git a/Source/Editor/Surface/Archetypes/BehaviorTree.cs b/Source/Editor/Surface/Archetypes/BehaviorTree.cs index f8cd7bb5a..38dd28e62 100644 --- a/Source/Editor/Surface/Archetypes/BehaviorTree.cs +++ b/Source/Editor/Surface/Archetypes/BehaviorTree.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Linq; using FlaxEditor.CustomEditors.Dedicated; using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.Drag; @@ -100,7 +101,7 @@ namespace FlaxEditor.Surface.Archetypes _debugRelevant = Behavior.GetNodeDebugRelevancy(instance, behavior); _debugInfo = Behavior.GetNodeDebugInfo(instance, behavior); if (!string.IsNullOrEmpty(_debugInfo)) - _debugInfoSize = Style.Current.FontSmall.MeasureText(_debugInfo); + _debugInfoSize = Style.Current.FontSmall.First().MeasureText(_debugInfo); } } diff --git a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs index 207875a92..812bec5d2 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs @@ -200,8 +200,8 @@ namespace FlaxEditor.Surface.ContextMenu var font = style.FontSmall; for (int i = 0; i < ranges.Length; i++) { - var start = font.GetCharPosition(_archetype.Title, ranges[i].StartIndex); - var end = font.GetCharPosition(_archetype.Title, ranges[i].EndIndex); + var start = font.First().GetCharPosition(_archetype.Title, ranges[i].StartIndex); + var end = font.First().GetCharPosition(_archetype.Title, ranges[i].EndIndex); _highlights.Add(new Rectangle(start.X + textRect.X, 0, end.X - start.X, Height)); if (ranges[i].StartIndex <= 0) @@ -222,8 +222,8 @@ namespace FlaxEditor.Surface.ContextMenu _highlights.Clear(); var style = Style.Current; var font = style.FontSmall; - var start = font.GetCharPosition(_archetype.Title, 0); - var end = font.GetCharPosition(_archetype.Title, _archetype.Title.Length - 1); + var start = font.First().GetCharPosition(_archetype.Title, 0); + var end = font.First().GetCharPosition(_archetype.Title, _archetype.Title.Length - 1); _highlights.Add(new Rectangle(start.X + textRect.X, 0, end.X - start.X, Height)); _isFullMatch = true; Visible = true; @@ -237,8 +237,8 @@ namespace FlaxEditor.Surface.ContextMenu _highlights.Clear(); var style = Style.Current; var font = style.FontSmall; - var start = font.GetCharPosition(_archetype.Title, 0); - var end = font.GetCharPosition(_archetype.Title, _archetype.Title.Length - 1); + var start = font.First().GetCharPosition(_archetype.Title, 0); + var end = font.First().GetCharPosition(_archetype.Title, _archetype.Title.Length - 1); _highlights.Add(new Rectangle(start.X + textRect.X, 0, end.X - start.X, Height)); Visible = true; @@ -286,7 +286,7 @@ namespace FlaxEditor.Surface.ContextMenu Render2D.DrawText(style.FontSmall, _archetype.Title, textRect, Enabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center); if (_archetype.SubTitle != null) { - var titleLength = style.FontSmall.MeasureText(_archetype.Title).X; + var titleLength = style.FontSmall.First().MeasureText(_archetype.Title).X; var subTitleRect = new Rectangle(textRect.X + titleLength, textRect.Y, textRect.Width - titleLength, textRect.Height); Render2D.DrawText(style.FontSmall, _archetype.SubTitle, subTitleRect, style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center); } diff --git a/Source/Editor/Surface/Elements/InputBox.cs b/Source/Editor/Surface/Elements/InputBox.cs index 2047bcd1a..611160611 100644 --- a/Source/Editor/Surface/Elements/InputBox.cs +++ b/Source/Editor/Surface/Elements/InputBox.cs @@ -1428,7 +1428,7 @@ namespace FlaxEditor.Surface.Elements if (_defaultValueEditor != null) { - _defaultValueEditor.Location = new Float2(X + Width + 8 + Style.Current.FontSmall.MeasureText(Text).X, Y); + _defaultValueEditor.Location = new Float2(X + Width + 8 + Style.Current.FontSmall.First().MeasureText(Text).X, Y); } } @@ -1635,7 +1635,7 @@ namespace FlaxEditor.Surface.Elements { if (DefaultValueEditors[i].CanUse(this, ref _currentType)) { - var bounds = new Rectangle(X + Width + 8 + Style.Current.FontSmall.MeasureText(Text).X, Y, 90, Height); + var bounds = new Rectangle(X + Width + 8 + Style.Current.FontSmall.First().MeasureText(Text).X, Y, 90, Height); _editor = DefaultValueEditors[i]; try { diff --git a/Source/Editor/Surface/SurfaceNode.cs b/Source/Editor/Surface/SurfaceNode.cs index 780ef81f0..82a9ab2bd 100644 --- a/Source/Editor/Surface/SurfaceNode.cs +++ b/Source/Editor/Surface/SurfaceNode.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Linq; using FlaxEditor.Scripting; using FlaxEditor.Surface.Elements; using FlaxEditor.Surface.Undo; @@ -199,7 +200,7 @@ namespace FlaxEditor.Surface continue; if (child is InputBox inputBox) { - var boxWidth = boxLabelFont.MeasureText(inputBox.Text).X + 20; + var boxWidth = boxLabelFont.First().MeasureText(inputBox.Text).X + 20; if (inputBox.DefaultValueEditor != null) boxWidth += inputBox.DefaultValueEditor.Width + 4; leftWidth = Mathf.Max(leftWidth, boxWidth); @@ -207,7 +208,7 @@ namespace FlaxEditor.Surface } else if (child is OutputBox outputBox) { - rightWidth = Mathf.Max(rightWidth, boxLabelFont.MeasureText(outputBox.Text).X + 20); + rightWidth = Mathf.Max(rightWidth, boxLabelFont.First().MeasureText(outputBox.Text).X + 20); rightHeight = Mathf.Max(rightHeight, outputBox.Archetype.Position.Y - Constants.NodeMarginY - Constants.NodeHeaderSize + 20.0f); } else if (child is Control control) diff --git a/Source/Editor/Tools/Foliage/FoliageTab.cs b/Source/Editor/Tools/Foliage/FoliageTab.cs index 1b46a42be..e88972b5c 100644 --- a/Source/Editor/Tools/Foliage/FoliageTab.cs +++ b/Source/Editor/Tools/Foliage/FoliageTab.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Linq; using FlaxEditor.GUI.Tabs; using FlaxEditor.Modules; using FlaxEditor.SceneGraph.Actors; @@ -147,7 +148,7 @@ namespace FlaxEditor.Tools.Foliage Parent = _noFoliagePanel, Enabled = false }; - var textSize = Style.Current.FontMedium.MeasureText(buttonText); + var textSize = Style.Current.FontMedium.First().MeasureText(buttonText); if (_createNewFoliage.Width < textSize.X) { _createNewFoliage.LocalX -= (textSize.X - _createNewFoliage.Width) / 2; diff --git a/Source/Editor/Tools/Terrain/CarveTab.cs b/Source/Editor/Tools/Terrain/CarveTab.cs index d51915acf..0a43cd2d2 100644 --- a/Source/Editor/Tools/Terrain/CarveTab.cs +++ b/Source/Editor/Tools/Terrain/CarveTab.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; +using System.Linq; using FlaxEditor.GUI.Tabs; using FlaxEditor.Modules; using FlaxEditor.SceneGraph.Actors; @@ -105,7 +106,7 @@ namespace FlaxEditor.Tools.Terrain Parent = _noTerrainPanel, Enabled = false }; - var textSize = Style.Current.FontMedium.MeasureText(buttonText); + var textSize = Style.Current.FontMedium.First().MeasureText(buttonText); if (_createTerrainButton.Width < textSize.X) { _createTerrainButton.LocalX -= (textSize.X - _createTerrainButton.Width) / 2; diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index c49392d01..f4ebfb51b 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -548,9 +548,9 @@ namespace FlaxEditor.Viewport #region Camera settings widget var largestText = "Relative Panning"; - var textSize = Style.Current.FontMedium.MeasureText(largestText); + var textSize = Style.Current.FontMedium.First().MeasureText(largestText); var xLocationForExtras = textSize.X + 5; - var cameraSpeedTextWidth = Style.Current.FontMedium.MeasureText("0.00").X; + var cameraSpeedTextWidth = Style.Current.FontMedium.First().MeasureText("0.00").X; // Camera Settings Widget _cameraWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); @@ -801,7 +801,7 @@ namespace FlaxEditor.Viewport #region View mode widget largestText = "Brightness"; - textSize = Style.Current.FontMedium.MeasureText(largestText); + textSize = Style.Current.FontMedium.First().MeasureText(largestText); xLocationForExtras = textSize.X + 5; var viewMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperLeft); diff --git a/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs b/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs index 77e94ae53..a58350ded 100644 --- a/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs +++ b/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; +using System.Linq; using FlaxEditor.GUI.ContextMenu; using FlaxEngine; using FlaxEngine.GUI; @@ -162,8 +163,8 @@ namespace FlaxEditor.Viewport.Widgets { var style = Style.Current; - if (style != null && style.FontMedium) - Width = CalculateButtonWidth(_forcedTextWidth > 0.0f ? _forcedTextWidth : style.FontMedium.MeasureText(_text).X, Icon.IsValid); + if (style != null && style.FontMedium.First()) + Width = CalculateButtonWidth(_forcedTextWidth > 0.0f ? _forcedTextWidth : style.FontMedium.First().MeasureText(_text).X, Icon.IsValid); } } } diff --git a/Source/Editor/Windows/AboutDialog.cs b/Source/Editor/Windows/AboutDialog.cs index b059dadcb..63ad44f29 100644 --- a/Source/Editor/Windows/AboutDialog.cs +++ b/Source/Editor/Windows/AboutDialog.cs @@ -2,6 +2,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System.Collections.Generic; +using System.Linq; using FlaxEditor.GUI.Dialogs; using FlaxEngine; using FlaxEngine.GUI; @@ -53,7 +54,7 @@ namespace FlaxEditor.Windows Parent = this }; var buttonText = "Copy version info"; - var fontSize = Style.Current.FontMedium.MeasureText(buttonText); + var fontSize = Style.Current.FontMedium.First().MeasureText(buttonText); var copyVersionButton = new Button(Width - fontSize.X - 8, 6, fontSize.X + 4, 20) { Text = buttonText, diff --git a/Source/Editor/Windows/Profiler/Timeline.cs b/Source/Editor/Windows/Profiler/Timeline.cs index 59a7a0e26..88019b329 100644 --- a/Source/Editor/Windows/Profiler/Timeline.cs +++ b/Source/Editor/Windows/Profiler/Timeline.cs @@ -2,6 +2,7 @@ using FlaxEngine; using FlaxEngine.GUI; +using System.Linq; namespace FlaxEditor.Windows.Profiler { @@ -84,8 +85,8 @@ namespace FlaxEditor.Windows.Profiler Render2D.FillRectangle(bounds, color); Render2D.DrawRectangle(bounds, color * 0.5f); - if (_nameLength < 0 && style.FontMedium) - _nameLength = style.FontMedium.MeasureText(_name).X; + if (_nameLength < 0 && style.FontMedium.First()) + _nameLength = style.FontMedium.First().MeasureText(_name).X; if (_nameLength < bounds.Width + 4) { diff --git a/Source/Editor/Windows/ToolboxWindow.cs b/Source/Editor/Windows/ToolboxWindow.cs index e2b04669b..e8fc1d56c 100644 --- a/Source/Editor/Windows/ToolboxWindow.cs +++ b/Source/Editor/Windows/ToolboxWindow.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Linq; using FlaxEditor.GUI.Input; using FlaxEditor.GUI.Tabs; using FlaxEditor.GUI.Tree; @@ -272,8 +273,8 @@ namespace FlaxEditor.Windows var textRect = item.TextRect; for (int i = 0; i < ranges.Length; i++) { - var start = font.GetCharPosition(text, ranges[i].StartIndex); - var end = font.GetCharPosition(text, ranges[i].EndIndex); + var start = font.First().GetCharPosition(text, ranges[i].StartIndex); + var end = font.First().GetCharPosition(text, ranges[i].EndIndex); highlights.Add(new Rectangle(start.X + textRect.X, textRect.Y, end.X - start.X, textRect.Height)); } item.SetHighlights(highlights); diff --git a/Source/Engine/Render2D/Font.cpp b/Source/Engine/Render2D/Font.cpp index 90423f5ce..13b17197c 100644 --- a/Source/Engine/Render2D/Font.cpp +++ b/Source/Engine/Render2D/Font.cpp @@ -283,6 +283,206 @@ void Font::ProcessText(const StringView& text, Array& outputLines } } +void Font::ProcessText(const Array& fonts, const StringView& text, Array& outputLines, API_PARAM(Ref) const TextLayoutOptions& layout) +{ + float cursorX = 0; + int32 kerning; + FontLineCache tmpLine; + FontCharacterEntry entry; + FontCharacterEntry previous; + int32 textLength = text.Length(); + float scale = layout.Scale / FontManager::FontScale; + float boundsWidth = layout.Bounds.GetWidth(); + float baseLinesDistanceScale = layout.BaseLinesGapScale * scale; + tmpLine.Location = Float2::Zero; + tmpLine.Size = Float2::Zero; + tmpLine.FirstCharIndex = 0; + tmpLine.LastCharIndex = -1; + + int32 lastWrapCharIndex = INVALID_INDEX; + float lastWrapCharX = 0; + bool lastMoveLine = false; + + int32 previousFontIndex = -1; + // The maximum font height of the current line + float maxHeight = 0; + // Process each character to split text into single lines + for (int32 currentIndex = 0; currentIndex < textLength;) + { + bool moveLine = false; + float xAdvance = 0; + int32 nextCharIndex = currentIndex + 1; + + // Cache current character + const Char currentChar = text[currentIndex]; + const bool isWhitespace = StringUtils::IsWhitespace(currentChar); + + // Check if character can wrap words + const bool isWrapChar = !StringUtils::IsAlnum(currentChar) || isWhitespace || StringUtils::IsUpper(currentChar); + if (isWrapChar && currentIndex != 0) + { + lastWrapCharIndex = currentIndex; + lastWrapCharX = cursorX; + } + + // Check if it's a newline character + if (currentChar == '\n') + { + // Break line + moveLine = true; + currentIndex++; + tmpLine.LastCharIndex++; + } + else + { + // Get character entry + int32 fontIndex = 0; + while (fontIndex < fonts.Count() && !fonts[fontIndex]->ContainsChar(currentChar)) + { + fontIndex++; + } + + // If no font can match the char, then use the first font + if (fontIndex == fonts.Count()) { + fontIndex = 0; + } + // Get character entry + fonts[fontIndex]->GetCharacter(currentChar, entry); + maxHeight = Math::Max(maxHeight, static_cast(fonts[fontIndex]->GetHeight())); + + // Get kerning, only when the font hasn't changed + if (!isWhitespace && previous.IsValid && previousFontIndex == fontIndex) + { + kerning = fonts[fontIndex]->GetKerning(previous.Character, entry.Character); + } + else + { + kerning = 0; + } + previous = entry; + previousFontIndex = fontIndex; + xAdvance = (kerning + entry.AdvanceX) * scale; + + // Check if character fits the line or skip wrapping + if (cursorX + xAdvance <= boundsWidth || layout.TextWrapping == TextWrapping::NoWrap) + { + // Move character + cursorX += xAdvance; + tmpLine.LastCharIndex++; + } + else if (layout.TextWrapping == TextWrapping::WrapWords) + { + if (lastWrapCharIndex != INVALID_INDEX) + { + // Skip moving twice for the same character + int32 lastLineLasCharIndex = outputLines.HasItems() ? outputLines.Last().LastCharIndex : -10000; + if (lastLineLasCharIndex == lastWrapCharIndex || lastLineLasCharIndex == lastWrapCharIndex - 1 || lastLineLasCharIndex == lastWrapCharIndex - 2) + { + currentIndex = nextCharIndex; + lastMoveLine = moveLine; + continue; + } + + // Move line + const Char wrapChar = text[lastWrapCharIndex]; + moveLine = true; + cursorX = lastWrapCharX; + if (StringUtils::IsWhitespace(wrapChar)) + { + // Skip whitespaces + tmpLine.LastCharIndex = lastWrapCharIndex - 1; + nextCharIndex = currentIndex = lastWrapCharIndex + 1; + } + else + { + tmpLine.LastCharIndex = lastWrapCharIndex - 1; + nextCharIndex = currentIndex = lastWrapCharIndex; + } + } + } + else if (layout.TextWrapping == TextWrapping::WrapChars) + { + // Move line + moveLine = true; + nextCharIndex = currentIndex; + + // Skip moving twice for the same character + if (lastMoveLine) + break; + } + } + + // Check if move to another line + if (moveLine) + { + // Add line + tmpLine.Size.X = cursorX; + tmpLine.Size.Y = baseLinesDistanceScale * maxHeight; + tmpLine.LastCharIndex = Math::Max(tmpLine.LastCharIndex, tmpLine.FirstCharIndex); + outputLines.Add(tmpLine); + + // Reset line + tmpLine.Location.Y += baseLinesDistanceScale * maxHeight; + tmpLine.FirstCharIndex = currentIndex; + tmpLine.LastCharIndex = currentIndex - 1; + cursorX = 0; + lastWrapCharIndex = INVALID_INDEX; + lastWrapCharX = 0; + previous.IsValid = false; + + // Reset max font height + maxHeight = 0; + } + + currentIndex = nextCharIndex; + lastMoveLine = moveLine; + } + + if (textLength != 0 && (tmpLine.LastCharIndex >= tmpLine.FirstCharIndex || text[textLength - 1] == '\n')) + { + // Add line + tmpLine.Size.X = cursorX; + tmpLine.Size.Y = baseLinesDistanceScale * maxHeight; + tmpLine.LastCharIndex = textLength - 1; + outputLines.Add(tmpLine); + + tmpLine.Location.Y += baseLinesDistanceScale * maxHeight; + } + + // Check amount of lines + if (outputLines.IsEmpty()) + return; + + float totalHeight = tmpLine.Location.Y; + + Float2 offset = Float2::Zero; + if (layout.VerticalAlignment == TextAlignment::Center) + { + offset.Y += (layout.Bounds.GetHeight() - totalHeight) * 0.5f; + } + else if (layout.VerticalAlignment == TextAlignment::Far) + { + offset.Y += layout.Bounds.GetHeight() - totalHeight; + } + for (int32 i = 0; i < outputLines.Count(); i++) + { + FontLineCache& line = outputLines[i]; + Float2 rootPos = line.Location + offset; + + // Fix upper left line corner to match desire text alignment + if (layout.HorizontalAlignment == TextAlignment::Center) + { + rootPos.X += (layout.Bounds.GetWidth() - line.Size.X) * 0.5f; + } + else if (layout.HorizontalAlignment == TextAlignment::Far) + { + rootPos.X += layout.Bounds.GetWidth() - line.Size.X; + } + + line.Location = rootPos; + } +} + Float2 Font::MeasureText(const StringView& text, const TextLayoutOptions& layout) { // Check if there is no need to do anything @@ -432,6 +632,11 @@ Float2 Font::GetCharPosition(const StringView& text, int32 index, const TextLayo return rootOffset + Float2(lines.Last().Size.X, static_cast((lines.Count() - 1) * baseLinesDistance)); } +bool Font::ContainsChar(Char c) +{ + return FT_Get_Char_Index(GetAsset()->GetFTFace(), c) > 0; +} + void Font::FlushFaceSize() const { // Set the character size diff --git a/Source/Engine/Render2D/Font.h b/Source/Engine/Render2D/Font.h index 90f723cd8..d8ac1b708 100644 --- a/Source/Engine/Render2D/Font.h +++ b/Source/Engine/Render2D/Font.h @@ -339,6 +339,15 @@ public: /// The output lines list. void ProcessText(const StringView& text, Array& outputLines, API_PARAM(Ref) const TextLayoutOptions& layout); + /// + /// Processes text to get cached lines for rendering. + /// + /// The font list. + /// The input text. + /// The layout properties. + /// The output lines list. + static void ProcessText(const Array& fonts, const StringView& text, Array& outputLines, API_PARAM(Ref) const TextLayoutOptions& layout); + /// /// Processes text to get cached lines for rendering. /// @@ -395,6 +404,15 @@ public: /// The minimum size for that text and fot to render properly. API_FUNCTION() Float2 MeasureText(const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout); + /// + /// Measures minimum size of the rectangle that will be needed to draw given text. + /// + /// The fonts to render with. + /// The input text to test. + /// The layout properties. + /// The minimum size for that text and fot to render properly. + API_FUNCTION() static Float2 MeasureText(const Array& fonts, const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout); + /// /// Measures minimum size of the rectangle that will be needed to draw given text. /// @@ -482,6 +500,16 @@ public: /// The character position (upper left corner which can be used for a caret position). API_FUNCTION() Float2 GetCharPosition(const StringView& text, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout); + /// + /// Calculates character position for given text and character index. + /// + /// The fonts to use. + /// The input text to test. + /// The text position to get coordinates of. + /// The text layout properties. + /// The character position (upper left corner which can be used for a caret position). + API_FUNCTION() static Float2 GetCharPosition(const Array& fonts, const StringView& text, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout); + /// /// Calculates character position for given text and character index. /// @@ -518,6 +546,13 @@ public: return GetCharPosition(textRange.Substring(text), index, TextLayoutOptions()); } + /// + /// Check if the font contains the glyph of a char + /// + /// The char to test. + /// True if the font contains the glyph of the char, otherwise false. + API_FUNCTION() FORCE_INLINE bool ContainsChar(Char c); + /// /// Flushes the size of the face with the Free Type library backend. /// diff --git a/Source/Engine/Render2D/FontManager.cpp b/Source/Engine/Render2D/FontManager.cpp index bb7edf974..5b08c1d87 100644 --- a/Source/Engine/Render2D/FontManager.cpp +++ b/Source/Engine/Render2D/FontManager.cpp @@ -155,6 +155,9 @@ bool FontManager::AddNewEntry(Font* font, Char c, FontCharacterEntry& entry) // Get the index to the glyph in the font face const FT_UInt glyphIndex = FT_Get_Char_Index(face, c); + if (glyphIndex == 0) { + LOG(Warning, "Font `{}` doesn't contain character `\\u{:x}`, consider choosing another font. ", String(face->family_name), c); + } // Load the glyph const FT_Error error = FT_Load_Glyph(face, glyphIndex, glyphFlags); diff --git a/Source/Engine/Render2D/Render2D.cpp b/Source/Engine/Render2D/Render2D.cpp index b56c3e1a2..eedfbeffe 100644 --- a/Source/Engine/Render2D/Render2D.cpp +++ b/Source/Engine/Render2D/Render2D.cpp @@ -27,11 +27,11 @@ #if USE_EDITOR #define RENDER2D_CHECK_RENDERING_STATE \ - if (!Render2D::IsRendering()) \ - { \ - LOG(Error, "Calling Render2D is only valid during rendering."); \ - return; \ - } + if (!Render2D::IsRendering()) \ + { \ + LOG(Error, "Calling Render2D is only valid during rendering."); \ + return; \ + } #else #define RENDER2D_CHECK_RENDERING_STATE #endif @@ -54,7 +54,7 @@ const bool DownsampleForBlur = false; PACK_STRUCT(struct Data { Matrix ViewProjection; - }); +}); PACK_STRUCT(struct BlurData { Float2 InvBufferSize; @@ -62,7 +62,7 @@ PACK_STRUCT(struct BlurData { float Dummy0; Float4 Bounds; Float4 WeightAndOffsets[RENDER2D_BLUR_MAX_SAMPLES / 2]; - }); +}); enum class DrawCallType : byte { @@ -1174,7 +1174,7 @@ void Render2D::DrawText(Font* font, const StringView& text, const Color& color, drawCall.AsChar.Mat = nullptr; } Float2 pointer = location; - for (int32 currentIndex = 0; currentIndex <= text.Length(); currentIndex++) + for (int32 currentIndex = 0; currentIndex < text.Length(); currentIndex++) { // Cache current character const Char currentChar = text[currentIndex]; @@ -1368,6 +1368,318 @@ void Render2D::DrawText(Font* font, const StringView& text, const TextRange& tex DrawText(font, textRange.Substring(text), color, layout, customMaterial); } +void Render2D::DrawText(const Array& fonts, const StringView& text, const Color& color, const Float2& location, MaterialBase* customMaterial) +{ + RENDER2D_CHECK_RENDERING_STATE; + + // Check if there is no need to do anything + if (fonts.IsEmpty() || text.Length() < 0) + return; + + // Temporary data + uint32 fontAtlasIndex = 0; + FontTextureAtlas* fontAtlas = nullptr; + Float2 invAtlasSize = Float2::One; + FontCharacterEntry previous; + int32 kerning; + float scale = 1.0f / FontManager::FontScale; + + // Render all characters + FontCharacterEntry entry; + Render2DDrawCall drawCall; + if (customMaterial) + { + drawCall.Type = DrawCallType::DrawCharMaterial; + drawCall.AsChar.Mat = customMaterial; + } + else + { + drawCall.Type = DrawCallType::DrawChar; + drawCall.AsChar.Mat = nullptr; + } + + // The following code cut the text into segments, according to the font used to render + Float2 pointer = location; + // The starting index of the current segment + int32 startIndex = 0; + // The index of the font used by the current segment + int32 segmentFontIndex = 0; + // The maximum font height of the current line + float maxHeight = 0; + for (int32 currentIndex = 0; currentIndex <= text.Length(); currentIndex++) + { + // Cache current character + const Char currentChar = currentIndex < text.Length() ? text[currentIndex] : 0; + + // Check if it isn't a newline character + if (currentChar != '\n') + { + int32 fontIndex = 0; + if (currentIndex < text.Length()) { + while (fontIndex < fonts.Count() && !fonts[fontIndex]->ContainsChar(currentChar)) + { + fontIndex++; + } + + // If no font can match the char, then use the segment font + if (fontIndex == fonts.Count()) { + fontIndex = segmentFontIndex; + } + + // Do nothing if the char still belongs to the current segment + if (fontIndex == segmentFontIndex) { + continue; + } + } + + // Render the pending segment before beginning the new segment + renderText:auto fontHeight = fonts[segmentFontIndex]->GetHeight(); + maxHeight = Math::Max(maxHeight, static_cast(fontHeight)); + auto fontDescender = fonts[segmentFontIndex]->GetDescender(); + for (int32 renderIndex = startIndex; renderIndex < currentIndex; renderIndex++) + { + // Get character entry + fonts[segmentFontIndex]->GetCharacter(text[renderIndex], entry); + + // Check if need to select/change font atlas (since characters even in the same font may be located in different atlases) + if (fontAtlas == nullptr || entry.TextureIndex != fontAtlasIndex) + { + // Get texture atlas that contains current character + fontAtlasIndex = entry.TextureIndex; + fontAtlas = FontManager::GetAtlas(fontAtlasIndex); + if (fontAtlas) + { + fontAtlas->EnsureTextureCreated(); + drawCall.AsChar.Tex = fontAtlas->GetTexture(); + invAtlasSize = 1.0f / fontAtlas->GetSize(); + } + else + { + drawCall.AsChar.Tex = nullptr; + invAtlasSize = 1.0f; + } + } + + // Check if character is a whitespace + const bool isWhitespace = StringUtils::IsWhitespace(text[renderIndex]); + + // Get kerning + if (!isWhitespace && previous.IsValid) + { + kerning = fonts[segmentFontIndex]->GetKerning(previous.Character, entry.Character); + } + else + { + kerning = 0; + } + pointer.X += kerning * scale; + previous = entry; + + // Omit whitespace characters + if (!isWhitespace) + { + // Calculate character size and atlas coordinates + const float x = pointer.X + entry.OffsetX * scale; + const float y = pointer.Y + (fontHeight + fontDescender - entry.OffsetY) * scale; + + Rectangle charRect(x, y, entry.UVSize.X * scale, entry.UVSize.Y * scale); + + Float2 upperLeftUV = entry.UV * invAtlasSize; + Float2 rightBottomUV = (entry.UV + entry.UVSize) * invAtlasSize; + + // Add draw call + drawCall.StartIB = IBIndex; + drawCall.CountIB = 6; + DrawCalls.Add(drawCall); + WriteRect(charRect, color, upperLeftUV, rightBottomUV); + } + + // Move + pointer.X += entry.AdvanceX * scale; + } + + // Start new segment + startIndex = currentIndex; + segmentFontIndex = fontIndex; + + if (currentIndex == text.Length() - 1) { + currentIndex++; + goto renderText; + } + } + else + { + // Move + pointer.X = location.X; + pointer.Y += maxHeight * scale; + // Clear max height + maxHeight = 0; + } + } +} + +void Render2D::DrawText(const Array& fonts, const StringView& text, const TextRange& textRange, const Color& color, const Float2& location, MaterialBase* customMaterial) +{ + DrawText(fonts, textRange.Substring(text), color, location, customMaterial); +} + +void Render2D::DrawText(const Array& fonts, const StringView& text, const Color& color, const TextLayoutOptions& layout, MaterialBase* customMaterial) +{ + RENDER2D_CHECK_RENDERING_STATE; + + // Check if there is no need to do anything + if (fonts.IsEmpty() || text.IsEmpty() || layout.Scale <= ZeroTolerance) + return; + + // Temporary data + uint32 fontAtlasIndex = 0; + FontTextureAtlas* fontAtlas = nullptr; + Float2 invAtlasSize = Float2::One; + FontCharacterEntry previous; + int32 kerning; + float scale = layout.Scale / FontManager::FontScale; + + // Process text to get lines + Lines.Clear(); + Font::ProcessText(fonts, text, Lines, layout); + + // Render all lines + FontCharacterEntry entry; + Render2DDrawCall drawCall; + if (customMaterial) + { + drawCall.Type = DrawCallType::DrawCharMaterial; + drawCall.AsChar.Mat = customMaterial; + } + else + { + drawCall.Type = DrawCallType::DrawChar; + drawCall.AsChar.Mat = nullptr; + } + + for (int32 lineIndex = 0; lineIndex < Lines.Count(); lineIndex++) + { + const FontLineCache& line = Lines[lineIndex]; + + // The following code cut the text into segments, according to the font used to render + Float2 pointer = line.Location; + // The starting index of the current segment + int32 startIndex = line.FirstCharIndex; + // The index of the font used by the current segment + int32 segmentFontIndex = 0; + // The maximum font height of the current line + float maxHeight = 0; + + // Partition and render all characters from the line + for (int32 charIndex = line.FirstCharIndex; charIndex <= line.LastCharIndex + 1; charIndex++) + { + const Char c = charIndex <= line.LastCharIndex ? text[charIndex] : 0; + + if (c != '\n') + { + int32 fontIndex = 0; + if (charIndex <= line.LastCharIndex) { + while (fontIndex < fonts.Count() && !fonts[fontIndex]->ContainsChar(c)) + { + fontIndex++; + } + + // If no font can match the char, then use the segment font + if (fontIndex == fonts.Count()) { + fontIndex = segmentFontIndex; + } + + + // Do nothing if the char still belongs to the current segment + if (fontIndex == segmentFontIndex) { + continue; + } + } + + + // Render the pending segment before beginning the new segment + auto fontHeight = fonts[segmentFontIndex]->GetHeight(); + maxHeight = Math::Max(maxHeight, static_cast(fontHeight)); + auto fontDescender = fonts[segmentFontIndex]->GetDescender(); + + const Char* pred = L"Type"; + if (text.Substring(0, Math::Min(4, text.Length())) == pred) { + // __debugbreak(); + } + for (int32 renderIndex = startIndex; renderIndex < charIndex; renderIndex++) + { + // Get character entry + fonts[segmentFontIndex]->GetCharacter(text[renderIndex], entry); + + // Check if need to select/change font atlas (since characters even in the same font may be located in different atlases) + if (fontAtlas == nullptr || entry.TextureIndex != fontAtlasIndex) + { + // Get texture atlas that contains current character + fontAtlasIndex = entry.TextureIndex; + fontAtlas = FontManager::GetAtlas(fontAtlasIndex); + if (fontAtlas) + { + fontAtlas->EnsureTextureCreated(); + invAtlasSize = 1.0f / fontAtlas->GetSize(); + drawCall.AsChar.Tex = fontAtlas->GetTexture(); + } + else + { + invAtlasSize = 1.0f; + drawCall.AsChar.Tex = nullptr; + } + } + + // Get kerning + const bool isWhitespace = StringUtils::IsWhitespace(text[renderIndex]); + if (!isWhitespace && previous.IsValid) + { + kerning = fonts[segmentFontIndex]->GetKerning(previous.Character, entry.Character); + } + else + { + kerning = 0; + } + pointer.X += (float)kerning * scale; + previous = entry; + + // Omit whitespace characters + if (!isWhitespace) + { + // Calculate character size and atlas coordinates + const float x = pointer.X + entry.OffsetX * scale; + const float y = pointer.Y - entry.OffsetY * scale + Math::Ceil((fontHeight + fontDescender) * scale); + + Rectangle charRect(x, y, entry.UVSize.X * scale, entry.UVSize.Y * scale); + charRect.Offset(layout.Bounds.Location); + + Float2 upperLeftUV = entry.UV * invAtlasSize; + Float2 rightBottomUV = (entry.UV + entry.UVSize) * invAtlasSize; + + // Add draw call + drawCall.StartIB = IBIndex; + drawCall.CountIB = 6; + DrawCalls.Add(drawCall); + WriteRect(charRect, color, upperLeftUV, rightBottomUV); + } + + // Move + pointer.X += entry.AdvanceX * scale; + } + + // Start new segment + startIndex = charIndex; + segmentFontIndex = fontIndex; + } + } + } +} + +void Render2D::DrawText(const Array& fonts, const StringView& text, const TextRange& textRange, const Color& color, const TextLayoutOptions& layout, MaterialBase* customMaterial) +{ + DrawText(fonts, textRange.Substring(text), color, layout, customMaterial); +} + FORCE_INLINE bool NeedAlphaWithTint(const Color& color) { return (color.A * TintLayersStack.Peek().A) < 1.0f; @@ -1931,7 +2243,7 @@ void Render2D::DrawBlur(const Rectangle& rect, float blurStrength) void Render2D::DrawTexturedTriangles(GPUTexture* t, const Span& vertices, const Span& uvs) { RENDER2D_CHECK_RENDERING_STATE; - CHECK(vertices.Length() == uvs.Length()) + CHECK(vertices.Length() == uvs.Length()); Render2DDrawCall& drawCall = DrawCalls.AddOne(); drawCall.Type = DrawCallType::FillTexture; @@ -1977,7 +2289,7 @@ void Render2D::DrawTexturedTriangles(GPUTexture* t, const Span& indices, drawCall.StartIB = IBIndex; drawCall.CountIB = indices.Length(); drawCall.AsTexture.Ptr = t; - + for (int32 i = 0; i < indices.Length();) { const uint16 i0 = indices.Get()[i++]; diff --git a/Source/Engine/Render2D/Render2D.cs b/Source/Engine/Render2D/Render2D.cs index c4d9e81b4..a73556f7b 100644 --- a/Source/Engine/Render2D/Render2D.cs +++ b/Source/Engine/Render2D/Render2D.cs @@ -152,6 +152,59 @@ namespace FlaxEngine DrawText(font, text, color, ref layout, customMaterial); } + /// + /// Draws a text. + /// + /// The fonts to use, ordered by priority. + /// The text to render. + /// The size and position of the area in which the text is drawn. + /// The text color. + /// The horizontal alignment of the text in a layout rectangle. + /// The vertical alignment of the text in a layout rectangle. + /// Describes how wrap text inside a layout rectangle. + /// The scale for distance one baseline from another. Default is 1. + /// The text drawing scale. Default is 1. + public static void DrawText(Font[] fonts, string text, Rectangle layoutRect, Color color, TextAlignment horizontalAlignment = TextAlignment.Near, TextAlignment verticalAlignment = TextAlignment.Near, TextWrapping textWrapping = TextWrapping.NoWrap, float baseLinesGapScale = 1.0f, float scale = 1.0f) + { + var layout = new TextLayoutOptions + { + Bounds = layoutRect, + HorizontalAlignment = horizontalAlignment, + VerticalAlignment = verticalAlignment, + TextWrapping = textWrapping, + Scale = scale, + BaseLinesGapScale = baseLinesGapScale, + }; + DrawText(fonts, text, color, ref layout); + } + + /// + /// Draws a text using a custom material shader. Given material must have GUI domain and a public parameter named Font (texture parameter used for a font atlas sampling). + /// + /// The fonts to use, ordered by priority. + /// Custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture. + /// The text to render. + /// The size and position of the area in which the text is drawn. + /// The text color. + /// The horizontal alignment of the text in a layout rectangle. + /// The vertical alignment of the text in a layout rectangle. + /// Describes how wrap text inside a layout rectangle. + /// The scale for distance one baseline from another. Default is 1. + /// The text drawing scale. Default is 1. + public static void DrawText(Font[] fonts, MaterialBase customMaterial, string text, Rectangle layoutRect, Color color, TextAlignment horizontalAlignment = TextAlignment.Near, TextAlignment verticalAlignment = TextAlignment.Near, TextWrapping textWrapping = TextWrapping.NoWrap, float baseLinesGapScale = 1.0f, float scale = 1.0f) + { + var layout = new TextLayoutOptions + { + Bounds = layoutRect, + HorizontalAlignment = horizontalAlignment, + VerticalAlignment = verticalAlignment, + TextWrapping = textWrapping, + Scale = scale, + BaseLinesGapScale = baseLinesGapScale, + }; + DrawText(fonts, text, color, ref layout, customMaterial); + } + /// /// Calls drawing GUI to the texture. /// diff --git a/Source/Engine/Render2D/Render2D.h b/Source/Engine/Render2D/Render2D.h index 2b890ced9..0ec88edc0 100644 --- a/Source/Engine/Render2D/Render2D.h +++ b/Source/Engine/Render2D/Render2D.h @@ -33,7 +33,7 @@ API_CLASS(Static) class FLAXENGINE_API Render2D /// /// The rendering features and options flags. /// - API_ENUM(Attributes="Flags") enum class RenderingFeatures + API_ENUM(Attributes = "Flags") enum class RenderingFeatures { /// /// The none. @@ -215,6 +215,49 @@ public: /// The custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture. API_FUNCTION() static void DrawText(Font* font, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Color& color, API_PARAM(Ref) const TextLayoutOptions& layout, MaterialBase* customMaterial = nullptr); + /// + /// Draws a text. + /// + /// The fonts to use, ordered by priority. + /// The text to render. + /// The input text range (substring range of the input text parameter). + /// The text color. + /// The text location. + /// The custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture. + API_FUNCTION() static void DrawText(const Array& fonts, const StringView& text, const Color& color, const Float2& location, MaterialBase* customMaterial = nullptr); + + /// + /// Draws a text with formatting. + /// + /// The fonts to use, ordered by priority. + /// The text to render. + /// The text color. + /// The text layout properties. + /// The custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture. + API_FUNCTION() static void DrawText(const Array& fonts, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Color& color, const Float2& location, MaterialBase* customMaterial = nullptr); + + /// + /// Draws a text with formatting. + /// + /// The fonts to use, ordered by priority. + /// The text to render. + /// The input text range (substring range of the input text parameter). + /// The text color. + /// The text layout properties. + /// The custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture. + API_FUNCTION() static void DrawText(const Array& fonts, const StringView& text, const Color& color, API_PARAM(Ref) const TextLayoutOptions& layout, MaterialBase* customMaterial = nullptr); + + /// + /// Draws a text with formatting. + /// + /// The fonts to use, ordered by priority. + /// The text to render. + /// The input text range (substring range of the input text parameter). + /// The text color. + /// The text layout properties. + /// The custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture. + API_FUNCTION() static void DrawText(const Array& fonts, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Color& color, API_PARAM(Ref) const TextLayoutOptions& layout, MaterialBase* customMaterial = nullptr); + /// /// Fills a rectangle area. /// diff --git a/Source/Engine/Scripting/Scripting.cs b/Source/Engine/Scripting/Scripting.cs index 188333ff1..8e71c9f31 100644 --- a/Source/Engine/Scripting/Scripting.cs +++ b/Source/Engine/Scripting/Scripting.cs @@ -295,12 +295,13 @@ namespace FlaxEngine // Use optionally bundled default font (matches Editor) var defaultFont = Content.LoadAsyncInternal("Editor/Fonts/Roboto-Regular"); + var cjkFont = Content.LoadAsyncInternal("NotoSansSC-Medium"); if (defaultFont) { style.FontTitle = defaultFont.CreateFont(18); style.FontLarge = defaultFont.CreateFont(14); - style.FontMedium = defaultFont.CreateFont(9); - style.FontSmall = defaultFont.CreateFont(9); + style.FontMedium = new Font[] { defaultFont.CreateFont(9), cjkFont.CreateFont(9) }; + style.FontSmall = new Font[] { defaultFont.CreateFont(9), cjkFont.CreateFont(9) }; } Style.Current = style; diff --git a/Source/Engine/UI/GUI/Common/Button.cs b/Source/Engine/UI/GUI/Common/Button.cs index 02337411b..1b965b8d1 100644 --- a/Source/Engine/UI/GUI/Common/Button.cs +++ b/Source/Engine/UI/GUI/Common/Button.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; +using System.Linq; namespace FlaxEngine.GUI { @@ -155,7 +156,7 @@ namespace FlaxEngine.GUI var style = Style.Current; if (style != null) { - _font = new FontReference(style.FontMedium); + _font = new FontReference(style.FontMedium.First()); TextColor = style.Foreground; BackgroundColor = style.BackgroundNormal; BorderColor = style.BorderNormal; diff --git a/Source/Engine/UI/GUI/Common/Dropdown.cs b/Source/Engine/UI/GUI/Common/Dropdown.cs index ecca2978f..21ca9cbea 100644 --- a/Source/Engine/UI/GUI/Common/Dropdown.cs +++ b/Source/Engine/UI/GUI/Common/Dropdown.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Linq; namespace FlaxEngine.GUI { @@ -358,7 +359,7 @@ namespace FlaxEngine.GUI : base(0, 0, 120, 18.0f) { var style = Style.Current; - Font = new FontReference(style.FontMedium); + Font = new FontReference(style.FontMedium.First()); TextColor = style.Foreground; BackgroundColor = style.BackgroundNormal; BackgroundColorHighlighted = BackgroundColor; diff --git a/Source/Engine/UI/GUI/Common/Label.cs b/Source/Engine/UI/GUI/Common/Label.cs index 3c7c04fb2..b92e762e1 100644 --- a/Source/Engine/UI/GUI/Common/Label.cs +++ b/Source/Engine/UI/GUI/Common/Label.cs @@ -1,6 +1,8 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. +using FlaxEditor.Options; using System.ComponentModel; +using System.Linq; namespace FlaxEngine.GUI { @@ -190,7 +192,7 @@ namespace FlaxEngine.GUI { AutoFocus = false; var style = Style.Current; - Font = new FontReference(style.FontMedium); + Font = new FontReference(style.FontMedium.First()); TextColor = style.Foreground; TextColorHighlighted = style.Foreground; } @@ -201,7 +203,7 @@ namespace FlaxEngine.GUI { AutoFocus = false; var style = Style.Current; - Font = new FontReference(style.FontMedium); + Font = new FontReference(style.FontMedium.First()); TextColor = style.Foreground; TextColorHighlighted = style.Foreground; } @@ -233,7 +235,7 @@ namespace FlaxEngine.GUI } } - Render2D.DrawText(_font.GetFont(), Material, _text, rect, color, hAlignment, wAlignment, Wrapping, BaseLinesGapScale, scale); + Render2D.DrawText(new Font[] { _font.GetFont(), Style.Current.FontCJK }, Material, _text, rect, color, hAlignment, wAlignment, Wrapping, BaseLinesGapScale, scale); if (ClipText) Render2D.PopClip(); diff --git a/Source/Engine/UI/GUI/Common/RichTextBox.cs b/Source/Engine/UI/GUI/Common/RichTextBox.cs index ab922d93d..e6a82c986 100644 --- a/Source/Engine/UI/GUI/Common/RichTextBox.cs +++ b/Source/Engine/UI/GUI/Common/RichTextBox.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System.Collections.Generic; +using System.Linq; namespace FlaxEngine.GUI { @@ -45,7 +46,7 @@ namespace FlaxEngine.GUI var style = Style.Current; _textStyle = new TextBlockStyle { - Font = new FontReference(style.FontMedium), + Font = new FontReference(style.FontMedium.First()), Color = style.Foreground, BackgroundSelectedBrush = new SolidColorBrush(style.BackgroundSelected), }; diff --git a/Source/Engine/UI/GUI/Common/TextBox.cs b/Source/Engine/UI/GUI/Common/TextBox.cs index ee4f744a6..967617649 100644 --- a/Source/Engine/UI/GUI/Common/TextBox.cs +++ b/Source/Engine/UI/GUI/Common/TextBox.cs @@ -1,5 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. +using System.Linq; + namespace FlaxEngine.GUI { /// @@ -88,7 +90,7 @@ namespace FlaxEngine.GUI _layout.Bounds = new Rectangle(DefaultMargin, 1, Width - 2 * DefaultMargin, Height - 2); var style = Style.Current; - Font = new FontReference(style.FontMedium); + Font = new FontReference(style.FontMedium.First()); TextColor = style.Foreground; WatermarkTextColor = style.ForegroundDisabled; SelectionColor = style.BackgroundSelected; diff --git a/Source/Engine/UI/GUI/Panels/DropPanel.cs b/Source/Engine/UI/GUI/Panels/DropPanel.cs index 66e7413eb..33c2e1605 100644 --- a/Source/Engine/UI/GUI/Panels/DropPanel.cs +++ b/Source/Engine/UI/GUI/Panels/DropPanel.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; +using System.Linq; namespace FlaxEngine.GUI { @@ -237,7 +238,7 @@ namespace FlaxEngine.GUI var style = Style.Current; HeaderColor = style.BackgroundNormal; HeaderColorMouseOver = style.BackgroundHighlighted; - HeaderTextFont = new FontReference(style.FontMedium); + HeaderTextFont = new FontReference(style.FontMedium.First()); HeaderTextColor = style.Foreground; ArrowImageOpened = new SpriteBrush(style.ArrowDown); ArrowImageClosed = new SpriteBrush(style.ArrowRight); diff --git a/Source/Engine/UI/GUI/Style.cs b/Source/Engine/UI/GUI/Style.cs index 22b8f52af..d3375a670 100644 --- a/Source/Engine/UI/GUI/Style.cs +++ b/Source/Engine/UI/GUI/Style.cs @@ -1,5 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. +using System.Linq; + namespace FlaxEngine.GUI { /// @@ -12,6 +14,14 @@ namespace FlaxEngine.GUI /// public static Style Current { get; set; } + public Font FontCJK + { + get => _fontCJK?.GetFont(); + set => _fontCJK = new FontReference(value); + } + + private FontReference _fontCJK; + [Serialize] private FontReference _fontTitle; @@ -41,31 +51,31 @@ namespace FlaxEngine.GUI } [Serialize] - private FontReference _fontMedium; + private FontReference[] _fontMedium; /// /// The font medium. /// [NoSerialize] [EditorOrder(30)] - public Font FontMedium + public Font[] FontMedium { - get => _fontMedium?.GetFont(); - set => _fontMedium = new FontReference(value); + get => _fontMedium?.Select((x)=>x.GetFont()).ToArray(); + set => _fontMedium = value.Select((x)=>new FontReference(x)).ToArray(); } [Serialize] - private FontReference _fontSmall; + private FontReference[] _fontSmall; /// /// The font small. /// [NoSerialize] [EditorOrder(40)] - public Font FontSmall + public Font[] FontSmall { - get => _fontSmall?.GetFont(); - set => _fontSmall = new FontReference(value); + get => _fontSmall?.Select((x) => x.GetFont()).ToArray(); + set => _fontSmall = value.Select((x) => new FontReference(x)).ToArray(); } /// diff --git a/Source/Engine/UI/GUI/Tooltip.cs b/Source/Engine/UI/GUI/Tooltip.cs index 734fb078f..e17b754c4 100644 --- a/Source/Engine/UI/GUI/Tooltip.cs +++ b/Source/Engine/UI/GUI/Tooltip.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; +using System.Linq; namespace FlaxEngine.GUI { @@ -255,14 +256,14 @@ namespace FlaxEngine.GUI // Calculate size of the tooltip var size = Float2.Zero; - if (style != null && style.FontMedium && !string.IsNullOrEmpty(_currentText)) + if (style != null && style.FontMedium.First() && !string.IsNullOrEmpty(_currentText)) { var layout = TextLayoutOptions.Default; layout.Bounds = new Rectangle(0, 0, MaxWidth, 10000000); layout.HorizontalAlignment = TextAlignment.Center; layout.VerticalAlignment = TextAlignment.Center; layout.TextWrapping = TextWrapping.WrapWords; - var items = style.FontMedium.ProcessText(_currentText, ref layout); + var items = style.FontMedium.First().ProcessText(_currentText, ref layout); for (int i = 0; i < items.Length; i++) { ref var item = ref items[i]; From a3bc394e4e0279c51b3acb133a6ad8ef5a2f27bf Mon Sep 17 00:00:00 2001 From: ExMatics HydrogenC <33123710+HydrogenC@users.noreply.github.com> Date: Wed, 29 Nov 2023 15:04:29 +0800 Subject: [PATCH 037/139] Fix a bunch of rendering bugs --- .gitignore | 1 + Source/Editor/EditorAssets.cs | 2 +- Source/Engine/Render2D/Font.cpp | 133 ++++++++++---- Source/Engine/Render2D/Font.h | 39 +++- Source/Engine/Render2D/MultiFont.cpp | 1 + Source/Engine/Render2D/MultiFont.h | 77 ++++++++ Source/Engine/Render2D/Render2D.cpp | 266 +++++++++++++-------------- Source/Engine/UI/GUI/Common/Label.cs | 2 +- 8 files changed, 332 insertions(+), 189 deletions(-) create mode 100644 Source/Engine/Render2D/MultiFont.cpp create mode 100644 Source/Engine/Render2D/MultiFont.h diff --git a/.gitignore b/.gitignore index b7e11e554..b653b7f77 100644 --- a/.gitignore +++ b/.gitignore @@ -157,3 +157,4 @@ obj/ .idea/ *.code-workspace omnisharp.json +Content/Editor/Fonts/NotoSansSC-Regular.flax diff --git a/Source/Editor/EditorAssets.cs b/Source/Editor/EditorAssets.cs index 237908a0c..0fe5ee47e 100644 --- a/Source/Editor/EditorAssets.cs +++ b/Source/Editor/EditorAssets.cs @@ -54,7 +54,7 @@ namespace FlaxEditor /// public static string PrimaryFont = "Editor/Fonts/Roboto-Regular"; - public static string CJKFont = "Editor/Fonts/NotoSansSC-Medium"; + public static string CJKFont = "Editor/Fonts/NotoSansSC-Regular"; /// /// The Inconsolata Regular font. diff --git a/Source/Engine/Render2D/Font.cpp b/Source/Engine/Render2D/Font.cpp index 13b17197c..277ad8ddd 100644 --- a/Source/Engine/Render2D/Font.cpp +++ b/Source/Engine/Render2D/Font.cpp @@ -6,6 +6,7 @@ #include "Engine/Core/Log.h" #include "Engine/Threading/Threading.h" #include "IncludeFreeType.h" +#include "MultiFont.h" Font::Font(FontAsset* parentAsset, float size) : ManagedScriptingObject(SpawnParams(Guid::New(), Font::TypeInitializer)) @@ -118,6 +119,10 @@ void Font::ProcessText(const StringView& text, Array& outputLines tmpLine.FirstCharIndex = 0; tmpLine.LastCharIndex = -1; + if (textLength == 0) { + return; + } + int32 lastWrapCharIndex = INVALID_INDEX; float lastWrapCharX = 0; bool lastMoveLine = false; @@ -129,6 +134,11 @@ void Font::ProcessText(const StringView& text, Array& outputLines float xAdvance = 0; int32 nextCharIndex = currentIndex + 1; + // Submit line if text ends + if (nextCharIndex == textLength) { + moveLine = true; + } + // Cache current character const Char currentChar = text[currentIndex]; const bool isWhitespace = StringUtils::IsWhitespace(currentChar); @@ -146,7 +156,6 @@ void Font::ProcessText(const StringView& text, Array& outputLines { // Break line moveLine = true; - currentIndex++; tmpLine.LastCharIndex++; } else @@ -178,8 +187,8 @@ void Font::ProcessText(const StringView& text, Array& outputLines if (lastWrapCharIndex != INVALID_INDEX) { // Skip moving twice for the same character - int32 lastLineLasCharIndex = outputLines.HasItems() ? outputLines.Last().LastCharIndex : -10000; - if (lastLineLasCharIndex == lastWrapCharIndex || lastLineLasCharIndex == lastWrapCharIndex - 1 || lastLineLasCharIndex == lastWrapCharIndex - 2) + int32 lastLineLastCharIndex = outputLines.HasItems() ? outputLines.Last().LastCharIndex : -10000; + if (lastLineLastCharIndex == lastWrapCharIndex || lastLineLastCharIndex == lastWrapCharIndex - 1 || lastLineLastCharIndex == lastWrapCharIndex - 2) { currentIndex = nextCharIndex; lastMoveLine = moveLine; @@ -226,8 +235,8 @@ void Font::ProcessText(const StringView& text, Array& outputLines // Reset line tmpLine.Location.Y += baseLinesDistance; - tmpLine.FirstCharIndex = currentIndex; - tmpLine.LastCharIndex = currentIndex - 1; + tmpLine.FirstCharIndex = nextCharIndex; + tmpLine.LastCharIndex = nextCharIndex - 1; cursorX = 0; lastWrapCharIndex = INVALID_INDEX; lastWrapCharX = 0; @@ -238,7 +247,8 @@ void Font::ProcessText(const StringView& text, Array& outputLines lastMoveLine = moveLine; } - if (textLength != 0 && (tmpLine.LastCharIndex >= tmpLine.FirstCharIndex || text[textLength - 1] == '\n')) + // Check if an additional line should be created + if (text[textLength - 1] == '\n') { // Add line tmpLine.Size.X = cursorX; @@ -283,84 +293,101 @@ void Font::ProcessText(const StringView& text, Array& outputLines } } -void Font::ProcessText(const Array& fonts, const StringView& text, Array& outputLines, API_PARAM(Ref) const TextLayoutOptions& layout) +void Font::ProcessText(const Array& fonts, const StringView& text, Array& outputLines, API_PARAM(Ref) const TextLayoutOptions& layout) { float cursorX = 0; int32 kerning; - FontLineCache tmpLine; + MultiFontLineCache tmpLine; + MultiFontSegmentCache tmpSegment; FontCharacterEntry entry; FontCharacterEntry previous; int32 textLength = text.Length(); float scale = layout.Scale / FontManager::FontScale; float boundsWidth = layout.Bounds.GetWidth(); float baseLinesDistanceScale = layout.BaseLinesGapScale * scale; + + tmpSegment.Location = Float2::Zero; + tmpSegment.Height = 0; + tmpSegment.FirstCharIndex = 0; + tmpSegment.LastCharIndex = -1; + tmpLine.Location = Float2::Zero; tmpLine.Size = Float2::Zero; - tmpLine.FirstCharIndex = 0; - tmpLine.LastCharIndex = -1; + tmpLine.Segments = Array(); + + if (textLength == 0) { + return; + } int32 lastWrapCharIndex = INVALID_INDEX; float lastWrapCharX = 0; bool lastMoveLine = false; - - int32 previousFontIndex = -1; + // The index of the font used by the current segment + int32 currentFontIndex = GetCharFontIndex(fonts, text[0], 0); // The maximum font height of the current line float maxHeight = 0; + float maxAscender = 0; + // Process each character to split text into single lines for (int32 currentIndex = 0; currentIndex < textLength;) { bool moveLine = false; + bool moveSegment = false; float xAdvance = 0; int32 nextCharIndex = currentIndex + 1; + // Submit line and segment if text ends + if (nextCharIndex == textLength) { + moveLine = moveSegment = true; + } + // Cache current character const Char currentChar = text[currentIndex]; const bool isWhitespace = StringUtils::IsWhitespace(currentChar); // Check if character can wrap words - const bool isWrapChar = !StringUtils::IsAlnum(currentChar) || isWhitespace || StringUtils::IsUpper(currentChar); + const bool isWrapChar = !StringUtils::IsAlnum(currentChar) || isWhitespace || StringUtils::IsUpper(currentChar) || (currentChar >= 0x3040 && currentChar <= 0x9FFF); if (isWrapChar && currentIndex != 0) { lastWrapCharIndex = currentIndex; lastWrapCharX = cursorX; } + int32 nextFontIndex = currentFontIndex; // Check if it's a newline character if (currentChar == '\n') { // Break line - moveLine = true; - currentIndex++; - tmpLine.LastCharIndex++; + moveLine = moveSegment = true; + tmpSegment.LastCharIndex++; } else { // Get character entry - int32 fontIndex = 0; - while (fontIndex < fonts.Count() && !fonts[fontIndex]->ContainsChar(currentChar)) - { - fontIndex++; + if (nextCharIndex < textLength) { + nextFontIndex = GetCharFontIndex(fonts, text[nextCharIndex], currentFontIndex); } - // If no font can match the char, then use the first font - if (fontIndex == fonts.Count()) { - fontIndex = 0; - } // Get character entry - fonts[fontIndex]->GetCharacter(currentChar, entry); - maxHeight = Math::Max(maxHeight, static_cast(fonts[fontIndex]->GetHeight())); + fonts[currentFontIndex]->GetCharacter(currentChar, entry); + maxHeight = Math::Max(maxHeight, static_cast(fonts[currentFontIndex]->GetHeight())); + maxAscender = Math::Max(maxAscender, static_cast(fonts[currentFontIndex]->GetAscender())); + + // Move segment if the font changes or text ends + if (nextFontIndex != currentFontIndex || nextCharIndex == textLength) { + moveSegment = true; + } // Get kerning, only when the font hasn't changed - if (!isWhitespace && previous.IsValid && previousFontIndex == fontIndex) + if (!isWhitespace && previous.IsValid && !moveSegment) { - kerning = fonts[fontIndex]->GetKerning(previous.Character, entry.Character); + kerning = fonts[currentFontIndex]->GetKerning(previous.Character, entry.Character); } else { kerning = 0; } previous = entry; - previousFontIndex = fontIndex; xAdvance = (kerning + entry.AdvanceX) * scale; // Check if character fits the line or skip wrapping @@ -368,15 +395,15 @@ void Font::ProcessText(const Array& fonts, const StringView& text, Array< { // Move character cursorX += xAdvance; - tmpLine.LastCharIndex++; + tmpSegment.LastCharIndex++; } else if (layout.TextWrapping == TextWrapping::WrapWords) { if (lastWrapCharIndex != INVALID_INDEX) { // Skip moving twice for the same character - int32 lastLineLasCharIndex = outputLines.HasItems() ? outputLines.Last().LastCharIndex : -10000; - if (lastLineLasCharIndex == lastWrapCharIndex || lastLineLasCharIndex == lastWrapCharIndex - 1 || lastLineLasCharIndex == lastWrapCharIndex - 2) + int32 lastLineLastCharIndex = outputLines.HasItems() && outputLines.Last().Segments.HasItems() ? outputLines.Last().Segments.Last().LastCharIndex : -10000; + if (lastLineLastCharIndex == lastWrapCharIndex || lastLineLastCharIndex == lastWrapCharIndex - 1 || lastLineLastCharIndex == lastWrapCharIndex - 2) { currentIndex = nextCharIndex; lastMoveLine = moveLine; @@ -386,16 +413,18 @@ void Font::ProcessText(const Array& fonts, const StringView& text, Array< // Move line const Char wrapChar = text[lastWrapCharIndex]; moveLine = true; + moveSegment = tmpSegment.FirstCharIndex < lastWrapCharIndex; + cursorX = lastWrapCharX; if (StringUtils::IsWhitespace(wrapChar)) { // Skip whitespaces - tmpLine.LastCharIndex = lastWrapCharIndex - 1; + tmpSegment.LastCharIndex = lastWrapCharIndex - 1; nextCharIndex = currentIndex = lastWrapCharIndex + 1; } else { - tmpLine.LastCharIndex = lastWrapCharIndex - 1; + tmpSegment.LastCharIndex = lastWrapCharIndex - 1; nextCharIndex = currentIndex = lastWrapCharIndex; } } @@ -404,6 +433,7 @@ void Font::ProcessText(const Array& fonts, const StringView& text, Array< { // Move line moveLine = true; + moveSegment = tmpSegment.FirstCharIndex < currentChar; nextCharIndex = currentIndex; // Skip moving twice for the same character @@ -412,38 +442,54 @@ void Font::ProcessText(const Array& fonts, const StringView& text, Array< } } + if (moveSegment) { + // Add segment + tmpSegment.Height = baseLinesDistanceScale * fonts[currentFontIndex]->GetHeight(); + tmpSegment.LastCharIndex = Math::Max(tmpSegment.LastCharIndex, tmpSegment.FirstCharIndex); + tmpSegment.FontIndex = currentFontIndex; + tmpLine.Segments.Add(tmpSegment); + + // Reset segment + tmpSegment.Location.X = cursorX; + tmpSegment.FirstCharIndex = nextCharIndex; + tmpSegment.LastCharIndex = nextCharIndex - 1; + + currentFontIndex = nextFontIndex; + } + // Check if move to another line if (moveLine) { // Add line tmpLine.Size.X = cursorX; tmpLine.Size.Y = baseLinesDistanceScale * maxHeight; - tmpLine.LastCharIndex = Math::Max(tmpLine.LastCharIndex, tmpLine.FirstCharIndex); + tmpLine.MaxAscender = maxAscender; outputLines.Add(tmpLine); // Reset line + tmpLine.Segments.Clear(); tmpLine.Location.Y += baseLinesDistanceScale * maxHeight; - tmpLine.FirstCharIndex = currentIndex; - tmpLine.LastCharIndex = currentIndex - 1; cursorX = 0; + tmpSegment.Location.X = cursorX; lastWrapCharIndex = INVALID_INDEX; lastWrapCharX = 0; previous.IsValid = false; // Reset max font height maxHeight = 0; + maxAscender = 0; } currentIndex = nextCharIndex; lastMoveLine = moveLine; } - if (textLength != 0 && (tmpLine.LastCharIndex >= tmpLine.FirstCharIndex || text[textLength - 1] == '\n')) + // Check if an additional line should be created + if (text[textLength - 1] == '\n') { // Add line tmpLine.Size.X = cursorX; tmpLine.Size.Y = baseLinesDistanceScale * maxHeight; - tmpLine.LastCharIndex = textLength - 1; outputLines.Add(tmpLine); tmpLine.Location.Y += baseLinesDistanceScale * maxHeight; @@ -466,7 +512,7 @@ void Font::ProcessText(const Array& fonts, const StringView& text, Array< } for (int32 i = 0; i < outputLines.Count(); i++) { - FontLineCache& line = outputLines[i]; + MultiFontLineCache& line = outputLines[i]; Float2 rootPos = line.Location + offset; // Fix upper left line corner to match desire text alignment @@ -480,6 +526,13 @@ void Font::ProcessText(const Array& fonts, const StringView& text, Array< } line.Location = rootPos; + + // Align all segments to center in case they have different heights + for (int32 j = 0; j < line.Segments.Count(); j++) + { + MultiFontSegmentCache& segment = line.Segments[j]; + segment.Location.Y += (line.MaxAscender - fonts[segment.FontIndex]->GetAscender()) / 2; + } } } diff --git a/Source/Engine/Render2D/Font.h b/Source/Engine/Render2D/Font.h index d8ac1b708..0425f2bc5 100644 --- a/Source/Engine/Render2D/Font.h +++ b/Source/Engine/Render2D/Font.h @@ -8,9 +8,11 @@ #include "Engine/Content/AssetReference.h" #include "Engine/Scripting/ScriptingObject.h" #include "TextLayoutOptions.h" +#include "MultiFont.h" class FontAsset; struct FontTextureAtlasSlot; +struct MultiFontLineCache; // The default DPI that engine is using #define DefaultDPI 96 @@ -20,7 +22,7 @@ struct FontTextureAtlasSlot; /// API_STRUCT(NoDefault) struct TextRange { -DECLARE_SCRIPTING_TYPE_MINIMAL(TextRange); + DECLARE_SCRIPTING_TYPE_MINIMAL(TextRange); /// /// The start index (inclusive). @@ -90,7 +92,7 @@ struct TIsPODType /// API_STRUCT(NoDefault) struct FontLineCache { -DECLARE_SCRIPTING_TYPE_MINIMAL(FontLineCache); + DECLARE_SCRIPTING_TYPE_MINIMAL(FontLineCache); /// /// The root position of the line (upper left corner). @@ -108,7 +110,7 @@ DECLARE_SCRIPTING_TYPE_MINIMAL(FontLineCache); API_FIELD() int32 FirstCharIndex; /// - /// The last character index (from the input text). + /// The last character index (from the input text), inclusive. /// API_FIELD() int32 LastCharIndex; }; @@ -154,7 +156,7 @@ struct TIsPODType /// API_STRUCT(NoDefault) struct FontCharacterEntry { -DECLARE_SCRIPTING_TYPE_MINIMAL(FontCharacterEntry); + DECLARE_SCRIPTING_TYPE_MINIMAL(FontCharacterEntry); /// /// The character represented by this entry. @@ -223,7 +225,7 @@ struct TIsPODType /// API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API Font : public ManagedScriptingObject { -DECLARE_SCRIPTING_TYPE_NO_SPAWN(Font); + DECLARE_SCRIPTING_TYPE_NO_SPAWN(Font); friend FontAsset; private: @@ -346,7 +348,7 @@ public: /// The input text. /// The layout properties. /// The output lines list. - static void ProcessText(const Array& fonts, const StringView& text, Array& outputLines, API_PARAM(Ref) const TextLayoutOptions& layout); + static void ProcessText(const Array& fonts, const StringView& text, Array& outputLines, API_PARAM(Ref) const TextLayoutOptions& layout); /// /// Processes text to get cached lines for rendering. @@ -404,6 +406,7 @@ public: /// The minimum size for that text and fot to render properly. API_FUNCTION() Float2 MeasureText(const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout); + /* /// /// Measures minimum size of the rectangle that will be needed to draw given text. /// @@ -412,6 +415,7 @@ public: /// The layout properties. /// The minimum size for that text and fot to render properly. API_FUNCTION() static Float2 MeasureText(const Array& fonts, const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout); + */ /// /// Measures minimum size of the rectangle that will be needed to draw given text. @@ -500,6 +504,7 @@ public: /// The character position (upper left corner which can be used for a caret position). API_FUNCTION() Float2 GetCharPosition(const StringView& text, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout); + /* /// /// Calculates character position for given text and character index. /// @@ -509,6 +514,7 @@ public: /// The text layout properties. /// The character position (upper left corner which can be used for a caret position). API_FUNCTION() static Float2 GetCharPosition(const Array& fonts, const StringView& text, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout); + */ /// /// Calculates character position for given text and character index. @@ -553,6 +559,27 @@ public: /// True if the font contains the glyph of the char, otherwise false. API_FUNCTION() FORCE_INLINE bool ContainsChar(Char c); + /// + /// Gets the index of the font that should be used to render the char + /// + /// The font list. + /// The char. + /// Number to return if char cannot be found. + /// + API_FUNCTION() FORCE_INLINE static int32 GetCharFontIndex(const Array& fonts, Char c, int32 missing = -1) { + int32 fontIndex = 0; + while (fontIndex < fonts.Count() && !fonts[fontIndex]->ContainsChar(c)) + { + fontIndex++; + } + + if (fontIndex == fonts.Count()) { + return missing; + } + + return fontIndex; + } + /// /// Flushes the size of the face with the Free Type library backend. /// diff --git a/Source/Engine/Render2D/MultiFont.cpp b/Source/Engine/Render2D/MultiFont.cpp new file mode 100644 index 000000000..9844c12e0 --- /dev/null +++ b/Source/Engine/Render2D/MultiFont.cpp @@ -0,0 +1 @@ +#include "MultiFont.h" diff --git a/Source/Engine/Render2D/MultiFont.h b/Source/Engine/Render2D/MultiFont.h new file mode 100644 index 000000000..b1da832d5 --- /dev/null +++ b/Source/Engine/Render2D/MultiFont.h @@ -0,0 +1,77 @@ +#pragma once + +#include "Engine/Core/Collections/Array.h" +#include "Engine/Core/Collections/Dictionary.h" +#include "Font.h" + +/// +/// The font segment info generated during text processing. +/// +API_STRUCT(NoDefault) struct MultiFontSegmentCache +{ + DECLARE_SCRIPTING_TYPE_MINIMAL(MultiFontSegmentCache); + + /// + /// The root position of the segment (upper left corner), relative to line. + /// + API_FIELD() Float2 Location; + + /// + /// The height of the current segment + /// + API_FIELD() float Height; + + /// + /// The first character index (from the input text). + /// + API_FIELD() int32 FirstCharIndex; + + /// + /// The last character index (from the input text), inclusive. + /// + API_FIELD() int32 LastCharIndex; + + /// + /// The index of the font to render with + /// + API_FIELD() int32 FontIndex; +}; + +template<> +struct TIsPODType +{ + enum { Value = true }; +}; + +/// +/// Line of font segments info generated during text processing. +/// +API_STRUCT(NoDefault) struct MultiFontLineCache +{ + DECLARE_SCRIPTING_TYPE_MINIMAL(MultiFontLineCache); + + /// + /// The root position of the line (upper left corner). + /// + API_FIELD() Float2 Location; + + /// + /// The line bounds (width and height). + /// + API_FIELD() Float2 Size; + + /// + /// The maximum ascendent of the line. + /// + API_FIELD() float MaxAscender; + + /// + /// The index of the font to render with + /// + API_FIELD() Array Segments; +}; + +API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API MultiFont : public ManagedScriptingObject +{ + DECLARE_SCRIPTING_TYPE_NO_SPAWN(MultiFont); +}; diff --git a/Source/Engine/Render2D/Render2D.cpp b/Source/Engine/Render2D/Render2D.cpp index eedfbeffe..8a39d29be 100644 --- a/Source/Engine/Render2D/Render2D.cpp +++ b/Source/Engine/Render2D/Render2D.cpp @@ -3,6 +3,7 @@ #include "Render2D.h" #include "Font.h" #include "FontManager.h" +#include "MultiFont.h" #include "FontTextureAtlas.h" #include "RotatedRectangle.h" #include "SpriteAtlas.h" @@ -194,6 +195,7 @@ namespace // Drawing Array DrawCalls; Array Lines; + Array MultiFontLines; Array Lines2; bool IsScissorsRectEmpty; bool IsScissorsRectEnabled; @@ -1384,6 +1386,9 @@ void Render2D::DrawText(const Array& fonts, const StringView& text, const int32 kerning; float scale = 1.0f / FontManager::FontScale; + // Process text to get lines + Array maxAscenders; + // Render all characters FontCharacterEntry entry; Render2DDrawCall drawCall; @@ -1398,48 +1403,70 @@ void Render2D::DrawText(const Array& fonts, const StringView& text, const drawCall.AsChar.Mat = nullptr; } + int32 lineIndex = 0; + maxAscenders.Add(0); + for (int32 currentIndex = 0; currentIndex < text.Length(); currentIndex++) + { + if (text[currentIndex] != '\n') { + int32 fontIndex = Font::GetCharFontIndex(fonts, text[currentIndex], 0); + maxAscenders[lineIndex] = Math::Max(maxAscenders[lineIndex], static_cast(fonts[fontIndex]->GetAscender())); + } + else { + lineIndex++; + maxAscenders.Add(0); + } + } + + lineIndex = 0; // The following code cut the text into segments, according to the font used to render Float2 pointer = location; // The starting index of the current segment int32 startIndex = 0; // The index of the font used by the current segment - int32 segmentFontIndex = 0; + int32 currentFontIndex = Font::GetCharFontIndex(fonts, text[0], 0); // The maximum font height of the current line float maxHeight = 0; - for (int32 currentIndex = 0; currentIndex <= text.Length(); currentIndex++) + for (int32 currentIndex = 0; currentIndex < text.Length(); currentIndex++) { // Cache current character - const Char currentChar = currentIndex < text.Length() ? text[currentIndex] : 0; + const Char currentChar = text[currentIndex]; + int32 nextCharIndex = currentIndex + 1; + bool moveSegment = false; + bool moveLine = false; + int32 nextFontIndex = currentFontIndex; + + // Submit segment if text ends + if (nextCharIndex == text.Length()) { + moveSegment = true; + } // Check if it isn't a newline character if (currentChar != '\n') { - int32 fontIndex = 0; - if (currentIndex < text.Length()) { - while (fontIndex < fonts.Count() && !fonts[fontIndex]->ContainsChar(currentChar)) - { - fontIndex++; - } - - // If no font can match the char, then use the segment font - if (fontIndex == fonts.Count()) { - fontIndex = segmentFontIndex; - } - - // Do nothing if the char still belongs to the current segment - if (fontIndex == segmentFontIndex) { - continue; - } + // Get character entry + if (nextCharIndex < text.Length()) { + nextFontIndex = Font::GetCharFontIndex(fonts, text[nextCharIndex], currentFontIndex); } + if (nextFontIndex != currentFontIndex) { + moveSegment = true; + } + } + else + { + // Move + moveLine = moveSegment = true; + } + + if (moveSegment) { // Render the pending segment before beginning the new segment - renderText:auto fontHeight = fonts[segmentFontIndex]->GetHeight(); + auto fontHeight = fonts[currentFontIndex]->GetHeight(); maxHeight = Math::Max(maxHeight, static_cast(fontHeight)); - auto fontDescender = fonts[segmentFontIndex]->GetDescender(); - for (int32 renderIndex = startIndex; renderIndex < currentIndex; renderIndex++) + auto fontDescender = fonts[currentFontIndex]->GetDescender(); + for (int32 renderIndex = startIndex; renderIndex <= currentIndex; renderIndex++) { // Get character entry - fonts[segmentFontIndex]->GetCharacter(text[renderIndex], entry); + fonts[currentFontIndex]->GetCharacter(text[renderIndex], entry); // Check if need to select/change font atlas (since characters even in the same font may be located in different atlases) if (fontAtlas == nullptr || entry.TextureIndex != fontAtlasIndex) @@ -1466,7 +1493,7 @@ void Render2D::DrawText(const Array& fonts, const StringView& text, const // Get kerning if (!isWhitespace && previous.IsValid) { - kerning = fonts[segmentFontIndex]->GetKerning(previous.Character, entry.Character); + kerning = fonts[currentFontIndex]->GetKerning(previous.Character, entry.Character); } else { @@ -1482,7 +1509,7 @@ void Render2D::DrawText(const Array& fonts, const StringView& text, const const float x = pointer.X + entry.OffsetX * scale; const float y = pointer.Y + (fontHeight + fontDescender - entry.OffsetY) * scale; - Rectangle charRect(x, y, entry.UVSize.X * scale, entry.UVSize.Y * scale); + Rectangle charRect(x, y + (maxAscenders[lineIndex] - fonts[currentFontIndex]->GetAscender()) / 2, entry.UVSize.X * scale, entry.UVSize.Y * scale); Float2 upperLeftUV = entry.UV * invAtlasSize; Float2 rightBottomUV = (entry.UV + entry.UVSize) * invAtlasSize; @@ -1498,22 +1525,17 @@ void Render2D::DrawText(const Array& fonts, const StringView& text, const pointer.X += entry.AdvanceX * scale; } - // Start new segment - startIndex = currentIndex; - segmentFontIndex = fontIndex; - - if (currentIndex == text.Length() - 1) { - currentIndex++; - goto renderText; + if (moveLine) { + pointer.X = location.X; + pointer.Y += maxHeight * scale; + // Clear max height + maxHeight = 0; + lineIndex++; } - } - else - { - // Move - pointer.X = location.X; - pointer.Y += maxHeight * scale; - // Clear max height - maxHeight = 0; + + // Start new segment + startIndex = nextCharIndex; + currentFontIndex = nextFontIndex; } } } @@ -1540,8 +1562,8 @@ void Render2D::DrawText(const Array& fonts, const StringView& text, const float scale = layout.Scale / FontManager::FontScale; // Process text to get lines - Lines.Clear(); - Font::ProcessText(fonts, text, Lines, layout); + MultiFontLines.Clear(); + Font::ProcessText(fonts, text, MultiFontLines, layout); // Render all lines FontCharacterEntry entry; @@ -1557,119 +1579,81 @@ void Render2D::DrawText(const Array& fonts, const StringView& text, const drawCall.AsChar.Mat = nullptr; } - for (int32 lineIndex = 0; lineIndex < Lines.Count(); lineIndex++) + for (int32 lineIndex = 0; lineIndex < MultiFontLines.Count(); lineIndex++) { - const FontLineCache& line = Lines[lineIndex]; - - // The following code cut the text into segments, according to the font used to render - Float2 pointer = line.Location; - // The starting index of the current segment - int32 startIndex = line.FirstCharIndex; - // The index of the font used by the current segment - int32 segmentFontIndex = 0; - // The maximum font height of the current line - float maxHeight = 0; - - // Partition and render all characters from the line - for (int32 charIndex = line.FirstCharIndex; charIndex <= line.LastCharIndex + 1; charIndex++) + const MultiFontLineCache& line = MultiFontLines[lineIndex]; + for (int32 segmentIndex = 0; segmentIndex < line.Segments.Count(); segmentIndex++) { - const Char c = charIndex <= line.LastCharIndex ? text[charIndex] : 0; + const MultiFontSegmentCache& segment = MultiFontLines[lineIndex].Segments[segmentIndex]; + auto fontHeight = fonts[segment.FontIndex]->GetHeight(); + auto fontDescender = fonts[segment.FontIndex]->GetDescender(); + Float2 pointer = line.Location + segment.Location; - if (c != '\n') + for (int32 charIndex = segment.FirstCharIndex; charIndex <= segment.LastCharIndex; charIndex++) { - int32 fontIndex = 0; - if (charIndex <= line.LastCharIndex) { - while (fontIndex < fonts.Count() && !fonts[fontIndex]->ContainsChar(c)) - { - fontIndex++; - } - - // If no font can match the char, then use the segment font - if (fontIndex == fonts.Count()) { - fontIndex = segmentFontIndex; - } - - - // Do nothing if the char still belongs to the current segment - if (fontIndex == segmentFontIndex) { - continue; - } - } - - - // Render the pending segment before beginning the new segment - auto fontHeight = fonts[segmentFontIndex]->GetHeight(); - maxHeight = Math::Max(maxHeight, static_cast(fontHeight)); - auto fontDescender = fonts[segmentFontIndex]->GetDescender(); - - const Char* pred = L"Type"; - if (text.Substring(0, Math::Min(4, text.Length())) == pred) { - // __debugbreak(); - } - for (int32 renderIndex = startIndex; renderIndex < charIndex; renderIndex++) + Char c = text[charIndex]; + if (c == '\n') { - // Get character entry - fonts[segmentFontIndex]->GetCharacter(text[renderIndex], entry); + continue; + } - // Check if need to select/change font atlas (since characters even in the same font may be located in different atlases) - if (fontAtlas == nullptr || entry.TextureIndex != fontAtlasIndex) - { - // Get texture atlas that contains current character - fontAtlasIndex = entry.TextureIndex; - fontAtlas = FontManager::GetAtlas(fontAtlasIndex); - if (fontAtlas) - { - fontAtlas->EnsureTextureCreated(); - invAtlasSize = 1.0f / fontAtlas->GetSize(); - drawCall.AsChar.Tex = fontAtlas->GetTexture(); - } - else - { - invAtlasSize = 1.0f; - drawCall.AsChar.Tex = nullptr; - } - } + // Get character entry + fonts[segment.FontIndex]->GetCharacter(c, entry); - // Get kerning - const bool isWhitespace = StringUtils::IsWhitespace(text[renderIndex]); - if (!isWhitespace && previous.IsValid) + // Check if need to select/change font atlas (since characters even in the same font may be located in different atlases) + if (fontAtlas == nullptr || entry.TextureIndex != fontAtlasIndex) + { + // Get texture atlas that contains current character + fontAtlasIndex = entry.TextureIndex; + fontAtlas = FontManager::GetAtlas(fontAtlasIndex); + if (fontAtlas) { - kerning = fonts[segmentFontIndex]->GetKerning(previous.Character, entry.Character); + fontAtlas->EnsureTextureCreated(); + invAtlasSize = 1.0f / fontAtlas->GetSize(); + drawCall.AsChar.Tex = fontAtlas->GetTexture(); } else { - kerning = 0; + invAtlasSize = 1.0f; + drawCall.AsChar.Tex = nullptr; } - pointer.X += (float)kerning * scale; - previous = entry; - - // Omit whitespace characters - if (!isWhitespace) - { - // Calculate character size and atlas coordinates - const float x = pointer.X + entry.OffsetX * scale; - const float y = pointer.Y - entry.OffsetY * scale + Math::Ceil((fontHeight + fontDescender) * scale); - - Rectangle charRect(x, y, entry.UVSize.X * scale, entry.UVSize.Y * scale); - charRect.Offset(layout.Bounds.Location); - - Float2 upperLeftUV = entry.UV * invAtlasSize; - Float2 rightBottomUV = (entry.UV + entry.UVSize) * invAtlasSize; - - // Add draw call - drawCall.StartIB = IBIndex; - drawCall.CountIB = 6; - DrawCalls.Add(drawCall); - WriteRect(charRect, color, upperLeftUV, rightBottomUV); - } - - // Move - pointer.X += entry.AdvanceX * scale; } - // Start new segment - startIndex = charIndex; - segmentFontIndex = fontIndex; + // Get kerning + const bool isWhitespace = StringUtils::IsWhitespace(c); + if (!isWhitespace && previous.IsValid) + { + kerning = fonts[segment.FontIndex]->GetKerning(previous.Character, entry.Character); + } + else + { + kerning = 0; + } + pointer.X += (float)kerning * scale; + previous = entry; + + // Omit whitespace characters + if (!isWhitespace) + { + // Calculate character size and atlas coordinates + const float x = pointer.X + entry.OffsetX * scale; + const float y = pointer.Y - entry.OffsetY * scale + Math::Ceil((fontHeight + fontDescender) * scale); + + Rectangle charRect(x, y, entry.UVSize.X * scale, entry.UVSize.Y * scale); + charRect.Offset(layout.Bounds.Location); + + Float2 upperLeftUV = entry.UV * invAtlasSize; + Float2 rightBottomUV = (entry.UV + entry.UVSize) * invAtlasSize; + + // Add draw call + drawCall.StartIB = IBIndex; + drawCall.CountIB = 6; + DrawCalls.Add(drawCall); + WriteRect(charRect, color, upperLeftUV, rightBottomUV); + } + + // Move + pointer.X += entry.AdvanceX * scale; } } } diff --git a/Source/Engine/UI/GUI/Common/Label.cs b/Source/Engine/UI/GUI/Common/Label.cs index b92e762e1..5fa8ad21d 100644 --- a/Source/Engine/UI/GUI/Common/Label.cs +++ b/Source/Engine/UI/GUI/Common/Label.cs @@ -235,7 +235,7 @@ namespace FlaxEngine.GUI } } - Render2D.DrawText(new Font[] { _font.GetFont(), Style.Current.FontCJK }, Material, _text, rect, color, hAlignment, wAlignment, Wrapping, BaseLinesGapScale, scale); + Render2D.DrawText([_font.GetFont(), Style.Current.FontCJK], Material, _text, rect, color, hAlignment, wAlignment, Wrapping, BaseLinesGapScale, scale); if (ClipText) Render2D.PopClip(); From 41bbce56f6935d9066579a6b1d1e5d1b03c7fcc6 Mon Sep 17 00:00:00 2001 From: ExMatics HydrogenC <33123710+HydrogenC@users.noreply.github.com> Date: Thu, 30 Nov 2023 13:41:45 +0800 Subject: [PATCH 038/139] Add multifont rendering to editor --- .../Content/Create/CreateFilesDialog.cs | 2 +- .../Content/Import/ImportFilesDialog.cs | 2 +- Source/Editor/Content/Tree/ContentTreeNode.cs | 4 +- .../CustomEditors/Dedicated/RagdollEditor.cs | 2 +- .../CustomEditors/Dedicated/ScriptsEditor.cs | 2 +- .../Dedicated/UIControlEditor.cs | 6 +- .../Editors/ActorTransformEditor.cs | 2 +- .../Editor/CustomEditors/Editors/TagEditor.cs | 2 +- .../CustomEditors/LayoutElementsContainer.cs | 4 +- Source/Editor/EditorAssets.cs | 2 +- Source/Editor/GUI/ColumnDefinition.cs | 2 +- Source/Editor/GUI/ComboBox.cs | 6 +- .../GUI/ContextMenu/ContextMenuButton.cs | 6 +- Source/Editor/GUI/CurveEditor.cs | 4 +- Source/Editor/GUI/Docking/DockWindow.cs | 2 +- Source/Editor/GUI/ItemsListContextMenu.cs | 4 +- Source/Editor/GUI/MainMenu.cs | 8 +- Source/Editor/GUI/MainMenuButton.cs | 4 +- Source/Editor/GUI/NavigationButton.cs | 4 +- Source/Editor/GUI/Row.cs | 4 +- Source/Editor/GUI/Table.cs | 2 +- .../Editor/GUI/Timeline/GUI/PositionHandle.cs | 21 +- .../Editor/GUI/Timeline/Tracks/MemberTrack.cs | 2 +- Source/Editor/GUI/ToolStripButton.cs | 4 +- Source/Editor/GUI/Tree/TreeNode.cs | 8 +- Source/Editor/Options/InterfaceOptions.cs | 63 ++- Source/Editor/Options/OptionsModule.cs | 17 +- Source/Editor/SceneGraph/GUI/ActorTreeNode.cs | 4 +- .../Archetypes/Animation.TransitionEditor.cs | 2 +- .../Editor/Surface/Archetypes/BehaviorTree.cs | 2 +- Source/Editor/Surface/Archetypes/Function.cs | 2 +- Source/Editor/Surface/AttributesEditor.cs | 2 +- .../Editor/Surface/ContextMenu/VisjectCM.cs | 2 +- .../Surface/ContextMenu/VisjectCMItem.cs | 14 +- Source/Editor/Surface/Elements/InputBox.cs | 4 +- Source/Editor/Surface/SurfaceNode.cs | 4 +- Source/Editor/Tools/Foliage/FoliageTab.cs | 2 +- Source/Editor/Tools/Terrain/CarveTab.cs | 2 +- .../Tools/Terrain/CreateTerrainDialog.cs | 2 +- Source/Editor/Viewport/EditorViewport.cs | 6 +- .../Viewport/Widgets/ViewportWidgetButton.cs | 4 +- Source/Editor/Windows/AboutDialog.cs | 4 +- Source/Editor/Windows/Assets/FontWindow.cs | 2 +- Source/Editor/Windows/ContentWindow.Search.cs | 2 +- Source/Editor/Windows/OutputLogWindow.cs | 74 +-- Source/Editor/Windows/PluginsWindow.cs | 9 +- Source/Editor/Windows/Profiler/Timeline.cs | 4 +- Source/Editor/Windows/ToolboxWindow.cs | 4 +- Source/Engine/Render2D/Font.cpp | 249 +--------- Source/Engine/Render2D/Font.h | 56 +-- Source/Engine/Render2D/MultiFont.cpp | 430 ++++++++++++++++++ Source/Engine/Render2D/MultiFont.h | 296 +++++++++++- Source/Engine/Render2D/MultiFontReference.cs | 74 +++ Source/Engine/Render2D/Render2D.cpp | 72 +-- Source/Engine/Render2D/Render2D.cs | 7 +- Source/Engine/Render2D/Render2D.h | 9 +- Source/Engine/Scripting/Scripting.cs | 15 +- Source/Engine/UI/GUI/Common/Button.cs | 8 +- Source/Engine/UI/GUI/Common/Dropdown.cs | 6 +- Source/Engine/UI/GUI/Common/Label.cs | 12 +- .../UI/GUI/Common/RichTextBox.Parsing.cs | 34 +- .../Engine/UI/GUI/Common/RichTextBox.Tags.cs | 72 +-- Source/Engine/UI/GUI/Common/RichTextBox.cs | 2 +- .../Engine/UI/GUI/Common/RichTextBoxBase.cs | 28 +- Source/Engine/UI/GUI/Common/TextBox.cs | 16 +- Source/Engine/UI/GUI/Panels/DropPanel.cs | 6 +- Source/Engine/UI/GUI/Style.cs | 40 +- Source/Engine/UI/GUI/TextBlockStyle.cs | 2 +- Source/Engine/UI/GUI/Tooltip.cs | 5 +- 69 files changed, 1132 insertions(+), 647 deletions(-) create mode 100644 Source/Engine/Render2D/MultiFontReference.cs diff --git a/Source/Editor/Content/Create/CreateFilesDialog.cs b/Source/Editor/Content/Create/CreateFilesDialog.cs index d48e878bc..48e4920bb 100644 --- a/Source/Editor/Content/Create/CreateFilesDialog.cs +++ b/Source/Editor/Content/Create/CreateFilesDialog.cs @@ -39,7 +39,7 @@ namespace FlaxEditor.Content.Create AnchorPreset = AnchorPresets.HorizontalStretchTop, Offsets = new Margin(0, 0, 0, 40), Parent = this, - Font = new FontReference(Style.Current.FontTitle) + Font = new MultiFontReference(Style.Current.FontTitle) }; var infoLabel = new Label { diff --git a/Source/Editor/Content/Import/ImportFilesDialog.cs b/Source/Editor/Content/Import/ImportFilesDialog.cs index 967583cf6..5a142d0f6 100644 --- a/Source/Editor/Content/Import/ImportFilesDialog.cs +++ b/Source/Editor/Content/Import/ImportFilesDialog.cs @@ -60,7 +60,7 @@ namespace FlaxEditor.Content.Import AnchorPreset = AnchorPresets.HorizontalStretchTop, Offsets = new Margin(0, 0, 0, 40), Parent = this, - Font = new FontReference(Style.Current.FontTitle) + Font = new MultiFontReference(Style.Current.FontTitle) }; var infoLabel = new Label { diff --git a/Source/Editor/Content/Tree/ContentTreeNode.cs b/Source/Editor/Content/Tree/ContentTreeNode.cs index 37c4e8dbb..ee9b463f7 100644 --- a/Source/Editor/Content/Tree/ContentTreeNode.cs +++ b/Source/Editor/Content/Tree/ContentTreeNode.cs @@ -151,8 +151,8 @@ namespace FlaxEditor.Content var textRect = TextRect; for (int i = 0; i < ranges.Length; i++) { - var start = font.First().GetCharPosition(text, ranges[i].StartIndex); - var end = font.First().GetCharPosition(text, ranges[i].EndIndex); + var start = font.GetCharPosition(text, ranges[i].StartIndex); + var end = font.GetCharPosition(text, ranges[i].EndIndex); _highlights.Add(new Rectangle(start.X + textRect.X, textRect.Y, end.X - start.X, textRect.Height)); } isThisVisible = true; diff --git a/Source/Editor/CustomEditors/Dedicated/RagdollEditor.cs b/Source/Editor/CustomEditors/Dedicated/RagdollEditor.cs index c4b334b3a..b6d14d81e 100644 --- a/Source/Editor/CustomEditors/Dedicated/RagdollEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/RagdollEditor.cs @@ -81,7 +81,7 @@ namespace FlaxEditor.CustomEditors.Dedicated // Title var title = new Label(2, 2, width - 4, 23.0f) { - Font = new FontReference(FlaxEngine.GUI.Style.Current.FontLarge), + Font = new MultiFontReference(FlaxEngine.GUI.Style.Current.FontLarge), Text = "Ragdoll Options", Parent = this }; diff --git a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs index 1f69074ce..fd56422cb 100644 --- a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs @@ -43,7 +43,7 @@ namespace FlaxEditor.CustomEditors.Dedicated // Add script button var buttonText = "Add script"; - var textSize = Style.Current.FontMedium.First().MeasureText(buttonText); + var textSize = Style.Current.FontMedium.MeasureText(buttonText); float addScriptButtonWidth = (textSize.X < 60.0f) ? 60.0f : textSize.X + 4; var buttonHeight = (textSize.Y < 18) ? 18 : textSize.Y + 4; _addScriptsButton = new Button diff --git a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs index 5b5c2f90d..9627edfd8 100644 --- a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs @@ -239,7 +239,7 @@ namespace FlaxEditor.CustomEditors.Dedicated // Title var title = new Label(2, 2, DialogWidth - 4, TitleHeight) { - Font = new FontReference(style.FontLarge), + Font = new MultiFontReference(style.FontLarge), Text = "Anchor Presets", Parent = this }; @@ -247,7 +247,7 @@ namespace FlaxEditor.CustomEditors.Dedicated // Info var info = new Label(0, title.Bottom, DialogWidth, InfoHeight) { - Font = new FontReference(style.FontSmall.First()), + Font = new MultiFontReference(style.FontSmall), Text = "Shift: also set bounds\nControl: also set pivot", Parent = this }; @@ -423,7 +423,7 @@ namespace FlaxEditor.CustomEditors.Dedicated // Set control type button var space = layout.Space(20); var buttonText = "Set Type"; - var textSize = FlaxEngine.GUI.Style.Current.FontMedium.First().MeasureText(buttonText); + var textSize = FlaxEngine.GUI.Style.Current.FontMedium.MeasureText(buttonText); float setTypeButtonWidth = (textSize.X < 60.0f) ? 60.0f : textSize.X + 4; var setTypeButton = new Button { diff --git a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs index d7059f712..0cad200fd 100644 --- a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs @@ -101,7 +101,7 @@ namespace FlaxEditor.CustomEditors.Editors _linkButton.Clicked += ToggleLink; ToggleEnabled(); SetLinkStyle(); - var textSize = FlaxEngine.GUI.Style.Current.FontMedium.First().MeasureText(LinkedLabel.Text.Value); + var textSize = FlaxEngine.GUI.Style.Current.FontMedium.MeasureText(LinkedLabel.Text.Value); _linkButton.LocalX += textSize.X + 10; LinkedLabel.SetupContextMenu += (label, menu, editor) => { diff --git a/Source/Editor/CustomEditors/Editors/TagEditor.cs b/Source/Editor/CustomEditors/Editors/TagEditor.cs index a174d02e4..dbd5d124c 100644 --- a/Source/Editor/CustomEditors/Editors/TagEditor.cs +++ b/Source/Editor/CustomEditors/Editors/TagEditor.cs @@ -631,7 +631,7 @@ namespace FlaxEditor.CustomEditors.Editors TooltipText = "Edit...", Parent = _label, }; - var textSize = FlaxEngine.GUI.Style.Current.FontMedium.First().MeasureText(buttonText); + var textSize = FlaxEngine.GUI.Style.Current.FontMedium.MeasureText(buttonText); if (textSize.Y > button.Width) button.Width = textSize.Y + 2; diff --git a/Source/Editor/CustomEditors/LayoutElementsContainer.cs b/Source/Editor/CustomEditors/LayoutElementsContainer.cs index 936851b15..1584e88de 100644 --- a/Source/Editor/CustomEditors/LayoutElementsContainer.cs +++ b/Source/Editor/CustomEditors/LayoutElementsContainer.cs @@ -276,7 +276,7 @@ namespace FlaxEditor.CustomEditors public LabelElement Header(string text) { var element = Label(text); - element.Label.Font = new FontReference(Style.Current.FontLarge); + element.Label.Font = new MultiFontReference(Style.Current.FontLarge); return element; } @@ -284,7 +284,7 @@ namespace FlaxEditor.CustomEditors { var element = Header(header.Text); if (header.FontSize != -1) - element.Label.Font = new FontReference(element.Label.Font.Font, header.FontSize); + element.Label.Font = new MultiFontReference(element.Label.Font, header.FontSize); if (header.Color != 0) element.Label.TextColor = Color.FromRGBA(header.Color); return element; diff --git a/Source/Editor/EditorAssets.cs b/Source/Editor/EditorAssets.cs index 0fe5ee47e..c894abe6b 100644 --- a/Source/Editor/EditorAssets.cs +++ b/Source/Editor/EditorAssets.cs @@ -54,7 +54,7 @@ namespace FlaxEditor /// public static string PrimaryFont = "Editor/Fonts/Roboto-Regular"; - public static string CJKFont = "Editor/Fonts/NotoSansSC-Regular"; + public static string CjkFont = "Editor/Fonts/NotoSansSC-Regular"; /// /// The Inconsolata Regular font. diff --git a/Source/Editor/GUI/ColumnDefinition.cs b/Source/Editor/GUI/ColumnDefinition.cs index aff1817c3..c6e8f2889 100644 --- a/Source/Editor/GUI/ColumnDefinition.cs +++ b/Source/Editor/GUI/ColumnDefinition.cs @@ -35,7 +35,7 @@ namespace FlaxEditor.GUI /// /// The title font. /// - public Font TitleFont; + public MultiFont TitleFont; /// /// The column title text color. diff --git a/Source/Editor/GUI/ComboBox.cs b/Source/Editor/GUI/ComboBox.cs index 7dc407698..8e6cf39a0 100644 --- a/Source/Editor/GUI/ComboBox.cs +++ b/Source/Editor/GUI/ComboBox.cs @@ -191,7 +191,7 @@ namespace FlaxEditor.GUI /// Gets or sets the font used to draw text. /// [EditorDisplay("Style"), EditorOrder(2000)] - public FontReference Font { get; set; } + public MultiFontReference Font { get; set; } /// /// Gets or sets the color of the text. @@ -273,7 +273,7 @@ namespace FlaxEditor.GUI MaximumItemsInViewCount = 20; var style = Style.Current; - Font = new FontReference(style.FontMedium.First()); + Font = new MultiFontReference(style.FontMedium); TextColor = style.Foreground; BackgroundColor = style.BackgroundNormal; BackgroundColorHighlighted = BackgroundColor; @@ -554,7 +554,7 @@ namespace FlaxEditor.GUI var textRect = new Rectangle(margin, 0, clientRect.Width - boxSize - 2.0f * margin, clientRect.Height); Render2D.PushClip(textRect); var textColor = TextColor; - Render2D.DrawText(Font.GetFont(), text, textRect, enabled ? textColor : textColor * 0.5f, TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, textScale); + Render2D.DrawText(Font.GetMultiFont(), text, textRect, enabled ? textColor : textColor * 0.5f, TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, textScale); Render2D.PopClip(); } diff --git a/Source/Editor/GUI/ContextMenu/ContextMenuButton.cs b/Source/Editor/GUI/ContextMenu/ContextMenuButton.cs index 872700a9b..ba3326412 100644 --- a/Source/Editor/GUI/ContextMenu/ContextMenuButton.cs +++ b/Source/Editor/GUI/ContextMenu/ContextMenuButton.cs @@ -234,11 +234,11 @@ namespace FlaxEditor.GUI.ContextMenu { var style = Style.Current; float width = 20; - if (style.FontMedium.First()) + if (style.FontMedium) { - width += style.FontMedium.First().MeasureText(Text).X; + width += style.FontMedium.MeasureText(Text).X; if (!string.IsNullOrEmpty(ShortKeys)) - width += 40 + style.FontMedium.First().MeasureText(ShortKeys).X; + width += 40 + style.FontMedium.MeasureText(ShortKeys).X; } return Mathf.Max(width, base.MinimumWidth); diff --git a/Source/Editor/GUI/CurveEditor.cs b/Source/Editor/GUI/CurveEditor.cs index f71409b20..ff14cdb24 100644 --- a/Source/Editor/GUI/CurveEditor.cs +++ b/Source/Editor/GUI/CurveEditor.cs @@ -317,7 +317,7 @@ namespace FlaxEditor.GUI private Color _contentsColor; private Color _linesColor; private Color _labelsColor; - private Font _labelsFont; + private MultiFont _labelsFont; /// /// The keyframe UI points. @@ -437,7 +437,7 @@ namespace FlaxEditor.GUI _contentsColor = style.Background.RGBMultiplied(0.7f); _linesColor = style.ForegroundDisabled.RGBMultiplied(0.7f); _labelsColor = style.ForegroundDisabled; - _labelsFont = style.FontSmall.First(); + _labelsFont = style.FontSmall; _mainPanel = new Panel(ScrollBars.Both) { diff --git a/Source/Editor/GUI/Docking/DockWindow.cs b/Source/Editor/GUI/Docking/DockWindow.cs index 8601b9c53..374885f01 100644 --- a/Source/Editor/GUI/Docking/DockWindow.cs +++ b/Source/Editor/GUI/Docking/DockWindow.cs @@ -489,7 +489,7 @@ namespace FlaxEditor.GUI.Docking { var style = Style.Current; if (style?.FontMedium != null) - _titleSize = style.FontMedium.First().MeasureText(_title); + _titleSize = style.FontMedium.MeasureText(_title); } base.PerformLayoutBeforeChildren(); diff --git a/Source/Editor/GUI/ItemsListContextMenu.cs b/Source/Editor/GUI/ItemsListContextMenu.cs index ce69f3544..b823cc907 100644 --- a/Source/Editor/GUI/ItemsListContextMenu.cs +++ b/Source/Editor/GUI/ItemsListContextMenu.cs @@ -87,8 +87,8 @@ namespace FlaxEditor.GUI var font = style.FontSmall; for (int i = 0; i < ranges.Length; i++) { - var start = font.First().GetCharPosition(Name, ranges[i].StartIndex); - var end = font.First().GetCharPosition(Name, ranges[i].EndIndex); + var start = font.GetCharPosition(Name, ranges[i].StartIndex); + var end = font.GetCharPosition(Name, ranges[i].EndIndex); _highlights.Add(new Rectangle(start.X + 2, 0, end.X - start.X, Height)); } Visible = true; diff --git a/Source/Editor/GUI/MainMenu.cs b/Source/Editor/GUI/MainMenu.cs index b313fed9f..aadfbf0a7 100644 --- a/Source/Editor/GUI/MainMenu.cs +++ b/Source/Editor/GUI/MainMenu.cs @@ -76,7 +76,7 @@ namespace FlaxEditor.GUI var windowIcon = FlaxEngine.Content.LoadAsyncInternal(EditorAssets.WindowIcon); FontAsset windowIconsFont = FlaxEngine.Content.LoadAsyncInternal(EditorAssets.WindowIconsFont); - Font iconFont = windowIconsFont?.CreateFont(9); + MultiFont iconFont = new MultiFontReference([windowIconsFont], 9).GetMultiFont(); _window = mainWindow.RootWindow.Window; _window.HitTest += OnHitTest; @@ -108,7 +108,7 @@ namespace FlaxEditor.GUI _closeButton = new Button { Text = ((char)EditorAssets.SegMDL2Icons.ChromeClose).ToString(), - Font = new FontReference(iconFont), + Font = new MultiFontReference(iconFont), BackgroundColor = Color.Transparent, BorderColor = Color.Transparent, BorderColorHighlighted = Color.Transparent, @@ -124,7 +124,7 @@ namespace FlaxEditor.GUI _minimizeButton = new Button { Text = ((char)EditorAssets.SegMDL2Icons.ChromeMinimize).ToString(), - Font = new FontReference(iconFont), + Font = new MultiFontReference(iconFont), BackgroundColor = Color.Transparent, BorderColor = Color.Transparent, BorderColorHighlighted = Color.Transparent, @@ -139,7 +139,7 @@ namespace FlaxEditor.GUI _maximizeButton = new Button { Text = ((char)(_window.IsMaximized ? EditorAssets.SegMDL2Icons.ChromeRestore : EditorAssets.SegMDL2Icons.ChromeMaximize)).ToString(), - Font = new FontReference(iconFont), + Font = new MultiFontReference(iconFont), BackgroundColor = Color.Transparent, BorderColor = Color.Transparent, BorderColorHighlighted = Color.Transparent, diff --git a/Source/Editor/GUI/MainMenuButton.cs b/Source/Editor/GUI/MainMenuButton.cs index 76687f8b9..3bc57479c 100644 --- a/Source/Editor/GUI/MainMenuButton.cs +++ b/Source/Editor/GUI/MainMenuButton.cs @@ -102,8 +102,8 @@ namespace FlaxEditor.GUI var style = Style.Current; float width = 18; - if (style.FontMedium.First()) - width += style.FontMedium.First().MeasureText(Text).X; + if (style.FontMedium) + width += style.FontMedium.MeasureText(Text).X; Width = width; } diff --git a/Source/Editor/GUI/NavigationButton.cs b/Source/Editor/GUI/NavigationButton.cs index 77f8a7656..6fd17332c 100644 --- a/Source/Editor/GUI/NavigationButton.cs +++ b/Source/Editor/GUI/NavigationButton.cs @@ -66,9 +66,9 @@ namespace FlaxEditor.GUI { var style = Style.Current; - if (style.FontMedium.First()) + if (style.FontMedium) { - Width = style.FontMedium.First().MeasureText(Text).X + 2 * DefaultMargin; + Width = style.FontMedium.MeasureText(Text).X + 2 * DefaultMargin; } } } diff --git a/Source/Editor/GUI/Row.cs b/Source/Editor/GUI/Row.cs index 458138aea..7533dfb17 100644 --- a/Source/Editor/GUI/Row.cs +++ b/Source/Editor/GUI/Row.cs @@ -39,8 +39,8 @@ namespace FlaxEditor.GUI { Depth = -1; - if (Height < Style.Current.FontMedium.First().Height) - Height = Style.Current.FontMedium.First().Height + 4; + if (Height < Style.Current.FontMedium.MaxHeight) + Height = Style.Current.FontMedium.MaxHeight + 4; } /// diff --git a/Source/Editor/GUI/Table.cs b/Source/Editor/GUI/Table.cs index fed33a336..54656a6c2 100644 --- a/Source/Editor/GUI/Table.cs +++ b/Source/Editor/GUI/Table.cs @@ -130,7 +130,7 @@ namespace FlaxEditor.GUI Render2D.FillRectangle(rect, column.TitleBackgroundColor); var style = Style.Current; - var font = column.TitleFont ?? style.FontMedium.First(); + var font = column.TitleFont ?? style.FontMedium; Render2D.DrawText(font, column.Title, rect, column.TitleColor, TextAlignment.Center, TextAlignment.Center); if (columnIndex < _columns.Length - 1) diff --git a/Source/Editor/GUI/Timeline/GUI/PositionHandle.cs b/Source/Editor/GUI/Timeline/GUI/PositionHandle.cs index bedb61a5e..791fb7133 100644 --- a/Source/Editor/GUI/Timeline/GUI/PositionHandle.cs +++ b/Source/Editor/GUI/Timeline/GUI/PositionHandle.cs @@ -2,6 +2,7 @@ using System; using System.Globalization; +using System.Linq; using FlaxEngine; using FlaxEngine.GUI; @@ -36,16 +37,16 @@ namespace FlaxEditor.GUI.Timeline.GUI string labelText; switch (_timeline.TimeShowMode) { - case Timeline.TimeShowModes.Frames: - labelText = _timeline.CurrentFrame.ToString("###0", CultureInfo.InvariantCulture); - break; - case Timeline.TimeShowModes.Seconds: - labelText = _timeline.CurrentTime.ToString("###0.##'s'", CultureInfo.InvariantCulture); - break; - case Timeline.TimeShowModes.Time: - labelText = TimeSpan.FromSeconds(_timeline.CurrentTime).ToString("g"); - break; - default: throw new ArgumentOutOfRangeException(); + case Timeline.TimeShowModes.Frames: + labelText = _timeline.CurrentFrame.ToString("###0", CultureInfo.InvariantCulture); + break; + case Timeline.TimeShowModes.Seconds: + labelText = _timeline.CurrentTime.ToString("###0.##'s'", CultureInfo.InvariantCulture); + break; + case Timeline.TimeShowModes.Time: + labelText = TimeSpan.FromSeconds(_timeline.CurrentTime).ToString("g"); + break; + default: throw new ArgumentOutOfRangeException(); } var color = (_timeline.IsMovingPositionHandle ? style.ProgressNormal : style.Foreground).AlphaMultiplied(0.6f); Matrix3x3.RotationZ(Mathf.PiOverTwo, out var m1); diff --git a/Source/Editor/GUI/Timeline/Tracks/MemberTrack.cs b/Source/Editor/GUI/Timeline/Tracks/MemberTrack.cs index 928129917..63787df2c 100644 --- a/Source/Editor/GUI/Timeline/Tracks/MemberTrack.cs +++ b/Source/Editor/GUI/Timeline/Tracks/MemberTrack.cs @@ -345,7 +345,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks if (_previewValue != null) { // Based on Track.Draw for track text placement - var left = _xOffset + 16 + Style.Current.FontSmall.First().MeasureText(Title ?? Name).X; + var left = _xOffset + 16 + Style.Current.FontSmall.MeasureText(Title ?? Name).X; if (Icon.IsValid) left += 18; if (IsExpanded) diff --git a/Source/Editor/GUI/ToolStripButton.cs b/Source/Editor/GUI/ToolStripButton.cs index b74c7c19f..d21fd5689 100644 --- a/Source/Editor/GUI/ToolStripButton.cs +++ b/Source/Editor/GUI/ToolStripButton.cs @@ -151,8 +151,8 @@ namespace FlaxEditor.GUI if (hasSprite) width += iconSize; - if (!string.IsNullOrEmpty(_text) && style.FontMedium.First()) - width += style.FontMedium.First().MeasureText(_text).X + (hasSprite ? DefaultMargin : 0); + if (!string.IsNullOrEmpty(_text) && style.FontMedium) + width += style.FontMedium.MeasureText(_text).X + (hasSprite ? DefaultMargin : 0); Width = width; } diff --git a/Source/Editor/GUI/Tree/TreeNode.cs b/Source/Editor/GUI/Tree/TreeNode.cs index f26929993..acb67ea8f 100644 --- a/Source/Editor/GUI/Tree/TreeNode.cs +++ b/Source/Editor/GUI/Tree/TreeNode.cs @@ -115,7 +115,7 @@ namespace FlaxEditor.GUI.Tree /// Gets or sets the font used to render text. /// [EditorDisplay("Style"), EditorOrder(2000)] - public FontReference TextFont { get; set; } + public MultiFontReference TextFont { get; set; } /// /// Gets or sets the color of the background when tree node is selected. @@ -318,7 +318,7 @@ namespace FlaxEditor.GUI.Tree BackgroundColorSelected = style.BackgroundSelected; BackgroundColorHighlighted = style.BackgroundHighlighted; BackgroundColorSelectedUnfocused = style.LightBackground; - TextFont = new FontReference(style.FontSmall.First()); + TextFont = new MultiFontReference(style.FontSmall); } /// @@ -573,7 +573,7 @@ namespace FlaxEditor.GUI.Tree { if (_textChanged) { - var font = TextFont.GetFont(); + var font = TextFont.GetMultiFont(); if (font) { _textWidth = font.MeasureText(_text).X; @@ -657,7 +657,7 @@ namespace FlaxEditor.GUI.Tree } // Draw text - Render2D.DrawText(TextFont.GetFont(), _text, textRect, _cachedTextColor, TextAlignment.Near, TextAlignment.Center); + Render2D.DrawText(TextFont.GetMultiFont(), _text, textRect, _cachedTextColor, TextAlignment.Near, TextAlignment.Center); // Draw drag and drop effect if (IsDragOver && _tree.DraggedOverNode == this) diff --git a/Source/Editor/Options/InterfaceOptions.cs b/Source/Editor/Options/InterfaceOptions.cs index c96a52fb1..e43751fd1 100644 --- a/Source/Editor/Options/InterfaceOptions.cs +++ b/Source/Editor/Options/InterfaceOptions.cs @@ -166,15 +166,13 @@ namespace FlaxEditor.Options /// Gets or sets the output log text font. /// [EditorDisplay("Output Log", "Text Font"), EditorOrder(320), Tooltip("The output log text font.")] - public FontReference OutputLogTextFont + public MultiFontReference OutputLogTextFont { get => _outputLogFont; set { - if (value == null) - _outputLogFont = new FontReference(FlaxEngine.Content.LoadAsyncInternal(EditorAssets.InconsolataRegularFont), 10); - else if (!value.Font) - _outputLogFont.Font = FlaxEngine.Content.LoadAsyncInternal(EditorAssets.InconsolataRegularFont); + if (value == null || !value.Verify()) + _outputLogFont = new MultiFontReference(ConsoleFonts, 10); else _outputLogFont = value; } @@ -236,32 +234,31 @@ namespace FlaxEditor.Options [EditorDisplay("Cook & Run"), EditorOrder(500)] public int NumberOfGameClientsToLaunch = 1; - private static FontAsset DefaultFont => FlaxEngine.Content.LoadAsyncInternal(EditorAssets.PrimaryFont); - private static FontAsset _cjkFont => FlaxEngine.Content.LoadAsyncInternal(EditorAssets.CJKFont); - private FontReference _titleFont = new FontReference(DefaultFont, 18); - private FontReference _largeFont = new FontReference(DefaultFont, 14); - private FontReference _mediumFont = new FontReference(DefaultFont, 9); - private FontReference _smallFont = new FontReference(DefaultFont, 9); - private FontReference _outputLogFont = new FontReference(FlaxEngine.Content.LoadAsyncInternal(EditorAssets.InconsolataRegularFont), 10); + private static FontAsset[] DefaultFonts => + [FlaxEngine.Content.LoadAsyncInternal(EditorAssets.PrimaryFont), + FlaxEngine.Content.LoadAsyncInternal(EditorAssets.CjkFont)]; + + private static FontAsset[] ConsoleFonts => [FlaxEngine.Content.LoadAsyncInternal(EditorAssets.PrimaryFont), + FlaxEngine.Content.LoadAsyncInternal(EditorAssets.CjkFont)]; + + private MultiFontReference _titleFont = new MultiFontReference(DefaultFonts, 18); + private MultiFontReference _largeFont = new MultiFontReference(DefaultFonts, 14); + private MultiFontReference _mediumFont = new MultiFontReference(DefaultFonts, 9); + private MultiFontReference _smallFont = new MultiFontReference(DefaultFonts, 9); + private MultiFontReference _outputLogFont = new MultiFontReference(ConsoleFonts, 10); - public FontReference CJKFont - { - get => new FontReference(_cjkFont, 9); - } /// /// Gets or sets the title font for editor UI. /// [EditorDisplay("Fonts"), EditorOrder(600), Tooltip("The title font for editor UI.")] - public FontReference TitleFont + public MultiFontReference TitleFont { get => _titleFont; set { - if (value == null) - _titleFont = new FontReference(DefaultFont, 18); - else if (!value.Font) - _titleFont.Font = DefaultFont; + if (value == null || !value.Verify()) + _titleFont = new MultiFontReference(DefaultFonts, 18); else _titleFont = value; } @@ -271,15 +268,13 @@ namespace FlaxEditor.Options /// Gets or sets the large font for editor UI. /// [EditorDisplay("Fonts"), EditorOrder(610), Tooltip("The large font for editor UI.")] - public FontReference LargeFont + public MultiFontReference LargeFont { get => _largeFont; set { - if (value == null) - _largeFont = new FontReference(DefaultFont, 14); - else if (!value.Font) - _largeFont.Font = DefaultFont; + if (value == null || !value.Verify()) + _largeFont = new MultiFontReference(DefaultFonts, 14); else _largeFont = value; } @@ -289,15 +284,13 @@ namespace FlaxEditor.Options /// Gets or sets the medium font for editor UI. /// [EditorDisplay("Fonts"), EditorOrder(620), Tooltip("The medium font for editor UI.")] - public FontReference MediumFont + public MultiFontReference MediumFont { get => _mediumFont; set { - if (value == null) - _mediumFont = new FontReference(DefaultFont, 9); - else if (!value.Font) - _mediumFont.Font = DefaultFont; + if (value == null || !value.Verify()) + _mediumFont = new MultiFontReference(DefaultFonts, 9); else _mediumFont = value; } @@ -307,15 +300,13 @@ namespace FlaxEditor.Options /// Gets or sets the small font for editor UI. /// [EditorDisplay("Fonts"), EditorOrder(630), Tooltip("The small font for editor UI.")] - public FontReference SmallFont + public MultiFontReference SmallFont { get => _smallFont; set { - if (value == null) - _smallFont = new FontReference(DefaultFont, 9); - else if (!value.Font) - _smallFont.Font = DefaultFont; + if (value == null || !value.Verify()) + _smallFont = new MultiFontReference(DefaultFonts, 9); else _smallFont = value; } diff --git a/Source/Editor/Options/OptionsModule.cs b/Source/Editor/Options/OptionsModule.cs index 15059bd56..d138d7d0d 100644 --- a/Source/Editor/Options/OptionsModule.cs +++ b/Source/Editor/Options/OptionsModule.cs @@ -259,11 +259,10 @@ namespace FlaxEditor.Options }, // Fonts - FontTitle = options.Interface.TitleFont.GetFont(), - FontLarge = options.Interface.LargeFont.GetFont(), - FontMedium = new Font[] { options.Interface.MediumFont.GetFont(), options.Interface.CJKFont.GetFont() }, - FontSmall = new Font[] { options.Interface.SmallFont.GetFont(), options.Interface.CJKFont.GetFont() }, - FontCJK = options.Interface.CJKFont.GetFont(), + FontTitle = options.Interface.TitleFont.GetMultiFont(), + FontLarge = options.Interface.LargeFont.GetMultiFont(), + FontMedium = options.Interface.MediumFont.GetMultiFont(), + FontSmall = options.Interface.SmallFont.GetMultiFont(), // Icons ArrowDown = Editor.Icons.ArrowDown12, @@ -313,10 +312,10 @@ namespace FlaxEditor.Options ProgressNormal = new Color(0.03f, 0.65f, 0.12f, 1f), // Fonts - FontTitle = options.Interface.TitleFont.GetFont(), - FontLarge = options.Interface.LargeFont.GetFont(), - FontMedium = new Font[] { options.Interface.MediumFont.GetFont(), options.Interface.CJKFont.GetFont() }, - FontSmall = new Font[] { options.Interface.SmallFont.GetFont(), options.Interface.CJKFont.GetFont() }, + FontTitle = options.Interface.TitleFont.GetMultiFont(), + FontLarge = options.Interface.LargeFont.GetMultiFont(), + FontMedium = options.Interface.MediumFont.GetMultiFont(), + FontSmall = options.Interface.SmallFont.GetMultiFont(), // Icons ArrowDown = Editor.Icons.ArrowDown12, diff --git a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs index 9a6fe4fd2..f64e46385 100644 --- a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs +++ b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs @@ -142,8 +142,8 @@ namespace FlaxEditor.SceneGraph.GUI var textRect = TextRect; for (int i = 0; i < ranges.Length; i++) { - var start = font.First().GetCharPosition(text, ranges[i].StartIndex); - var end = font.First().GetCharPosition(text, ranges[i].EndIndex); + var start = font.GetCharPosition(text, ranges[i].StartIndex); + var end = font.GetCharPosition(text, ranges[i].EndIndex); _highlights.Add(new Rectangle(start.X + textRect.X, textRect.Y, end.X - start.X, textRect.Height)); } isThisVisible = true; diff --git a/Source/Editor/Surface/Archetypes/Animation.TransitionEditor.cs b/Source/Editor/Surface/Archetypes/Animation.TransitionEditor.cs index 84c2144b2..e1fdb0a42 100644 --- a/Source/Editor/Surface/Archetypes/Animation.TransitionEditor.cs +++ b/Source/Editor/Surface/Archetypes/Animation.TransitionEditor.cs @@ -39,7 +39,7 @@ namespace FlaxEditor.Surface.Archetypes // Title var title = new Label(2, 2, width - 4, 23.0f) { - Font = new FontReference(Style.Current.FontLarge), + Font = new MultiFontReference(Style.Current.FontLarge), Text = transition.SurfaceName, Parent = this }; diff --git a/Source/Editor/Surface/Archetypes/BehaviorTree.cs b/Source/Editor/Surface/Archetypes/BehaviorTree.cs index 38dd28e62..cca6856ae 100644 --- a/Source/Editor/Surface/Archetypes/BehaviorTree.cs +++ b/Source/Editor/Surface/Archetypes/BehaviorTree.cs @@ -101,7 +101,7 @@ namespace FlaxEditor.Surface.Archetypes _debugRelevant = Behavior.GetNodeDebugRelevancy(instance, behavior); _debugInfo = Behavior.GetNodeDebugInfo(instance, behavior); if (!string.IsNullOrEmpty(_debugInfo)) - _debugInfoSize = Style.Current.FontSmall.First().MeasureText(_debugInfo); + _debugInfoSize = Style.Current.FontSmall.MeasureText(_debugInfo); } } diff --git a/Source/Editor/Surface/Archetypes/Function.cs b/Source/Editor/Surface/Archetypes/Function.cs index 53950dad2..bc982a510 100644 --- a/Source/Editor/Surface/Archetypes/Function.cs +++ b/Source/Editor/Surface/Archetypes/Function.cs @@ -1407,7 +1407,7 @@ namespace FlaxEditor.Surface.Archetypes // Title var title = new Label(2, 2, width - 4, 23.0f) { - Font = new FontReference(Style.Current.FontLarge), + Font = new MultiFontReference(Style.Current.FontLarge), Text = "Edit function signature", Parent = this }; diff --git a/Source/Editor/Surface/AttributesEditor.cs b/Source/Editor/Surface/AttributesEditor.cs index 81b11bb68..c9e32e23a 100644 --- a/Source/Editor/Surface/AttributesEditor.cs +++ b/Source/Editor/Surface/AttributesEditor.cs @@ -83,7 +83,7 @@ namespace FlaxEditor.Surface // Title var title = new Label(2, 2, width - 4, 23.0f) { - Font = new FontReference(Style.Current.FontLarge), + Font = new MultiFontReference(Style.Current.FontLarge), Text = "Edit attributes", Parent = this }; diff --git a/Source/Editor/Surface/ContextMenu/VisjectCM.cs b/Source/Editor/Surface/ContextMenu/VisjectCM.cs index 930741807..0624d44b3 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCM.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCM.cs @@ -141,7 +141,7 @@ namespace FlaxEditor.Surface.ContextMenu }; // Title bar - var titleFontReference = new FontReference(Style.Current.FontLarge.Asset, 10); + var titleFontReference = new MultiFontReference(Style.Current.FontLarge); var titleLabel = new Label { Width = Width * 0.5f - 8f, diff --git a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs index 812bec5d2..207875a92 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs @@ -200,8 +200,8 @@ namespace FlaxEditor.Surface.ContextMenu var font = style.FontSmall; for (int i = 0; i < ranges.Length; i++) { - var start = font.First().GetCharPosition(_archetype.Title, ranges[i].StartIndex); - var end = font.First().GetCharPosition(_archetype.Title, ranges[i].EndIndex); + var start = font.GetCharPosition(_archetype.Title, ranges[i].StartIndex); + var end = font.GetCharPosition(_archetype.Title, ranges[i].EndIndex); _highlights.Add(new Rectangle(start.X + textRect.X, 0, end.X - start.X, Height)); if (ranges[i].StartIndex <= 0) @@ -222,8 +222,8 @@ namespace FlaxEditor.Surface.ContextMenu _highlights.Clear(); var style = Style.Current; var font = style.FontSmall; - var start = font.First().GetCharPosition(_archetype.Title, 0); - var end = font.First().GetCharPosition(_archetype.Title, _archetype.Title.Length - 1); + var start = font.GetCharPosition(_archetype.Title, 0); + var end = font.GetCharPosition(_archetype.Title, _archetype.Title.Length - 1); _highlights.Add(new Rectangle(start.X + textRect.X, 0, end.X - start.X, Height)); _isFullMatch = true; Visible = true; @@ -237,8 +237,8 @@ namespace FlaxEditor.Surface.ContextMenu _highlights.Clear(); var style = Style.Current; var font = style.FontSmall; - var start = font.First().GetCharPosition(_archetype.Title, 0); - var end = font.First().GetCharPosition(_archetype.Title, _archetype.Title.Length - 1); + var start = font.GetCharPosition(_archetype.Title, 0); + var end = font.GetCharPosition(_archetype.Title, _archetype.Title.Length - 1); _highlights.Add(new Rectangle(start.X + textRect.X, 0, end.X - start.X, Height)); Visible = true; @@ -286,7 +286,7 @@ namespace FlaxEditor.Surface.ContextMenu Render2D.DrawText(style.FontSmall, _archetype.Title, textRect, Enabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center); if (_archetype.SubTitle != null) { - var titleLength = style.FontSmall.First().MeasureText(_archetype.Title).X; + var titleLength = style.FontSmall.MeasureText(_archetype.Title).X; var subTitleRect = new Rectangle(textRect.X + titleLength, textRect.Y, textRect.Width - titleLength, textRect.Height); Render2D.DrawText(style.FontSmall, _archetype.SubTitle, subTitleRect, style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center); } diff --git a/Source/Editor/Surface/Elements/InputBox.cs b/Source/Editor/Surface/Elements/InputBox.cs index 611160611..2047bcd1a 100644 --- a/Source/Editor/Surface/Elements/InputBox.cs +++ b/Source/Editor/Surface/Elements/InputBox.cs @@ -1428,7 +1428,7 @@ namespace FlaxEditor.Surface.Elements if (_defaultValueEditor != null) { - _defaultValueEditor.Location = new Float2(X + Width + 8 + Style.Current.FontSmall.First().MeasureText(Text).X, Y); + _defaultValueEditor.Location = new Float2(X + Width + 8 + Style.Current.FontSmall.MeasureText(Text).X, Y); } } @@ -1635,7 +1635,7 @@ namespace FlaxEditor.Surface.Elements { if (DefaultValueEditors[i].CanUse(this, ref _currentType)) { - var bounds = new Rectangle(X + Width + 8 + Style.Current.FontSmall.First().MeasureText(Text).X, Y, 90, Height); + var bounds = new Rectangle(X + Width + 8 + Style.Current.FontSmall.MeasureText(Text).X, Y, 90, Height); _editor = DefaultValueEditors[i]; try { diff --git a/Source/Editor/Surface/SurfaceNode.cs b/Source/Editor/Surface/SurfaceNode.cs index 82a9ab2bd..b6436e542 100644 --- a/Source/Editor/Surface/SurfaceNode.cs +++ b/Source/Editor/Surface/SurfaceNode.cs @@ -200,7 +200,7 @@ namespace FlaxEditor.Surface continue; if (child is InputBox inputBox) { - var boxWidth = boxLabelFont.First().MeasureText(inputBox.Text).X + 20; + var boxWidth = boxLabelFont.MeasureText(inputBox.Text).X + 20; if (inputBox.DefaultValueEditor != null) boxWidth += inputBox.DefaultValueEditor.Width + 4; leftWidth = Mathf.Max(leftWidth, boxWidth); @@ -208,7 +208,7 @@ namespace FlaxEditor.Surface } else if (child is OutputBox outputBox) { - rightWidth = Mathf.Max(rightWidth, boxLabelFont.First().MeasureText(outputBox.Text).X + 20); + rightWidth = Mathf.Max(rightWidth, boxLabelFont.MeasureText(outputBox.Text).X + 20); rightHeight = Mathf.Max(rightHeight, outputBox.Archetype.Position.Y - Constants.NodeMarginY - Constants.NodeHeaderSize + 20.0f); } else if (child is Control control) diff --git a/Source/Editor/Tools/Foliage/FoliageTab.cs b/Source/Editor/Tools/Foliage/FoliageTab.cs index e88972b5c..1badb8c0c 100644 --- a/Source/Editor/Tools/Foliage/FoliageTab.cs +++ b/Source/Editor/Tools/Foliage/FoliageTab.cs @@ -148,7 +148,7 @@ namespace FlaxEditor.Tools.Foliage Parent = _noFoliagePanel, Enabled = false }; - var textSize = Style.Current.FontMedium.First().MeasureText(buttonText); + var textSize = Style.Current.FontMedium.MeasureText(buttonText); if (_createNewFoliage.Width < textSize.X) { _createNewFoliage.LocalX -= (textSize.X - _createNewFoliage.Width) / 2; diff --git a/Source/Editor/Tools/Terrain/CarveTab.cs b/Source/Editor/Tools/Terrain/CarveTab.cs index 0a43cd2d2..4ff85ca23 100644 --- a/Source/Editor/Tools/Terrain/CarveTab.cs +++ b/Source/Editor/Tools/Terrain/CarveTab.cs @@ -106,7 +106,7 @@ namespace FlaxEditor.Tools.Terrain Parent = _noTerrainPanel, Enabled = false }; - var textSize = Style.Current.FontMedium.First().MeasureText(buttonText); + var textSize = Style.Current.FontMedium.MeasureText(buttonText); if (_createTerrainButton.Width < textSize.X) { _createTerrainButton.LocalX -= (textSize.X - _createTerrainButton.Width) / 2; diff --git a/Source/Editor/Tools/Terrain/CreateTerrainDialog.cs b/Source/Editor/Tools/Terrain/CreateTerrainDialog.cs index 252891d44..cba52283d 100644 --- a/Source/Editor/Tools/Terrain/CreateTerrainDialog.cs +++ b/Source/Editor/Tools/Terrain/CreateTerrainDialog.cs @@ -96,7 +96,7 @@ namespace FlaxEditor.Tools.Terrain AnchorPreset = AnchorPresets.HorizontalStretchTop, Offsets = new Margin(0, 0, 0, 40), Parent = this, - Font = new FontReference(Style.Current.FontTitle) + Font = new MultiFontReference(Style.Current.FontTitle) }; var infoLabel = new Label { diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index f4ebfb51b..c49392d01 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -548,9 +548,9 @@ namespace FlaxEditor.Viewport #region Camera settings widget var largestText = "Relative Panning"; - var textSize = Style.Current.FontMedium.First().MeasureText(largestText); + var textSize = Style.Current.FontMedium.MeasureText(largestText); var xLocationForExtras = textSize.X + 5; - var cameraSpeedTextWidth = Style.Current.FontMedium.First().MeasureText("0.00").X; + var cameraSpeedTextWidth = Style.Current.FontMedium.MeasureText("0.00").X; // Camera Settings Widget _cameraWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); @@ -801,7 +801,7 @@ namespace FlaxEditor.Viewport #region View mode widget largestText = "Brightness"; - textSize = Style.Current.FontMedium.First().MeasureText(largestText); + textSize = Style.Current.FontMedium.MeasureText(largestText); xLocationForExtras = textSize.X + 5; var viewMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperLeft); diff --git a/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs b/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs index a58350ded..fe73048fd 100644 --- a/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs +++ b/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs @@ -163,8 +163,8 @@ namespace FlaxEditor.Viewport.Widgets { var style = Style.Current; - if (style != null && style.FontMedium.First()) - Width = CalculateButtonWidth(_forcedTextWidth > 0.0f ? _forcedTextWidth : style.FontMedium.First().MeasureText(_text).X, Icon.IsValid); + if (style != null && style.FontMedium) + Width = CalculateButtonWidth(_forcedTextWidth > 0.0f ? _forcedTextWidth : style.FontMedium.MeasureText(_text).X, Icon.IsValid); } } } diff --git a/Source/Editor/Windows/AboutDialog.cs b/Source/Editor/Windows/AboutDialog.cs index 63ad44f29..1a81a9421 100644 --- a/Source/Editor/Windows/AboutDialog.cs +++ b/Source/Editor/Windows/AboutDialog.cs @@ -41,7 +41,7 @@ namespace FlaxEditor.Windows var nameLabel = new Label(icon.Right + 10, icon.Top, 200, 34) { Text = "Flax Engine", - Font = new FontReference(Style.Current.FontTitle), + Font = new MultiFontReference(Style.Current.FontTitle), HorizontalAlignment = TextAlignment.Near, VerticalAlignment = TextAlignment.Center, Parent = this @@ -54,7 +54,7 @@ namespace FlaxEditor.Windows Parent = this }; var buttonText = "Copy version info"; - var fontSize = Style.Current.FontMedium.First().MeasureText(buttonText); + var fontSize = Style.Current.FontMedium.MeasureText(buttonText); var copyVersionButton = new Button(Width - fontSize.X - 8, 6, fontSize.X + 4, 20) { Text = buttonText, diff --git a/Source/Editor/Windows/Assets/FontWindow.cs b/Source/Editor/Windows/Assets/FontWindow.cs index ff4135165..2e2e14d99 100644 --- a/Source/Editor/Windows/Assets/FontWindow.cs +++ b/Source/Editor/Windows/Assets/FontWindow.cs @@ -144,7 +144,7 @@ namespace FlaxEditor.Windows.Assets protected override void OnAssetLinked() { Asset.WaitForLoaded(); - _textPreview.Font = new FontReference(Asset.CreateFont(30)); + _textPreview.Font = new MultiFontReference([Asset], 30); _inputText.Text = string.Format("This is a sample text using font {0}.", Asset.FamilyName); var options = Asset.Options; _proxy.Set(ref options); diff --git a/Source/Editor/Windows/ContentWindow.Search.cs b/Source/Editor/Windows/ContentWindow.Search.cs index a1072d158..f29dc0bd6 100644 --- a/Source/Editor/Windows/ContentWindow.Search.cs +++ b/Source/Editor/Windows/ContentWindow.Search.cs @@ -57,7 +57,7 @@ namespace FlaxEditor.Windows var textRect = new Rectangle(margin, 0, clientRect.Width - boxSize - 2.0f * margin, clientRect.Height); Render2D.PushClip(textRect); var textColor = TextColor; - Render2D.DrawText(Font.GetFont(), "View", textRect, enabled ? textColor : textColor * 0.5f, TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, textScale); + Render2D.DrawText(Font.GetMultiFont(), "View", textRect, enabled ? textColor : textColor * 0.5f, TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, textScale); Render2D.PopClip(); // Arrow diff --git a/Source/Editor/Windows/OutputLogWindow.cs b/Source/Editor/Windows/OutputLogWindow.cs index 6526d7c8a..ac4fea5ba 100644 --- a/Source/Editor/Windows/OutputLogWindow.cs +++ b/Source/Editor/Windows/OutputLogWindow.cs @@ -470,9 +470,9 @@ namespace FlaxEditor.Windows var wasEmpty = _output.TextLength == 0; // Cache fonts - _output.DefaultStyle.Font.GetFont(); - _output.WarningStyle.Font.GetFont(); - _output.ErrorStyle.Font.GetFont(); + _output.DefaultStyle.Font.GetMultiFont(); + _output.WarningStyle.Font.GetMultiFont(); + _output.ErrorStyle.Font.GetMultiFont(); // Generate the output log Span entries = CollectionsMarshal.AsSpan(_entries); @@ -536,7 +536,7 @@ namespace FlaxEditor.Windows } var prevBlockBottom = _textBlocks.Count == 0 ? 0.0f : _textBlocks[_textBlocks.Count - 1].Bounds.Bottom; var entryText = _textBuffer.ToString(startIndex, endIndex - startIndex); - var font = textBlock.Style.Font.GetFont(); + var font = textBlock.Style.Font.GetMultiFont(); if (!font) continue; var style = textBlock.Style; @@ -544,46 +544,52 @@ namespace FlaxEditor.Windows for (int j = 0; j < lines.Length; j++) { ref var line = ref lines[j]; - textBlock.Range.StartIndex = startIndex + line.FirstCharIndex; - textBlock.Range.EndIndex = startIndex + line.LastCharIndex + 1; - textBlock.Bounds = new Rectangle(new Float2(0.0f, prevBlockBottom), line.Size); - - if (textBlock.Range.Length > 0) + for (int k = 0; k < line.Blocks.Length; k++) { - // Parse compilation error/warning - var regexStart = line.FirstCharIndex; - if (j == 0) - regexStart += prefixLength; - var regexLength = line.LastCharIndex + 1 - regexStart; - if (regexLength > 0) + ref var block = ref line.Blocks[k]; + + textBlock.Range.StartIndex = startIndex + block.FirstCharIndex; + textBlock.Range.EndIndex = startIndex + block.LastCharIndex + 1; + textBlock.Bounds = new Rectangle(new Float2(block.Location.X, prevBlockBottom), block.Size); + + if (textBlock.Range.Length > 0) { - var match = _compileRegex.Match(entryText, regexStart, regexLength); - if (match.Success) + // Parse compilation error/warning + var regexStart = block.FirstCharIndex; + if (j == 0) + regexStart += prefixLength; + var regexLength = block.LastCharIndex + 1 - regexStart; + if (regexLength > 0) { - switch (match.Groups["level"].Value) + var match = _compileRegex.Match(entryText, regexStart, regexLength); + if (match.Success) { - case "error": - textBlock.Style = _output.ErrorStyle; - break; - case "warning": - textBlock.Style = _output.WarningStyle; - break; + switch (match.Groups["level"].Value) + { + case "error": + textBlock.Style = _output.ErrorStyle; + break; + case "warning": + textBlock.Style = _output.WarningStyle; + break; + } + textBlock.Tag = new TextBlockTag + { + Type = TextBlockTag.Types.CodeLocation, + Url = match.Groups["path"].Value, + Line = int.Parse(match.Groups["line"].Value), + }; } - textBlock.Tag = new TextBlockTag - { - Type = TextBlockTag.Types.CodeLocation, - Url = match.Groups["path"].Value, - Line = int.Parse(match.Groups["line"].Value), - }; + // TODO: parsing hyperlinks with link + // TODO: parsing file paths with link } - // TODO: parsing hyperlinks with link - // TODO: parsing file paths with link } + + _textBlocks.Add(textBlock); + textBlock.Style = style; } prevBlockBottom += line.Size.Y; - _textBlocks.Add(textBlock); - textBlock.Style = style; } } diff --git a/Source/Editor/Windows/PluginsWindow.cs b/Source/Editor/Windows/PluginsWindow.cs index 372505795..d6c14f4e4 100644 --- a/Source/Editor/Windows/PluginsWindow.cs +++ b/Source/Editor/Windows/PluginsWindow.cs @@ -14,6 +14,7 @@ using FlaxEditor.GUI.Tabs; using FlaxEngine; using FlaxEngine.GUI; using FlaxEngine.Json; +using FlaxEngine.Utilities; namespace FlaxEditor.Windows { @@ -78,7 +79,7 @@ namespace FlaxEditor.Windows HorizontalAlignment = TextAlignment.Near, AnchorPreset = AnchorPresets.HorizontalStretchTop, Text = desc.Name, - Font = new FontReference(Style.Current.FontLarge), + Font = new MultiFontReference(Style.Current.FontLarge), Parent = this, Bounds = new Rectangle(tmp1, margin, Width - tmp1 - margin, 28), }; @@ -119,8 +120,8 @@ namespace FlaxEditor.Windows url = desc.HomepageUrl; else if (!string.IsNullOrEmpty(desc.RepositoryUrl)) url = desc.RepositoryUrl; - versionLabel.Font.Font.WaitForLoaded(); - var font = versionLabel.Font.GetFont(); + versionLabel.Font.ForEach(x => x.Font.WaitForLoaded()); + var font = versionLabel.Font.GetMultiFont(); var authorWidth = font.MeasureText(desc.Author).X + 8; var authorLabel = new ClickableLabel { @@ -391,7 +392,7 @@ namespace FlaxEditor.Windows } Editor.Log("Plugin project has been cloned."); - + try { // Start git submodule clone diff --git a/Source/Editor/Windows/Profiler/Timeline.cs b/Source/Editor/Windows/Profiler/Timeline.cs index 88019b329..917647f23 100644 --- a/Source/Editor/Windows/Profiler/Timeline.cs +++ b/Source/Editor/Windows/Profiler/Timeline.cs @@ -85,8 +85,8 @@ namespace FlaxEditor.Windows.Profiler Render2D.FillRectangle(bounds, color); Render2D.DrawRectangle(bounds, color * 0.5f); - if (_nameLength < 0 && style.FontMedium.First()) - _nameLength = style.FontMedium.First().MeasureText(_name).X; + if (_nameLength < 0 && style.FontMedium) + _nameLength = style.FontMedium.MeasureText(_name).X; if (_nameLength < bounds.Width + 4) { diff --git a/Source/Editor/Windows/ToolboxWindow.cs b/Source/Editor/Windows/ToolboxWindow.cs index e8fc1d56c..16b9165e1 100644 --- a/Source/Editor/Windows/ToolboxWindow.cs +++ b/Source/Editor/Windows/ToolboxWindow.cs @@ -273,8 +273,8 @@ namespace FlaxEditor.Windows var textRect = item.TextRect; for (int i = 0; i < ranges.Length; i++) { - var start = font.First().GetCharPosition(text, ranges[i].StartIndex); - var end = font.First().GetCharPosition(text, ranges[i].EndIndex); + var start = font.GetCharPosition(text, ranges[i].StartIndex); + var end = font.GetCharPosition(text, ranges[i].EndIndex); highlights.Add(new Rectangle(start.X + textRect.X, textRect.Y, end.X - start.X, textRect.Height)); } item.SetHighlights(highlights); diff --git a/Source/Engine/Render2D/Font.cpp b/Source/Engine/Render2D/Font.cpp index 277ad8ddd..b33b10899 100644 --- a/Source/Engine/Render2D/Font.cpp +++ b/Source/Engine/Render2D/Font.cpp @@ -293,249 +293,6 @@ void Font::ProcessText(const StringView& text, Array& outputLines } } -void Font::ProcessText(const Array& fonts, const StringView& text, Array& outputLines, API_PARAM(Ref) const TextLayoutOptions& layout) -{ - float cursorX = 0; - int32 kerning; - MultiFontLineCache tmpLine; - MultiFontSegmentCache tmpSegment; - FontCharacterEntry entry; - FontCharacterEntry previous; - int32 textLength = text.Length(); - float scale = layout.Scale / FontManager::FontScale; - float boundsWidth = layout.Bounds.GetWidth(); - float baseLinesDistanceScale = layout.BaseLinesGapScale * scale; - - tmpSegment.Location = Float2::Zero; - tmpSegment.Height = 0; - tmpSegment.FirstCharIndex = 0; - tmpSegment.LastCharIndex = -1; - - tmpLine.Location = Float2::Zero; - tmpLine.Size = Float2::Zero; - tmpLine.Segments = Array(); - - if (textLength == 0) { - return; - } - - int32 lastWrapCharIndex = INVALID_INDEX; - float lastWrapCharX = 0; - bool lastMoveLine = false; - // The index of the font used by the current segment - int32 currentFontIndex = GetCharFontIndex(fonts, text[0], 0); - // The maximum font height of the current line - float maxHeight = 0; - float maxAscender = 0; - - // Process each character to split text into single lines - for (int32 currentIndex = 0; currentIndex < textLength;) - { - bool moveLine = false; - bool moveSegment = false; - float xAdvance = 0; - int32 nextCharIndex = currentIndex + 1; - - // Submit line and segment if text ends - if (nextCharIndex == textLength) { - moveLine = moveSegment = true; - } - - // Cache current character - const Char currentChar = text[currentIndex]; - const bool isWhitespace = StringUtils::IsWhitespace(currentChar); - - // Check if character can wrap words - const bool isWrapChar = !StringUtils::IsAlnum(currentChar) || isWhitespace || StringUtils::IsUpper(currentChar) || (currentChar >= 0x3040 && currentChar <= 0x9FFF); - if (isWrapChar && currentIndex != 0) - { - lastWrapCharIndex = currentIndex; - lastWrapCharX = cursorX; - } - - int32 nextFontIndex = currentFontIndex; - // Check if it's a newline character - if (currentChar == '\n') - { - // Break line - moveLine = moveSegment = true; - tmpSegment.LastCharIndex++; - } - else - { - // Get character entry - if (nextCharIndex < textLength) { - nextFontIndex = GetCharFontIndex(fonts, text[nextCharIndex], currentFontIndex); - } - - // Get character entry - fonts[currentFontIndex]->GetCharacter(currentChar, entry); - maxHeight = Math::Max(maxHeight, static_cast(fonts[currentFontIndex]->GetHeight())); - maxAscender = Math::Max(maxAscender, static_cast(fonts[currentFontIndex]->GetAscender())); - - // Move segment if the font changes or text ends - if (nextFontIndex != currentFontIndex || nextCharIndex == textLength) { - moveSegment = true; - } - - // Get kerning, only when the font hasn't changed - if (!isWhitespace && previous.IsValid && !moveSegment) - { - kerning = fonts[currentFontIndex]->GetKerning(previous.Character, entry.Character); - } - else - { - kerning = 0; - } - previous = entry; - xAdvance = (kerning + entry.AdvanceX) * scale; - - // Check if character fits the line or skip wrapping - if (cursorX + xAdvance <= boundsWidth || layout.TextWrapping == TextWrapping::NoWrap) - { - // Move character - cursorX += xAdvance; - tmpSegment.LastCharIndex++; - } - else if (layout.TextWrapping == TextWrapping::WrapWords) - { - if (lastWrapCharIndex != INVALID_INDEX) - { - // Skip moving twice for the same character - int32 lastLineLastCharIndex = outputLines.HasItems() && outputLines.Last().Segments.HasItems() ? outputLines.Last().Segments.Last().LastCharIndex : -10000; - if (lastLineLastCharIndex == lastWrapCharIndex || lastLineLastCharIndex == lastWrapCharIndex - 1 || lastLineLastCharIndex == lastWrapCharIndex - 2) - { - currentIndex = nextCharIndex; - lastMoveLine = moveLine; - continue; - } - - // Move line - const Char wrapChar = text[lastWrapCharIndex]; - moveLine = true; - moveSegment = tmpSegment.FirstCharIndex < lastWrapCharIndex; - - cursorX = lastWrapCharX; - if (StringUtils::IsWhitespace(wrapChar)) - { - // Skip whitespaces - tmpSegment.LastCharIndex = lastWrapCharIndex - 1; - nextCharIndex = currentIndex = lastWrapCharIndex + 1; - } - else - { - tmpSegment.LastCharIndex = lastWrapCharIndex - 1; - nextCharIndex = currentIndex = lastWrapCharIndex; - } - } - } - else if (layout.TextWrapping == TextWrapping::WrapChars) - { - // Move line - moveLine = true; - moveSegment = tmpSegment.FirstCharIndex < currentChar; - nextCharIndex = currentIndex; - - // Skip moving twice for the same character - if (lastMoveLine) - break; - } - } - - if (moveSegment) { - // Add segment - tmpSegment.Height = baseLinesDistanceScale * fonts[currentFontIndex]->GetHeight(); - tmpSegment.LastCharIndex = Math::Max(tmpSegment.LastCharIndex, tmpSegment.FirstCharIndex); - tmpSegment.FontIndex = currentFontIndex; - tmpLine.Segments.Add(tmpSegment); - - // Reset segment - tmpSegment.Location.X = cursorX; - tmpSegment.FirstCharIndex = nextCharIndex; - tmpSegment.LastCharIndex = nextCharIndex - 1; - - currentFontIndex = nextFontIndex; - } - - // Check if move to another line - if (moveLine) - { - // Add line - tmpLine.Size.X = cursorX; - tmpLine.Size.Y = baseLinesDistanceScale * maxHeight; - tmpLine.MaxAscender = maxAscender; - outputLines.Add(tmpLine); - - // Reset line - tmpLine.Segments.Clear(); - tmpLine.Location.Y += baseLinesDistanceScale * maxHeight; - cursorX = 0; - tmpSegment.Location.X = cursorX; - lastWrapCharIndex = INVALID_INDEX; - lastWrapCharX = 0; - previous.IsValid = false; - - // Reset max font height - maxHeight = 0; - maxAscender = 0; - } - - currentIndex = nextCharIndex; - lastMoveLine = moveLine; - } - - // Check if an additional line should be created - if (text[textLength - 1] == '\n') - { - // Add line - tmpLine.Size.X = cursorX; - tmpLine.Size.Y = baseLinesDistanceScale * maxHeight; - outputLines.Add(tmpLine); - - tmpLine.Location.Y += baseLinesDistanceScale * maxHeight; - } - - // Check amount of lines - if (outputLines.IsEmpty()) - return; - - float totalHeight = tmpLine.Location.Y; - - Float2 offset = Float2::Zero; - if (layout.VerticalAlignment == TextAlignment::Center) - { - offset.Y += (layout.Bounds.GetHeight() - totalHeight) * 0.5f; - } - else if (layout.VerticalAlignment == TextAlignment::Far) - { - offset.Y += layout.Bounds.GetHeight() - totalHeight; - } - for (int32 i = 0; i < outputLines.Count(); i++) - { - MultiFontLineCache& line = outputLines[i]; - Float2 rootPos = line.Location + offset; - - // Fix upper left line corner to match desire text alignment - if (layout.HorizontalAlignment == TextAlignment::Center) - { - rootPos.X += (layout.Bounds.GetWidth() - line.Size.X) * 0.5f; - } - else if (layout.HorizontalAlignment == TextAlignment::Far) - { - rootPos.X += layout.Bounds.GetWidth() - line.Size.X; - } - - line.Location = rootPos; - - // Align all segments to center in case they have different heights - for (int32 j = 0; j < line.Segments.Count(); j++) - { - MultiFontSegmentCache& segment = line.Segments[j]; - segment.Location.Y += (line.MaxAscender - fonts[segment.FontIndex]->GetAscender()) / 2; - } - } -} - Float2 Font::MeasureText(const StringView& text, const TextLayoutOptions& layout) { // Check if there is no need to do anything @@ -643,7 +400,7 @@ Float2 Font::GetCharPosition(const StringView& text, int32 index, const TextLayo ASSERT(lines.HasItems()); float scale = layout.Scale / FontManager::FontScale; float baseLinesDistance = static_cast(_height) * layout.BaseLinesGapScale * scale; - Float2 rootOffset = layout.Bounds.Location + lines.First().Location; + Float2 rootOffset = layout.Bounds.Location; // Find line with that position FontCharacterEntry previous; @@ -682,10 +439,10 @@ Float2 Font::GetCharPosition(const StringView& text, int32 index, const TextLayo } // Position after last character in the last line - return rootOffset + Float2(lines.Last().Size.X, static_cast((lines.Count() - 1) * baseLinesDistance)); + return rootOffset + Float2(lines.Last().Location.X + lines.Last().Size.X, static_cast((lines.Count() - 1) * baseLinesDistance)); } -bool Font::ContainsChar(Char c) +bool Font::ContainsChar(Char c) const { return FT_Get_Char_Index(GetAsset()->GetFTFace(), c) > 0; } diff --git a/Source/Engine/Render2D/Font.h b/Source/Engine/Render2D/Font.h index 0425f2bc5..453872582 100644 --- a/Source/Engine/Render2D/Font.h +++ b/Source/Engine/Render2D/Font.h @@ -8,7 +8,6 @@ #include "Engine/Content/AssetReference.h" #include "Engine/Scripting/ScriptingObject.h" #include "TextLayoutOptions.h" -#include "MultiFont.h" class FontAsset; struct FontTextureAtlasSlot; @@ -341,15 +340,6 @@ public: /// The output lines list. void ProcessText(const StringView& text, Array& outputLines, API_PARAM(Ref) const TextLayoutOptions& layout); - /// - /// Processes text to get cached lines for rendering. - /// - /// The font list. - /// The input text. - /// The layout properties. - /// The output lines list. - static void ProcessText(const Array& fonts, const StringView& text, Array& outputLines, API_PARAM(Ref) const TextLayoutOptions& layout); - /// /// Processes text to get cached lines for rendering. /// @@ -406,17 +396,6 @@ public: /// The minimum size for that text and fot to render properly. API_FUNCTION() Float2 MeasureText(const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout); - /* - /// - /// Measures minimum size of the rectangle that will be needed to draw given text. - /// - /// The fonts to render with. - /// The input text to test. - /// The layout properties. - /// The minimum size for that text and fot to render properly. - API_FUNCTION() static Float2 MeasureText(const Array& fonts, const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout); - */ - /// /// Measures minimum size of the rectangle that will be needed to draw given text. /// @@ -504,18 +483,6 @@ public: /// The character position (upper left corner which can be used for a caret position). API_FUNCTION() Float2 GetCharPosition(const StringView& text, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout); - /* - /// - /// Calculates character position for given text and character index. - /// - /// The fonts to use. - /// The input text to test. - /// The text position to get coordinates of. - /// The text layout properties. - /// The character position (upper left corner which can be used for a caret position). - API_FUNCTION() static Float2 GetCharPosition(const Array& fonts, const StringView& text, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout); - */ - /// /// Calculates character position for given text and character index. /// @@ -557,28 +524,7 @@ public: /// /// The char to test. /// True if the font contains the glyph of the char, otherwise false. - API_FUNCTION() FORCE_INLINE bool ContainsChar(Char c); - - /// - /// Gets the index of the font that should be used to render the char - /// - /// The font list. - /// The char. - /// Number to return if char cannot be found. - /// - API_FUNCTION() FORCE_INLINE static int32 GetCharFontIndex(const Array& fonts, Char c, int32 missing = -1) { - int32 fontIndex = 0; - while (fontIndex < fonts.Count() && !fonts[fontIndex]->ContainsChar(c)) - { - fontIndex++; - } - - if (fontIndex == fonts.Count()) { - return missing; - } - - return fontIndex; - } + API_FUNCTION() FORCE_INLINE bool ContainsChar(Char c) const; /// /// Flushes the size of the face with the Free Type library backend. diff --git a/Source/Engine/Render2D/MultiFont.cpp b/Source/Engine/Render2D/MultiFont.cpp index 9844c12e0..4a510a9f8 100644 --- a/Source/Engine/Render2D/MultiFont.cpp +++ b/Source/Engine/Render2D/MultiFont.cpp @@ -1 +1,431 @@ #include "MultiFont.h" +#include "FontManager.h" +#include "Engine/Core/Math/Math.h" + +MultiFont::MultiFont(const Array& fonts) + : ManagedScriptingObject(SpawnParams(Guid::New(), Font::TypeInitializer)), + _fonts(fonts) +{ + +} + +void MultiFont::ProcessText(const StringView& text, Array& outputLines, API_PARAM(Ref) const TextLayoutOptions& layout) +{ + float cursorX = 0; + int32 kerning; + MultiFontLineCache tmpLine; + MultiFontBlockCache tmpBlock; + FontCharacterEntry entry; + FontCharacterEntry previous; + int32 textLength = text.Length(); + float scale = layout.Scale / FontManager::FontScale; + float boundsWidth = layout.Bounds.GetWidth(); + float baseLinesDistanceScale = layout.BaseLinesGapScale * scale; + + tmpBlock.Location = Float2::Zero; + tmpBlock.Size = Float2::Zero; + tmpBlock.FirstCharIndex = 0; + tmpBlock.LastCharIndex = -1; + + tmpLine.Location = Float2::Zero; + tmpLine.Size = Float2::Zero; + tmpLine.Blocks = Array(); + + if (textLength == 0) { + return; + } + + int32 lastWrapCharIndex = INVALID_INDEX; + float lastWrapCharX = 0; + bool lastMoveLine = false; + // The index of the font used by the current block + int32 currentFontIndex = GetCharFontIndex(text[0], 0); + // The maximum font height of the current line + float maxHeight = 0; + float maxAscender = 0; + float lastCursorX = 0; + + // Process each character to split text into single lines + for (int32 currentIndex = 0; currentIndex < textLength;) + { + bool moveLine = false; + bool moveBlock = false; + float xAdvance = 0; + int32 nextCharIndex = currentIndex + 1; + + // Submit line and block if text ends + if (nextCharIndex == textLength) { + moveLine = moveBlock = true; + } + + // Cache current character + const Char currentChar = text[currentIndex]; + const bool isWhitespace = StringUtils::IsWhitespace(currentChar); + + // Check if character can wrap words + const bool isWrapChar = !StringUtils::IsAlnum(currentChar) || isWhitespace || StringUtils::IsUpper(currentChar) || (currentChar >= 0x3040 && currentChar <= 0x9FFF); + if (isWrapChar && currentIndex != 0) + { + lastWrapCharIndex = currentIndex; + lastWrapCharX = cursorX; + } + + int32 nextFontIndex = currentFontIndex; + // Check if it's a newline character + if (currentChar == '\n') + { + // Break line + moveLine = moveBlock = true; + tmpBlock.LastCharIndex++; + } + else + { + // Get character entry + if (nextCharIndex < textLength) { + nextFontIndex = GetCharFontIndex(text[nextCharIndex], currentFontIndex); + } + + // Get character entry + _fonts[currentFontIndex]->GetCharacter(currentChar, entry); + maxHeight = Math::Max(maxHeight, static_cast(_fonts[currentFontIndex]->GetHeight())); + maxAscender = Math::Max(maxAscender, static_cast(_fonts[currentFontIndex]->GetAscender())); + + // Move block if the font changes or text ends + if (nextFontIndex != currentFontIndex || nextCharIndex == textLength) { + moveBlock = true; + } + + // Get kerning, only when the font hasn't changed + if (!isWhitespace && previous.IsValid && !moveBlock) + { + kerning = _fonts[currentFontIndex]->GetKerning(previous.Character, entry.Character); + } + else + { + kerning = 0; + } + previous = entry; + xAdvance = (kerning + entry.AdvanceX) * scale; + + // Check if character fits the line or skip wrapping + if (cursorX + xAdvance <= boundsWidth || layout.TextWrapping == TextWrapping::NoWrap) + { + // Move character + cursorX += xAdvance; + tmpBlock.LastCharIndex++; + } + else if (layout.TextWrapping == TextWrapping::WrapWords) + { + if (lastWrapCharIndex != INVALID_INDEX) + { + // Skip moving twice for the same character + int32 lastLineLastCharIndex = outputLines.HasItems() && outputLines.Last().Blocks.HasItems() ? outputLines.Last().Blocks.Last().LastCharIndex : -10000; + if (lastLineLastCharIndex == lastWrapCharIndex || lastLineLastCharIndex == lastWrapCharIndex - 1 || lastLineLastCharIndex == lastWrapCharIndex - 2) + { + currentIndex = nextCharIndex; + lastMoveLine = moveLine; + continue; + } + + // Move line + const Char wrapChar = text[lastWrapCharIndex]; + moveLine = true; + moveBlock = tmpBlock.FirstCharIndex < lastWrapCharIndex; + + cursorX = lastWrapCharX; + if (StringUtils::IsWhitespace(wrapChar)) + { + // Skip whitespaces + tmpBlock.LastCharIndex = lastWrapCharIndex - 1; + nextCharIndex = currentIndex = lastWrapCharIndex + 1; + } + else + { + tmpBlock.LastCharIndex = lastWrapCharIndex - 1; + nextCharIndex = currentIndex = lastWrapCharIndex; + } + } + } + else if (layout.TextWrapping == TextWrapping::WrapChars) + { + // Move line + moveLine = true; + moveBlock = tmpBlock.FirstCharIndex < currentChar; + nextCharIndex = currentIndex; + + // Skip moving twice for the same character + if (lastMoveLine) + break; + } + } + + if (moveBlock) { + // Add block + tmpBlock.Size.X = lastCursorX - cursorX; + tmpBlock.Size.Y = baseLinesDistanceScale * _fonts[currentFontIndex]->GetHeight(); + tmpBlock.LastCharIndex = Math::Max(tmpBlock.LastCharIndex, tmpBlock.FirstCharIndex); + tmpBlock.FontIndex = currentFontIndex; + tmpLine.Blocks.Add(tmpBlock); + + // Reset block + tmpBlock.Location.X = cursorX; + tmpBlock.FirstCharIndex = nextCharIndex; + tmpBlock.LastCharIndex = nextCharIndex - 1; + + currentFontIndex = nextFontIndex; + lastCursorX = cursorX; + } + + // Check if move to another line + if (moveLine) + { + // Add line + tmpLine.Size.X = cursorX; + tmpLine.Size.Y = baseLinesDistanceScale * maxHeight; + tmpLine.MaxAscender = maxAscender; + outputLines.Add(tmpLine); + + // Reset line + tmpLine.Blocks.Clear(); + tmpLine.Location.Y += baseLinesDistanceScale * maxHeight; + cursorX = 0; + tmpBlock.Location.X = cursorX; + lastWrapCharIndex = INVALID_INDEX; + lastWrapCharX = 0; + previous.IsValid = false; + + // Reset max font height + maxHeight = 0; + maxAscender = 0; + lastCursorX = 0; + } + + currentIndex = nextCharIndex; + lastMoveLine = moveLine; + } + + // Check if an additional line should be created + if (text[textLength - 1] == '\n') + { + // Add line + tmpLine.Size.X = cursorX; + tmpLine.Size.Y = baseLinesDistanceScale * maxHeight; + outputLines.Add(tmpLine); + + tmpLine.Location.Y += baseLinesDistanceScale * maxHeight; + } + + // Check amount of lines + if (outputLines.IsEmpty()) + return; + + float totalHeight = tmpLine.Location.Y; + + Float2 offset = Float2::Zero; + if (layout.VerticalAlignment == TextAlignment::Center) + { + offset.Y += (layout.Bounds.GetHeight() - totalHeight) * 0.5f; + } + else if (layout.VerticalAlignment == TextAlignment::Far) + { + offset.Y += layout.Bounds.GetHeight() - totalHeight; + } + for (int32 i = 0; i < outputLines.Count(); i++) + { + MultiFontLineCache& line = outputLines[i]; + Float2 rootPos = line.Location + offset; + + // Fix upper left line corner to match desire text alignment + if (layout.HorizontalAlignment == TextAlignment::Center) + { + rootPos.X += (layout.Bounds.GetWidth() - line.Size.X) * 0.5f; + } + else if (layout.HorizontalAlignment == TextAlignment::Far) + { + rootPos.X += layout.Bounds.GetWidth() - line.Size.X; + } + + line.Location = rootPos; + + // Align all blocks to center in case they have different heights + for (int32 j = 0; j < line.Blocks.Count(); j++) + { + MultiFontBlockCache& block = line.Blocks[j]; + block.Location.Y += (line.MaxAscender - _fonts[block.FontIndex]->GetAscender()) / 2; + } + } +} + +Float2 MultiFont::GetCharPosition(const StringView& text, int32 index, const TextLayoutOptions& layout) +{ + // Check if there is no need to do anything + if (text.IsEmpty()) + return layout.Bounds.Location; + + // Process text + Array lines; + ProcessText(text, lines, layout); + ASSERT(lines.HasItems()); + float scale = layout.Scale / FontManager::FontScale; + float baseLinesDistance = layout.BaseLinesGapScale * scale; + Float2 rootOffset = layout.Bounds.Location; + + // Find line with that position + FontCharacterEntry previous; + FontCharacterEntry entry; + for (int32 lineIndex = 0; lineIndex < lines.Count(); lineIndex++) + { + const MultiFontLineCache& line = lines[lineIndex]; + for (int32 blockIndex = 0; blockIndex < line.Blocks.Count(); blockIndex++) + { + const MultiFontBlockCache& block = line.Blocks[blockIndex]; + // Check if desire position is somewhere inside characters in line range + if (Math::IsInRange(index, block.FirstCharIndex, block.LastCharIndex)) + { + float x = line.Location.X + block.Location.X; + float y = line.Location.Y + block.Location.Y; + + // Check all characters in the line + for (int32 currentIndex = block.FirstCharIndex; currentIndex < index; currentIndex++) + { + // Cache current character + const Char currentChar = text[currentIndex]; + _fonts[block.FontIndex]->GetCharacter(currentChar, entry); + const bool isWhitespace = StringUtils::IsWhitespace(currentChar); + + // Apply kerning + if (!isWhitespace && previous.IsValid) + { + x += _fonts[block.FontIndex]->GetKerning(previous.Character, entry.Character); + } + previous = entry; + + // Move + x += entry.AdvanceX * scale; + } + + // Upper left corner of the character + return rootOffset + Float2(x, y); + } + } + } + + // Position after last character in the last line + return rootOffset + Float2(lines.Last().Location.X + lines.Last().Size.X, lines.Last().Location.Y); +} + +int32 MultiFont::HitTestText(const StringView& text, const Float2& location, const TextLayoutOptions& layout) +{ + // Check if there is no need to do anything + if (text.Length() <= 0) + return 0; + + // Process text + Array lines; + ProcessText(text, lines, layout); + ASSERT(lines.HasItems()); + float scale = layout.Scale / FontManager::FontScale; + + // Offset position to match lines origin space + Float2 rootOffset = layout.Bounds.Location + lines.First().Location; + Float2 testPoint = location - rootOffset; + + // Get block which may intersect with the position (it's possible because lines have fixed height) + int32 lineIndex = 0; + while (lineIndex < lines.Count()) + { + if (lines[lineIndex].Location.Y + lines[lineIndex].Size.Y >= location.Y) { + break; + } + + lineIndex++; + } + lineIndex = Math::Clamp(lineIndex, 0, lines.Count() - 1); + const MultiFontLineCache& line = lines[lineIndex]; + + int32 blockIndex = 0; + while (blockIndex < line.Blocks.Count() - 1) + { + if (line.Location.X + line.Blocks[blockIndex + 1].Location.X >= location.X) { + break; + } + + blockIndex++; + } + const MultiFontBlockCache& block = line.Blocks[blockIndex]; + float x = line.Location.X; + + // Check all characters in the line to find hit point + FontCharacterEntry previous; + FontCharacterEntry entry; + int32 smallestIndex = INVALID_INDEX; + float dst, smallestDst = MAX_float; + for (int32 currentIndex = block.FirstCharIndex; currentIndex <= block.LastCharIndex; currentIndex++) + { + // Cache current character + const Char currentChar = text[currentIndex]; + + _fonts[block.FontIndex]->GetCharacter(currentChar, entry); + const bool isWhitespace = StringUtils::IsWhitespace(currentChar); + + // Apply kerning + if (!isWhitespace && previous.IsValid) + { + x += _fonts[block.FontIndex]->GetKerning(previous.Character, entry.Character); + } + previous = entry; + + // Test + dst = Math::Abs(testPoint.X - x); + if (dst < smallestDst) + { + // Found closer character + smallestIndex = currentIndex; + smallestDst = dst; + } + else if (dst > smallestDst) + { + // Current char is worse so return the best result + return smallestIndex; + } + + // Move + x += entry.AdvanceX * scale; + } + + // Test line end edge + dst = Math::Abs(testPoint.X - x); + if (dst < smallestDst) + { + // Pointer is behind the last character in the line + smallestIndex = block.LastCharIndex; + + // Fix for last line + if (lineIndex == lines.Count() - 1) + smallestIndex++; + } + + return smallestIndex; +} + +Float2 MultiFont::MeasureText(const StringView& text, const TextLayoutOptions& layout) +{ + // Check if there is no need to do anything + if (text.IsEmpty()) + return Float2::Zero; + + // Process text + Array lines; + ProcessText(text, lines, layout); + + // Calculate bounds + Float2 max = Float2::Zero; + for (int32 i = 0; i < lines.Count(); i++) + { + const MultiFontLineCache& line = lines[i]; + max = Float2::Max(max, line.Location + line.Size); + } + + return max; +} + diff --git a/Source/Engine/Render2D/MultiFont.h b/Source/Engine/Render2D/MultiFont.h index b1da832d5..870b8e668 100644 --- a/Source/Engine/Render2D/MultiFont.h +++ b/Source/Engine/Render2D/MultiFont.h @@ -3,23 +3,28 @@ #include "Engine/Core/Collections/Array.h" #include "Engine/Core/Collections/Dictionary.h" #include "Font.h" +#include "FontAsset.h" + +struct TextRange; +class Font; +class FontAsset; /// -/// The font segment info generated during text processing. +/// The font block info generated during text processing. /// -API_STRUCT(NoDefault) struct MultiFontSegmentCache +API_STRUCT(NoDefault) struct MultiFontBlockCache { - DECLARE_SCRIPTING_TYPE_MINIMAL(MultiFontSegmentCache); + DECLARE_SCRIPTING_TYPE_MINIMAL(MultiFontBlockCache); /// - /// The root position of the segment (upper left corner), relative to line. + /// The root position of the block (upper left corner), relative to line. /// API_FIELD() Float2 Location; /// - /// The height of the current segment + /// The height of the current block /// - API_FIELD() float Height; + API_FIELD() Float2 Size; /// /// The first character index (from the input text). @@ -38,13 +43,13 @@ API_STRUCT(NoDefault) struct MultiFontSegmentCache }; template<> -struct TIsPODType +struct TIsPODType { enum { Value = true }; }; /// -/// Line of font segments info generated during text processing. +/// Line of font blocks info generated during text processing. /// API_STRUCT(NoDefault) struct MultiFontLineCache { @@ -61,17 +66,288 @@ API_STRUCT(NoDefault) struct MultiFontLineCache API_FIELD() Float2 Size; /// - /// The maximum ascendent of the line. + /// The maximum ascender of the line. /// API_FIELD() float MaxAscender; /// /// The index of the font to render with /// - API_FIELD() Array Segments; + API_FIELD() Array Blocks; }; API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API MultiFont : public ManagedScriptingObject { DECLARE_SCRIPTING_TYPE_NO_SPAWN(MultiFont); +private: + Array _fonts; + +public: + MultiFont(const Array& fonts); + + API_FUNCTION() FORCE_INLINE static MultiFont* Create(const Array& fonts) { + return New(fonts); + } + + API_FUNCTION() FORCE_INLINE static MultiFont* Create(const Array& fontAssets, float size) { + Array fonts; + fonts.Resize(fontAssets.Count()); + for (int32 i = 0; i < fontAssets.Count(); i++) + { + fonts[i] = fontAssets[i]->CreateFont(size); + } + + return New(fonts); + } + + API_PROPERTY() FORCE_INLINE Array& GetFonts() { + return _fonts; + } + + API_PROPERTY() FORCE_INLINE void SetFonts(const Array& val) { + _fonts = val; + } + + API_PROPERTY() FORCE_INLINE int32 GetMaxHeight() { + int32 maxHeight = 0; + for (int32 i = 0; i < _fonts.Count(); i++) + { + if (_fonts[i]) { + maxHeight = Math::Max(maxHeight, _fonts[i]->GetHeight()); + } + } + + return maxHeight; + } + + API_PROPERTY() FORCE_INLINE int32 GetMaxAscender() { + int32 maxAsc = 0; + for (int32 i = 0; i < _fonts.Count(); i++) + { + if (_fonts[i]) { + maxAsc = Math::Max(maxAsc, _fonts[i]->GetAscender()); + } + } + + return maxAsc; + } + + /// + /// Processes text to get cached lines for rendering. + /// + /// The input text. + /// The layout properties. + /// The output lines list. + void ProcessText(const StringView& text, Array& outputLines, API_PARAM(Ref) const TextLayoutOptions& layout); + + /// + /// Processes text to get cached lines for rendering. + /// + /// The input text. + /// The layout properties. + /// The output lines list. + API_FUNCTION() Array ProcessText(const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout) + { + Array lines; + ProcessText(text, lines, layout); + return lines; + } + + /// + /// Processes text to get cached lines for rendering. + /// + /// The input text. + /// The input text range (substring range of the input text parameter). + /// The layout properties. + /// The output lines list. + API_FUNCTION() Array ProcessText(const StringView& text, API_PARAM(Ref) const TextRange& textRange, API_PARAM(Ref) const TextLayoutOptions& layout) + { + Array lines; + ProcessText(textRange.Substring(text), lines, layout); + return lines; + } + + /// + /// Processes text to get cached lines for rendering. + /// + /// The input text. + /// The output lines list. + API_FUNCTION() FORCE_INLINE Array ProcessText(const StringView& text) + { + return ProcessText(text, TextLayoutOptions()); + } + + /// + /// Processes text to get cached lines for rendering. + /// + /// The input text. + /// The input text range (substring range of the input text parameter). + /// The output lines list. + API_FUNCTION() FORCE_INLINE Array ProcessText(const StringView& text, API_PARAM(Ref) const TextRange& textRange) + { + return ProcessText(textRange.Substring(text), TextLayoutOptions()); + } + + /// + /// Measures minimum size of the rectangle that will be needed to draw given text. + /// + /// The input text to test. + /// The layout properties. + /// The minimum size for that text and fot to render properly. + API_FUNCTION() Float2 MeasureText(const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout); + + /// + /// Measures minimum size of the rectangle that will be needed to draw given text. + /// + /// The input text to test. + /// The input text range (substring range of the input text parameter). + /// The layout properties. + /// The minimum size for that text and fot to render properly. + API_FUNCTION() Float2 MeasureText(const StringView& text, API_PARAM(Ref) const TextRange& textRange, API_PARAM(Ref) const TextLayoutOptions& layout) + { + return MeasureText(textRange.Substring(text), layout); + } + + /// + /// Measures minimum size of the rectangle that will be needed to draw given text + /// . + /// The input text to test. + /// The minimum size for that text and fot to render properly. + API_FUNCTION() FORCE_INLINE Float2 MeasureText(const StringView& text) + { + return MeasureText(text, TextLayoutOptions()); + } + + /// + /// Measures minimum size of the rectangle that will be needed to draw given text + /// . + /// The input text to test. + /// The input text range (substring range of the input text parameter). + /// The minimum size for that text and fot to render properly. + API_FUNCTION() FORCE_INLINE Float2 MeasureText(const StringView& text, API_PARAM(Ref) const TextRange& textRange) + { + return MeasureText(textRange.Substring(text), TextLayoutOptions()); + } + + /// + /// Calculates hit character index at given location. + /// + /// The input text to test. + /// The input text range (substring range of the input text parameter). + /// The input location to test. + /// The text layout properties. + /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). + API_FUNCTION() int32 HitTestText(const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Float2& location, API_PARAM(Ref) const TextLayoutOptions& layout) + { + return HitTestText(textRange.Substring(text), location, layout); + } + + /// + /// Calculates hit character index at given location. + /// + /// The input text to test. + /// The input location to test. + /// The text layout properties. + /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). + API_FUNCTION() int32 HitTestText(const StringView& text, const Float2& location, API_PARAM(Ref) const TextLayoutOptions& layout); + + /// + /// Calculates hit character index at given location. + /// + /// The input text to test. + /// The input location to test. + /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). + API_FUNCTION() FORCE_INLINE int32 HitTestText(const StringView& text, const Float2& location) + { + return HitTestText(text, location, TextLayoutOptions()); + } + + /// + /// Calculates hit character index at given location. + /// + /// The input text to test. + /// The input text range (substring range of the input text parameter). + /// The input location to test. + /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). + API_FUNCTION() FORCE_INLINE int32 HitTestText(const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Float2& location) + { + return HitTestText(textRange.Substring(text), location, TextLayoutOptions()); + } + + /// + /// Calculates character position for given text and character index. + /// + /// The input text to test. + /// The text position to get coordinates of. + /// The text layout properties. + /// The character position (upper left corner which can be used for a caret position). + API_FUNCTION() Float2 GetCharPosition(const StringView& text, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout); + + /// + /// Calculates character position for given text and character index. + /// + /// The input text to test. + /// The input text range (substring range of the input text parameter). + /// The text position to get coordinates of. + /// The text layout properties. + /// The character position (upper left corner which can be used for a caret position). + API_FUNCTION() Float2 GetCharPosition(const StringView& text, API_PARAM(Ref) const TextRange& textRange, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout) + { + return GetCharPosition(textRange.Substring(text), index, layout); + } + + /// + /// Calculates character position for given text and character index + /// + /// The input text to test. + /// The text position to get coordinates of. + /// The character position (upper left corner which can be used for a caret position). + API_FUNCTION() FORCE_INLINE Float2 GetCharPosition(const StringView& text, int32 index) + { + return GetCharPosition(text, index, TextLayoutOptions()); + } + + /// + /// Calculates character position for given text and character index + /// + /// The input text to test. + /// The input text range (substring range of the input text parameter). + /// The text position to get coordinates of. + /// The character position (upper left corner which can be used for a caret position). + API_FUNCTION() FORCE_INLINE Float2 GetCharPosition(const StringView& text, API_PARAM(Ref) const TextRange& textRange, int32 index) + { + return GetCharPosition(textRange.Substring(text), index, TextLayoutOptions()); + } + + /// + /// Gets the index of the font that should be used to render the char + /// + /// The font list. + /// The char. + /// Number to return if char cannot be found. + /// + API_FUNCTION() FORCE_INLINE int32 GetCharFontIndex(Char c, int32 missing = -1) { + int32 fontIndex = 0; + while (fontIndex < _fonts.Count() && _fonts[fontIndex] && !_fonts[fontIndex]->ContainsChar(c)) + { + fontIndex++; + } + + if (fontIndex == _fonts.Count()) { + return missing; + } + + return fontIndex; + } + + API_FUNCTION() FORCE_INLINE bool Verify() { + for (int32 i = 0; i < _fonts.Count(); i++) + { + if (!_fonts[i]) { + return false; + } + } + + return true; + } }; diff --git a/Source/Engine/Render2D/MultiFontReference.cs b/Source/Engine/Render2D/MultiFontReference.cs new file mode 100644 index 000000000..dd3f4d179 --- /dev/null +++ b/Source/Engine/Render2D/MultiFontReference.cs @@ -0,0 +1,74 @@ + + +using System.Collections.Generic; +using System.Linq; + +namespace FlaxEngine +{ + /// + /// Reference to multiple font references + /// + public class MultiFontReference : List + { + public MultiFontReference() + { + _cachedFont = null; + } + + public MultiFontReference(IEnumerable other) + { + AddRange(other); + _cachedFont = null; + } + + public MultiFontReference(MultiFontReference other) + { + AddRange(other); + _cachedFont = other._cachedFont; + } + + public MultiFontReference(MultiFontReference other, float size) + { + AddRange(other.Select(x => new FontReference(x) { Size = size })); + _cachedFont = null; + } + + public MultiFontReference(MultiFont other) + { + AddRange(other.Fonts.Select(x => new FontReference(x))); + _cachedFont = other; + } + + public MultiFontReference(FontAsset[] assets, float size) + { + AddRange(assets.Select(x => new FontReference(x, size))); + _cachedFont = null; + } + + [EditorOrder(0), Tooltip("The font asset to use as characters source.")] + public MultiFont GetMultiFont() + { + if (_cachedFont) + return _cachedFont; + var fontList = this.Where(x => x.Font).Select(x => x.GetFont()).ToArray(); + _cachedFont = MultiFont.Create(fontList); + return _cachedFont; + } + + public bool Verify() + { + foreach (var i in this) + { + if (!i.Font) + { + return false; + } + } + + return true; + } + + [NoSerialize] + private MultiFont _cachedFont; + } +} diff --git a/Source/Engine/Render2D/Render2D.cpp b/Source/Engine/Render2D/Render2D.cpp index 8a39d29be..c68c2445d 100644 --- a/Source/Engine/Render2D/Render2D.cpp +++ b/Source/Engine/Render2D/Render2D.cpp @@ -1370,10 +1370,11 @@ void Render2D::DrawText(Font* font, const StringView& text, const TextRange& tex DrawText(font, textRange.Substring(text), color, layout, customMaterial); } -void Render2D::DrawText(const Array& fonts, const StringView& text, const Color& color, const Float2& location, MaterialBase* customMaterial) +void Render2D::DrawText(MultiFont* multiFont, const StringView& text, const Color& color, const Float2& location, MaterialBase* customMaterial) { RENDER2D_CHECK_RENDERING_STATE; + const Array& fonts = multiFont->GetFonts(); // Check if there is no need to do anything if (fonts.IsEmpty() || text.Length() < 0) return; @@ -1408,7 +1409,7 @@ void Render2D::DrawText(const Array& fonts, const StringView& text, const for (int32 currentIndex = 0; currentIndex < text.Length(); currentIndex++) { if (text[currentIndex] != '\n') { - int32 fontIndex = Font::GetCharFontIndex(fonts, text[currentIndex], 0); + int32 fontIndex = multiFont->GetCharFontIndex(text[currentIndex], 0); maxAscenders[lineIndex] = Math::Max(maxAscenders[lineIndex], static_cast(fonts[fontIndex]->GetAscender())); } else { @@ -1418,12 +1419,12 @@ void Render2D::DrawText(const Array& fonts, const StringView& text, const } lineIndex = 0; - // The following code cut the text into segments, according to the font used to render + // The following code cut the text into blocks, according to the font used to render Float2 pointer = location; - // The starting index of the current segment + // The starting index of the current block int32 startIndex = 0; - // The index of the font used by the current segment - int32 currentFontIndex = Font::GetCharFontIndex(fonts, text[0], 0); + // The index of the font used by the current block + int32 currentFontIndex = multiFont->GetCharFontIndex(text[0], 0); // The maximum font height of the current line float maxHeight = 0; for (int32 currentIndex = 0; currentIndex < text.Length(); currentIndex++) @@ -1431,13 +1432,13 @@ void Render2D::DrawText(const Array& fonts, const StringView& text, const // Cache current character const Char currentChar = text[currentIndex]; int32 nextCharIndex = currentIndex + 1; - bool moveSegment = false; + bool moveBlock = false; bool moveLine = false; int32 nextFontIndex = currentFontIndex; - // Submit segment if text ends + // Submit block if text ends if (nextCharIndex == text.Length()) { - moveSegment = true; + moveBlock = true; } // Check if it isn't a newline character @@ -1445,21 +1446,21 @@ void Render2D::DrawText(const Array& fonts, const StringView& text, const { // Get character entry if (nextCharIndex < text.Length()) { - nextFontIndex = Font::GetCharFontIndex(fonts, text[nextCharIndex], currentFontIndex); + nextFontIndex = multiFont->GetCharFontIndex(text[nextCharIndex], currentFontIndex); } if (nextFontIndex != currentFontIndex) { - moveSegment = true; + moveBlock = true; } } else { // Move - moveLine = moveSegment = true; + moveLine = moveBlock = true; } - if (moveSegment) { - // Render the pending segment before beginning the new segment + if (moveBlock) { + // Render the pending block before beginning the new block auto fontHeight = fonts[currentFontIndex]->GetHeight(); maxHeight = Math::Max(maxHeight, static_cast(fontHeight)); auto fontDescender = fonts[currentFontIndex]->GetDescender(); @@ -1533,22 +1534,23 @@ void Render2D::DrawText(const Array& fonts, const StringView& text, const lineIndex++; } - // Start new segment + // Start new block startIndex = nextCharIndex; currentFontIndex = nextFontIndex; } } } -void Render2D::DrawText(const Array& fonts, const StringView& text, const TextRange& textRange, const Color& color, const Float2& location, MaterialBase* customMaterial) +void Render2D::DrawText(MultiFont* multiFont, const StringView& text, const TextRange& textRange, const Color& color, const Float2& location, MaterialBase* customMaterial) { - DrawText(fonts, textRange.Substring(text), color, location, customMaterial); + DrawText(multiFont, textRange.Substring(text), color, location, customMaterial); } -void Render2D::DrawText(const Array& fonts, const StringView& text, const Color& color, const TextLayoutOptions& layout, MaterialBase* customMaterial) +void Render2D::DrawText(MultiFont* multiFont, const StringView& text, const Color& color, const TextLayoutOptions& layout, MaterialBase* customMaterial) { RENDER2D_CHECK_RENDERING_STATE; + const Array& fonts = multiFont->GetFonts(); // Check if there is no need to do anything if (fonts.IsEmpty() || text.IsEmpty() || layout.Scale <= ZeroTolerance) return; @@ -1563,7 +1565,7 @@ void Render2D::DrawText(const Array& fonts, const StringView& text, const // Process text to get lines MultiFontLines.Clear(); - Font::ProcessText(fonts, text, MultiFontLines, layout); + multiFont->ProcessText(text, MultiFontLines, layout); // Render all lines FontCharacterEntry entry; @@ -1582,14 +1584,14 @@ void Render2D::DrawText(const Array& fonts, const StringView& text, const for (int32 lineIndex = 0; lineIndex < MultiFontLines.Count(); lineIndex++) { const MultiFontLineCache& line = MultiFontLines[lineIndex]; - for (int32 segmentIndex = 0; segmentIndex < line.Segments.Count(); segmentIndex++) + for (int32 blockIndex = 0; blockIndex < line.Blocks.Count(); blockIndex++) { - const MultiFontSegmentCache& segment = MultiFontLines[lineIndex].Segments[segmentIndex]; - auto fontHeight = fonts[segment.FontIndex]->GetHeight(); - auto fontDescender = fonts[segment.FontIndex]->GetDescender(); - Float2 pointer = line.Location + segment.Location; + const MultiFontBlockCache& block = MultiFontLines[lineIndex].Blocks[blockIndex]; + auto fontHeight = fonts[block.FontIndex]->GetHeight(); + auto fontDescender = fonts[block.FontIndex]->GetDescender(); + Float2 pointer = line.Location + block.Location; - for (int32 charIndex = segment.FirstCharIndex; charIndex <= segment.LastCharIndex; charIndex++) + for (int32 charIndex = block.FirstCharIndex; charIndex <= block.LastCharIndex; charIndex++) { Char c = text[charIndex]; if (c == '\n') @@ -1598,7 +1600,7 @@ void Render2D::DrawText(const Array& fonts, const StringView& text, const } // Get character entry - fonts[segment.FontIndex]->GetCharacter(c, entry); + fonts[block.FontIndex]->GetCharacter(c, entry); // Check if need to select/change font atlas (since characters even in the same font may be located in different atlases) if (fontAtlas == nullptr || entry.TextureIndex != fontAtlasIndex) @@ -1623,7 +1625,7 @@ void Render2D::DrawText(const Array& fonts, const StringView& text, const const bool isWhitespace = StringUtils::IsWhitespace(c); if (!isWhitespace && previous.IsValid) { - kerning = fonts[segment.FontIndex]->GetKerning(previous.Character, entry.Character); + kerning = fonts[block.FontIndex]->GetKerning(previous.Character, entry.Character); } else { @@ -1659,9 +1661,9 @@ void Render2D::DrawText(const Array& fonts, const StringView& text, const } } -void Render2D::DrawText(const Array& fonts, const StringView& text, const TextRange& textRange, const Color& color, const TextLayoutOptions& layout, MaterialBase* customMaterial) +void Render2D::DrawText(MultiFont* multiFont, const StringView& text, const TextRange& textRange, const Color& color, const TextLayoutOptions& layout, MaterialBase* customMaterial) { - DrawText(fonts, textRange.Substring(text), color, layout, customMaterial); + DrawText(multiFont, textRange.Substring(text), color, layout, customMaterial); } FORCE_INLINE bool NeedAlphaWithTint(const Color& color) @@ -2165,22 +2167,22 @@ void Render2D::DrawBezier(const Float2& p1, const Float2& p2, const Float2& p3, { RENDER2D_CHECK_RENDERING_STATE; - // Find amount of segments to use + // Find amount of blocks to use const Float2 d1 = p2 - p1; const Float2 d2 = p3 - p2; const Float2 d3 = p4 - p3; const float len = d1.Length() + d2.Length() + d3.Length(); - const int32 segmentCount = Math::Clamp(Math::CeilToInt(len * 0.05f), 1, 100); - const float segmentCountInv = 1.0f / segmentCount; + const int32 blockCount = Math::Clamp(Math::CeilToInt(len * 0.05f), 1, 100); + const float blockCountInv = 1.0f / blockCount; - // Draw segmented curve + // Draw blocked curve Float2 p; AnimationUtils::Bezier(p1, p2, p3, p4, 0, p); Lines2.Clear(); Lines2.Add(p); - for (int32 i = 1; i <= segmentCount; i++) + for (int32 i = 1; i <= blockCount; i++) { - const float t = i * segmentCountInv; + const float t = i * blockCountInv; AnimationUtils::Bezier(p1, p2, p3, p4, t, p); Lines2.Add(p); } diff --git a/Source/Engine/Render2D/Render2D.cs b/Source/Engine/Render2D/Render2D.cs index a73556f7b..cad8380ab 100644 --- a/Source/Engine/Render2D/Render2D.cs +++ b/Source/Engine/Render2D/Render2D.cs @@ -164,7 +164,7 @@ namespace FlaxEngine /// Describes how wrap text inside a layout rectangle. /// The scale for distance one baseline from another. Default is 1. /// The text drawing scale. Default is 1. - public static void DrawText(Font[] fonts, string text, Rectangle layoutRect, Color color, TextAlignment horizontalAlignment = TextAlignment.Near, TextAlignment verticalAlignment = TextAlignment.Near, TextWrapping textWrapping = TextWrapping.NoWrap, float baseLinesGapScale = 1.0f, float scale = 1.0f) + public static void DrawText(MultiFont fonts, string text, Rectangle layoutRect, Color color, TextAlignment horizontalAlignment = TextAlignment.Near, TextAlignment verticalAlignment = TextAlignment.Near, TextWrapping textWrapping = TextWrapping.NoWrap, float baseLinesGapScale = 1.0f, float scale = 1.0f) { var layout = new TextLayoutOptions { @@ -175,6 +175,8 @@ namespace FlaxEngine Scale = scale, BaseLinesGapScale = baseLinesGapScale, }; + + DrawText(fonts, text, color, ref layout); } @@ -191,7 +193,7 @@ namespace FlaxEngine /// Describes how wrap text inside a layout rectangle. /// The scale for distance one baseline from another. Default is 1. /// The text drawing scale. Default is 1. - public static void DrawText(Font[] fonts, MaterialBase customMaterial, string text, Rectangle layoutRect, Color color, TextAlignment horizontalAlignment = TextAlignment.Near, TextAlignment verticalAlignment = TextAlignment.Near, TextWrapping textWrapping = TextWrapping.NoWrap, float baseLinesGapScale = 1.0f, float scale = 1.0f) + public static void DrawText(MultiFont fonts, MaterialBase customMaterial, string text, Rectangle layoutRect, Color color, TextAlignment horizontalAlignment = TextAlignment.Near, TextAlignment verticalAlignment = TextAlignment.Near, TextWrapping textWrapping = TextWrapping.NoWrap, float baseLinesGapScale = 1.0f, float scale = 1.0f) { var layout = new TextLayoutOptions { @@ -202,6 +204,7 @@ namespace FlaxEngine Scale = scale, BaseLinesGapScale = baseLinesGapScale, }; + DrawText(fonts, text, color, ref layout, customMaterial); } diff --git a/Source/Engine/Render2D/Render2D.h b/Source/Engine/Render2D/Render2D.h index 0ec88edc0..5050171b2 100644 --- a/Source/Engine/Render2D/Render2D.h +++ b/Source/Engine/Render2D/Render2D.h @@ -15,6 +15,7 @@ struct Matrix3x3; struct Viewport; struct TextRange; class Font; +class MultiFont; class GPUPipelineState; class GPUTexture; class GPUTextureView; @@ -224,7 +225,7 @@ public: /// The text color. /// The text location. /// The custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture. - API_FUNCTION() static void DrawText(const Array& fonts, const StringView& text, const Color& color, const Float2& location, MaterialBase* customMaterial = nullptr); + API_FUNCTION() static void DrawText(MultiFont* multiFont, const StringView& text, const Color& color, const Float2& location, MaterialBase* customMaterial = nullptr); /// /// Draws a text with formatting. @@ -234,7 +235,7 @@ public: /// The text color. /// The text layout properties. /// The custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture. - API_FUNCTION() static void DrawText(const Array& fonts, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Color& color, const Float2& location, MaterialBase* customMaterial = nullptr); + API_FUNCTION() static void DrawText(MultiFont* multiFont, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Color& color, const Float2& location, MaterialBase* customMaterial = nullptr); /// /// Draws a text with formatting. @@ -245,7 +246,7 @@ public: /// The text color. /// The text layout properties. /// The custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture. - API_FUNCTION() static void DrawText(const Array& fonts, const StringView& text, const Color& color, API_PARAM(Ref) const TextLayoutOptions& layout, MaterialBase* customMaterial = nullptr); + API_FUNCTION() static void DrawText(MultiFont* multiFont, const StringView& text, const Color& color, API_PARAM(Ref) const TextLayoutOptions& layout, MaterialBase* customMaterial = nullptr); /// /// Draws a text with formatting. @@ -256,7 +257,7 @@ public: /// The text color. /// The text layout properties. /// The custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture. - API_FUNCTION() static void DrawText(const Array& fonts, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Color& color, API_PARAM(Ref) const TextLayoutOptions& layout, MaterialBase* customMaterial = nullptr); + API_FUNCTION() static void DrawText(MultiFont* multiFont, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Color& color, API_PARAM(Ref) const TextLayoutOptions& layout, MaterialBase* customMaterial = nullptr); /// /// Fills a rectangle area. diff --git a/Source/Engine/Scripting/Scripting.cs b/Source/Engine/Scripting/Scripting.cs index 8e71c9f31..bc800f88a 100644 --- a/Source/Engine/Scripting/Scripting.cs +++ b/Source/Engine/Scripting/Scripting.cs @@ -294,15 +294,12 @@ namespace FlaxEngine style.DragWindow = style.BackgroundSelected * 0.7f; // Use optionally bundled default font (matches Editor) - var defaultFont = Content.LoadAsyncInternal("Editor/Fonts/Roboto-Regular"); - var cjkFont = Content.LoadAsyncInternal("NotoSansSC-Medium"); - if (defaultFont) - { - style.FontTitle = defaultFont.CreateFont(18); - style.FontLarge = defaultFont.CreateFont(14); - style.FontMedium = new Font[] { defaultFont.CreateFont(9), cjkFont.CreateFont(9) }; - style.FontSmall = new Font[] { defaultFont.CreateFont(9), cjkFont.CreateFont(9) }; - } + FontAsset[] defaultFont = [Content.LoadAsyncInternal("Editor/Fonts/Roboto-Regular"), Content.LoadAsyncInternal("Editor/Fonts/NotoSansSC-Regular")]; + + style.FontTitle = new MultiFontReference(defaultFont, 18).GetMultiFont(); + style.FontLarge = new MultiFontReference(defaultFont, 14).GetMultiFont(); + style.FontMedium = new MultiFontReference(defaultFont, 9).GetMultiFont(); + style.FontSmall = new MultiFontReference(defaultFont, 9).GetMultiFont(); Style.Current = style; } diff --git a/Source/Engine/UI/GUI/Common/Button.cs b/Source/Engine/UI/GUI/Common/Button.cs index 1b965b8d1..f21a29191 100644 --- a/Source/Engine/UI/GUI/Common/Button.cs +++ b/Source/Engine/UI/GUI/Common/Button.cs @@ -23,7 +23,7 @@ namespace FlaxEngine.GUI /// /// The font. /// - protected FontReference _font; + protected MultiFontReference _font; /// /// The text. @@ -44,7 +44,7 @@ namespace FlaxEngine.GUI /// Gets or sets the font used to draw button text. /// [EditorDisplay("Text Style"), EditorOrder(2022), ExpandGroups] - public FontReference Font + public MultiFontReference Font { get => _font; set => _font = value; @@ -156,7 +156,7 @@ namespace FlaxEngine.GUI var style = Style.Current; if (style != null) { - _font = new FontReference(style.FontMedium.First()); + _font = new MultiFontReference(style.FontMedium); TextColor = style.Foreground; BackgroundColor = style.BackgroundNormal; BorderColor = style.BorderNormal; @@ -262,7 +262,7 @@ namespace FlaxEngine.GUI Render2D.DrawRectangle(clientRect, borderColor, BorderThickness); // Draw text - Render2D.DrawText(_font?.GetFont(), TextMaterial, _text, clientRect, textColor, TextAlignment.Center, TextAlignment.Center); + Render2D.DrawText(_font?.GetMultiFont(), TextMaterial, _text, clientRect, textColor, TextAlignment.Center, TextAlignment.Center); } /// diff --git a/Source/Engine/UI/GUI/Common/Dropdown.cs b/Source/Engine/UI/GUI/Common/Dropdown.cs index 21ca9cbea..2681aac1b 100644 --- a/Source/Engine/UI/GUI/Common/Dropdown.cs +++ b/Source/Engine/UI/GUI/Common/Dropdown.cs @@ -278,7 +278,7 @@ namespace FlaxEngine.GUI /// Gets or sets the font used to draw text. /// [EditorDisplay("Text Style"), EditorOrder(2021)] - public FontReference Font { get; set; } + public MultiFontReference Font { get; set; } /// /// Gets or sets the custom material used to render the text. It must has domain set to GUI and have a public texture parameter named Font used to sample font atlas texture with font characters data. @@ -359,7 +359,7 @@ namespace FlaxEngine.GUI : base(0, 0, 120, 18.0f) { var style = Style.Current; - Font = new FontReference(style.FontMedium.First()); + Font = new MultiFontReference(style.FontMedium); TextColor = style.Foreground; BackgroundColor = style.BackgroundNormal; BackgroundColorHighlighted = BackgroundColor; @@ -674,7 +674,7 @@ namespace FlaxEngine.GUI var textRect = new Rectangle(margin, 0, clientRect.Width - boxSize - 2.0f * margin, clientRect.Height); Render2D.PushClip(textRect); var textColor = TextColor; - Render2D.DrawText(Font.GetFont(), FontMaterial, _items[_selectedIndex], textRect, enabled ? textColor : textColor * 0.5f, TextAlignment.Near, TextAlignment.Center); + Render2D.DrawText(Font.GetMultiFont(), FontMaterial, _items[_selectedIndex], textRect, enabled ? textColor : textColor * 0.5f, TextAlignment.Near, TextAlignment.Center); Render2D.PopClip(); } diff --git a/Source/Engine/UI/GUI/Common/Label.cs b/Source/Engine/UI/GUI/Common/Label.cs index 5fa8ad21d..ccc1cc190 100644 --- a/Source/Engine/UI/GUI/Common/Label.cs +++ b/Source/Engine/UI/GUI/Common/Label.cs @@ -26,7 +26,7 @@ namespace FlaxEngine.GUI /// /// The font. /// - protected FontReference _font; + protected MultiFontReference _font; /// /// Gets or sets the text. @@ -86,7 +86,7 @@ namespace FlaxEngine.GUI /// Gets or sets the font. /// [EditorDisplay("Text Style"), EditorOrder(2024)] - public FontReference Font + public MultiFontReference Font { get => _font; set @@ -192,7 +192,7 @@ namespace FlaxEngine.GUI { AutoFocus = false; var style = Style.Current; - Font = new FontReference(style.FontMedium.First()); + Font = new MultiFontReference(style.FontMedium); TextColor = style.Foreground; TextColorHighlighted = style.Foreground; } @@ -203,7 +203,7 @@ namespace FlaxEngine.GUI { AutoFocus = false; var style = Style.Current; - Font = new FontReference(style.FontMedium.First()); + Font = new MultiFontReference(style.FontMedium); TextColor = style.Foreground; TextColorHighlighted = style.Foreground; } @@ -235,7 +235,7 @@ namespace FlaxEngine.GUI } } - Render2D.DrawText([_font.GetFont(), Style.Current.FontCJK], Material, _text, rect, color, hAlignment, wAlignment, Wrapping, BaseLinesGapScale, scale); + Render2D.DrawText(_font.GetMultiFont(), Material, _text, rect, color, hAlignment, wAlignment, Wrapping, BaseLinesGapScale, scale); if (ClipText) Render2D.PopClip(); @@ -246,7 +246,7 @@ namespace FlaxEngine.GUI { if (_autoWidth || _autoHeight || _autoFitText) { - var font = _font.GetFont(); + var font = _font.GetMultiFont(); if (font) { // Calculate text size diff --git a/Source/Engine/UI/GUI/Common/RichTextBox.Parsing.cs b/Source/Engine/UI/GUI/Common/RichTextBox.Parsing.cs index 2270136ae..9523ad30a 100644 --- a/Source/Engine/UI/GUI/Common/RichTextBox.Parsing.cs +++ b/Source/Engine/UI/GUI/Common/RichTextBox.Parsing.cs @@ -1,5 +1,6 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. +using System; using System.Collections.Generic; using System.Runtime.InteropServices; using FlaxEngine.Utilities; @@ -185,7 +186,7 @@ namespace FlaxEngine.GUI }; // Process text into text blocks (handle newlines etc.) - var font = textBlock.Style.Font.GetFont(); + var font = textBlock.Style.Font.GetMultiFont(); if (!font) return; var lines = font.ProcessText(_text, ref textBlock.Range); @@ -194,20 +195,27 @@ namespace FlaxEngine.GUI for (int i = 0; i < lines.Length; i++) { ref var line = ref lines[i]; - textBlock.Range = new TextRange - { - StartIndex = start + line.FirstCharIndex, - EndIndex = start + line.LastCharIndex, - }; + if (i != 0) { context.Caret.X = 0; OnLineAdded(ref context, textBlock.Range.StartIndex - 1); } - textBlock.Bounds = new Rectangle(context.Caret, line.Size); - textBlock.Bounds.X += line.Location.X; + for (int k = 0; k < line.Blocks.Length; k++) + { + ref var block = ref line.Blocks[k]; - context.AddTextBlock(ref textBlock); + textBlock.Range = new TextRange + { + StartIndex = start + block.FirstCharIndex, + EndIndex = start + block.LastCharIndex, + }; + + textBlock.Bounds = new Rectangle(context.Caret, block.Size); + textBlock.Bounds.X += block.Location.X; + + context.AddTextBlock(ref textBlock); + } } // Update the caret location @@ -236,9 +244,9 @@ namespace FlaxEngine.GUI var ascender = textBlock.Ascender; //if (ascender <= 0) { - var textBlockFont = textBlock.Style.Font.GetFont(); + var textBlockFont = textBlock.Style.Font.GetMultiFont(); if (textBlockFont) - ascender = textBlockFont.Ascender; + ascender = textBlockFont.MaxAscender; } lineAscender = Mathf.Max(lineAscender, ascender); lineSize = Float2.Max(lineSize, textBlockSize); @@ -259,9 +267,9 @@ namespace FlaxEngine.GUI var ascender = textBlock.Ascender; if (ascender <= 0) { - var textBlockFont = textBlock.Style.Font.GetFont(); + var textBlockFont = textBlock.Style.Font.GetMultiFont(); if (textBlockFont) - ascender = textBlockFont.Ascender; + ascender = textBlockFont.MaxAscender; } vOffset = lineAscender - ascender; textBlock.Bounds.Location.Y += vOffset; diff --git a/Source/Engine/UI/GUI/Common/RichTextBox.Tags.cs b/Source/Engine/UI/GUI/Common/RichTextBox.Tags.cs index 044c65044..08637918d 100644 --- a/Source/Engine/UI/GUI/Common/RichTextBox.Tags.cs +++ b/Source/Engine/UI/GUI/Common/RichTextBox.Tags.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using FlaxEngine.Utilities; +using System.Linq; namespace FlaxEngine.GUI { @@ -10,9 +11,9 @@ namespace FlaxEngine.GUI { context.Caret.X = 0; var style = context.StyleStack.Peek(); - var font = style.Font.GetFont(); + var font = style.Font.GetMultiFont(); if (font) - context.Caret.Y += font.Height; + context.Caret.Y += font.MaxHeight; } private static void ProcessColor(ref ParsingContext context, ref HtmlTag tag) @@ -86,15 +87,14 @@ namespace FlaxEngine.GUI else { var style = context.StyleStack.Peek(); - style.Font = new FontReference(style.Font); - if (tag.Attributes.TryGetValue(string.Empty, out var fontName)) + style.Font = new MultiFontReference(style.Font); + if (tag.Attributes.TryGetValue("size", out var sizeText) && int.TryParse(sizeText, out var size) && tag.Attributes.TryGetValue(string.Empty, out var fontName)) { var font = (FontAsset)FindAsset(fontName, typeof(FontAsset)); if (font) - style.Font.Font = font; + style.Font = new MultiFontReference([font], size); } - if (tag.Attributes.TryGetValue("size", out var sizeText) && int.TryParse(sizeText, out var size)) - style.Font.Size = size; + context.StyleStack.Push(style); } } @@ -108,7 +108,7 @@ namespace FlaxEngine.GUI else { var style = context.StyleStack.Peek(); - style.Font = style.Font.GetBold(); + // style.Font = style.Font.GetBold(); context.StyleStack.Push(style); } } @@ -122,7 +122,7 @@ namespace FlaxEngine.GUI else { var style = context.StyleStack.Peek(); - style.Font = style.Font.GetItalic(); + // style.Font = style.Font.GetItalic(); context.StyleStack.Push(style); } } @@ -136,9 +136,9 @@ namespace FlaxEngine.GUI else { var style = context.StyleStack.Peek(); - style.Font = new FontReference(style.Font); - TryParseNumberTag(ref tag, string.Empty, style.Font.Size, out var size); - style.Font.Size = (int)size; + style.Font = new MultiFontReference(style.Font); + TryParseNumberTag(ref tag, string.Empty, style.Font.First().Size, out var size); + style.Font = new MultiFontReference(style.Font, (int)size); context.StyleStack.Push(style); } } @@ -173,9 +173,9 @@ namespace FlaxEngine.GUI imageBlock.Style.BackgroundBrush = image; // Setup size - var font = imageBlock.Style.Font.GetFont(); + var font = imageBlock.Style.Font.GetMultiFont(); if (font) - imageBlock.Bounds.Size = new Float2(font.Height); + imageBlock.Bounds.Size = new Float2(font.MaxHeight); imageBlock.Bounds.Size.X *= image.Size.X / image.Size.Y; // Keep original aspect ratio bool hasWidth = TryParseNumberTag(ref tag, "width", imageBlock.Bounds.Width, out var width); imageBlock.Bounds.Width = width; @@ -213,18 +213,18 @@ namespace FlaxEngine.GUI style.Alignment &= ~TextBlockStyle.Alignments.VerticalMask; switch (valign) { - case "top": - style.Alignment = TextBlockStyle.Alignments.Top; - break; - case "bottom": - style.Alignment = TextBlockStyle.Alignments.Bottom; - break; - case "middle": - style.Alignment = TextBlockStyle.Alignments.Middle; - break; - case "baseline": - style.Alignment = TextBlockStyle.Alignments.Baseline; - break; + case "top": + style.Alignment = TextBlockStyle.Alignments.Top; + break; + case "bottom": + style.Alignment = TextBlockStyle.Alignments.Bottom; + break; + case "middle": + style.Alignment = TextBlockStyle.Alignments.Middle; + break; + case "baseline": + style.Alignment = TextBlockStyle.Alignments.Baseline; + break; } } context.StyleStack.Push(style); @@ -245,15 +245,15 @@ namespace FlaxEngine.GUI style.Alignment &= ~TextBlockStyle.Alignments.VerticalMask; switch (valign) { - case "left": - style.Alignment = TextBlockStyle.Alignments.Left; - break; - case "right": - style.Alignment = TextBlockStyle.Alignments.Right; - break; - case "center": - style.Alignment = TextBlockStyle.Alignments.Center; - break; + case "left": + style.Alignment = TextBlockStyle.Alignments.Left; + break; + case "right": + style.Alignment = TextBlockStyle.Alignments.Right; + break; + case "center": + style.Alignment = TextBlockStyle.Alignments.Center; + break; } } context.StyleStack.Push(style); @@ -280,7 +280,7 @@ namespace FlaxEngine.GUI foreach (var id in ids) { var path = Content.GetEditorAssetPath(id); - if (!string.IsNullOrEmpty(path) && + if (!string.IsNullOrEmpty(path) && string.Equals(name, System.IO.Path.GetFileNameWithoutExtension(path), System.StringComparison.OrdinalIgnoreCase)) { return Content.LoadAsync(id, type); diff --git a/Source/Engine/UI/GUI/Common/RichTextBox.cs b/Source/Engine/UI/GUI/Common/RichTextBox.cs index e6a82c986..78da9621c 100644 --- a/Source/Engine/UI/GUI/Common/RichTextBox.cs +++ b/Source/Engine/UI/GUI/Common/RichTextBox.cs @@ -46,7 +46,7 @@ namespace FlaxEngine.GUI var style = Style.Current; _textStyle = new TextBlockStyle { - Font = new FontReference(style.FontMedium.First()), + Font = new MultiFontReference(style.FontMedium), Color = style.Foreground, BackgroundSelectedBrush = new SolidColorBrush(style.BackgroundSelected), }; diff --git a/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs b/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs index 438a7e3d8..6247cb885 100644 --- a/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs +++ b/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs @@ -123,10 +123,10 @@ namespace FlaxEngine.GUI if (index <= 0) { ref TextBlock textBlock = ref textBlocks[0]; - var font = textBlock.Style.Font.GetFont(); + var font = textBlock.Style.Font.GetMultiFont(); if (font) { - height = font.Height / DpiScale; + height = font.MaxHeight / DpiScale; return textBlock.Bounds.UpperLeft; } } @@ -135,10 +135,10 @@ namespace FlaxEngine.GUI if (index >= _text.Length) { ref TextBlock textBlock = ref textBlocks[count - 1]; - var font = textBlock.Style.Font.GetFont(); + var font = textBlock.Style.Font.GetMultiFont(); if (font) { - height = font.Height / DpiScale; + height = font.MaxHeight / DpiScale; return textBlock.Bounds.UpperRight; } } @@ -150,10 +150,10 @@ namespace FlaxEngine.GUI if (textBlock.Range.Contains(index)) { - var font = textBlock.Style.Font.GetFont(); + var font = textBlock.Style.Font.GetMultiFont(); if (!font) break; - height = font.Height / DpiScale; + height = font.MaxHeight / DpiScale; return textBlock.Bounds.Location + font.GetCharPosition(_text, ref textBlock.Range, index - textBlock.Range.StartIndex); } } @@ -165,10 +165,10 @@ namespace FlaxEngine.GUI if (index >= textBlock.Range.EndIndex) { - var font = textBlock.Style.Font.GetFont(); + var font = textBlock.Style.Font.GetMultiFont(); if (!font) break; - height = font.Height / DpiScale; + height = font.MaxHeight / DpiScale; return textBlock.Bounds.UpperRight; } } @@ -193,7 +193,7 @@ namespace FlaxEngine.GUI if (containsY && (containsX || (i + 1 < count && textBlocks[i + 1].Bounds.Location.Y > textBlock.Bounds.Location.Y + 1.0f))) { - var font = textBlock.Style.Font.GetFont(); + var font = textBlock.Style.Font.GetMultiFont(); if (!font && textBlock.Range.Length > 0) break; return font.HitTestText(_text, ref textBlock.Range, location - textBlock.Bounds.Location) + textBlock.Range.StartIndex; @@ -281,7 +281,7 @@ namespace FlaxEngine.GUI } // Pick font - var font = textBlock.Style.Font.GetFont(); + var font = textBlock.Style.Font.GetMultiFont(); if (!font) continue; @@ -290,7 +290,7 @@ namespace FlaxEngine.GUI { var leftEdge = selection.StartIndex <= textBlock.Range.StartIndex ? textBlock.Bounds.UpperLeft : font.GetCharPosition(_text, selection.StartIndex); var rightEdge = selection.EndIndex >= textBlock.Range.EndIndex ? textBlock.Bounds.UpperRight : font.GetCharPosition(_text, selection.EndIndex); - float height = font.Height / DpiScale; + float height = font.MaxHeight / DpiScale; float alpha = Mathf.Min(1.0f, Mathf.Cos(_animateTime * BackgroundSelectedFlashSpeed) * 0.5f + 1.3f); alpha *= alpha; Color selectionColor = Color.White * alpha; @@ -305,7 +305,7 @@ namespace FlaxEngine.GUI ref TextBlock textBlock = ref textBlocks[i]; // Pick font - var font = textBlock.Style.Font.GetFont(); + var font = textBlock.Style.Font.GetMultiFont(); if (!font) continue; @@ -332,7 +332,7 @@ namespace FlaxEngine.GUI ref TextBlock textBlock = ref textBlocks[i]; // Pick font - var font = textBlock.Style.Font.GetFont(); + var font = textBlock.Style.Font.GetMultiFont(); if (!font) continue; @@ -340,7 +340,7 @@ namespace FlaxEngine.GUI if (textBlock.Style.UnderlineBrush != null) { var underLineHeight = 2.0f; - var height = font.Height / DpiScale; + var height = font.MaxHeight / DpiScale; var underlineRect = new Rectangle(textBlock.Bounds.Location.X, textBlock.Bounds.Location.Y + height - underLineHeight * 0.5f, textBlock.Bounds.Width, underLineHeight); textBlock.Style.UnderlineBrush.Draw(underlineRect, textBlock.Style.Color); } diff --git a/Source/Engine/UI/GUI/Common/TextBox.cs b/Source/Engine/UI/GUI/Common/TextBox.cs index 967617649..d9c7c1a80 100644 --- a/Source/Engine/UI/GUI/Common/TextBox.cs +++ b/Source/Engine/UI/GUI/Common/TextBox.cs @@ -40,7 +40,7 @@ namespace FlaxEngine.GUI /// Gets or sets the font. /// [EditorDisplay("Text Style"), EditorOrder(2024)] - public FontReference Font { get; set; } + public MultiFontReference Font { get; set; } /// /// Gets or sets the custom material used to render the text. It must has domain set to GUI and have a public texture parameter named Font used to sample font atlas texture with font characters data. @@ -90,7 +90,7 @@ namespace FlaxEngine.GUI _layout.Bounds = new Rectangle(DefaultMargin, 1, Width - 2 * DefaultMargin, Height - 2); var style = Style.Current; - Font = new FontReference(style.FontMedium.First()); + Font = new MultiFontReference(style.FontMedium); TextColor = style.Foreground; WatermarkTextColor = style.ForegroundDisabled; SelectionColor = style.BackgroundSelected; @@ -99,7 +99,7 @@ namespace FlaxEngine.GUI /// public override Float2 GetTextSize() { - var font = Font.GetFont(); + var font = Font.GetMultiFont(); if (font == null) { return Float2.Zero; @@ -111,21 +111,21 @@ namespace FlaxEngine.GUI /// public override Float2 GetCharPosition(int index, out float height) { - var font = Font.GetFont(); + var font = Font.GetMultiFont(); if (font == null) { height = Height; return Float2.Zero; } - height = font.Height / DpiScale; + height = font.MaxHeight / DpiScale; return font.GetCharPosition(_text, index, ref _layout); } /// public override int HitTestText(Float2 location) { - var font = Font.GetFont(); + var font = Font.GetMultiFont(); if (font == null) { return 0; @@ -148,7 +148,7 @@ namespace FlaxEngine.GUI // Cache data var rect = new Rectangle(Float2.Zero, Size); bool enabled = EnabledInHierarchy; - var font = Font.GetFont(); + var font = Font.GetMultiFont(); if (!font) return; @@ -172,7 +172,7 @@ namespace FlaxEngine.GUI { var leftEdge = font.GetCharPosition(_text, SelectionLeft, ref _layout); var rightEdge = font.GetCharPosition(_text, SelectionRight, ref _layout); - float fontHeight = font.Height / DpiScale; + float fontHeight = font.MaxHeight / DpiScale; // Draw selection background float alpha = Mathf.Min(1.0f, Mathf.Cos(_animateTime * BackgroundSelectedFlashSpeed) * 0.5f + 1.3f); diff --git a/Source/Engine/UI/GUI/Panels/DropPanel.cs b/Source/Engine/UI/GUI/Panels/DropPanel.cs index 33c2e1605..06256c9f8 100644 --- a/Source/Engine/UI/GUI/Panels/DropPanel.cs +++ b/Source/Engine/UI/GUI/Panels/DropPanel.cs @@ -130,7 +130,7 @@ namespace FlaxEngine.GUI /// Gets or sets the font used to render panel header text. /// [EditorDisplay("Header Text Style"), EditorOrder(2020), ExpandGroups] - public FontReference HeaderTextFont { get; set; } + public MultiFontReference HeaderTextFont { get; set; } /// /// Gets or sets the custom material used to render the text. It must has domain set to GUI and have a public texture parameter named Font used to sample font atlas texture with font characters data. @@ -238,7 +238,7 @@ namespace FlaxEngine.GUI var style = Style.Current; HeaderColor = style.BackgroundNormal; HeaderColorMouseOver = style.BackgroundHighlighted; - HeaderTextFont = new FontReference(style.FontMedium.First()); + HeaderTextFont = new MultiFontReference(style.FontMedium); HeaderTextColor = style.Foreground; ArrowImageOpened = new SpriteBrush(style.ArrowDown); ArrowImageClosed = new SpriteBrush(style.ArrowRight); @@ -375,7 +375,7 @@ namespace FlaxEngine.GUI textColor *= 0.6f; } - Render2D.DrawText(HeaderTextFont.GetFont(), HeaderTextMaterial, HeaderText, textRect, textColor, TextAlignment.Near, TextAlignment.Center); + Render2D.DrawText(HeaderTextFont.GetMultiFont(), HeaderTextMaterial, HeaderText, textRect, textColor, TextAlignment.Near, TextAlignment.Center); if (!_isClosed && EnableContainmentLines) { diff --git a/Source/Engine/UI/GUI/Style.cs b/Source/Engine/UI/GUI/Style.cs index d3375a670..a8b87fb3a 100644 --- a/Source/Engine/UI/GUI/Style.cs +++ b/Source/Engine/UI/GUI/Style.cs @@ -14,68 +14,60 @@ namespace FlaxEngine.GUI /// public static Style Current { get; set; } - public Font FontCJK - { - get => _fontCJK?.GetFont(); - set => _fontCJK = new FontReference(value); - } - - private FontReference _fontCJK; - [Serialize] - private FontReference _fontTitle; + private MultiFontReference _fontTitle; /// /// The font title. /// [NoSerialize] [EditorOrder(10)] - public Font FontTitle + public MultiFont FontTitle { - get => _fontTitle?.GetFont(); - set => _fontTitle = new FontReference(value); + get => _fontTitle?.GetMultiFont(); + set => _fontTitle = new MultiFontReference(value); } [Serialize] - private FontReference _fontLarge; + private MultiFontReference _fontLarge; /// /// The font large. /// [NoSerialize] [EditorOrder(20)] - public Font FontLarge + public MultiFont FontLarge { - get => _fontLarge?.GetFont(); - set => _fontLarge = new FontReference(value); + get => _fontLarge?.GetMultiFont(); + set => _fontLarge = new MultiFontReference(value); } [Serialize] - private FontReference[] _fontMedium; + private MultiFontReference _fontMedium; /// /// The font medium. /// [NoSerialize] [EditorOrder(30)] - public Font[] FontMedium + public MultiFont FontMedium { - get => _fontMedium?.Select((x)=>x.GetFont()).ToArray(); - set => _fontMedium = value.Select((x)=>new FontReference(x)).ToArray(); + get => _fontMedium?.GetMultiFont(); + set => _fontMedium = new MultiFontReference(value); } [Serialize] - private FontReference[] _fontSmall; + private MultiFontReference _fontSmall; /// /// The font small. /// [NoSerialize] [EditorOrder(40)] - public Font[] FontSmall + public MultiFont FontSmall { - get => _fontSmall?.Select((x) => x.GetFont()).ToArray(); - set => _fontSmall = value.Select((x) => new FontReference(x)).ToArray(); + get => _fontSmall?.GetMultiFont(); + set => _fontSmall = new MultiFontReference(value); } /// diff --git a/Source/Engine/UI/GUI/TextBlockStyle.cs b/Source/Engine/UI/GUI/TextBlockStyle.cs index c4c87715f..08f74e7c8 100644 --- a/Source/Engine/UI/GUI/TextBlockStyle.cs +++ b/Source/Engine/UI/GUI/TextBlockStyle.cs @@ -64,7 +64,7 @@ namespace FlaxEngine.GUI /// The text font. /// [EditorOrder(0)] - public FontReference Font; + public MultiFontReference Font; /// /// The custom material for the text rendering (must be GUI domain). diff --git a/Source/Engine/UI/GUI/Tooltip.cs b/Source/Engine/UI/GUI/Tooltip.cs index e17b754c4..7a1026e55 100644 --- a/Source/Engine/UI/GUI/Tooltip.cs +++ b/Source/Engine/UI/GUI/Tooltip.cs @@ -244,6 +244,7 @@ namespace FlaxEngine.GUI TextAlignment.Center, TextWrapping.WrapWords ); + } /// @@ -256,14 +257,14 @@ namespace FlaxEngine.GUI // Calculate size of the tooltip var size = Float2.Zero; - if (style != null && style.FontMedium.First() && !string.IsNullOrEmpty(_currentText)) + if (style != null && style.FontMedium && !string.IsNullOrEmpty(_currentText)) { var layout = TextLayoutOptions.Default; layout.Bounds = new Rectangle(0, 0, MaxWidth, 10000000); layout.HorizontalAlignment = TextAlignment.Center; layout.VerticalAlignment = TextAlignment.Center; layout.TextWrapping = TextWrapping.WrapWords; - var items = style.FontMedium.First().ProcessText(_currentText, ref layout); + var items = style.FontMedium.ProcessText(_currentText, ref layout); for (int i = 0; i < items.Length; i++) { ref var item = ref items[i]; From 3365fb5afc6f7600acaa5e730a257bb378f2776f Mon Sep 17 00:00:00 2001 From: ExMatics HydrogenC <33123710+HydrogenC@users.noreply.github.com> Date: Thu, 30 Nov 2023 14:59:43 +0800 Subject: [PATCH 039/139] Fix console rendering bug --- Source/Editor/Options/InterfaceOptions.cs | 11 ++- Source/Editor/Windows/OutputLogWindow.cs | 74 +++++++++---------- .../UI/GUI/Common/RichTextBox.Parsing.cs | 33 ++++----- .../Engine/UI/GUI/Common/RichTextBox.Tags.cs | 27 +++---- Source/Engine/UI/GUI/Common/RichTextBox.cs | 2 +- .../Engine/UI/GUI/Common/RichTextBoxBase.cs | 28 +++---- Source/Engine/UI/GUI/TextBlockStyle.cs | 2 +- 7 files changed, 82 insertions(+), 95 deletions(-) diff --git a/Source/Editor/Options/InterfaceOptions.cs b/Source/Editor/Options/InterfaceOptions.cs index e43751fd1..91eebe4eb 100644 --- a/Source/Editor/Options/InterfaceOptions.cs +++ b/Source/Editor/Options/InterfaceOptions.cs @@ -166,13 +166,13 @@ namespace FlaxEditor.Options /// Gets or sets the output log text font. /// [EditorDisplay("Output Log", "Text Font"), EditorOrder(320), Tooltip("The output log text font.")] - public MultiFontReference OutputLogTextFont + public FontReference OutputLogTextFont { get => _outputLogFont; set { - if (value == null || !value.Verify()) - _outputLogFont = new MultiFontReference(ConsoleFonts, 10); + if (value == null || !value.Font) + _outputLogFont = new FontReference(ConsoleFont, 10); else _outputLogFont = value; } @@ -238,14 +238,13 @@ namespace FlaxEditor.Options [FlaxEngine.Content.LoadAsyncInternal(EditorAssets.PrimaryFont), FlaxEngine.Content.LoadAsyncInternal(EditorAssets.CjkFont)]; - private static FontAsset[] ConsoleFonts => [FlaxEngine.Content.LoadAsyncInternal(EditorAssets.PrimaryFont), - FlaxEngine.Content.LoadAsyncInternal(EditorAssets.CjkFont)]; + private static FontAsset ConsoleFont => FlaxEngine.Content.LoadAsyncInternal(EditorAssets.PrimaryFont); private MultiFontReference _titleFont = new MultiFontReference(DefaultFonts, 18); private MultiFontReference _largeFont = new MultiFontReference(DefaultFonts, 14); private MultiFontReference _mediumFont = new MultiFontReference(DefaultFonts, 9); private MultiFontReference _smallFont = new MultiFontReference(DefaultFonts, 9); - private MultiFontReference _outputLogFont = new MultiFontReference(ConsoleFonts, 10); + private FontReference _outputLogFont = new FontReference(ConsoleFont, 10); /// diff --git a/Source/Editor/Windows/OutputLogWindow.cs b/Source/Editor/Windows/OutputLogWindow.cs index ac4fea5ba..f168d8d45 100644 --- a/Source/Editor/Windows/OutputLogWindow.cs +++ b/Source/Editor/Windows/OutputLogWindow.cs @@ -470,9 +470,9 @@ namespace FlaxEditor.Windows var wasEmpty = _output.TextLength == 0; // Cache fonts - _output.DefaultStyle.Font.GetMultiFont(); - _output.WarningStyle.Font.GetMultiFont(); - _output.ErrorStyle.Font.GetMultiFont(); + _output.DefaultStyle.Font.GetFont(); + _output.WarningStyle.Font.GetFont(); + _output.ErrorStyle.Font.GetFont(); // Generate the output log Span entries = CollectionsMarshal.AsSpan(_entries); @@ -536,7 +536,7 @@ namespace FlaxEditor.Windows } var prevBlockBottom = _textBlocks.Count == 0 ? 0.0f : _textBlocks[_textBlocks.Count - 1].Bounds.Bottom; var entryText = _textBuffer.ToString(startIndex, endIndex - startIndex); - var font = textBlock.Style.Font.GetMultiFont(); + var font = textBlock.Style.Font.GetFont(); if (!font) continue; var style = textBlock.Style; @@ -544,52 +544,46 @@ namespace FlaxEditor.Windows for (int j = 0; j < lines.Length; j++) { ref var line = ref lines[j]; - for (int k = 0; k < line.Blocks.Length; k++) + textBlock.Range.StartIndex = startIndex + line.FirstCharIndex; + textBlock.Range.EndIndex = startIndex + line.LastCharIndex + 1; + textBlock.Bounds = new Rectangle(new Float2(0.0f, prevBlockBottom), line.Size); + + if (textBlock.Range.Length > 0) { - ref var block = ref line.Blocks[k]; - - textBlock.Range.StartIndex = startIndex + block.FirstCharIndex; - textBlock.Range.EndIndex = startIndex + block.LastCharIndex + 1; - textBlock.Bounds = new Rectangle(new Float2(block.Location.X, prevBlockBottom), block.Size); - - if (textBlock.Range.Length > 0) + // Parse compilation error/warning + var regexStart = line.FirstCharIndex; + if (j == 0) + regexStart += prefixLength; + var regexLength = line.LastCharIndex + 1 - regexStart; + if (regexLength > 0) { - // Parse compilation error/warning - var regexStart = block.FirstCharIndex; - if (j == 0) - regexStart += prefixLength; - var regexLength = block.LastCharIndex + 1 - regexStart; - if (regexLength > 0) + var match = _compileRegex.Match(entryText, regexStart, regexLength); + if (match.Success) { - var match = _compileRegex.Match(entryText, regexStart, regexLength); - if (match.Success) + switch (match.Groups["level"].Value) { - switch (match.Groups["level"].Value) - { - case "error": - textBlock.Style = _output.ErrorStyle; - break; - case "warning": - textBlock.Style = _output.WarningStyle; - break; - } - textBlock.Tag = new TextBlockTag - { - Type = TextBlockTag.Types.CodeLocation, - Url = match.Groups["path"].Value, - Line = int.Parse(match.Groups["line"].Value), - }; + case "error": + textBlock.Style = _output.ErrorStyle; + break; + case "warning": + textBlock.Style = _output.WarningStyle; + break; } - // TODO: parsing hyperlinks with link - // TODO: parsing file paths with link + textBlock.Tag = new TextBlockTag + { + Type = TextBlockTag.Types.CodeLocation, + Url = match.Groups["path"].Value, + Line = int.Parse(match.Groups["line"].Value), + }; } + // TODO: parsing hyperlinks with link + // TODO: parsing file paths with link } - - _textBlocks.Add(textBlock); - textBlock.Style = style; } prevBlockBottom += line.Size.Y; + _textBlocks.Add(textBlock); + textBlock.Style = style; } } diff --git a/Source/Engine/UI/GUI/Common/RichTextBox.Parsing.cs b/Source/Engine/UI/GUI/Common/RichTextBox.Parsing.cs index 9523ad30a..ab01df12b 100644 --- a/Source/Engine/UI/GUI/Common/RichTextBox.Parsing.cs +++ b/Source/Engine/UI/GUI/Common/RichTextBox.Parsing.cs @@ -186,7 +186,7 @@ namespace FlaxEngine.GUI }; // Process text into text blocks (handle newlines etc.) - var font = textBlock.Style.Font.GetMultiFont(); + var font = textBlock.Style.Font.GetFont(); if (!font) return; var lines = font.ProcessText(_text, ref textBlock.Range); @@ -195,27 +195,20 @@ namespace FlaxEngine.GUI for (int i = 0; i < lines.Length; i++) { ref var line = ref lines[i]; - + textBlock.Range = new TextRange + { + StartIndex = start + line.FirstCharIndex, + EndIndex = start + line.LastCharIndex, + }; if (i != 0) { context.Caret.X = 0; OnLineAdded(ref context, textBlock.Range.StartIndex - 1); } - for (int k = 0; k < line.Blocks.Length; k++) - { - ref var block = ref line.Blocks[k]; + textBlock.Bounds = new Rectangle(context.Caret, line.Size); + textBlock.Bounds.X += line.Location.X; - textBlock.Range = new TextRange - { - StartIndex = start + block.FirstCharIndex, - EndIndex = start + block.LastCharIndex, - }; - - textBlock.Bounds = new Rectangle(context.Caret, block.Size); - textBlock.Bounds.X += block.Location.X; - - context.AddTextBlock(ref textBlock); - } + context.AddTextBlock(ref textBlock); } // Update the caret location @@ -244,9 +237,9 @@ namespace FlaxEngine.GUI var ascender = textBlock.Ascender; //if (ascender <= 0) { - var textBlockFont = textBlock.Style.Font.GetMultiFont(); + var textBlockFont = textBlock.Style.Font.GetFont(); if (textBlockFont) - ascender = textBlockFont.MaxAscender; + ascender = textBlockFont.Ascender; } lineAscender = Mathf.Max(lineAscender, ascender); lineSize = Float2.Max(lineSize, textBlockSize); @@ -267,9 +260,9 @@ namespace FlaxEngine.GUI var ascender = textBlock.Ascender; if (ascender <= 0) { - var textBlockFont = textBlock.Style.Font.GetMultiFont(); + var textBlockFont = textBlock.Style.Font.GetFont(); if (textBlockFont) - ascender = textBlockFont.MaxAscender; + ascender = textBlockFont.Ascender; } vOffset = lineAscender - ascender; textBlock.Bounds.Location.Y += vOffset; diff --git a/Source/Engine/UI/GUI/Common/RichTextBox.Tags.cs b/Source/Engine/UI/GUI/Common/RichTextBox.Tags.cs index 08637918d..d3c2feefe 100644 --- a/Source/Engine/UI/GUI/Common/RichTextBox.Tags.cs +++ b/Source/Engine/UI/GUI/Common/RichTextBox.Tags.cs @@ -11,9 +11,9 @@ namespace FlaxEngine.GUI { context.Caret.X = 0; var style = context.StyleStack.Peek(); - var font = style.Font.GetMultiFont(); + var font = style.Font.GetFont(); if (font) - context.Caret.Y += font.MaxHeight; + context.Caret.Y += font.Height; } private static void ProcessColor(ref ParsingContext context, ref HtmlTag tag) @@ -87,14 +87,15 @@ namespace FlaxEngine.GUI else { var style = context.StyleStack.Peek(); - style.Font = new MultiFontReference(style.Font); - if (tag.Attributes.TryGetValue("size", out var sizeText) && int.TryParse(sizeText, out var size) && tag.Attributes.TryGetValue(string.Empty, out var fontName)) + style.Font = new FontReference(style.Font); + if (tag.Attributes.TryGetValue(string.Empty, out var fontName)) { var font = (FontAsset)FindAsset(fontName, typeof(FontAsset)); if (font) - style.Font = new MultiFontReference([font], size); + style.Font.Font = font; } - + if (tag.Attributes.TryGetValue("size", out var sizeText) && int.TryParse(sizeText, out var size)) + style.Font.Size = size; context.StyleStack.Push(style); } } @@ -108,7 +109,7 @@ namespace FlaxEngine.GUI else { var style = context.StyleStack.Peek(); - // style.Font = style.Font.GetBold(); + style.Font = style.Font.GetBold(); context.StyleStack.Push(style); } } @@ -122,7 +123,7 @@ namespace FlaxEngine.GUI else { var style = context.StyleStack.Peek(); - // style.Font = style.Font.GetItalic(); + style.Font = style.Font.GetItalic(); context.StyleStack.Push(style); } } @@ -136,9 +137,9 @@ namespace FlaxEngine.GUI else { var style = context.StyleStack.Peek(); - style.Font = new MultiFontReference(style.Font); - TryParseNumberTag(ref tag, string.Empty, style.Font.First().Size, out var size); - style.Font = new MultiFontReference(style.Font, (int)size); + style.Font = new FontReference(style.Font); + TryParseNumberTag(ref tag, string.Empty, style.Font.Size, out var size); + style.Font.Size = (int)size; context.StyleStack.Push(style); } } @@ -173,9 +174,9 @@ namespace FlaxEngine.GUI imageBlock.Style.BackgroundBrush = image; // Setup size - var font = imageBlock.Style.Font.GetMultiFont(); + var font = imageBlock.Style.Font.GetFont(); if (font) - imageBlock.Bounds.Size = new Float2(font.MaxHeight); + imageBlock.Bounds.Size = new Float2(font.Height); imageBlock.Bounds.Size.X *= image.Size.X / image.Size.Y; // Keep original aspect ratio bool hasWidth = TryParseNumberTag(ref tag, "width", imageBlock.Bounds.Width, out var width); imageBlock.Bounds.Width = width; diff --git a/Source/Engine/UI/GUI/Common/RichTextBox.cs b/Source/Engine/UI/GUI/Common/RichTextBox.cs index 78da9621c..f8ac7d353 100644 --- a/Source/Engine/UI/GUI/Common/RichTextBox.cs +++ b/Source/Engine/UI/GUI/Common/RichTextBox.cs @@ -46,7 +46,7 @@ namespace FlaxEngine.GUI var style = Style.Current; _textStyle = new TextBlockStyle { - Font = new MultiFontReference(style.FontMedium), + Font = new FontReference(style.FontMedium.Fonts.First()), Color = style.Foreground, BackgroundSelectedBrush = new SolidColorBrush(style.BackgroundSelected), }; diff --git a/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs b/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs index 6247cb885..438a7e3d8 100644 --- a/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs +++ b/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs @@ -123,10 +123,10 @@ namespace FlaxEngine.GUI if (index <= 0) { ref TextBlock textBlock = ref textBlocks[0]; - var font = textBlock.Style.Font.GetMultiFont(); + var font = textBlock.Style.Font.GetFont(); if (font) { - height = font.MaxHeight / DpiScale; + height = font.Height / DpiScale; return textBlock.Bounds.UpperLeft; } } @@ -135,10 +135,10 @@ namespace FlaxEngine.GUI if (index >= _text.Length) { ref TextBlock textBlock = ref textBlocks[count - 1]; - var font = textBlock.Style.Font.GetMultiFont(); + var font = textBlock.Style.Font.GetFont(); if (font) { - height = font.MaxHeight / DpiScale; + height = font.Height / DpiScale; return textBlock.Bounds.UpperRight; } } @@ -150,10 +150,10 @@ namespace FlaxEngine.GUI if (textBlock.Range.Contains(index)) { - var font = textBlock.Style.Font.GetMultiFont(); + var font = textBlock.Style.Font.GetFont(); if (!font) break; - height = font.MaxHeight / DpiScale; + height = font.Height / DpiScale; return textBlock.Bounds.Location + font.GetCharPosition(_text, ref textBlock.Range, index - textBlock.Range.StartIndex); } } @@ -165,10 +165,10 @@ namespace FlaxEngine.GUI if (index >= textBlock.Range.EndIndex) { - var font = textBlock.Style.Font.GetMultiFont(); + var font = textBlock.Style.Font.GetFont(); if (!font) break; - height = font.MaxHeight / DpiScale; + height = font.Height / DpiScale; return textBlock.Bounds.UpperRight; } } @@ -193,7 +193,7 @@ namespace FlaxEngine.GUI if (containsY && (containsX || (i + 1 < count && textBlocks[i + 1].Bounds.Location.Y > textBlock.Bounds.Location.Y + 1.0f))) { - var font = textBlock.Style.Font.GetMultiFont(); + var font = textBlock.Style.Font.GetFont(); if (!font && textBlock.Range.Length > 0) break; return font.HitTestText(_text, ref textBlock.Range, location - textBlock.Bounds.Location) + textBlock.Range.StartIndex; @@ -281,7 +281,7 @@ namespace FlaxEngine.GUI } // Pick font - var font = textBlock.Style.Font.GetMultiFont(); + var font = textBlock.Style.Font.GetFont(); if (!font) continue; @@ -290,7 +290,7 @@ namespace FlaxEngine.GUI { var leftEdge = selection.StartIndex <= textBlock.Range.StartIndex ? textBlock.Bounds.UpperLeft : font.GetCharPosition(_text, selection.StartIndex); var rightEdge = selection.EndIndex >= textBlock.Range.EndIndex ? textBlock.Bounds.UpperRight : font.GetCharPosition(_text, selection.EndIndex); - float height = font.MaxHeight / DpiScale; + float height = font.Height / DpiScale; float alpha = Mathf.Min(1.0f, Mathf.Cos(_animateTime * BackgroundSelectedFlashSpeed) * 0.5f + 1.3f); alpha *= alpha; Color selectionColor = Color.White * alpha; @@ -305,7 +305,7 @@ namespace FlaxEngine.GUI ref TextBlock textBlock = ref textBlocks[i]; // Pick font - var font = textBlock.Style.Font.GetMultiFont(); + var font = textBlock.Style.Font.GetFont(); if (!font) continue; @@ -332,7 +332,7 @@ namespace FlaxEngine.GUI ref TextBlock textBlock = ref textBlocks[i]; // Pick font - var font = textBlock.Style.Font.GetMultiFont(); + var font = textBlock.Style.Font.GetFont(); if (!font) continue; @@ -340,7 +340,7 @@ namespace FlaxEngine.GUI if (textBlock.Style.UnderlineBrush != null) { var underLineHeight = 2.0f; - var height = font.MaxHeight / DpiScale; + var height = font.Height / DpiScale; var underlineRect = new Rectangle(textBlock.Bounds.Location.X, textBlock.Bounds.Location.Y + height - underLineHeight * 0.5f, textBlock.Bounds.Width, underLineHeight); textBlock.Style.UnderlineBrush.Draw(underlineRect, textBlock.Style.Color); } diff --git a/Source/Engine/UI/GUI/TextBlockStyle.cs b/Source/Engine/UI/GUI/TextBlockStyle.cs index 08f74e7c8..c4c87715f 100644 --- a/Source/Engine/UI/GUI/TextBlockStyle.cs +++ b/Source/Engine/UI/GUI/TextBlockStyle.cs @@ -64,7 +64,7 @@ namespace FlaxEngine.GUI /// The text font. /// [EditorOrder(0)] - public MultiFontReference Font; + public FontReference Font; /// /// The custom material for the text rendering (must be GUI domain). From d3840bb1f36fafb2ca6d5154d9922b3c16b500fb Mon Sep 17 00:00:00 2001 From: ExMatics HydrogenC <33123710+HydrogenC@users.noreply.github.com> Date: Thu, 30 Nov 2023 16:25:32 +0800 Subject: [PATCH 040/139] Refractor C++ code structure --- Source/Engine/Render2D/FallbackFonts.cpp | 11 + Source/Engine/Render2D/FallbackFonts.h | 94 +++++ Source/Engine/Render2D/Font.cpp | 441 ++++++++++++++++++++++- Source/Engine/Render2D/Font.h | 276 +++++++++++++- Source/Engine/Render2D/FontAsset.cpp | 4 + Source/Engine/Render2D/FontAsset.h | 7 + Source/Engine/Render2D/MultiFont.cpp | 431 ---------------------- Source/Engine/Render2D/MultiFont.h | 353 ------------------ Source/Engine/Render2D/Render2D.cpp | 74 ++-- Source/Engine/Render2D/Render2D.h | 10 +- 10 files changed, 864 insertions(+), 837 deletions(-) create mode 100644 Source/Engine/Render2D/FallbackFonts.cpp create mode 100644 Source/Engine/Render2D/FallbackFonts.h delete mode 100644 Source/Engine/Render2D/MultiFont.cpp delete mode 100644 Source/Engine/Render2D/MultiFont.h diff --git a/Source/Engine/Render2D/FallbackFonts.cpp b/Source/Engine/Render2D/FallbackFonts.cpp new file mode 100644 index 000000000..b79a0db4e --- /dev/null +++ b/Source/Engine/Render2D/FallbackFonts.cpp @@ -0,0 +1,11 @@ +#include "FallbackFonts.h" +#include "FontManager.h" +#include "Engine/Core/Math/Math.h" + +FallbackFonts::FallbackFonts(const Array& fonts) + : ManagedScriptingObject(SpawnParams(Guid::New(), Font::TypeInitializer)), + _fontAssets(fonts) +{ + +} + diff --git a/Source/Engine/Render2D/FallbackFonts.h b/Source/Engine/Render2D/FallbackFonts.h new file mode 100644 index 000000000..4b97e1c11 --- /dev/null +++ b/Source/Engine/Render2D/FallbackFonts.h @@ -0,0 +1,94 @@ +#pragma once + +#include "Engine/Core/Collections/Array.h" +#include "Engine/Core/Collections/Dictionary.h" +#include "Font.h" +#include "FontAsset.h" + +struct TextRange; +class Font; +class FontAsset; + +API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API FallbackFonts : public ManagedScriptingObject +{ + DECLARE_SCRIPTING_TYPE_NO_SPAWN(FallbackFonts); +private: + /// + /// The list of fallback fonts, ordered by priority. + /// The first element is reserved for the primary font, fallback fonts starts from the second element. + /// + Array _fontAssets; + + Dictionary*> _cache; + +public: + FallbackFonts(const Array& fonts); + + API_FUNCTION() FORCE_INLINE static FallbackFonts* Create(const Array& fonts) { + return New(fonts); + } + + API_PROPERTY() FORCE_INLINE Array& GetFonts() { + return _fontAssets; + } + + API_PROPERTY() FORCE_INLINE void SetFonts(const Array& val) { + _fontAssets = val; + } + + /// + /// Combine the primary fonts with the fallback fonts to get a font list + /// + API_PROPERTY() FORCE_INLINE Array& GetFontList(float size) { + Array* result; + if (_cache.TryGet(size, result)) { + return *result; + } + + result = New>(_fontAssets.Count()); + auto& arr = *result; + for (int32 i = 0; i < _fontAssets.Count(); i++) + { + arr.Add(_fontAssets[i]->CreateFont(size)); + } + + _cache[size] = result; + return *result; + } + + + /// + /// Gets the index of the fallback font that should be used to render the char + /// + /// The char. + /// -1 if char can be rendered with primary font, index if it matches a fallback font. + API_FUNCTION() FORCE_INLINE int32 GetCharFallbackIndex(Char c, Font* primaryFont = nullptr, int32 missing = -1) { + if (primaryFont && primaryFont->GetAsset()->ContainsChar(c)) { + return -1; + } + + int32 fontIndex = 0; + while (fontIndex < _fontAssets.Count() && _fontAssets[fontIndex] && !_fontAssets[fontIndex]->ContainsChar(c)) + { + fontIndex++; + } + + if (fontIndex < _fontAssets.Count()) { + return fontIndex; + + } + + return missing; + } + + API_FUNCTION() FORCE_INLINE bool Verify() { + for (int32 i = 0; i < _fontAssets.Count(); i++) + { + if (!_fontAssets[i]) { + return false; + } + } + + return true; + } +}; diff --git a/Source/Engine/Render2D/Font.cpp b/Source/Engine/Render2D/Font.cpp index b33b10899..4cf1f73a2 100644 --- a/Source/Engine/Render2D/Font.cpp +++ b/Source/Engine/Render2D/Font.cpp @@ -6,7 +6,7 @@ #include "Engine/Core/Log.h" #include "Engine/Threading/Threading.h" #include "IncludeFreeType.h" -#include "MultiFont.h" +#include "FallbackFonts.h" Font::Font(FontAsset* parentAsset, float size) : ManagedScriptingObject(SpawnParams(Guid::New(), Font::TypeInitializer)) @@ -293,6 +293,261 @@ void Font::ProcessText(const StringView& text, Array& outputLines } } +void Font::ProcessText(FallbackFonts* fallbacks, const StringView& text, Array& outputLines, API_PARAM(Ref) const TextLayoutOptions& layout) +{ + const Array& fallbackFonts = fallbacks->GetFontList(GetSize()); + float cursorX = 0; + int32 kerning; + BlockedTextLineCache tmpLine; + FontBlockCache tmpBlock; + FontCharacterEntry entry; + FontCharacterEntry previous; + int32 textLength = text.Length(); + float scale = layout.Scale / FontManager::FontScale; + float boundsWidth = layout.Bounds.GetWidth(); + float baseLinesDistanceScale = layout.BaseLinesGapScale * scale; + + tmpBlock.Location = Float2::Zero; + tmpBlock.Size = Float2::Zero; + tmpBlock.FirstCharIndex = 0; + tmpBlock.LastCharIndex = -1; + + tmpLine.Location = Float2::Zero; + tmpLine.Size = Float2::Zero; + tmpLine.Blocks = Array(); + + if (textLength == 0) { + return; + } + + int32 lastWrapCharIndex = INVALID_INDEX; + float lastWrapCharX = 0; + bool lastMoveLine = false; + // The index of the font used by the current block + int32 currentFontIndex = fallbacks->GetCharFallbackIndex(text[0], this); + // The maximum font height of the current line + float maxHeight = 0; + float maxAscender = 0; + float lastCursorX = 0; + + auto getFont = [&](int32 index)->Font* { + return index >= 0 ? fallbackFonts[index] : this; + }; + + // Process each character to split text into single blocks + for (int32 currentIndex = 0; currentIndex < textLength;) + { + bool moveLine = false; + bool moveBlock = false; + float xAdvance = 0; + int32 nextCharIndex = currentIndex + 1; + + // Submit line and block if text ends + if (nextCharIndex == textLength) { + moveLine = moveBlock = true; + } + + // Cache current character + const Char currentChar = text[currentIndex]; + const bool isWhitespace = StringUtils::IsWhitespace(currentChar); + + // Check if character can wrap words + const bool isWrapChar = !StringUtils::IsAlnum(currentChar) || isWhitespace || StringUtils::IsUpper(currentChar) || (currentChar >= 0x3040 && currentChar <= 0x9FFF); + if (isWrapChar && currentIndex != 0) + { + lastWrapCharIndex = currentIndex; + lastWrapCharX = cursorX; + } + + int32 nextFontIndex = currentFontIndex; + // Check if it's a newline character + if (currentChar == '\n') + { + // Break line + moveLine = moveBlock = true; + tmpBlock.LastCharIndex++; + } + else + { + // Get character entry + if (nextCharIndex < textLength) { + nextFontIndex = fallbacks->GetCharFallbackIndex(text[nextCharIndex], this, currentFontIndex); + } + + // Get character entry + getFont(currentFontIndex)->GetCharacter(currentChar, entry); + + maxHeight = Math::Max(maxHeight, + static_cast(getFont(currentFontIndex)->GetHeight())); + maxAscender = Math::Max(maxAscender, + static_cast(getFont(currentFontIndex)->GetAscender())); + + // Move block if the font changes or text ends + if (nextFontIndex != currentFontIndex || nextCharIndex == textLength) { + moveBlock = true; + } + + // Get kerning, only when the font hasn't changed + if (!isWhitespace && previous.IsValid && !moveBlock) + { + kerning = getFont(currentFontIndex)->GetKerning(previous.Character, entry.Character); + } + else + { + kerning = 0; + } + previous = entry; + xAdvance = (kerning + entry.AdvanceX) * scale; + + // Check if character fits the line or skip wrapping + if (cursorX + xAdvance <= boundsWidth || layout.TextWrapping == TextWrapping::NoWrap) + { + // Move character + cursorX += xAdvance; + tmpBlock.LastCharIndex++; + } + else if (layout.TextWrapping == TextWrapping::WrapWords) + { + if (lastWrapCharIndex != INVALID_INDEX) + { + // Skip moving twice for the same character + int32 lastLineLastCharIndex = outputLines.HasItems() && outputLines.Last().Blocks.HasItems() ? outputLines.Last().Blocks.Last().LastCharIndex : -10000; + if (lastLineLastCharIndex == lastWrapCharIndex || lastLineLastCharIndex == lastWrapCharIndex - 1 || lastLineLastCharIndex == lastWrapCharIndex - 2) + { + currentIndex = nextCharIndex; + lastMoveLine = moveLine; + continue; + } + + // Move line + const Char wrapChar = text[lastWrapCharIndex]; + moveLine = true; + moveBlock = tmpBlock.FirstCharIndex < lastWrapCharIndex; + + cursorX = lastWrapCharX; + if (StringUtils::IsWhitespace(wrapChar)) + { + // Skip whitespaces + tmpBlock.LastCharIndex = lastWrapCharIndex - 1; + nextCharIndex = currentIndex = lastWrapCharIndex + 1; + } + else + { + tmpBlock.LastCharIndex = lastWrapCharIndex - 1; + nextCharIndex = currentIndex = lastWrapCharIndex; + } + } + } + else if (layout.TextWrapping == TextWrapping::WrapChars) + { + // Move line + moveLine = true; + moveBlock = tmpBlock.FirstCharIndex < currentChar; + nextCharIndex = currentIndex; + + // Skip moving twice for the same character + if (lastMoveLine) + break; + } + } + + if (moveBlock) { + // Add block + tmpBlock.Size.X = lastCursorX - cursorX; + tmpBlock.Size.Y = baseLinesDistanceScale * getFont(currentFontIndex)->GetHeight(); + tmpBlock.LastCharIndex = Math::Max(tmpBlock.LastCharIndex, tmpBlock.FirstCharIndex); + tmpBlock.FallbackFontIndex = currentFontIndex; + tmpLine.Blocks.Add(tmpBlock); + + // Reset block + tmpBlock.Location.X = cursorX; + tmpBlock.FirstCharIndex = nextCharIndex; + tmpBlock.LastCharIndex = nextCharIndex - 1; + + currentFontIndex = nextFontIndex; + lastCursorX = cursorX; + } + + // Check if move to another line + if (moveLine) + { + // Add line + tmpLine.Size.X = cursorX; + tmpLine.Size.Y = baseLinesDistanceScale * maxHeight; + tmpLine.MaxAscender = maxAscender; + outputLines.Add(tmpLine); + + // Reset line + tmpLine.Blocks.Clear(); + tmpLine.Location.Y += baseLinesDistanceScale * maxHeight; + cursorX = 0; + tmpBlock.Location.X = cursorX; + lastWrapCharIndex = INVALID_INDEX; + lastWrapCharX = 0; + previous.IsValid = false; + + // Reset max font height + maxHeight = 0; + maxAscender = 0; + lastCursorX = 0; + } + + currentIndex = nextCharIndex; + lastMoveLine = moveLine; + } + + // Check if an additional line should be created + if (text[textLength - 1] == '\n') + { + // Add line + tmpLine.Size.X = cursorX; + tmpLine.Size.Y = baseLinesDistanceScale * maxHeight; + outputLines.Add(tmpLine); + + tmpLine.Location.Y += baseLinesDistanceScale * maxHeight; + } + + // Check amount of lines + if (outputLines.IsEmpty()) + return; + + float totalHeight = tmpLine.Location.Y; + + Float2 offset = Float2::Zero; + if (layout.VerticalAlignment == TextAlignment::Center) + { + offset.Y += (layout.Bounds.GetHeight() - totalHeight) * 0.5f; + } + else if (layout.VerticalAlignment == TextAlignment::Far) + { + offset.Y += layout.Bounds.GetHeight() - totalHeight; + } + for (int32 i = 0; i < outputLines.Count(); i++) + { + BlockedTextLineCache& line = outputLines[i]; + Float2 rootPos = line.Location + offset; + + // Fix upper left line corner to match desire text alignment + if (layout.HorizontalAlignment == TextAlignment::Center) + { + rootPos.X += (layout.Bounds.GetWidth() - line.Size.X) * 0.5f; + } + else if (layout.HorizontalAlignment == TextAlignment::Far) + { + rootPos.X += layout.Bounds.GetWidth() - line.Size.X; + } + + line.Location = rootPos; + + // Align all blocks to center in case they have different heights + for (int32 j = 0; j < line.Blocks.Count(); j++) + { + FontBlockCache& block = line.Blocks[j]; + block.Location.Y += (line.MaxAscender - getFont(block.FallbackFontIndex)->GetAscender()) / 2; + } + } +} + Float2 Font::MeasureText(const StringView& text, const TextLayoutOptions& layout) { // Check if there is no need to do anything @@ -314,6 +569,27 @@ Float2 Font::MeasureText(const StringView& text, const TextLayoutOptions& layout return max; } +Float2 Font::MeasureText(FallbackFonts* fallbacks, const StringView& text, const TextLayoutOptions& layout) +{ + // Check if there is no need to do anything + if (text.IsEmpty()) + return Float2::Zero; + + // Process text + Array lines; + ProcessText(fallbacks, text, lines, layout); + + // Calculate bounds + Float2 max = Float2::Zero; + for (int32 i = 0; i < lines.Count(); i++) + { + const BlockedTextLineCache& line = lines[i]; + max = Float2::Max(max, line.Location + line.Size); + } + + return max; +} + int32 Font::HitTestText(const StringView& text, const Float2& location, const TextLayoutOptions& layout) { // Check if there is no need to do anything @@ -388,6 +664,106 @@ int32 Font::HitTestText(const StringView& text, const Float2& location, const Te return smallestIndex; } +int32 Font::HitTestText(FallbackFonts* fallbacks, const StringView& text, const Float2& location, const TextLayoutOptions& layout) +{ + // Check if there is no need to do anything + if (text.Length() <= 0) + return 0; + + // Process text + const Array& fallbackFonts = fallbacks->GetFontList(GetSize()); + Array lines; + ProcessText(fallbacks, text, lines, layout); + ASSERT(lines.HasItems()); + float scale = layout.Scale / FontManager::FontScale; + + // Offset position to match lines origin space + Float2 rootOffset = layout.Bounds.Location + lines.First().Location; + Float2 testPoint = location - rootOffset; + + // Get block which may intersect with the position (it's possible because lines have fixed height) + int32 lineIndex = 0; + while (lineIndex < lines.Count()) + { + if (lines[lineIndex].Location.Y + lines[lineIndex].Size.Y >= location.Y) { + break; + } + + lineIndex++; + } + lineIndex = Math::Clamp(lineIndex, 0, lines.Count() - 1); + const BlockedTextLineCache& line = lines[lineIndex]; + + int32 blockIndex = 0; + while (blockIndex < line.Blocks.Count() - 1) + { + if (line.Location.X + line.Blocks[blockIndex + 1].Location.X >= location.X) { + break; + } + + blockIndex++; + } + const FontBlockCache& block = line.Blocks[blockIndex]; + float x = line.Location.X; + + // Check all characters in the line to find hit point + FontCharacterEntry previous; + FontCharacterEntry entry; + int32 smallestIndex = INVALID_INDEX; + float dst, smallestDst = MAX_float; + + auto getFont = [&](int32 index)->Font* { + return index >= 0 ? fallbackFonts[index] : this; + }; + + for (int32 currentIndex = block.FirstCharIndex; currentIndex <= block.LastCharIndex; currentIndex++) + { + // Cache current character + const Char currentChar = text[currentIndex]; + + getFont(block.FallbackFontIndex)->GetCharacter(currentChar, entry); + const bool isWhitespace = StringUtils::IsWhitespace(currentChar); + + // Apply kerning + if (!isWhitespace && previous.IsValid) + { + x += getFont(block.FallbackFontIndex)->GetKerning(previous.Character, entry.Character); + } + previous = entry; + + // Test + dst = Math::Abs(testPoint.X - x); + if (dst < smallestDst) + { + // Found closer character + smallestIndex = currentIndex; + smallestDst = dst; + } + else if (dst > smallestDst) + { + // Current char is worse so return the best result + return smallestIndex; + } + + // Move + x += entry.AdvanceX * scale; + } + + // Test line end edge + dst = Math::Abs(testPoint.X - x); + if (dst < smallestDst) + { + // Pointer is behind the last character in the line + smallestIndex = block.LastCharIndex; + + // Fix for last line + if (lineIndex == lines.Count() - 1) + smallestIndex++; + } + + return smallestIndex; +} + Float2 Font::GetCharPosition(const StringView& text, int32 index, const TextLayoutOptions& layout) { // Check if there is no need to do anything @@ -442,9 +818,68 @@ Float2 Font::GetCharPosition(const StringView& text, int32 index, const TextLayo return rootOffset + Float2(lines.Last().Location.X + lines.Last().Size.X, static_cast((lines.Count() - 1) * baseLinesDistance)); } -bool Font::ContainsChar(Char c) const +Float2 Font::GetCharPosition(FallbackFonts* fallbacks, const StringView& text, int32 index, const TextLayoutOptions& layout) { - return FT_Get_Char_Index(GetAsset()->GetFTFace(), c) > 0; + // Check if there is no need to do anything + if (text.IsEmpty()) + return layout.Bounds.Location; + + // Process text + const Array& fallbackFonts = fallbacks->GetFontList(GetSize()); + Array lines; + ProcessText(fallbacks, text, lines, layout); + ASSERT(lines.HasItems()); + float scale = layout.Scale / FontManager::FontScale; + float baseLinesDistance = layout.BaseLinesGapScale * scale; + Float2 rootOffset = layout.Bounds.Location; + + // Find line with that position + FontCharacterEntry previous; + FontCharacterEntry entry; + + auto getFont = [&](int32 index)->Font* { + return index >= 0 ? fallbackFonts[index] : this; + }; + + for (int32 lineIndex = 0; lineIndex < lines.Count(); lineIndex++) + { + const BlockedTextLineCache& line = lines[lineIndex]; + for (int32 blockIndex = 0; blockIndex < line.Blocks.Count(); blockIndex++) + { + const FontBlockCache& block = line.Blocks[blockIndex]; + // Check if desire position is somewhere inside characters in line range + if (Math::IsInRange(index, block.FirstCharIndex, block.LastCharIndex)) + { + float x = line.Location.X + block.Location.X; + float y = line.Location.Y + block.Location.Y; + + // Check all characters in the line + for (int32 currentIndex = block.FirstCharIndex; currentIndex < index; currentIndex++) + { + // Cache current character + const Char currentChar = text[currentIndex]; + getFont(block.FallbackFontIndex)->GetCharacter(currentChar, entry); + const bool isWhitespace = StringUtils::IsWhitespace(currentChar); + + // Apply kerning + if (!isWhitespace && previous.IsValid) + { + x += getFont(block.FallbackFontIndex)->GetKerning(previous.Character, entry.Character); + } + previous = entry; + + // Move + x += entry.AdvanceX * scale; + } + + // Upper left corner of the character + return rootOffset + Float2(x, y); + } + } + } + + // Position after last character in the last line + return rootOffset + Float2(lines.Last().Location.X + lines.Last().Size.X, lines.Last().Location.Y); } void Font::FlushFaceSize() const diff --git a/Source/Engine/Render2D/Font.h b/Source/Engine/Render2D/Font.h index 453872582..d932888fb 100644 --- a/Source/Engine/Render2D/Font.h +++ b/Source/Engine/Render2D/Font.h @@ -10,8 +10,9 @@ #include "TextLayoutOptions.h" class FontAsset; +class FallbackFonts; struct FontTextureAtlasSlot; -struct MultiFontLineCache; +struct BlockedTextLineCache; // The default DPI that engine is using #define DefaultDPI 96 @@ -120,6 +121,73 @@ struct TIsPODType enum { Value = true }; }; +/// +/// The font block info generated during text processing. +/// +API_STRUCT(NoDefault) struct FontBlockCache +{ + DECLARE_SCRIPTING_TYPE_MINIMAL(FontBlockCache); + + /// + /// The root position of the block (upper left corner), relative to line. + /// + API_FIELD() Float2 Location; + + /// + /// The height of the current block + /// + API_FIELD() Float2 Size; + + /// + /// The first character index (from the input text). + /// + API_FIELD() int32 FirstCharIndex; + + /// + /// The last character index (from the input text), inclusive. + /// + API_FIELD() int32 LastCharIndex; + + /// + /// Indicates the fallback font to render this block with, -1 if doesn't require fallback. + /// + API_FIELD() int32 FallbackFontIndex; +}; + +template<> +struct TIsPODType +{ + enum { Value = true }; +}; + +/// +/// Line of font blocks info generated during text processing. +/// +API_STRUCT(NoDefault) struct BlockedTextLineCache +{ + DECLARE_SCRIPTING_TYPE_MINIMAL(BlockedTextLineCache); + + /// + /// The root position of the line (upper left corner). + /// + API_FIELD() Float2 Location; + + /// + /// The line bounds (width and height). + /// + API_FIELD() Float2 Size; + + /// + /// The maximum ascender of the line. + /// + API_FIELD() float MaxAscender; + + /// + /// The index of the font to render with + /// + API_FIELD() Array Blocks; +}; + // Font glyph metrics: // // xmin xmax @@ -388,6 +456,62 @@ public: return ProcessText(textRange.Substring(text), TextLayoutOptions()); } + /// + /// Processes text to get cached lines for rendering. + /// + /// The input text. + /// The layout properties. + /// The output lines list. + void ProcessText(FallbackFonts* fallbacks, const StringView& text, Array& outputLines, API_PARAM(Ref) const TextLayoutOptions& layout); + + /// + /// Processes text to get cached lines for rendering. + /// + /// The input text. + /// The layout properties. + /// The output lines list. + API_FUNCTION() Array ProcessText(FallbackFonts* fallbacks, const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout) + { + Array lines; + ProcessText(fallbacks, text, lines, layout); + return lines; + } + + /// + /// Processes text to get cached lines for rendering. + /// + /// The input text. + /// The input text range (substring range of the input text parameter). + /// The layout properties. + /// The output lines list. + API_FUNCTION() Array ProcessText(FallbackFonts* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange, API_PARAM(Ref) const TextLayoutOptions& layout) + { + Array lines; + ProcessText(fallbacks, textRange.Substring(text), lines, layout); + return lines; + } + + /// + /// Processes text to get cached lines for rendering. + /// + /// The input text. + /// The output lines list. + API_FUNCTION() FORCE_INLINE Array ProcessText(FallbackFonts* fallbacks, const StringView& text) + { + return ProcessText(fallbacks, text, TextLayoutOptions()); + } + + /// + /// Processes text to get cached lines for rendering. + /// + /// The input text. + /// The input text range (substring range of the input text parameter). + /// The output lines list. + API_FUNCTION() FORCE_INLINE Array ProcessText(FallbackFonts* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange) + { + return ProcessText(fallbacks, textRange.Substring(text), TextLayoutOptions()); + } + /// /// Measures minimum size of the rectangle that will be needed to draw given text. /// @@ -429,6 +553,56 @@ public: return MeasureText(textRange.Substring(text), TextLayoutOptions()); } + /// + /// Measures minimum size of the rectangle that will be needed to draw given text. + /// + /// The input text to test. + /// The layout properties. + /// The minimum size for that text and fot to render properly. + API_FUNCTION() Float2 MeasureText(FallbackFonts* fallbacks, const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout); + + /// + /// Measures minimum size of the rectangle that will be needed to draw given text. + /// + /// The input text to test. + /// The input text range (substring range of the input text parameter). + /// The layout properties. + /// The minimum size for that text and fot to render properly. + API_FUNCTION() Float2 MeasureText(FallbackFonts* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange, API_PARAM(Ref) const TextLayoutOptions& layout) + { + return MeasureText(fallbacks, textRange.Substring(text), layout); + } + + /// + /// Measures minimum size of the rectangle that will be needed to draw given text + /// . + /// The input text to test. + /// The minimum size for that text and fot to render properly. + API_FUNCTION() FORCE_INLINE Float2 MeasureText(FallbackFonts* fallbacks, const StringView& text) + { + return MeasureText(fallbacks, text, TextLayoutOptions()); + } + + /// + /// Measures minimum size of the rectangle that will be needed to draw given text + /// . + /// The input text to test. + /// The input text range (substring range of the input text parameter). + /// The minimum size for that text and fot to render properly. + API_FUNCTION() FORCE_INLINE Float2 MeasureText(FallbackFonts* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange) + { + return MeasureText(fallbacks, textRange.Substring(text), TextLayoutOptions()); + } + + /// + /// Calculates hit character index at given location. + /// + /// The input text to test. + /// The input location to test. + /// The text layout properties. + /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). + API_FUNCTION() int32 HitTestText(const StringView& text, const Float2& location, API_PARAM(Ref) const TextLayoutOptions& layout); + /// /// Calculates hit character index at given location. /// @@ -442,15 +616,6 @@ public: return HitTestText(textRange.Substring(text), location, layout); } - /// - /// Calculates hit character index at given location. - /// - /// The input text to test. - /// The input location to test. - /// The text layout properties. - /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). - API_FUNCTION() int32 HitTestText(const StringView& text, const Float2& location, API_PARAM(Ref) const TextLayoutOptions& layout); - /// /// Calculates hit character index at given location. /// @@ -474,6 +639,51 @@ public: return HitTestText(textRange.Substring(text), location, TextLayoutOptions()); } + /// + /// Calculates hit character index at given location. + /// + /// The input text to test. + /// The input location to test. + /// The text layout properties. + /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). + API_FUNCTION() int32 HitTestText(FallbackFonts* fallbacks, const StringView& text, const Float2& location, API_PARAM(Ref) const TextLayoutOptions& layout); + + /// + /// Calculates hit character index at given location. + /// + /// The input text to test. + /// The input text range (substring range of the input text parameter). + /// The input location to test. + /// The text layout properties. + /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). + API_FUNCTION() int32 HitTestText(FallbackFonts* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Float2& location, API_PARAM(Ref) const TextLayoutOptions& layout) + { + return HitTestText(fallbacks, textRange.Substring(text), location, layout); + } + + /// + /// Calculates hit character index at given location. + /// + /// The input text to test. + /// The input location to test. + /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). + API_FUNCTION() FORCE_INLINE int32 HitTestText(FallbackFonts* fallbacks, const StringView& text, const Float2& location) + { + return HitTestText(fallbacks, text, location, TextLayoutOptions()); + } + + /// + /// Calculates hit character index at given location. + /// + /// The input text to test. + /// The input text range (substring range of the input text parameter). + /// The input location to test. + /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). + API_FUNCTION() FORCE_INLINE int32 HitTestText(FallbackFonts* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Float2& location) + { + return HitTestText(fallbacks, textRange.Substring(text), location, TextLayoutOptions()); + } + /// /// Calculates character position for given text and character index. /// @@ -520,11 +730,49 @@ public: } /// - /// Check if the font contains the glyph of a char + /// Calculates character position for given text and character index. /// - /// The char to test. - /// True if the font contains the glyph of the char, otherwise false. - API_FUNCTION() FORCE_INLINE bool ContainsChar(Char c) const; + /// The input text to test. + /// The text position to get coordinates of. + /// The text layout properties. + /// The character position (upper left corner which can be used for a caret position). + API_FUNCTION() Float2 GetCharPosition(FallbackFonts* fallbacks, const StringView& text, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout); + + /// + /// Calculates character position for given text and character index. + /// + /// The input text to test. + /// The input text range (substring range of the input text parameter). + /// The text position to get coordinates of. + /// The text layout properties. + /// The character position (upper left corner which can be used for a caret position). + API_FUNCTION() Float2 GetCharPosition(FallbackFonts* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout) + { + return GetCharPosition(fallbacks, textRange.Substring(text), index, layout); + } + + /// + /// Calculates character position for given text and character index + /// + /// The input text to test. + /// The text position to get coordinates of. + /// The character position (upper left corner which can be used for a caret position). + API_FUNCTION() FORCE_INLINE Float2 GetCharPosition(FallbackFonts* fallbacks, const StringView& text, int32 index) + { + return GetCharPosition(fallbacks, text, index, TextLayoutOptions()); + } + + /// + /// Calculates character position for given text and character index + /// + /// The input text to test. + /// The input text range (substring range of the input text parameter). + /// The text position to get coordinates of. + /// The character position (upper left corner which can be used for a caret position). + API_FUNCTION() FORCE_INLINE Float2 GetCharPosition(FallbackFonts* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange, int32 index) + { + return GetCharPosition(fallbacks, textRange.Substring(text), index, TextLayoutOptions()); + } /// /// Flushes the size of the face with the Free Type library backend. diff --git a/Source/Engine/Render2D/FontAsset.cpp b/Source/Engine/Render2D/FontAsset.cpp index a477e5f4d..1e94b19b1 100644 --- a/Source/Engine/Render2D/FontAsset.cpp +++ b/Source/Engine/Render2D/FontAsset.cpp @@ -199,6 +199,10 @@ bool FontAsset::Save(const StringView& path) #endif +bool FontAsset::ContainsChar(Char c) const { + return FT_Get_Char_Index(GetFTFace(), c) > 0; +} + void FontAsset::Invalidate() { ScopeLock lock(Locker); diff --git a/Source/Engine/Render2D/FontAsset.h b/Source/Engine/Render2D/FontAsset.h index 4dea84a5b..a773a8ad6 100644 --- a/Source/Engine/Render2D/FontAsset.h +++ b/Source/Engine/Render2D/FontAsset.h @@ -174,6 +174,13 @@ public: API_FUNCTION() bool Save(const StringView& path = StringView::Empty); #endif + /// + /// Check if the font contains the glyph of a char + /// + /// The char to test. + /// True if the font contains the glyph of the char, otherwise false. + API_FUNCTION() FORCE_INLINE bool ContainsChar(Char c) const; + /// /// Invalidates all cached dynamic font atlases using this font. Can be used to reload font characters after changing font asset options. /// diff --git a/Source/Engine/Render2D/MultiFont.cpp b/Source/Engine/Render2D/MultiFont.cpp deleted file mode 100644 index 4a510a9f8..000000000 --- a/Source/Engine/Render2D/MultiFont.cpp +++ /dev/null @@ -1,431 +0,0 @@ -#include "MultiFont.h" -#include "FontManager.h" -#include "Engine/Core/Math/Math.h" - -MultiFont::MultiFont(const Array& fonts) - : ManagedScriptingObject(SpawnParams(Guid::New(), Font::TypeInitializer)), - _fonts(fonts) -{ - -} - -void MultiFont::ProcessText(const StringView& text, Array& outputLines, API_PARAM(Ref) const TextLayoutOptions& layout) -{ - float cursorX = 0; - int32 kerning; - MultiFontLineCache tmpLine; - MultiFontBlockCache tmpBlock; - FontCharacterEntry entry; - FontCharacterEntry previous; - int32 textLength = text.Length(); - float scale = layout.Scale / FontManager::FontScale; - float boundsWidth = layout.Bounds.GetWidth(); - float baseLinesDistanceScale = layout.BaseLinesGapScale * scale; - - tmpBlock.Location = Float2::Zero; - tmpBlock.Size = Float2::Zero; - tmpBlock.FirstCharIndex = 0; - tmpBlock.LastCharIndex = -1; - - tmpLine.Location = Float2::Zero; - tmpLine.Size = Float2::Zero; - tmpLine.Blocks = Array(); - - if (textLength == 0) { - return; - } - - int32 lastWrapCharIndex = INVALID_INDEX; - float lastWrapCharX = 0; - bool lastMoveLine = false; - // The index of the font used by the current block - int32 currentFontIndex = GetCharFontIndex(text[0], 0); - // The maximum font height of the current line - float maxHeight = 0; - float maxAscender = 0; - float lastCursorX = 0; - - // Process each character to split text into single lines - for (int32 currentIndex = 0; currentIndex < textLength;) - { - bool moveLine = false; - bool moveBlock = false; - float xAdvance = 0; - int32 nextCharIndex = currentIndex + 1; - - // Submit line and block if text ends - if (nextCharIndex == textLength) { - moveLine = moveBlock = true; - } - - // Cache current character - const Char currentChar = text[currentIndex]; - const bool isWhitespace = StringUtils::IsWhitespace(currentChar); - - // Check if character can wrap words - const bool isWrapChar = !StringUtils::IsAlnum(currentChar) || isWhitespace || StringUtils::IsUpper(currentChar) || (currentChar >= 0x3040 && currentChar <= 0x9FFF); - if (isWrapChar && currentIndex != 0) - { - lastWrapCharIndex = currentIndex; - lastWrapCharX = cursorX; - } - - int32 nextFontIndex = currentFontIndex; - // Check if it's a newline character - if (currentChar == '\n') - { - // Break line - moveLine = moveBlock = true; - tmpBlock.LastCharIndex++; - } - else - { - // Get character entry - if (nextCharIndex < textLength) { - nextFontIndex = GetCharFontIndex(text[nextCharIndex], currentFontIndex); - } - - // Get character entry - _fonts[currentFontIndex]->GetCharacter(currentChar, entry); - maxHeight = Math::Max(maxHeight, static_cast(_fonts[currentFontIndex]->GetHeight())); - maxAscender = Math::Max(maxAscender, static_cast(_fonts[currentFontIndex]->GetAscender())); - - // Move block if the font changes or text ends - if (nextFontIndex != currentFontIndex || nextCharIndex == textLength) { - moveBlock = true; - } - - // Get kerning, only when the font hasn't changed - if (!isWhitespace && previous.IsValid && !moveBlock) - { - kerning = _fonts[currentFontIndex]->GetKerning(previous.Character, entry.Character); - } - else - { - kerning = 0; - } - previous = entry; - xAdvance = (kerning + entry.AdvanceX) * scale; - - // Check if character fits the line or skip wrapping - if (cursorX + xAdvance <= boundsWidth || layout.TextWrapping == TextWrapping::NoWrap) - { - // Move character - cursorX += xAdvance; - tmpBlock.LastCharIndex++; - } - else if (layout.TextWrapping == TextWrapping::WrapWords) - { - if (lastWrapCharIndex != INVALID_INDEX) - { - // Skip moving twice for the same character - int32 lastLineLastCharIndex = outputLines.HasItems() && outputLines.Last().Blocks.HasItems() ? outputLines.Last().Blocks.Last().LastCharIndex : -10000; - if (lastLineLastCharIndex == lastWrapCharIndex || lastLineLastCharIndex == lastWrapCharIndex - 1 || lastLineLastCharIndex == lastWrapCharIndex - 2) - { - currentIndex = nextCharIndex; - lastMoveLine = moveLine; - continue; - } - - // Move line - const Char wrapChar = text[lastWrapCharIndex]; - moveLine = true; - moveBlock = tmpBlock.FirstCharIndex < lastWrapCharIndex; - - cursorX = lastWrapCharX; - if (StringUtils::IsWhitespace(wrapChar)) - { - // Skip whitespaces - tmpBlock.LastCharIndex = lastWrapCharIndex - 1; - nextCharIndex = currentIndex = lastWrapCharIndex + 1; - } - else - { - tmpBlock.LastCharIndex = lastWrapCharIndex - 1; - nextCharIndex = currentIndex = lastWrapCharIndex; - } - } - } - else if (layout.TextWrapping == TextWrapping::WrapChars) - { - // Move line - moveLine = true; - moveBlock = tmpBlock.FirstCharIndex < currentChar; - nextCharIndex = currentIndex; - - // Skip moving twice for the same character - if (lastMoveLine) - break; - } - } - - if (moveBlock) { - // Add block - tmpBlock.Size.X = lastCursorX - cursorX; - tmpBlock.Size.Y = baseLinesDistanceScale * _fonts[currentFontIndex]->GetHeight(); - tmpBlock.LastCharIndex = Math::Max(tmpBlock.LastCharIndex, tmpBlock.FirstCharIndex); - tmpBlock.FontIndex = currentFontIndex; - tmpLine.Blocks.Add(tmpBlock); - - // Reset block - tmpBlock.Location.X = cursorX; - tmpBlock.FirstCharIndex = nextCharIndex; - tmpBlock.LastCharIndex = nextCharIndex - 1; - - currentFontIndex = nextFontIndex; - lastCursorX = cursorX; - } - - // Check if move to another line - if (moveLine) - { - // Add line - tmpLine.Size.X = cursorX; - tmpLine.Size.Y = baseLinesDistanceScale * maxHeight; - tmpLine.MaxAscender = maxAscender; - outputLines.Add(tmpLine); - - // Reset line - tmpLine.Blocks.Clear(); - tmpLine.Location.Y += baseLinesDistanceScale * maxHeight; - cursorX = 0; - tmpBlock.Location.X = cursorX; - lastWrapCharIndex = INVALID_INDEX; - lastWrapCharX = 0; - previous.IsValid = false; - - // Reset max font height - maxHeight = 0; - maxAscender = 0; - lastCursorX = 0; - } - - currentIndex = nextCharIndex; - lastMoveLine = moveLine; - } - - // Check if an additional line should be created - if (text[textLength - 1] == '\n') - { - // Add line - tmpLine.Size.X = cursorX; - tmpLine.Size.Y = baseLinesDistanceScale * maxHeight; - outputLines.Add(tmpLine); - - tmpLine.Location.Y += baseLinesDistanceScale * maxHeight; - } - - // Check amount of lines - if (outputLines.IsEmpty()) - return; - - float totalHeight = tmpLine.Location.Y; - - Float2 offset = Float2::Zero; - if (layout.VerticalAlignment == TextAlignment::Center) - { - offset.Y += (layout.Bounds.GetHeight() - totalHeight) * 0.5f; - } - else if (layout.VerticalAlignment == TextAlignment::Far) - { - offset.Y += layout.Bounds.GetHeight() - totalHeight; - } - for (int32 i = 0; i < outputLines.Count(); i++) - { - MultiFontLineCache& line = outputLines[i]; - Float2 rootPos = line.Location + offset; - - // Fix upper left line corner to match desire text alignment - if (layout.HorizontalAlignment == TextAlignment::Center) - { - rootPos.X += (layout.Bounds.GetWidth() - line.Size.X) * 0.5f; - } - else if (layout.HorizontalAlignment == TextAlignment::Far) - { - rootPos.X += layout.Bounds.GetWidth() - line.Size.X; - } - - line.Location = rootPos; - - // Align all blocks to center in case they have different heights - for (int32 j = 0; j < line.Blocks.Count(); j++) - { - MultiFontBlockCache& block = line.Blocks[j]; - block.Location.Y += (line.MaxAscender - _fonts[block.FontIndex]->GetAscender()) / 2; - } - } -} - -Float2 MultiFont::GetCharPosition(const StringView& text, int32 index, const TextLayoutOptions& layout) -{ - // Check if there is no need to do anything - if (text.IsEmpty()) - return layout.Bounds.Location; - - // Process text - Array lines; - ProcessText(text, lines, layout); - ASSERT(lines.HasItems()); - float scale = layout.Scale / FontManager::FontScale; - float baseLinesDistance = layout.BaseLinesGapScale * scale; - Float2 rootOffset = layout.Bounds.Location; - - // Find line with that position - FontCharacterEntry previous; - FontCharacterEntry entry; - for (int32 lineIndex = 0; lineIndex < lines.Count(); lineIndex++) - { - const MultiFontLineCache& line = lines[lineIndex]; - for (int32 blockIndex = 0; blockIndex < line.Blocks.Count(); blockIndex++) - { - const MultiFontBlockCache& block = line.Blocks[blockIndex]; - // Check if desire position is somewhere inside characters in line range - if (Math::IsInRange(index, block.FirstCharIndex, block.LastCharIndex)) - { - float x = line.Location.X + block.Location.X; - float y = line.Location.Y + block.Location.Y; - - // Check all characters in the line - for (int32 currentIndex = block.FirstCharIndex; currentIndex < index; currentIndex++) - { - // Cache current character - const Char currentChar = text[currentIndex]; - _fonts[block.FontIndex]->GetCharacter(currentChar, entry); - const bool isWhitespace = StringUtils::IsWhitespace(currentChar); - - // Apply kerning - if (!isWhitespace && previous.IsValid) - { - x += _fonts[block.FontIndex]->GetKerning(previous.Character, entry.Character); - } - previous = entry; - - // Move - x += entry.AdvanceX * scale; - } - - // Upper left corner of the character - return rootOffset + Float2(x, y); - } - } - } - - // Position after last character in the last line - return rootOffset + Float2(lines.Last().Location.X + lines.Last().Size.X, lines.Last().Location.Y); -} - -int32 MultiFont::HitTestText(const StringView& text, const Float2& location, const TextLayoutOptions& layout) -{ - // Check if there is no need to do anything - if (text.Length() <= 0) - return 0; - - // Process text - Array lines; - ProcessText(text, lines, layout); - ASSERT(lines.HasItems()); - float scale = layout.Scale / FontManager::FontScale; - - // Offset position to match lines origin space - Float2 rootOffset = layout.Bounds.Location + lines.First().Location; - Float2 testPoint = location - rootOffset; - - // Get block which may intersect with the position (it's possible because lines have fixed height) - int32 lineIndex = 0; - while (lineIndex < lines.Count()) - { - if (lines[lineIndex].Location.Y + lines[lineIndex].Size.Y >= location.Y) { - break; - } - - lineIndex++; - } - lineIndex = Math::Clamp(lineIndex, 0, lines.Count() - 1); - const MultiFontLineCache& line = lines[lineIndex]; - - int32 blockIndex = 0; - while (blockIndex < line.Blocks.Count() - 1) - { - if (line.Location.X + line.Blocks[blockIndex + 1].Location.X >= location.X) { - break; - } - - blockIndex++; - } - const MultiFontBlockCache& block = line.Blocks[blockIndex]; - float x = line.Location.X; - - // Check all characters in the line to find hit point - FontCharacterEntry previous; - FontCharacterEntry entry; - int32 smallestIndex = INVALID_INDEX; - float dst, smallestDst = MAX_float; - for (int32 currentIndex = block.FirstCharIndex; currentIndex <= block.LastCharIndex; currentIndex++) - { - // Cache current character - const Char currentChar = text[currentIndex]; - - _fonts[block.FontIndex]->GetCharacter(currentChar, entry); - const bool isWhitespace = StringUtils::IsWhitespace(currentChar); - - // Apply kerning - if (!isWhitespace && previous.IsValid) - { - x += _fonts[block.FontIndex]->GetKerning(previous.Character, entry.Character); - } - previous = entry; - - // Test - dst = Math::Abs(testPoint.X - x); - if (dst < smallestDst) - { - // Found closer character - smallestIndex = currentIndex; - smallestDst = dst; - } - else if (dst > smallestDst) - { - // Current char is worse so return the best result - return smallestIndex; - } - - // Move - x += entry.AdvanceX * scale; - } - - // Test line end edge - dst = Math::Abs(testPoint.X - x); - if (dst < smallestDst) - { - // Pointer is behind the last character in the line - smallestIndex = block.LastCharIndex; - - // Fix for last line - if (lineIndex == lines.Count() - 1) - smallestIndex++; - } - - return smallestIndex; -} - -Float2 MultiFont::MeasureText(const StringView& text, const TextLayoutOptions& layout) -{ - // Check if there is no need to do anything - if (text.IsEmpty()) - return Float2::Zero; - - // Process text - Array lines; - ProcessText(text, lines, layout); - - // Calculate bounds - Float2 max = Float2::Zero; - for (int32 i = 0; i < lines.Count(); i++) - { - const MultiFontLineCache& line = lines[i]; - max = Float2::Max(max, line.Location + line.Size); - } - - return max; -} - diff --git a/Source/Engine/Render2D/MultiFont.h b/Source/Engine/Render2D/MultiFont.h deleted file mode 100644 index 870b8e668..000000000 --- a/Source/Engine/Render2D/MultiFont.h +++ /dev/null @@ -1,353 +0,0 @@ -#pragma once - -#include "Engine/Core/Collections/Array.h" -#include "Engine/Core/Collections/Dictionary.h" -#include "Font.h" -#include "FontAsset.h" - -struct TextRange; -class Font; -class FontAsset; - -/// -/// The font block info generated during text processing. -/// -API_STRUCT(NoDefault) struct MultiFontBlockCache -{ - DECLARE_SCRIPTING_TYPE_MINIMAL(MultiFontBlockCache); - - /// - /// The root position of the block (upper left corner), relative to line. - /// - API_FIELD() Float2 Location; - - /// - /// The height of the current block - /// - API_FIELD() Float2 Size; - - /// - /// The first character index (from the input text). - /// - API_FIELD() int32 FirstCharIndex; - - /// - /// The last character index (from the input text), inclusive. - /// - API_FIELD() int32 LastCharIndex; - - /// - /// The index of the font to render with - /// - API_FIELD() int32 FontIndex; -}; - -template<> -struct TIsPODType -{ - enum { Value = true }; -}; - -/// -/// Line of font blocks info generated during text processing. -/// -API_STRUCT(NoDefault) struct MultiFontLineCache -{ - DECLARE_SCRIPTING_TYPE_MINIMAL(MultiFontLineCache); - - /// - /// The root position of the line (upper left corner). - /// - API_FIELD() Float2 Location; - - /// - /// The line bounds (width and height). - /// - API_FIELD() Float2 Size; - - /// - /// The maximum ascender of the line. - /// - API_FIELD() float MaxAscender; - - /// - /// The index of the font to render with - /// - API_FIELD() Array Blocks; -}; - -API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API MultiFont : public ManagedScriptingObject -{ - DECLARE_SCRIPTING_TYPE_NO_SPAWN(MultiFont); -private: - Array _fonts; - -public: - MultiFont(const Array& fonts); - - API_FUNCTION() FORCE_INLINE static MultiFont* Create(const Array& fonts) { - return New(fonts); - } - - API_FUNCTION() FORCE_INLINE static MultiFont* Create(const Array& fontAssets, float size) { - Array fonts; - fonts.Resize(fontAssets.Count()); - for (int32 i = 0; i < fontAssets.Count(); i++) - { - fonts[i] = fontAssets[i]->CreateFont(size); - } - - return New(fonts); - } - - API_PROPERTY() FORCE_INLINE Array& GetFonts() { - return _fonts; - } - - API_PROPERTY() FORCE_INLINE void SetFonts(const Array& val) { - _fonts = val; - } - - API_PROPERTY() FORCE_INLINE int32 GetMaxHeight() { - int32 maxHeight = 0; - for (int32 i = 0; i < _fonts.Count(); i++) - { - if (_fonts[i]) { - maxHeight = Math::Max(maxHeight, _fonts[i]->GetHeight()); - } - } - - return maxHeight; - } - - API_PROPERTY() FORCE_INLINE int32 GetMaxAscender() { - int32 maxAsc = 0; - for (int32 i = 0; i < _fonts.Count(); i++) - { - if (_fonts[i]) { - maxAsc = Math::Max(maxAsc, _fonts[i]->GetAscender()); - } - } - - return maxAsc; - } - - /// - /// Processes text to get cached lines for rendering. - /// - /// The input text. - /// The layout properties. - /// The output lines list. - void ProcessText(const StringView& text, Array& outputLines, API_PARAM(Ref) const TextLayoutOptions& layout); - - /// - /// Processes text to get cached lines for rendering. - /// - /// The input text. - /// The layout properties. - /// The output lines list. - API_FUNCTION() Array ProcessText(const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout) - { - Array lines; - ProcessText(text, lines, layout); - return lines; - } - - /// - /// Processes text to get cached lines for rendering. - /// - /// The input text. - /// The input text range (substring range of the input text parameter). - /// The layout properties. - /// The output lines list. - API_FUNCTION() Array ProcessText(const StringView& text, API_PARAM(Ref) const TextRange& textRange, API_PARAM(Ref) const TextLayoutOptions& layout) - { - Array lines; - ProcessText(textRange.Substring(text), lines, layout); - return lines; - } - - /// - /// Processes text to get cached lines for rendering. - /// - /// The input text. - /// The output lines list. - API_FUNCTION() FORCE_INLINE Array ProcessText(const StringView& text) - { - return ProcessText(text, TextLayoutOptions()); - } - - /// - /// Processes text to get cached lines for rendering. - /// - /// The input text. - /// The input text range (substring range of the input text parameter). - /// The output lines list. - API_FUNCTION() FORCE_INLINE Array ProcessText(const StringView& text, API_PARAM(Ref) const TextRange& textRange) - { - return ProcessText(textRange.Substring(text), TextLayoutOptions()); - } - - /// - /// Measures minimum size of the rectangle that will be needed to draw given text. - /// - /// The input text to test. - /// The layout properties. - /// The minimum size for that text and fot to render properly. - API_FUNCTION() Float2 MeasureText(const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout); - - /// - /// Measures minimum size of the rectangle that will be needed to draw given text. - /// - /// The input text to test. - /// The input text range (substring range of the input text parameter). - /// The layout properties. - /// The minimum size for that text and fot to render properly. - API_FUNCTION() Float2 MeasureText(const StringView& text, API_PARAM(Ref) const TextRange& textRange, API_PARAM(Ref) const TextLayoutOptions& layout) - { - return MeasureText(textRange.Substring(text), layout); - } - - /// - /// Measures minimum size of the rectangle that will be needed to draw given text - /// . - /// The input text to test. - /// The minimum size for that text and fot to render properly. - API_FUNCTION() FORCE_INLINE Float2 MeasureText(const StringView& text) - { - return MeasureText(text, TextLayoutOptions()); - } - - /// - /// Measures minimum size of the rectangle that will be needed to draw given text - /// . - /// The input text to test. - /// The input text range (substring range of the input text parameter). - /// The minimum size for that text and fot to render properly. - API_FUNCTION() FORCE_INLINE Float2 MeasureText(const StringView& text, API_PARAM(Ref) const TextRange& textRange) - { - return MeasureText(textRange.Substring(text), TextLayoutOptions()); - } - - /// - /// Calculates hit character index at given location. - /// - /// The input text to test. - /// The input text range (substring range of the input text parameter). - /// The input location to test. - /// The text layout properties. - /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). - API_FUNCTION() int32 HitTestText(const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Float2& location, API_PARAM(Ref) const TextLayoutOptions& layout) - { - return HitTestText(textRange.Substring(text), location, layout); - } - - /// - /// Calculates hit character index at given location. - /// - /// The input text to test. - /// The input location to test. - /// The text layout properties. - /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). - API_FUNCTION() int32 HitTestText(const StringView& text, const Float2& location, API_PARAM(Ref) const TextLayoutOptions& layout); - - /// - /// Calculates hit character index at given location. - /// - /// The input text to test. - /// The input location to test. - /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). - API_FUNCTION() FORCE_INLINE int32 HitTestText(const StringView& text, const Float2& location) - { - return HitTestText(text, location, TextLayoutOptions()); - } - - /// - /// Calculates hit character index at given location. - /// - /// The input text to test. - /// The input text range (substring range of the input text parameter). - /// The input location to test. - /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). - API_FUNCTION() FORCE_INLINE int32 HitTestText(const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Float2& location) - { - return HitTestText(textRange.Substring(text), location, TextLayoutOptions()); - } - - /// - /// Calculates character position for given text and character index. - /// - /// The input text to test. - /// The text position to get coordinates of. - /// The text layout properties. - /// The character position (upper left corner which can be used for a caret position). - API_FUNCTION() Float2 GetCharPosition(const StringView& text, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout); - - /// - /// Calculates character position for given text and character index. - /// - /// The input text to test. - /// The input text range (substring range of the input text parameter). - /// The text position to get coordinates of. - /// The text layout properties. - /// The character position (upper left corner which can be used for a caret position). - API_FUNCTION() Float2 GetCharPosition(const StringView& text, API_PARAM(Ref) const TextRange& textRange, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout) - { - return GetCharPosition(textRange.Substring(text), index, layout); - } - - /// - /// Calculates character position for given text and character index - /// - /// The input text to test. - /// The text position to get coordinates of. - /// The character position (upper left corner which can be used for a caret position). - API_FUNCTION() FORCE_INLINE Float2 GetCharPosition(const StringView& text, int32 index) - { - return GetCharPosition(text, index, TextLayoutOptions()); - } - - /// - /// Calculates character position for given text and character index - /// - /// The input text to test. - /// The input text range (substring range of the input text parameter). - /// The text position to get coordinates of. - /// The character position (upper left corner which can be used for a caret position). - API_FUNCTION() FORCE_INLINE Float2 GetCharPosition(const StringView& text, API_PARAM(Ref) const TextRange& textRange, int32 index) - { - return GetCharPosition(textRange.Substring(text), index, TextLayoutOptions()); - } - - /// - /// Gets the index of the font that should be used to render the char - /// - /// The font list. - /// The char. - /// Number to return if char cannot be found. - /// - API_FUNCTION() FORCE_INLINE int32 GetCharFontIndex(Char c, int32 missing = -1) { - int32 fontIndex = 0; - while (fontIndex < _fonts.Count() && _fonts[fontIndex] && !_fonts[fontIndex]->ContainsChar(c)) - { - fontIndex++; - } - - if (fontIndex == _fonts.Count()) { - return missing; - } - - return fontIndex; - } - - API_FUNCTION() FORCE_INLINE bool Verify() { - for (int32 i = 0; i < _fonts.Count(); i++) - { - if (!_fonts[i]) { - return false; - } - } - - return true; - } -}; diff --git a/Source/Engine/Render2D/Render2D.cpp b/Source/Engine/Render2D/Render2D.cpp index c68c2445d..4e0cacb44 100644 --- a/Source/Engine/Render2D/Render2D.cpp +++ b/Source/Engine/Render2D/Render2D.cpp @@ -3,7 +3,7 @@ #include "Render2D.h" #include "Font.h" #include "FontManager.h" -#include "MultiFont.h" +#include "FallbackFonts.h" #include "FontTextureAtlas.h" #include "RotatedRectangle.h" #include "SpriteAtlas.h" @@ -195,7 +195,7 @@ namespace // Drawing Array DrawCalls; Array Lines; - Array MultiFontLines; + Array BlockedTextLines; Array Lines2; bool IsScissorsRectEmpty; bool IsScissorsRectEnabled; @@ -1370,16 +1370,16 @@ void Render2D::DrawText(Font* font, const StringView& text, const TextRange& tex DrawText(font, textRange.Substring(text), color, layout, customMaterial); } -void Render2D::DrawText(MultiFont* multiFont, const StringView& text, const Color& color, const Float2& location, MaterialBase* customMaterial) +void Render2D::DrawText(Font* font, FallbackFonts* fallbacks, const StringView& text, const Color& color, const Float2& location, MaterialBase* customMaterial) { RENDER2D_CHECK_RENDERING_STATE; - const Array& fonts = multiFont->GetFonts(); // Check if there is no need to do anything - if (fonts.IsEmpty() || text.Length() < 0) + if (font == nullptr || text.Length() < 0) return; // Temporary data + const Array& fallbackFonts = fallbacks->GetFontList(font->GetSize()); uint32 fontAtlasIndex = 0; FontTextureAtlas* fontAtlas = nullptr; Float2 invAtlasSize = Float2::One; @@ -1406,11 +1406,19 @@ void Render2D::DrawText(MultiFont* multiFont, const StringView& text, const Colo int32 lineIndex = 0; maxAscenders.Add(0); + + auto getFont = [&](int32 index)->Font* { + return index >= 0 ? fallbackFonts[index] : font; + }; + + // Preprocess the text to determine vertical offset of blocks for (int32 currentIndex = 0; currentIndex < text.Length(); currentIndex++) { - if (text[currentIndex] != '\n') { - int32 fontIndex = multiFont->GetCharFontIndex(text[currentIndex], 0); - maxAscenders[lineIndex] = Math::Max(maxAscenders[lineIndex], static_cast(fonts[fontIndex]->GetAscender())); + const Char c = text[currentIndex]; + if (c != '\n') { + int32 fontIndex = fallbacks->GetCharFallbackIndex(c, font); + maxAscenders[lineIndex] = Math::Max(maxAscenders[lineIndex], + static_cast(getFont(fontIndex)->GetAscender())); } else { lineIndex++; @@ -1424,7 +1432,7 @@ void Render2D::DrawText(MultiFont* multiFont, const StringView& text, const Colo // The starting index of the current block int32 startIndex = 0; // The index of the font used by the current block - int32 currentFontIndex = multiFont->GetCharFontIndex(text[0], 0); + int32 currentFontIndex = fallbacks->GetCharFallbackIndex(text[0], font); // The maximum font height of the current line float maxHeight = 0; for (int32 currentIndex = 0; currentIndex < text.Length(); currentIndex++) @@ -1446,7 +1454,7 @@ void Render2D::DrawText(MultiFont* multiFont, const StringView& text, const Colo { // Get character entry if (nextCharIndex < text.Length()) { - nextFontIndex = multiFont->GetCharFontIndex(text[nextCharIndex], currentFontIndex); + nextFontIndex = fallbacks->GetCharFallbackIndex(text[nextCharIndex], font); } if (nextFontIndex != currentFontIndex) { @@ -1461,13 +1469,13 @@ void Render2D::DrawText(MultiFont* multiFont, const StringView& text, const Colo if (moveBlock) { // Render the pending block before beginning the new block - auto fontHeight = fonts[currentFontIndex]->GetHeight(); + auto fontHeight = getFont(currentFontIndex)->GetHeight(); maxHeight = Math::Max(maxHeight, static_cast(fontHeight)); - auto fontDescender = fonts[currentFontIndex]->GetDescender(); + auto fontDescender = getFont(currentFontIndex)->GetDescender(); for (int32 renderIndex = startIndex; renderIndex <= currentIndex; renderIndex++) { // Get character entry - fonts[currentFontIndex]->GetCharacter(text[renderIndex], entry); + getFont(currentFontIndex)->GetCharacter(text[renderIndex], entry); // Check if need to select/change font atlas (since characters even in the same font may be located in different atlases) if (fontAtlas == nullptr || entry.TextureIndex != fontAtlasIndex) @@ -1494,7 +1502,7 @@ void Render2D::DrawText(MultiFont* multiFont, const StringView& text, const Colo // Get kerning if (!isWhitespace && previous.IsValid) { - kerning = fonts[currentFontIndex]->GetKerning(previous.Character, entry.Character); + kerning = getFont(currentFontIndex)->GetKerning(previous.Character, entry.Character); } else { @@ -1510,7 +1518,7 @@ void Render2D::DrawText(MultiFont* multiFont, const StringView& text, const Colo const float x = pointer.X + entry.OffsetX * scale; const float y = pointer.Y + (fontHeight + fontDescender - entry.OffsetY) * scale; - Rectangle charRect(x, y + (maxAscenders[lineIndex] - fonts[currentFontIndex]->GetAscender()) / 2, entry.UVSize.X * scale, entry.UVSize.Y * scale); + Rectangle charRect(x, y + (maxAscenders[lineIndex] - getFont(currentFontIndex)->GetAscender()) / 2, entry.UVSize.X * scale, entry.UVSize.Y * scale); Float2 upperLeftUV = entry.UV * invAtlasSize; Float2 rightBottomUV = (entry.UV + entry.UVSize) * invAtlasSize; @@ -1541,21 +1549,21 @@ void Render2D::DrawText(MultiFont* multiFont, const StringView& text, const Colo } } -void Render2D::DrawText(MultiFont* multiFont, const StringView& text, const TextRange& textRange, const Color& color, const Float2& location, MaterialBase* customMaterial) +void Render2D::DrawText(Font* font, FallbackFonts* fallbacks, const StringView& text, const TextRange& textRange, const Color& color, const Float2& location, MaterialBase* customMaterial) { - DrawText(multiFont, textRange.Substring(text), color, location, customMaterial); + DrawText(font, fallbacks, textRange.Substring(text), color, location, customMaterial); } -void Render2D::DrawText(MultiFont* multiFont, const StringView& text, const Color& color, const TextLayoutOptions& layout, MaterialBase* customMaterial) +void Render2D::DrawText(Font* font, FallbackFonts* fallbacks, const StringView& text, const Color& color, const TextLayoutOptions& layout, MaterialBase* customMaterial) { RENDER2D_CHECK_RENDERING_STATE; - const Array& fonts = multiFont->GetFonts(); // Check if there is no need to do anything - if (fonts.IsEmpty() || text.IsEmpty() || layout.Scale <= ZeroTolerance) + if (font == nullptr || text.IsEmpty() || layout.Scale <= ZeroTolerance) return; // Temporary data + const Array& fallbackFonts = fallbacks->GetFontList(font->GetSize()); uint32 fontAtlasIndex = 0; FontTextureAtlas* fontAtlas = nullptr; Float2 invAtlasSize = Float2::One; @@ -1564,8 +1572,8 @@ void Render2D::DrawText(MultiFont* multiFont, const StringView& text, const Colo float scale = layout.Scale / FontManager::FontScale; // Process text to get lines - MultiFontLines.Clear(); - multiFont->ProcessText(text, MultiFontLines, layout); + BlockedTextLines.Clear(); + font->ProcessText(fallbacks, text, BlockedTextLines, layout); // Render all lines FontCharacterEntry entry; @@ -1581,14 +1589,18 @@ void Render2D::DrawText(MultiFont* multiFont, const StringView& text, const Colo drawCall.AsChar.Mat = nullptr; } - for (int32 lineIndex = 0; lineIndex < MultiFontLines.Count(); lineIndex++) + auto getFont = [&](int32 index)->Font* { + return index >= 0 ? fallbackFonts[index] : font; + }; + + for (int32 lineIndex = 0; lineIndex < BlockedTextLines.Count(); lineIndex++) { - const MultiFontLineCache& line = MultiFontLines[lineIndex]; + const BlockedTextLineCache& line = BlockedTextLines[lineIndex]; for (int32 blockIndex = 0; blockIndex < line.Blocks.Count(); blockIndex++) { - const MultiFontBlockCache& block = MultiFontLines[lineIndex].Blocks[blockIndex]; - auto fontHeight = fonts[block.FontIndex]->GetHeight(); - auto fontDescender = fonts[block.FontIndex]->GetDescender(); + const FontBlockCache& block = BlockedTextLines[lineIndex].Blocks[blockIndex]; + auto fontHeight = getFont(block.FallbackFontIndex)->GetHeight(); + auto fontDescender = getFont(block.FallbackFontIndex)->GetDescender(); Float2 pointer = line.Location + block.Location; for (int32 charIndex = block.FirstCharIndex; charIndex <= block.LastCharIndex; charIndex++) @@ -1600,7 +1612,7 @@ void Render2D::DrawText(MultiFont* multiFont, const StringView& text, const Colo } // Get character entry - fonts[block.FontIndex]->GetCharacter(c, entry); + getFont(block.FallbackFontIndex)->GetCharacter(c, entry); // Check if need to select/change font atlas (since characters even in the same font may be located in different atlases) if (fontAtlas == nullptr || entry.TextureIndex != fontAtlasIndex) @@ -1625,7 +1637,7 @@ void Render2D::DrawText(MultiFont* multiFont, const StringView& text, const Colo const bool isWhitespace = StringUtils::IsWhitespace(c); if (!isWhitespace && previous.IsValid) { - kerning = fonts[block.FontIndex]->GetKerning(previous.Character, entry.Character); + kerning = getFont(block.FallbackFontIndex)->GetKerning(previous.Character, entry.Character); } else { @@ -1661,9 +1673,9 @@ void Render2D::DrawText(MultiFont* multiFont, const StringView& text, const Colo } } -void Render2D::DrawText(MultiFont* multiFont, const StringView& text, const TextRange& textRange, const Color& color, const TextLayoutOptions& layout, MaterialBase* customMaterial) +void Render2D::DrawText(Font* font, FallbackFonts* fallbacks, const StringView& text, const TextRange& textRange, const Color& color, const TextLayoutOptions& layout, MaterialBase* customMaterial) { - DrawText(multiFont, textRange.Substring(text), color, layout, customMaterial); + DrawText(font, fallbacks, textRange.Substring(text), color, layout, customMaterial); } FORCE_INLINE bool NeedAlphaWithTint(const Color& color) diff --git a/Source/Engine/Render2D/Render2D.h b/Source/Engine/Render2D/Render2D.h index 5050171b2..c99d38104 100644 --- a/Source/Engine/Render2D/Render2D.h +++ b/Source/Engine/Render2D/Render2D.h @@ -15,7 +15,7 @@ struct Matrix3x3; struct Viewport; struct TextRange; class Font; -class MultiFont; +class FallbackFonts; class GPUPipelineState; class GPUTexture; class GPUTextureView; @@ -225,7 +225,7 @@ public: /// The text color. /// The text location. /// The custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture. - API_FUNCTION() static void DrawText(MultiFont* multiFont, const StringView& text, const Color& color, const Float2& location, MaterialBase* customMaterial = nullptr); + API_FUNCTION() static void DrawText(Font* font, FallbackFonts* fallbacks, const StringView& text, const Color& color, const Float2& location, MaterialBase* customMaterial = nullptr); /// /// Draws a text with formatting. @@ -235,7 +235,7 @@ public: /// The text color. /// The text layout properties. /// The custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture. - API_FUNCTION() static void DrawText(MultiFont* multiFont, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Color& color, const Float2& location, MaterialBase* customMaterial = nullptr); + API_FUNCTION() static void DrawText(Font* font, FallbackFonts* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Color& color, const Float2& location, MaterialBase* customMaterial = nullptr); /// /// Draws a text with formatting. @@ -246,7 +246,7 @@ public: /// The text color. /// The text layout properties. /// The custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture. - API_FUNCTION() static void DrawText(MultiFont* multiFont, const StringView& text, const Color& color, API_PARAM(Ref) const TextLayoutOptions& layout, MaterialBase* customMaterial = nullptr); + API_FUNCTION() static void DrawText(Font* font, FallbackFonts* fallbacks, const StringView& text, const Color& color, API_PARAM(Ref) const TextLayoutOptions& layout, MaterialBase* customMaterial = nullptr); /// /// Draws a text with formatting. @@ -257,7 +257,7 @@ public: /// The text color. /// The text layout properties. /// The custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture. - API_FUNCTION() static void DrawText(MultiFont* multiFont, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Color& color, API_PARAM(Ref) const TextLayoutOptions& layout, MaterialBase* customMaterial = nullptr); + API_FUNCTION() static void DrawText(Font* font, FallbackFonts* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Color& color, API_PARAM(Ref) const TextLayoutOptions& layout, MaterialBase* customMaterial = nullptr); /// /// Fills a rectangle area. From cdbe59a3fbe4988660d9684b2fc9122ad2be4907 Mon Sep 17 00:00:00 2001 From: ExMatics HydrogenC <33123710+HydrogenC@users.noreply.github.com> Date: Thu, 30 Nov 2023 23:12:56 +0800 Subject: [PATCH 041/139] Add fallback settings to CSharp --- .../Content/Create/CreateFilesDialog.cs | 2 +- .../Content/Import/ImportFilesDialog.cs | 2 +- .../CustomEditors/Dedicated/RagdollEditor.cs | 2 +- .../CustomEditors/Dedicated/ScriptsEditor.cs | 2 +- .../Dedicated/UIControlEditor.cs | 6 +- .../Editors/ActorTransformEditor.cs | 2 +- .../Editor/CustomEditors/Editors/TagEditor.cs | 2 +- .../CustomEditors/LayoutElementsContainer.cs | 4 +- Source/Editor/GUI/ColumnDefinition.cs | 2 +- Source/Editor/GUI/ComboBox.cs | 6 +- .../GUI/ContextMenu/ContextMenuButton.cs | 4 +- Source/Editor/GUI/CurveEditor.cs | 2 +- Source/Editor/GUI/Docking/DockWindow.cs | 2 +- Source/Editor/GUI/MainMenu.cs | 8 +- Source/Editor/GUI/MainMenuButton.cs | 2 +- Source/Editor/GUI/NavigationButton.cs | 2 +- Source/Editor/GUI/Row.cs | 4 +- .../Editor/GUI/Timeline/Tracks/MemberTrack.cs | 2 +- Source/Editor/GUI/ToolStripButton.cs | 2 +- Source/Editor/GUI/Tree/TreeNode.cs | 10 +- Source/Editor/Options/InterfaceOptions.cs | 54 ++++---- Source/Editor/Options/OptionsModule.cs | 20 +-- .../Archetypes/Animation.StateMachine.cs | 4 +- .../Archetypes/Animation.TransitionEditor.cs | 2 +- Source/Editor/Surface/Archetypes/Animation.cs | 2 +- .../Editor/Surface/Archetypes/BehaviorTree.cs | 4 +- Source/Editor/Surface/Archetypes/Function.cs | 2 +- Source/Editor/Surface/AttributesEditor.cs | 2 +- .../Editor/Surface/ContextMenu/VisjectCM.cs | 2 +- .../Surface/ContextMenu/VisjectCMItem.cs | 2 +- Source/Editor/Surface/Elements/InputBox.cs | 4 +- Source/Editor/Surface/SurfaceNode.cs | 6 +- Source/Editor/Tools/Foliage/FoliageTab.cs | 2 +- Source/Editor/Tools/Terrain/CarveTab.cs | 2 +- .../Tools/Terrain/CreateTerrainDialog.cs | 2 +- Source/Editor/Utilities/TextRenderUtils.cs | 108 ++++++++++++++++ Source/Editor/Viewport/EditorViewport.cs | 6 +- .../Viewport/Widgets/ViewportWidgetButton.cs | 2 +- Source/Editor/Windows/AboutDialog.cs | 4 +- Source/Editor/Windows/Assets/FontWindow.cs | 2 +- Source/Editor/Windows/ContentWindow.Search.cs | 2 +- Source/Editor/Windows/PluginsWindow.cs | 9 +- Source/Editor/Windows/Profiler/Timeline.cs | 2 +- Source/Engine/Render2D/FallbackFonts.h | 2 +- Source/Engine/Render2D/MultiFontReference.cs | 74 ----------- Source/Engine/Render2D/Render2D.cs | 118 ++++++++++-------- Source/Engine/Scripting/Scripting.cs | 10 +- Source/Engine/UI/GUI/Common/Button.cs | 8 +- Source/Engine/UI/GUI/Common/Dropdown.cs | 8 +- Source/Engine/UI/GUI/Common/Label.cs | 14 +-- Source/Engine/UI/GUI/Common/RichTextBox.cs | 2 +- Source/Engine/UI/GUI/Common/TextBox.cs | 37 ++++-- Source/Engine/UI/GUI/Panels/DropPanel.cs | 6 +- Source/Engine/UI/GUI/Style.cs | 38 +++--- 54 files changed, 357 insertions(+), 273 deletions(-) create mode 100644 Source/Editor/Utilities/TextRenderUtils.cs delete mode 100644 Source/Engine/Render2D/MultiFontReference.cs diff --git a/Source/Editor/Content/Create/CreateFilesDialog.cs b/Source/Editor/Content/Create/CreateFilesDialog.cs index 48e4920bb..d48e878bc 100644 --- a/Source/Editor/Content/Create/CreateFilesDialog.cs +++ b/Source/Editor/Content/Create/CreateFilesDialog.cs @@ -39,7 +39,7 @@ namespace FlaxEditor.Content.Create AnchorPreset = AnchorPresets.HorizontalStretchTop, Offsets = new Margin(0, 0, 0, 40), Parent = this, - Font = new MultiFontReference(Style.Current.FontTitle) + Font = new FontReference(Style.Current.FontTitle) }; var infoLabel = new Label { diff --git a/Source/Editor/Content/Import/ImportFilesDialog.cs b/Source/Editor/Content/Import/ImportFilesDialog.cs index 5a142d0f6..967583cf6 100644 --- a/Source/Editor/Content/Import/ImportFilesDialog.cs +++ b/Source/Editor/Content/Import/ImportFilesDialog.cs @@ -60,7 +60,7 @@ namespace FlaxEditor.Content.Import AnchorPreset = AnchorPresets.HorizontalStretchTop, Offsets = new Margin(0, 0, 0, 40), Parent = this, - Font = new MultiFontReference(Style.Current.FontTitle) + Font = new FontReference(Style.Current.FontTitle) }; var infoLabel = new Label { diff --git a/Source/Editor/CustomEditors/Dedicated/RagdollEditor.cs b/Source/Editor/CustomEditors/Dedicated/RagdollEditor.cs index b6d14d81e..c4b334b3a 100644 --- a/Source/Editor/CustomEditors/Dedicated/RagdollEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/RagdollEditor.cs @@ -81,7 +81,7 @@ namespace FlaxEditor.CustomEditors.Dedicated // Title var title = new Label(2, 2, width - 4, 23.0f) { - Font = new MultiFontReference(FlaxEngine.GUI.Style.Current.FontLarge), + Font = new FontReference(FlaxEngine.GUI.Style.Current.FontLarge), Text = "Ragdoll Options", Parent = this }; diff --git a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs index fd56422cb..90ae9ae54 100644 --- a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs @@ -43,7 +43,7 @@ namespace FlaxEditor.CustomEditors.Dedicated // Add script button var buttonText = "Add script"; - var textSize = Style.Current.FontMedium.MeasureText(buttonText); + var textSize = Render2D.MeasureText(Style.Current.FontMedium, buttonText); float addScriptButtonWidth = (textSize.X < 60.0f) ? 60.0f : textSize.X + 4; var buttonHeight = (textSize.Y < 18) ? 18 : textSize.Y + 4; _addScriptsButton = new Button diff --git a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs index 9627edfd8..b8250918f 100644 --- a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs @@ -239,7 +239,7 @@ namespace FlaxEditor.CustomEditors.Dedicated // Title var title = new Label(2, 2, DialogWidth - 4, TitleHeight) { - Font = new MultiFontReference(style.FontLarge), + Font = new FontReference(style.FontLarge), Text = "Anchor Presets", Parent = this }; @@ -247,7 +247,7 @@ namespace FlaxEditor.CustomEditors.Dedicated // Info var info = new Label(0, title.Bottom, DialogWidth, InfoHeight) { - Font = new MultiFontReference(style.FontSmall), + Font = new FontReference(style.FontSmall), Text = "Shift: also set bounds\nControl: also set pivot", Parent = this }; @@ -423,7 +423,7 @@ namespace FlaxEditor.CustomEditors.Dedicated // Set control type button var space = layout.Space(20); var buttonText = "Set Type"; - var textSize = FlaxEngine.GUI.Style.Current.FontMedium.MeasureText(buttonText); + var textSize = Render2D.MeasureText(FlaxEngine.GUI.Style.Current.FontMedium, buttonText); float setTypeButtonWidth = (textSize.X < 60.0f) ? 60.0f : textSize.X + 4; var setTypeButton = new Button { diff --git a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs index 0cad200fd..e84bf5914 100644 --- a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs @@ -101,7 +101,7 @@ namespace FlaxEditor.CustomEditors.Editors _linkButton.Clicked += ToggleLink; ToggleEnabled(); SetLinkStyle(); - var textSize = FlaxEngine.GUI.Style.Current.FontMedium.MeasureText(LinkedLabel.Text.Value); + var textSize = Render2D.MeasureText(FlaxEngine.GUI.Style.Current.FontMedium, LinkedLabel.Text.Value); _linkButton.LocalX += textSize.X + 10; LinkedLabel.SetupContextMenu += (label, menu, editor) => { diff --git a/Source/Editor/CustomEditors/Editors/TagEditor.cs b/Source/Editor/CustomEditors/Editors/TagEditor.cs index dbd5d124c..49ac9d937 100644 --- a/Source/Editor/CustomEditors/Editors/TagEditor.cs +++ b/Source/Editor/CustomEditors/Editors/TagEditor.cs @@ -631,7 +631,7 @@ namespace FlaxEditor.CustomEditors.Editors TooltipText = "Edit...", Parent = _label, }; - var textSize = FlaxEngine.GUI.Style.Current.FontMedium.MeasureText(buttonText); + var textSize = Render2D.MeasureText(FlaxEngine.GUI.Style.Current.FontMedium, buttonText); if (textSize.Y > button.Width) button.Width = textSize.Y + 2; diff --git a/Source/Editor/CustomEditors/LayoutElementsContainer.cs b/Source/Editor/CustomEditors/LayoutElementsContainer.cs index 1584e88de..936851b15 100644 --- a/Source/Editor/CustomEditors/LayoutElementsContainer.cs +++ b/Source/Editor/CustomEditors/LayoutElementsContainer.cs @@ -276,7 +276,7 @@ namespace FlaxEditor.CustomEditors public LabelElement Header(string text) { var element = Label(text); - element.Label.Font = new MultiFontReference(Style.Current.FontLarge); + element.Label.Font = new FontReference(Style.Current.FontLarge); return element; } @@ -284,7 +284,7 @@ namespace FlaxEditor.CustomEditors { var element = Header(header.Text); if (header.FontSize != -1) - element.Label.Font = new MultiFontReference(element.Label.Font, header.FontSize); + element.Label.Font = new FontReference(element.Label.Font.Font, header.FontSize); if (header.Color != 0) element.Label.TextColor = Color.FromRGBA(header.Color); return element; diff --git a/Source/Editor/GUI/ColumnDefinition.cs b/Source/Editor/GUI/ColumnDefinition.cs index c6e8f2889..aff1817c3 100644 --- a/Source/Editor/GUI/ColumnDefinition.cs +++ b/Source/Editor/GUI/ColumnDefinition.cs @@ -35,7 +35,7 @@ namespace FlaxEditor.GUI /// /// The title font. /// - public MultiFont TitleFont; + public Font TitleFont; /// /// The column title text color. diff --git a/Source/Editor/GUI/ComboBox.cs b/Source/Editor/GUI/ComboBox.cs index 8e6cf39a0..0417cc7e3 100644 --- a/Source/Editor/GUI/ComboBox.cs +++ b/Source/Editor/GUI/ComboBox.cs @@ -191,7 +191,7 @@ namespace FlaxEditor.GUI /// Gets or sets the font used to draw text. /// [EditorDisplay("Style"), EditorOrder(2000)] - public MultiFontReference Font { get; set; } + public FontReference Font { get; set; } /// /// Gets or sets the color of the text. @@ -273,7 +273,7 @@ namespace FlaxEditor.GUI MaximumItemsInViewCount = 20; var style = Style.Current; - Font = new MultiFontReference(style.FontMedium); + Font = new FontReference(style.FontMedium); TextColor = style.Foreground; BackgroundColor = style.BackgroundNormal; BackgroundColorHighlighted = BackgroundColor; @@ -554,7 +554,7 @@ namespace FlaxEditor.GUI var textRect = new Rectangle(margin, 0, clientRect.Width - boxSize - 2.0f * margin, clientRect.Height); Render2D.PushClip(textRect); var textColor = TextColor; - Render2D.DrawText(Font.GetMultiFont(), text, textRect, enabled ? textColor : textColor * 0.5f, TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, textScale); + Render2D.DrawText(Font.GetFont(), text, textRect, enabled ? textColor : textColor * 0.5f, TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, textScale); Render2D.PopClip(); } diff --git a/Source/Editor/GUI/ContextMenu/ContextMenuButton.cs b/Source/Editor/GUI/ContextMenu/ContextMenuButton.cs index ba3326412..3137de240 100644 --- a/Source/Editor/GUI/ContextMenu/ContextMenuButton.cs +++ b/Source/Editor/GUI/ContextMenu/ContextMenuButton.cs @@ -236,9 +236,9 @@ namespace FlaxEditor.GUI.ContextMenu float width = 20; if (style.FontMedium) { - width += style.FontMedium.MeasureText(Text).X; + width += Render2D.MeasureText(style.FontMedium, Text).X; if (!string.IsNullOrEmpty(ShortKeys)) - width += 40 + style.FontMedium.MeasureText(ShortKeys).X; + width += 40 + Render2D.MeasureText(style.FontMedium, ShortKeys).X; } return Mathf.Max(width, base.MinimumWidth); diff --git a/Source/Editor/GUI/CurveEditor.cs b/Source/Editor/GUI/CurveEditor.cs index ff14cdb24..22deec120 100644 --- a/Source/Editor/GUI/CurveEditor.cs +++ b/Source/Editor/GUI/CurveEditor.cs @@ -317,7 +317,7 @@ namespace FlaxEditor.GUI private Color _contentsColor; private Color _linesColor; private Color _labelsColor; - private MultiFont _labelsFont; + private Font _labelsFont; /// /// The keyframe UI points. diff --git a/Source/Editor/GUI/Docking/DockWindow.cs b/Source/Editor/GUI/Docking/DockWindow.cs index 374885f01..36a4c112e 100644 --- a/Source/Editor/GUI/Docking/DockWindow.cs +++ b/Source/Editor/GUI/Docking/DockWindow.cs @@ -489,7 +489,7 @@ namespace FlaxEditor.GUI.Docking { var style = Style.Current; if (style?.FontMedium != null) - _titleSize = style.FontMedium.MeasureText(_title); + _titleSize = Render2D.MeasureText(style.FontMedium, _title); } base.PerformLayoutBeforeChildren(); diff --git a/Source/Editor/GUI/MainMenu.cs b/Source/Editor/GUI/MainMenu.cs index aadfbf0a7..b313fed9f 100644 --- a/Source/Editor/GUI/MainMenu.cs +++ b/Source/Editor/GUI/MainMenu.cs @@ -76,7 +76,7 @@ namespace FlaxEditor.GUI var windowIcon = FlaxEngine.Content.LoadAsyncInternal(EditorAssets.WindowIcon); FontAsset windowIconsFont = FlaxEngine.Content.LoadAsyncInternal(EditorAssets.WindowIconsFont); - MultiFont iconFont = new MultiFontReference([windowIconsFont], 9).GetMultiFont(); + Font iconFont = windowIconsFont?.CreateFont(9); _window = mainWindow.RootWindow.Window; _window.HitTest += OnHitTest; @@ -108,7 +108,7 @@ namespace FlaxEditor.GUI _closeButton = new Button { Text = ((char)EditorAssets.SegMDL2Icons.ChromeClose).ToString(), - Font = new MultiFontReference(iconFont), + Font = new FontReference(iconFont), BackgroundColor = Color.Transparent, BorderColor = Color.Transparent, BorderColorHighlighted = Color.Transparent, @@ -124,7 +124,7 @@ namespace FlaxEditor.GUI _minimizeButton = new Button { Text = ((char)EditorAssets.SegMDL2Icons.ChromeMinimize).ToString(), - Font = new MultiFontReference(iconFont), + Font = new FontReference(iconFont), BackgroundColor = Color.Transparent, BorderColor = Color.Transparent, BorderColorHighlighted = Color.Transparent, @@ -139,7 +139,7 @@ namespace FlaxEditor.GUI _maximizeButton = new Button { Text = ((char)(_window.IsMaximized ? EditorAssets.SegMDL2Icons.ChromeRestore : EditorAssets.SegMDL2Icons.ChromeMaximize)).ToString(), - Font = new MultiFontReference(iconFont), + Font = new FontReference(iconFont), BackgroundColor = Color.Transparent, BorderColor = Color.Transparent, BorderColorHighlighted = Color.Transparent, diff --git a/Source/Editor/GUI/MainMenuButton.cs b/Source/Editor/GUI/MainMenuButton.cs index 3bc57479c..3440996aa 100644 --- a/Source/Editor/GUI/MainMenuButton.cs +++ b/Source/Editor/GUI/MainMenuButton.cs @@ -103,7 +103,7 @@ namespace FlaxEditor.GUI float width = 18; if (style.FontMedium) - width += style.FontMedium.MeasureText(Text).X; + width += Render2D.MeasureText(style.FontMedium, Text).X; Width = width; } diff --git a/Source/Editor/GUI/NavigationButton.cs b/Source/Editor/GUI/NavigationButton.cs index 6fd17332c..6face21ef 100644 --- a/Source/Editor/GUI/NavigationButton.cs +++ b/Source/Editor/GUI/NavigationButton.cs @@ -68,7 +68,7 @@ namespace FlaxEditor.GUI if (style.FontMedium) { - Width = style.FontMedium.MeasureText(Text).X + 2 * DefaultMargin; + Width = Render2D.MeasureText(style.FontMedium, Text).X + 2 * DefaultMargin; } } } diff --git a/Source/Editor/GUI/Row.cs b/Source/Editor/GUI/Row.cs index 7533dfb17..ab74cbe96 100644 --- a/Source/Editor/GUI/Row.cs +++ b/Source/Editor/GUI/Row.cs @@ -39,8 +39,8 @@ namespace FlaxEditor.GUI { Depth = -1; - if (Height < Style.Current.FontMedium.MaxHeight) - Height = Style.Current.FontMedium.MaxHeight + 4; + if (Height < Style.Current.FontMedium.Height) + Height = Style.Current.FontMedium.Height + 4; } /// diff --git a/Source/Editor/GUI/Timeline/Tracks/MemberTrack.cs b/Source/Editor/GUI/Timeline/Tracks/MemberTrack.cs index 63787df2c..b4433622e 100644 --- a/Source/Editor/GUI/Timeline/Tracks/MemberTrack.cs +++ b/Source/Editor/GUI/Timeline/Tracks/MemberTrack.cs @@ -345,7 +345,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks if (_previewValue != null) { // Based on Track.Draw for track text placement - var left = _xOffset + 16 + Style.Current.FontSmall.MeasureText(Title ?? Name).X; + var left = _xOffset + 16 + Render2D.MeasureText(Style.Current.FontSmall, Title ?? Name).X; if (Icon.IsValid) left += 18; if (IsExpanded) diff --git a/Source/Editor/GUI/ToolStripButton.cs b/Source/Editor/GUI/ToolStripButton.cs index d21fd5689..cf34fd36b 100644 --- a/Source/Editor/GUI/ToolStripButton.cs +++ b/Source/Editor/GUI/ToolStripButton.cs @@ -152,7 +152,7 @@ namespace FlaxEditor.GUI if (hasSprite) width += iconSize; if (!string.IsNullOrEmpty(_text) && style.FontMedium) - width += style.FontMedium.MeasureText(_text).X + (hasSprite ? DefaultMargin : 0); + width += Render2D.MeasureText(style.FontMedium, _text).X + (hasSprite ? DefaultMargin : 0); Width = width; } diff --git a/Source/Editor/GUI/Tree/TreeNode.cs b/Source/Editor/GUI/Tree/TreeNode.cs index acb67ea8f..7b47e5eb6 100644 --- a/Source/Editor/GUI/Tree/TreeNode.cs +++ b/Source/Editor/GUI/Tree/TreeNode.cs @@ -115,7 +115,7 @@ namespace FlaxEditor.GUI.Tree /// Gets or sets the font used to render text. /// [EditorDisplay("Style"), EditorOrder(2000)] - public MultiFontReference TextFont { get; set; } + public FontReference TextFont { get; set; } /// /// Gets or sets the color of the background when tree node is selected. @@ -318,7 +318,7 @@ namespace FlaxEditor.GUI.Tree BackgroundColorSelected = style.BackgroundSelected; BackgroundColorHighlighted = style.BackgroundHighlighted; BackgroundColorSelectedUnfocused = style.LightBackground; - TextFont = new MultiFontReference(style.FontSmall); + TextFont = new FontReference(style.FontSmall); } /// @@ -573,10 +573,10 @@ namespace FlaxEditor.GUI.Tree { if (_textChanged) { - var font = TextFont.GetMultiFont(); + var font = TextFont.GetFont(); if (font) { - _textWidth = font.MeasureText(_text).X; + _textWidth = Render2D.MeasureText(font, _text).X; _textChanged = false; } } @@ -657,7 +657,7 @@ namespace FlaxEditor.GUI.Tree } // Draw text - Render2D.DrawText(TextFont.GetMultiFont(), _text, textRect, _cachedTextColor, TextAlignment.Near, TextAlignment.Center); + Render2D.DrawText(TextFont.GetFont(), _text, textRect, _cachedTextColor, TextAlignment.Near, TextAlignment.Center); // Draw drag and drop effect if (IsDragOver && _tree.DraggedOverNode == this) diff --git a/Source/Editor/Options/InterfaceOptions.cs b/Source/Editor/Options/InterfaceOptions.cs index 91eebe4eb..af432baa7 100644 --- a/Source/Editor/Options/InterfaceOptions.cs +++ b/Source/Editor/Options/InterfaceOptions.cs @@ -171,8 +171,10 @@ namespace FlaxEditor.Options get => _outputLogFont; set { - if (value == null || !value.Font) - _outputLogFont = new FontReference(ConsoleFont, 10); + if (value == null) + _outputLogFont = new FontReference(FlaxEngine.Content.LoadAsyncInternal(EditorAssets.InconsolataRegularFont), 10); + else if (!value.Font) + _outputLogFont.Font = FlaxEngine.Content.LoadAsyncInternal(EditorAssets.InconsolataRegularFont); else _outputLogFont = value; } @@ -234,30 +236,34 @@ namespace FlaxEditor.Options [EditorDisplay("Cook & Run"), EditorOrder(500)] public int NumberOfGameClientsToLaunch = 1; - private static FontAsset[] DefaultFonts => - [FlaxEngine.Content.LoadAsyncInternal(EditorAssets.PrimaryFont), - FlaxEngine.Content.LoadAsyncInternal(EditorAssets.CjkFont)]; + private static FontAsset DefaultFont => FlaxEngine.Content.LoadAsyncInternal(EditorAssets.PrimaryFont); private static FontAsset ConsoleFont => FlaxEngine.Content.LoadAsyncInternal(EditorAssets.PrimaryFont); - private MultiFontReference _titleFont = new MultiFontReference(DefaultFonts, 18); - private MultiFontReference _largeFont = new MultiFontReference(DefaultFonts, 14); - private MultiFontReference _mediumFont = new MultiFontReference(DefaultFonts, 9); - private MultiFontReference _smallFont = new MultiFontReference(DefaultFonts, 9); + private FontReference _titleFont = new FontReference(DefaultFont, 18); + private FontReference _largeFont = new FontReference(DefaultFont, 14); + private FontReference _mediumFont = new FontReference(DefaultFont, 9); + private FontReference _smallFont = new FontReference(DefaultFont, 9); private FontReference _outputLogFont = new FontReference(ConsoleFont, 10); + /// + /// The fallback fonts. + /// + public FontAsset[] Fallbacks = [FlaxEngine.Content.LoadAsyncInternal(EditorAssets.CjkFont)]; /// /// Gets or sets the title font for editor UI. /// [EditorDisplay("Fonts"), EditorOrder(600), Tooltip("The title font for editor UI.")] - public MultiFontReference TitleFont + public FontReference TitleFont { get => _titleFont; set { - if (value == null || !value.Verify()) - _titleFont = new MultiFontReference(DefaultFonts, 18); + if (value == null) + _titleFont = new FontReference(DefaultFont, 18); + else if (!value.Font) + _titleFont.Font = DefaultFont; else _titleFont = value; } @@ -267,13 +273,15 @@ namespace FlaxEditor.Options /// Gets or sets the large font for editor UI. /// [EditorDisplay("Fonts"), EditorOrder(610), Tooltip("The large font for editor UI.")] - public MultiFontReference LargeFont + public FontReference LargeFont { get => _largeFont; set { - if (value == null || !value.Verify()) - _largeFont = new MultiFontReference(DefaultFonts, 14); + if (value == null) + _largeFont = new FontReference(DefaultFont, 14); + else if (!value.Font) + _largeFont.Font = DefaultFont; else _largeFont = value; } @@ -283,13 +291,15 @@ namespace FlaxEditor.Options /// Gets or sets the medium font for editor UI. /// [EditorDisplay("Fonts"), EditorOrder(620), Tooltip("The medium font for editor UI.")] - public MultiFontReference MediumFont + public FontReference MediumFont { get => _mediumFont; set { - if (value == null || !value.Verify()) - _mediumFont = new MultiFontReference(DefaultFonts, 9); + if (value == null) + _mediumFont = new FontReference(DefaultFont, 9); + else if (!value.Font) + _mediumFont.Font = DefaultFont; else _mediumFont = value; } @@ -299,13 +309,15 @@ namespace FlaxEditor.Options /// Gets or sets the small font for editor UI. /// [EditorDisplay("Fonts"), EditorOrder(630), Tooltip("The small font for editor UI.")] - public MultiFontReference SmallFont + public FontReference SmallFont { get => _smallFont; set { - if (value == null || !value.Verify()) - _smallFont = new MultiFontReference(DefaultFonts, 9); + if (value == null) + _smallFont = new FontReference(DefaultFont, 9); + else if (!value.Font) + _smallFont.Font = DefaultFont; else _smallFont = value; } diff --git a/Source/Editor/Options/OptionsModule.cs b/Source/Editor/Options/OptionsModule.cs index d138d7d0d..b13548016 100644 --- a/Source/Editor/Options/OptionsModule.cs +++ b/Source/Editor/Options/OptionsModule.cs @@ -217,12 +217,14 @@ namespace FlaxEditor.Options if (styleName == ThemeOptions.LightDefault) { Style.Current = CreateLightStyle(); - } + } else { Style.Current = CreateDefaultStyle(); } } + + Render2D.Fallbacks = FallbackFonts.Create(Options.Interface.Fallbacks); } /// @@ -259,10 +261,10 @@ namespace FlaxEditor.Options }, // Fonts - FontTitle = options.Interface.TitleFont.GetMultiFont(), - FontLarge = options.Interface.LargeFont.GetMultiFont(), - FontMedium = options.Interface.MediumFont.GetMultiFont(), - FontSmall = options.Interface.SmallFont.GetMultiFont(), + FontTitle = options.Interface.TitleFont.GetFont(), + FontLarge = options.Interface.LargeFont.GetFont(), + FontMedium = options.Interface.MediumFont.GetFont(), + FontSmall = options.Interface.SmallFont.GetFont(), // Icons ArrowDown = Editor.Icons.ArrowDown12, @@ -312,10 +314,10 @@ namespace FlaxEditor.Options ProgressNormal = new Color(0.03f, 0.65f, 0.12f, 1f), // Fonts - FontTitle = options.Interface.TitleFont.GetMultiFont(), - FontLarge = options.Interface.LargeFont.GetMultiFont(), - FontMedium = options.Interface.MediumFont.GetMultiFont(), - FontSmall = options.Interface.SmallFont.GetMultiFont(), + FontTitle = options.Interface.TitleFont.GetFont(), + FontLarge = options.Interface.LargeFont.GetFont(), + FontMedium = options.Interface.MediumFont.GetFont(), + FontSmall = options.Interface.SmallFont.GetFont(), // Icons ArrowDown = Editor.Icons.ArrowDown12, diff --git a/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs b/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs index 621c7c25e..19a995b1e 100644 --- a/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs +++ b/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs @@ -337,7 +337,7 @@ namespace FlaxEditor.Surface.Archetypes _textRect = new Rectangle(Float2.Zero, Size); var style = Style.Current; - var titleSize = style.FontLarge.MeasureText(Title); + var titleSize = Render2D.MeasureText(style.FontLarge, Title); var width = Mathf.Max(100, titleSize.X + 50); Resize(width, 0); titleSize.X += 8.0f; @@ -1402,7 +1402,7 @@ namespace FlaxEditor.Surface.Archetypes { Title = StateTitle; var style = Style.Current; - var titleSize = style.FontLarge.MeasureText(Title); + var titleSize = Render2D.MeasureText(style.FontLarge, Title); var width = Mathf.Max(100, titleSize.X + 50); Resize(width, 0); titleSize.X += 8.0f; diff --git a/Source/Editor/Surface/Archetypes/Animation.TransitionEditor.cs b/Source/Editor/Surface/Archetypes/Animation.TransitionEditor.cs index e1fdb0a42..84c2144b2 100644 --- a/Source/Editor/Surface/Archetypes/Animation.TransitionEditor.cs +++ b/Source/Editor/Surface/Archetypes/Animation.TransitionEditor.cs @@ -39,7 +39,7 @@ namespace FlaxEditor.Surface.Archetypes // Title var title = new Label(2, 2, width - 4, 23.0f) { - Font = new MultiFontReference(Style.Current.FontLarge), + Font = new FontReference(Style.Current.FontLarge), Text = transition.SurfaceName, Parent = this }; diff --git a/Source/Editor/Surface/Archetypes/Animation.cs b/Source/Editor/Surface/Archetypes/Animation.cs index 40a3d2a63..dcf4a9689 100644 --- a/Source/Editor/Surface/Archetypes/Animation.cs +++ b/Source/Editor/Surface/Archetypes/Animation.cs @@ -77,7 +77,7 @@ namespace FlaxEditor.Surface.Archetypes Title = asset?.ShortName ?? "Animation"; var style = Style.Current; - Resize(Mathf.Max(230, style.FontLarge.MeasureText(Title).X + 30), 160); + Resize(Mathf.Max(230, Render2D.MeasureText(style.FontLarge, Title).X + 30), 160); } /// diff --git a/Source/Editor/Surface/Archetypes/BehaviorTree.cs b/Source/Editor/Surface/Archetypes/BehaviorTree.cs index cca6856ae..364dfa1ef 100644 --- a/Source/Editor/Surface/Archetypes/BehaviorTree.cs +++ b/Source/Editor/Surface/Archetypes/BehaviorTree.cs @@ -101,7 +101,7 @@ namespace FlaxEditor.Surface.Archetypes _debugRelevant = Behavior.GetNodeDebugRelevancy(instance, behavior); _debugInfo = Behavior.GetNodeDebugInfo(instance, behavior); if (!string.IsNullOrEmpty(_debugInfo)) - _debugInfoSize = Style.Current.FontSmall.MeasureText(_debugInfo); + _debugInfoSize = Render2D.MeasureText(Style.Current.FontSmall, _debugInfo); } } @@ -488,7 +488,7 @@ namespace FlaxEditor.Surface.Archetypes var height = 0.0f; var titleLabelFont = Style.Current.FontLarge; width = Mathf.Max(width, 100.0f); - width = Mathf.Max(width, titleLabelFont.MeasureText(Title).X + 30); + width = Mathf.Max(width, Render2D.MeasureText(titleLabelFont, Title).X + 30); if (_debugInfoSize.X > 0) { width = Mathf.Max(width, _debugInfoSize.X + 8.0f); diff --git a/Source/Editor/Surface/Archetypes/Function.cs b/Source/Editor/Surface/Archetypes/Function.cs index bc982a510..53950dad2 100644 --- a/Source/Editor/Surface/Archetypes/Function.cs +++ b/Source/Editor/Surface/Archetypes/Function.cs @@ -1407,7 +1407,7 @@ namespace FlaxEditor.Surface.Archetypes // Title var title = new Label(2, 2, width - 4, 23.0f) { - Font = new MultiFontReference(Style.Current.FontLarge), + Font = new FontReference(Style.Current.FontLarge), Text = "Edit function signature", Parent = this }; diff --git a/Source/Editor/Surface/AttributesEditor.cs b/Source/Editor/Surface/AttributesEditor.cs index c9e32e23a..81b11bb68 100644 --- a/Source/Editor/Surface/AttributesEditor.cs +++ b/Source/Editor/Surface/AttributesEditor.cs @@ -83,7 +83,7 @@ namespace FlaxEditor.Surface // Title var title = new Label(2, 2, width - 4, 23.0f) { - Font = new MultiFontReference(Style.Current.FontLarge), + Font = new FontReference(Style.Current.FontLarge), Text = "Edit attributes", Parent = this }; diff --git a/Source/Editor/Surface/ContextMenu/VisjectCM.cs b/Source/Editor/Surface/ContextMenu/VisjectCM.cs index 0624d44b3..5256b3cb7 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCM.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCM.cs @@ -141,7 +141,7 @@ namespace FlaxEditor.Surface.ContextMenu }; // Title bar - var titleFontReference = new MultiFontReference(Style.Current.FontLarge); + var titleFontReference = new FontReference(Style.Current.FontLarge); var titleLabel = new Label { Width = Width * 0.5f - 8f, diff --git a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs index 207875a92..fd9f0c63b 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs @@ -286,7 +286,7 @@ namespace FlaxEditor.Surface.ContextMenu Render2D.DrawText(style.FontSmall, _archetype.Title, textRect, Enabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center); if (_archetype.SubTitle != null) { - var titleLength = style.FontSmall.MeasureText(_archetype.Title).X; + var titleLength = Render2D.MeasureText(style.FontSmall, _archetype.Title).X; var subTitleRect = new Rectangle(textRect.X + titleLength, textRect.Y, textRect.Width - titleLength, textRect.Height); Render2D.DrawText(style.FontSmall, _archetype.SubTitle, subTitleRect, style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center); } diff --git a/Source/Editor/Surface/Elements/InputBox.cs b/Source/Editor/Surface/Elements/InputBox.cs index 2047bcd1a..2ab286bd9 100644 --- a/Source/Editor/Surface/Elements/InputBox.cs +++ b/Source/Editor/Surface/Elements/InputBox.cs @@ -1428,7 +1428,7 @@ namespace FlaxEditor.Surface.Elements if (_defaultValueEditor != null) { - _defaultValueEditor.Location = new Float2(X + Width + 8 + Style.Current.FontSmall.MeasureText(Text).X, Y); + _defaultValueEditor.Location = new Float2(X + Width + 8 + Render2D.MeasureText(Style.Current.FontSmall, Text).X, Y); } } @@ -1635,7 +1635,7 @@ namespace FlaxEditor.Surface.Elements { if (DefaultValueEditors[i].CanUse(this, ref _currentType)) { - var bounds = new Rectangle(X + Width + 8 + Style.Current.FontSmall.MeasureText(Text).X, Y, 90, Height); + var bounds = new Rectangle(X + Width + 8 + Render2D.MeasureText(Style.Current.FontSmall, Text).X, Y, 90, Height); _editor = DefaultValueEditors[i]; try { diff --git a/Source/Editor/Surface/SurfaceNode.cs b/Source/Editor/Surface/SurfaceNode.cs index b6436e542..60d776102 100644 --- a/Source/Editor/Surface/SurfaceNode.cs +++ b/Source/Editor/Surface/SurfaceNode.cs @@ -200,7 +200,7 @@ namespace FlaxEditor.Surface continue; if (child is InputBox inputBox) { - var boxWidth = boxLabelFont.MeasureText(inputBox.Text).X + 20; + var boxWidth = Render2D.MeasureText(boxLabelFont, inputBox.Text).X + 20; if (inputBox.DefaultValueEditor != null) boxWidth += inputBox.DefaultValueEditor.Width + 4; leftWidth = Mathf.Max(leftWidth, boxWidth); @@ -208,7 +208,7 @@ namespace FlaxEditor.Surface } else if (child is OutputBox outputBox) { - rightWidth = Mathf.Max(rightWidth, boxLabelFont.MeasureText(outputBox.Text).X + 20); + rightWidth = Mathf.Max(rightWidth, Render2D.MeasureText(boxLabelFont, outputBox.Text).X + 20); rightHeight = Mathf.Max(rightHeight, outputBox.Archetype.Position.Y - Constants.NodeMarginY - Constants.NodeHeaderSize + 20.0f); } else if (child is Control control) @@ -226,7 +226,7 @@ namespace FlaxEditor.Surface } } width = Mathf.Max(width, leftWidth + rightWidth + 10); - width = Mathf.Max(width, titleLabelFont.MeasureText(Title).X + 30); + width = Mathf.Max(width, Render2D.MeasureText(titleLabelFont, Title).X + 30); height = Mathf.Max(height, Mathf.Max(leftHeight, rightHeight)); Resize(width, height); } diff --git a/Source/Editor/Tools/Foliage/FoliageTab.cs b/Source/Editor/Tools/Foliage/FoliageTab.cs index 1badb8c0c..58cf6f204 100644 --- a/Source/Editor/Tools/Foliage/FoliageTab.cs +++ b/Source/Editor/Tools/Foliage/FoliageTab.cs @@ -148,7 +148,7 @@ namespace FlaxEditor.Tools.Foliage Parent = _noFoliagePanel, Enabled = false }; - var textSize = Style.Current.FontMedium.MeasureText(buttonText); + var textSize = Render2D.MeasureText(Style.Current.FontMedium, buttonText); if (_createNewFoliage.Width < textSize.X) { _createNewFoliage.LocalX -= (textSize.X - _createNewFoliage.Width) / 2; diff --git a/Source/Editor/Tools/Terrain/CarveTab.cs b/Source/Editor/Tools/Terrain/CarveTab.cs index 4ff85ca23..12c9fd148 100644 --- a/Source/Editor/Tools/Terrain/CarveTab.cs +++ b/Source/Editor/Tools/Terrain/CarveTab.cs @@ -106,7 +106,7 @@ namespace FlaxEditor.Tools.Terrain Parent = _noTerrainPanel, Enabled = false }; - var textSize = Style.Current.FontMedium.MeasureText(buttonText); + var textSize = Render2D.MeasureText(Style.Current.FontMedium, buttonText); if (_createTerrainButton.Width < textSize.X) { _createTerrainButton.LocalX -= (textSize.X - _createTerrainButton.Width) / 2; diff --git a/Source/Editor/Tools/Terrain/CreateTerrainDialog.cs b/Source/Editor/Tools/Terrain/CreateTerrainDialog.cs index cba52283d..252891d44 100644 --- a/Source/Editor/Tools/Terrain/CreateTerrainDialog.cs +++ b/Source/Editor/Tools/Terrain/CreateTerrainDialog.cs @@ -96,7 +96,7 @@ namespace FlaxEditor.Tools.Terrain AnchorPreset = AnchorPresets.HorizontalStretchTop, Offsets = new Margin(0, 0, 0, 40), Parent = this, - Font = new MultiFontReference(Style.Current.FontTitle) + Font = new FontReference(Style.Current.FontTitle) }; var infoLabel = new Label { diff --git a/Source/Editor/Utilities/TextRenderUtils.cs b/Source/Editor/Utilities/TextRenderUtils.cs new file mode 100644 index 000000000..d7b79ae81 --- /dev/null +++ b/Source/Editor/Utilities/TextRenderUtils.cs @@ -0,0 +1,108 @@ +using FlaxEngine.GUI; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FlaxEngine +{ + public static class TextRenderUtils + { + /// + /// Draws a text using the global fallback defined in styles. + /// + /// The font to use. + /// The text to render. + /// The size and position of the area in which the text is drawn. + /// The text color. + /// The horizontal alignment of the text in a layout rectangle. + /// The vertical alignment of the text in a layout rectangle. + /// Describes how wrap text inside a layout rectangle. + /// The scale for distance one baseline from another. Default is 1. + /// The text drawing scale. Default is 1. + public static void DrawTextWithFallback(Font font, string text, Rectangle layoutRect, Color color, TextAlignment horizontalAlignment = TextAlignment.Near, TextAlignment verticalAlignment = TextAlignment.Near, TextWrapping textWrapping = TextWrapping.NoWrap, float baseLinesGapScale = 1.0f, float scale = 1.0f) + { + var layout = new TextLayoutOptions + { + Bounds = layoutRect, + HorizontalAlignment = horizontalAlignment, + VerticalAlignment = verticalAlignment, + TextWrapping = textWrapping, + Scale = scale, + BaseLinesGapScale = baseLinesGapScale, + }; + + + Render2D.DrawText(font, Style.Current.Fallbacks, text, color, ref layout); + } + + /// + /// Draws a text using the global fallback defined in styles. Given material must have GUI domain and a public parameter named Font (texture parameter used for a font atlas sampling). + /// + /// The font to use. + /// Custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture. + /// The text to render. + /// The size and position of the area in which the text is drawn. + /// The text color. + /// The horizontal alignment of the text in a layout rectangle. + /// The vertical alignment of the text in a layout rectangle. + /// Describes how wrap text inside a layout rectangle. + /// The scale for distance one baseline from another. Default is 1. + /// The text drawing scale. Default is 1. + public static void DrawTextWithFallback(Font font, MaterialBase customMaterial, string text, Rectangle layoutRect, Color color, TextAlignment horizontalAlignment = TextAlignment.Near, TextAlignment verticalAlignment = TextAlignment.Near, TextWrapping textWrapping = TextWrapping.NoWrap, float baseLinesGapScale = 1.0f, float scale = 1.0f) + { + var layout = new TextLayoutOptions + { + Bounds = layoutRect, + HorizontalAlignment = horizontalAlignment, + VerticalAlignment = verticalAlignment, + TextWrapping = textWrapping, + Scale = scale, + BaseLinesGapScale = baseLinesGapScale, + }; + + Render2D.DrawText(font, Style.Current.Fallbacks, text, color, ref layout, customMaterial); + } + + public static Float2 MeasureTextWithFallback(Font font, string text) + { + return font.MeasureText(Style.Current.Fallbacks, text); + } + + public static Float2 MeasureTextWithFallback(Font font, string text, ref TextRange textRange) + { + return font.MeasureText(Style.Current.Fallbacks, text, ref textRange); + } + + public static Float2 MeasureTextWithFallback(Font font, string text, ref TextLayoutOptions layout) + { + return font.MeasureText(Style.Current.Fallbacks, text, ref layout); + } + + public static Float2 MeasureTextWithFallback(Font font, string text, ref TextRange textRange, ref TextLayoutOptions layout) + { + return font.MeasureText(Style.Current.Fallbacks, text, ref textRange, ref layout); + } + + public static Float2 GetCharPositionWithFallback(Font font, string text, int index) + { + return font.GetCharPosition(Style.Current.Fallbacks, text, index); + } + + public static Float2 GetCharPositionWithFallback(Font font, string text, ref TextRange textRange, int index) + { + return font.GetCharPosition(Style.Current.Fallbacks, text, ref textRange, index); + } + + public static Float2 GetCharPositionWithFallback(Font font, string text, int index, ref TextLayoutOptions layout) + { + return font.GetCharPosition(Style.Current.Fallbacks, text, index, ref layout); + } + + public static Float2 GetCharPositionWithFallback(Font font, string text, ref TextRange textRange, int index, ref TextLayoutOptions layout) + { + return font.GetCharPosition(Style.Current.Fallbacks, text, ref textRange, index, ref layout); + } + } +} diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index c49392d01..77a677c0c 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -548,9 +548,9 @@ namespace FlaxEditor.Viewport #region Camera settings widget var largestText = "Relative Panning"; - var textSize = Style.Current.FontMedium.MeasureText(largestText); + var textSize = Render2D.MeasureText(Style.Current.FontMedium, largestText); var xLocationForExtras = textSize.X + 5; - var cameraSpeedTextWidth = Style.Current.FontMedium.MeasureText("0.00").X; + var cameraSpeedTextWidth = Render2D.MeasureText(Style.Current.FontMedium, "0.00").X; // Camera Settings Widget _cameraWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); @@ -801,7 +801,7 @@ namespace FlaxEditor.Viewport #region View mode widget largestText = "Brightness"; - textSize = Style.Current.FontMedium.MeasureText(largestText); + textSize = Render2D.MeasureText(Style.Current.FontMedium, largestText); xLocationForExtras = textSize.X + 5; var viewMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperLeft); diff --git a/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs b/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs index fe73048fd..250790621 100644 --- a/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs +++ b/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs @@ -164,7 +164,7 @@ namespace FlaxEditor.Viewport.Widgets var style = Style.Current; if (style != null && style.FontMedium) - Width = CalculateButtonWidth(_forcedTextWidth > 0.0f ? _forcedTextWidth : style.FontMedium.MeasureText(_text).X, Icon.IsValid); + Width = CalculateButtonWidth(_forcedTextWidth > 0.0f ? _forcedTextWidth : Render2D.MeasureText(style.FontMedium, _text).X, Icon.IsValid); } } } diff --git a/Source/Editor/Windows/AboutDialog.cs b/Source/Editor/Windows/AboutDialog.cs index 1a81a9421..ac887dadf 100644 --- a/Source/Editor/Windows/AboutDialog.cs +++ b/Source/Editor/Windows/AboutDialog.cs @@ -41,7 +41,7 @@ namespace FlaxEditor.Windows var nameLabel = new Label(icon.Right + 10, icon.Top, 200, 34) { Text = "Flax Engine", - Font = new MultiFontReference(Style.Current.FontTitle), + Font = new FontReference(Style.Current.FontTitle), HorizontalAlignment = TextAlignment.Near, VerticalAlignment = TextAlignment.Center, Parent = this @@ -54,7 +54,7 @@ namespace FlaxEditor.Windows Parent = this }; var buttonText = "Copy version info"; - var fontSize = Style.Current.FontMedium.MeasureText(buttonText); + var fontSize = Render2D.MeasureText(Style.Current.FontMedium, buttonText); var copyVersionButton = new Button(Width - fontSize.X - 8, 6, fontSize.X + 4, 20) { Text = buttonText, diff --git a/Source/Editor/Windows/Assets/FontWindow.cs b/Source/Editor/Windows/Assets/FontWindow.cs index 2e2e14d99..bc91ca5e2 100644 --- a/Source/Editor/Windows/Assets/FontWindow.cs +++ b/Source/Editor/Windows/Assets/FontWindow.cs @@ -144,7 +144,7 @@ namespace FlaxEditor.Windows.Assets protected override void OnAssetLinked() { Asset.WaitForLoaded(); - _textPreview.Font = new MultiFontReference([Asset], 30); + _textPreview.Font = new FontReference(Asset, 30); _inputText.Text = string.Format("This is a sample text using font {0}.", Asset.FamilyName); var options = Asset.Options; _proxy.Set(ref options); diff --git a/Source/Editor/Windows/ContentWindow.Search.cs b/Source/Editor/Windows/ContentWindow.Search.cs index f29dc0bd6..a1072d158 100644 --- a/Source/Editor/Windows/ContentWindow.Search.cs +++ b/Source/Editor/Windows/ContentWindow.Search.cs @@ -57,7 +57,7 @@ namespace FlaxEditor.Windows var textRect = new Rectangle(margin, 0, clientRect.Width - boxSize - 2.0f * margin, clientRect.Height); Render2D.PushClip(textRect); var textColor = TextColor; - Render2D.DrawText(Font.GetMultiFont(), "View", textRect, enabled ? textColor : textColor * 0.5f, TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, textScale); + Render2D.DrawText(Font.GetFont(), "View", textRect, enabled ? textColor : textColor * 0.5f, TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, textScale); Render2D.PopClip(); // Arrow diff --git a/Source/Editor/Windows/PluginsWindow.cs b/Source/Editor/Windows/PluginsWindow.cs index d6c14f4e4..4dfa0ed08 100644 --- a/Source/Editor/Windows/PluginsWindow.cs +++ b/Source/Editor/Windows/PluginsWindow.cs @@ -79,7 +79,7 @@ namespace FlaxEditor.Windows HorizontalAlignment = TextAlignment.Near, AnchorPreset = AnchorPresets.HorizontalStretchTop, Text = desc.Name, - Font = new MultiFontReference(Style.Current.FontLarge), + Font = new FontReference(Style.Current.FontLarge), Parent = this, Bounds = new Rectangle(tmp1, margin, Width - tmp1 - margin, 28), }; @@ -120,9 +120,9 @@ namespace FlaxEditor.Windows url = desc.HomepageUrl; else if (!string.IsNullOrEmpty(desc.RepositoryUrl)) url = desc.RepositoryUrl; - versionLabel.Font.ForEach(x => x.Font.WaitForLoaded()); - var font = versionLabel.Font.GetMultiFont(); - var authorWidth = font.MeasureText(desc.Author).X + 8; + versionLabel.Font.Font.WaitForLoaded(); + var font = versionLabel.Font.GetFont(); + var authorWidth = Render2D.MeasureText(font, desc.Author).X + 8; var authorLabel = new ClickableLabel { HorizontalAlignment = TextAlignment.Far, @@ -392,7 +392,6 @@ namespace FlaxEditor.Windows } Editor.Log("Plugin project has been cloned."); - try { // Start git submodule clone diff --git a/Source/Editor/Windows/Profiler/Timeline.cs b/Source/Editor/Windows/Profiler/Timeline.cs index 917647f23..00365f74a 100644 --- a/Source/Editor/Windows/Profiler/Timeline.cs +++ b/Source/Editor/Windows/Profiler/Timeline.cs @@ -86,7 +86,7 @@ namespace FlaxEditor.Windows.Profiler Render2D.DrawRectangle(bounds, color * 0.5f); if (_nameLength < 0 && style.FontMedium) - _nameLength = style.FontMedium.MeasureText(_name).X; + _nameLength = Render2D.MeasureText(style.FontMedium, _name).X; if (_nameLength < bounds.Width + 4) { diff --git a/Source/Engine/Render2D/FallbackFonts.h b/Source/Engine/Render2D/FallbackFonts.h index 4b97e1c11..5861354fc 100644 --- a/Source/Engine/Render2D/FallbackFonts.h +++ b/Source/Engine/Render2D/FallbackFonts.h @@ -39,7 +39,7 @@ public: /// /// Combine the primary fonts with the fallback fonts to get a font list /// - API_PROPERTY() FORCE_INLINE Array& GetFontList(float size) { + API_FUNCTION() FORCE_INLINE Array& GetFontList(float size) { Array* result; if (_cache.TryGet(size, result)) { return *result; diff --git a/Source/Engine/Render2D/MultiFontReference.cs b/Source/Engine/Render2D/MultiFontReference.cs deleted file mode 100644 index dd3f4d179..000000000 --- a/Source/Engine/Render2D/MultiFontReference.cs +++ /dev/null @@ -1,74 +0,0 @@ - - -using System.Collections.Generic; -using System.Linq; - -namespace FlaxEngine -{ - /// - /// Reference to multiple font references - /// - public class MultiFontReference : List - { - public MultiFontReference() - { - _cachedFont = null; - } - - public MultiFontReference(IEnumerable other) - { - AddRange(other); - _cachedFont = null; - } - - public MultiFontReference(MultiFontReference other) - { - AddRange(other); - _cachedFont = other._cachedFont; - } - - public MultiFontReference(MultiFontReference other, float size) - { - AddRange(other.Select(x => new FontReference(x) { Size = size })); - _cachedFont = null; - } - - public MultiFontReference(MultiFont other) - { - AddRange(other.Fonts.Select(x => new FontReference(x))); - _cachedFont = other; - } - - public MultiFontReference(FontAsset[] assets, float size) - { - AddRange(assets.Select(x => new FontReference(x, size))); - _cachedFont = null; - } - - [EditorOrder(0), Tooltip("The font asset to use as characters source.")] - public MultiFont GetMultiFont() - { - if (_cachedFont) - return _cachedFont; - var fontList = this.Where(x => x.Font).Select(x => x.GetFont()).ToArray(); - _cachedFont = MultiFont.Create(fontList); - return _cachedFont; - } - - public bool Verify() - { - foreach (var i in this) - { - if (!i.Font) - { - return false; - } - } - - return true; - } - - [NoSerialize] - private MultiFont _cachedFont; - } -} diff --git a/Source/Engine/Render2D/Render2D.cs b/Source/Engine/Render2D/Render2D.cs index cad8380ab..b7158ccc7 100644 --- a/Source/Engine/Render2D/Render2D.cs +++ b/Source/Engine/Render2D/Render2D.cs @@ -1,11 +1,17 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. +using FlaxEngine.GUI; using System; namespace FlaxEngine { partial class Render2D { + public static FallbackFonts Fallbacks + { + get; set; + } = null; + /// /// Pushes transformation layer. /// @@ -111,7 +117,8 @@ namespace FlaxEngine /// Describes how wrap text inside a layout rectangle. /// The scale for distance one baseline from another. Default is 1. /// The text drawing scale. Default is 1. - public static void DrawText(Font font, string text, Rectangle layoutRect, Color color, TextAlignment horizontalAlignment = TextAlignment.Near, TextAlignment verticalAlignment = TextAlignment.Near, TextWrapping textWrapping = TextWrapping.NoWrap, float baseLinesGapScale = 1.0f, float scale = 1.0f) + /// Whether to use fallback fonts for chars not renderable by the font. + public static void DrawText(Font font, string text, Rectangle layoutRect, Color color, TextAlignment horizontalAlignment = TextAlignment.Near, TextAlignment verticalAlignment = TextAlignment.Near, TextWrapping textWrapping = TextWrapping.NoWrap, float baseLinesGapScale = 1.0f, float scale = 1.0f, bool useFallback = true) { var layout = new TextLayoutOptions { @@ -122,7 +129,15 @@ namespace FlaxEngine Scale = scale, BaseLinesGapScale = baseLinesGapScale, }; - DrawText(font, text, color, ref layout); + + if (useFallback && Fallbacks != null) + { + DrawText(font, Fallbacks, text, color, ref layout); + } + else + { + DrawText(font, text, color, ref layout); + } } /// @@ -138,7 +153,8 @@ namespace FlaxEngine /// Describes how wrap text inside a layout rectangle. /// The scale for distance one baseline from another. Default is 1. /// The text drawing scale. Default is 1. - public static void DrawText(Font font, MaterialBase customMaterial, string text, Rectangle layoutRect, Color color, TextAlignment horizontalAlignment = TextAlignment.Near, TextAlignment verticalAlignment = TextAlignment.Near, TextWrapping textWrapping = TextWrapping.NoWrap, float baseLinesGapScale = 1.0f, float scale = 1.0f) + /// Whether to use fallback fonts for chars not renderable by the font. + public static void DrawText(Font font, MaterialBase customMaterial, string text, Rectangle layoutRect, Color color, TextAlignment horizontalAlignment = TextAlignment.Near, TextAlignment verticalAlignment = TextAlignment.Near, TextWrapping textWrapping = TextWrapping.NoWrap, float baseLinesGapScale = 1.0f, float scale = 1.0f, bool useFallback = true) { var layout = new TextLayoutOptions { @@ -149,63 +165,63 @@ namespace FlaxEngine Scale = scale, BaseLinesGapScale = baseLinesGapScale, }; - DrawText(font, text, color, ref layout, customMaterial); + + if (useFallback && Fallbacks != null) + { + DrawText(font, Fallbacks, text, color, ref layout, customMaterial); + } + else + { + DrawText(font, text, color, ref layout, customMaterial); + } } - /// - /// Draws a text. - /// - /// The fonts to use, ordered by priority. - /// The text to render. - /// The size and position of the area in which the text is drawn. - /// The text color. - /// The horizontal alignment of the text in a layout rectangle. - /// The vertical alignment of the text in a layout rectangle. - /// Describes how wrap text inside a layout rectangle. - /// The scale for distance one baseline from another. Default is 1. - /// The text drawing scale. Default is 1. - public static void DrawText(MultiFont fonts, string text, Rectangle layoutRect, Color color, TextAlignment horizontalAlignment = TextAlignment.Near, TextAlignment verticalAlignment = TextAlignment.Near, TextWrapping textWrapping = TextWrapping.NoWrap, float baseLinesGapScale = 1.0f, float scale = 1.0f) + public static Float2 MeasureText(Font font, string text, bool useFallback = true) { - var layout = new TextLayoutOptions + if (useFallback && Fallbacks != null) { - Bounds = layoutRect, - HorizontalAlignment = horizontalAlignment, - VerticalAlignment = verticalAlignment, - TextWrapping = textWrapping, - Scale = scale, - BaseLinesGapScale = baseLinesGapScale, - }; - - - DrawText(fonts, text, color, ref layout); + return font.MeasureText(Style.Current.Fallbacks, text); + } + else + { + return font.MeasureText(text); + } } - /// - /// Draws a text using a custom material shader. Given material must have GUI domain and a public parameter named Font (texture parameter used for a font atlas sampling). - /// - /// The fonts to use, ordered by priority. - /// Custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture. - /// The text to render. - /// The size and position of the area in which the text is drawn. - /// The text color. - /// The horizontal alignment of the text in a layout rectangle. - /// The vertical alignment of the text in a layout rectangle. - /// Describes how wrap text inside a layout rectangle. - /// The scale for distance one baseline from another. Default is 1. - /// The text drawing scale. Default is 1. - public static void DrawText(MultiFont fonts, MaterialBase customMaterial, string text, Rectangle layoutRect, Color color, TextAlignment horizontalAlignment = TextAlignment.Near, TextAlignment verticalAlignment = TextAlignment.Near, TextWrapping textWrapping = TextWrapping.NoWrap, float baseLinesGapScale = 1.0f, float scale = 1.0f) + public static Float2 MeasureText(Font font, string text, ref TextRange textRange, bool useFallback = true) { - var layout = new TextLayoutOptions + if (useFallback && Fallbacks != null) { - Bounds = layoutRect, - HorizontalAlignment = horizontalAlignment, - VerticalAlignment = verticalAlignment, - TextWrapping = textWrapping, - Scale = scale, - BaseLinesGapScale = baseLinesGapScale, - }; + return font.MeasureText(Style.Current.Fallbacks, text, ref textRange); + } + else + { + return font.MeasureText(text, ref textRange); + } + } - DrawText(fonts, text, color, ref layout, customMaterial); + public static Float2 MeasureText(Font font, string text, ref TextLayoutOptions layout, bool useFallback = true) + { + if (useFallback && Fallbacks != null) + { + return font.MeasureText(Style.Current.Fallbacks, text, ref layout); + } + else + { + return font.MeasureText(text, ref layout); + } + } + + public static Float2 MeasureText(Font font, string text, ref TextRange textRange, ref TextLayoutOptions layout, bool useFallback = true) + { + if (useFallback && Fallbacks != null) + { + return font.MeasureText(Style.Current.Fallbacks, text, ref textRange, ref layout); + } + else + { + return font.MeasureText(text, ref textRange, ref layout); + } } /// diff --git a/Source/Engine/Scripting/Scripting.cs b/Source/Engine/Scripting/Scripting.cs index bc800f88a..a4cfd76b1 100644 --- a/Source/Engine/Scripting/Scripting.cs +++ b/Source/Engine/Scripting/Scripting.cs @@ -294,12 +294,12 @@ namespace FlaxEngine style.DragWindow = style.BackgroundSelected * 0.7f; // Use optionally bundled default font (matches Editor) - FontAsset[] defaultFont = [Content.LoadAsyncInternal("Editor/Fonts/Roboto-Regular"), Content.LoadAsyncInternal("Editor/Fonts/NotoSansSC-Regular")]; + FontAsset defaultFont = Content.LoadAsyncInternal("Editor/Fonts/Roboto-Regular"); - style.FontTitle = new MultiFontReference(defaultFont, 18).GetMultiFont(); - style.FontLarge = new MultiFontReference(defaultFont, 14).GetMultiFont(); - style.FontMedium = new MultiFontReference(defaultFont, 9).GetMultiFont(); - style.FontSmall = new MultiFontReference(defaultFont, 9).GetMultiFont(); + style.FontTitle = new FontReference(defaultFont, 18).GetFont(); + style.FontLarge = new FontReference(defaultFont, 14).GetFont(); + style.FontMedium = new FontReference(defaultFont, 9).GetFont(); + style.FontSmall = new FontReference(defaultFont, 9).GetFont(); Style.Current = style; } diff --git a/Source/Engine/UI/GUI/Common/Button.cs b/Source/Engine/UI/GUI/Common/Button.cs index f21a29191..756501c5e 100644 --- a/Source/Engine/UI/GUI/Common/Button.cs +++ b/Source/Engine/UI/GUI/Common/Button.cs @@ -23,7 +23,7 @@ namespace FlaxEngine.GUI /// /// The font. /// - protected MultiFontReference _font; + protected FontReference _font; /// /// The text. @@ -44,7 +44,7 @@ namespace FlaxEngine.GUI /// Gets or sets the font used to draw button text. /// [EditorDisplay("Text Style"), EditorOrder(2022), ExpandGroups] - public MultiFontReference Font + public FontReference Font { get => _font; set => _font = value; @@ -156,7 +156,7 @@ namespace FlaxEngine.GUI var style = Style.Current; if (style != null) { - _font = new MultiFontReference(style.FontMedium); + _font = new FontReference(style.FontMedium); TextColor = style.Foreground; BackgroundColor = style.BackgroundNormal; BorderColor = style.BorderNormal; @@ -262,7 +262,7 @@ namespace FlaxEngine.GUI Render2D.DrawRectangle(clientRect, borderColor, BorderThickness); // Draw text - Render2D.DrawText(_font?.GetMultiFont(), TextMaterial, _text, clientRect, textColor, TextAlignment.Center, TextAlignment.Center); + Render2D.DrawText(_font?.GetFont(), TextMaterial, _text, clientRect, textColor, TextAlignment.Center, TextAlignment.Center); } /// diff --git a/Source/Engine/UI/GUI/Common/Dropdown.cs b/Source/Engine/UI/GUI/Common/Dropdown.cs index 2681aac1b..c9fef131c 100644 --- a/Source/Engine/UI/GUI/Common/Dropdown.cs +++ b/Source/Engine/UI/GUI/Common/Dropdown.cs @@ -278,7 +278,7 @@ namespace FlaxEngine.GUI /// Gets or sets the font used to draw text. /// [EditorDisplay("Text Style"), EditorOrder(2021)] - public MultiFontReference Font { get; set; } + public FontReference Font { get; set; } /// /// Gets or sets the custom material used to render the text. It must has domain set to GUI and have a public texture parameter named Font used to sample font atlas texture with font characters data. @@ -359,7 +359,7 @@ namespace FlaxEngine.GUI : base(0, 0, 120, 18.0f) { var style = Style.Current; - Font = new MultiFontReference(style.FontMedium); + Font = new FontReference(style.FontMedium); TextColor = style.Foreground; BackgroundColor = style.BackgroundNormal; BackgroundColorHighlighted = BackgroundColor; @@ -476,7 +476,7 @@ namespace FlaxEngine.GUI var font = Font.GetFont(); for (int i = 0; i < _items.Count; i++) { - itemsWidth = Mathf.Max(itemsWidth, itemsMargin + 4 + font.MeasureText(_items[i]).X); + itemsWidth = Mathf.Max(itemsWidth, itemsMargin + 4 + Render2D.MeasureText(font, _items[i]).X); } */ var itemsWidth = Width; @@ -674,7 +674,7 @@ namespace FlaxEngine.GUI var textRect = new Rectangle(margin, 0, clientRect.Width - boxSize - 2.0f * margin, clientRect.Height); Render2D.PushClip(textRect); var textColor = TextColor; - Render2D.DrawText(Font.GetMultiFont(), FontMaterial, _items[_selectedIndex], textRect, enabled ? textColor : textColor * 0.5f, TextAlignment.Near, TextAlignment.Center); + Render2D.DrawText(Font.GetFont(), FontMaterial, _items[_selectedIndex], textRect, enabled ? textColor : textColor * 0.5f, TextAlignment.Near, TextAlignment.Center); Render2D.PopClip(); } diff --git a/Source/Engine/UI/GUI/Common/Label.cs b/Source/Engine/UI/GUI/Common/Label.cs index ccc1cc190..d7147d3ab 100644 --- a/Source/Engine/UI/GUI/Common/Label.cs +++ b/Source/Engine/UI/GUI/Common/Label.cs @@ -26,7 +26,7 @@ namespace FlaxEngine.GUI /// /// The font. /// - protected MultiFontReference _font; + protected FontReference _font; /// /// Gets or sets the text. @@ -86,7 +86,7 @@ namespace FlaxEngine.GUI /// Gets or sets the font. /// [EditorDisplay("Text Style"), EditorOrder(2024)] - public MultiFontReference Font + public FontReference Font { get => _font; set @@ -192,7 +192,7 @@ namespace FlaxEngine.GUI { AutoFocus = false; var style = Style.Current; - Font = new MultiFontReference(style.FontMedium); + Font = new FontReference(style.FontMedium); TextColor = style.Foreground; TextColorHighlighted = style.Foreground; } @@ -203,7 +203,7 @@ namespace FlaxEngine.GUI { AutoFocus = false; var style = Style.Current; - Font = new MultiFontReference(style.FontMedium); + Font = new FontReference(style.FontMedium); TextColor = style.Foreground; TextColorHighlighted = style.Foreground; } @@ -235,7 +235,7 @@ namespace FlaxEngine.GUI } } - Render2D.DrawText(_font.GetMultiFont(), Material, _text, rect, color, hAlignment, wAlignment, Wrapping, BaseLinesGapScale, scale); + Render2D.DrawText(_font.GetFont(), Material, _text, rect, color, hAlignment, wAlignment, Wrapping, BaseLinesGapScale, scale); if (ClipText) Render2D.PopClip(); @@ -246,7 +246,7 @@ namespace FlaxEngine.GUI { if (_autoWidth || _autoHeight || _autoFitText) { - var font = _font.GetMultiFont(); + var font = _font.GetFont(); if (font) { // Calculate text size @@ -256,7 +256,7 @@ namespace FlaxEngine.GUI layout.Bounds.Size.X = Width - Margin.Width; else if (_autoWidth && !_autoHeight) layout.Bounds.Size.Y = Height - Margin.Height; - _textSize = font.MeasureText(_text, ref layout); + _textSize = Render2D.MeasureText(font, _text, ref layout); _textSize.Y *= BaseLinesGapScale; // Check if size is controlled via text diff --git a/Source/Engine/UI/GUI/Common/RichTextBox.cs b/Source/Engine/UI/GUI/Common/RichTextBox.cs index f8ac7d353..514878b37 100644 --- a/Source/Engine/UI/GUI/Common/RichTextBox.cs +++ b/Source/Engine/UI/GUI/Common/RichTextBox.cs @@ -46,7 +46,7 @@ namespace FlaxEngine.GUI var style = Style.Current; _textStyle = new TextBlockStyle { - Font = new FontReference(style.FontMedium.Fonts.First()), + Font = new FontReference(style.FontMedium), Color = style.Foreground, BackgroundSelectedBrush = new SolidColorBrush(style.BackgroundSelected), }; diff --git a/Source/Engine/UI/GUI/Common/TextBox.cs b/Source/Engine/UI/GUI/Common/TextBox.cs index d9c7c1a80..a32f3a76c 100644 --- a/Source/Engine/UI/GUI/Common/TextBox.cs +++ b/Source/Engine/UI/GUI/Common/TextBox.cs @@ -1,5 +1,6 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. +using System.Drawing; using System.Linq; namespace FlaxEngine.GUI @@ -40,7 +41,7 @@ namespace FlaxEngine.GUI /// Gets or sets the font. /// [EditorDisplay("Text Style"), EditorOrder(2024)] - public MultiFontReference Font { get; set; } + public FontReference Font { get; set; } /// /// Gets or sets the custom material used to render the text. It must has domain set to GUI and have a public texture parameter named Font used to sample font atlas texture with font characters data. @@ -90,7 +91,7 @@ namespace FlaxEngine.GUI _layout.Bounds = new Rectangle(DefaultMargin, 1, Width - 2 * DefaultMargin, Height - 2); var style = Style.Current; - Font = new MultiFontReference(style.FontMedium); + Font = new FontReference(style.FontMedium); TextColor = style.Foreground; WatermarkTextColor = style.ForegroundDisabled; SelectionColor = style.BackgroundSelected; @@ -99,33 +100,33 @@ namespace FlaxEngine.GUI /// public override Float2 GetTextSize() { - var font = Font.GetMultiFont(); + var font = Font.GetFont(); if (font == null) { return Float2.Zero; } - return font.MeasureText(_text, ref _layout); + return Render2D.MeasureText(font, _text, ref _layout); } /// public override Float2 GetCharPosition(int index, out float height) { - var font = Font.GetMultiFont(); + var font = Font.GetFont(); if (font == null) { height = Height; return Float2.Zero; } - height = font.MaxHeight / DpiScale; + height = font.Height / DpiScale; return font.GetCharPosition(_text, index, ref _layout); } /// public override int HitTestText(Float2 location) { - var font = Font.GetMultiFont(); + var font = Font.GetFont(); if (font == null) { return 0; @@ -148,7 +149,7 @@ namespace FlaxEngine.GUI // Cache data var rect = new Rectangle(Float2.Zero, Size); bool enabled = EnabledInHierarchy; - var font = Font.GetMultiFont(); + var font = Font.GetFont(); if (!font) return; @@ -172,7 +173,7 @@ namespace FlaxEngine.GUI { var leftEdge = font.GetCharPosition(_text, SelectionLeft, ref _layout); var rightEdge = font.GetCharPosition(_text, SelectionRight, ref _layout); - float fontHeight = font.MaxHeight / DpiScale; + float fontHeight = font.Height / DpiScale; // Draw selection background float alpha = Mathf.Min(1.0f, Mathf.Cos(_animateTime * BackgroundSelectedFlashSpeed) * 0.5f + 1.3f); @@ -212,11 +213,25 @@ namespace FlaxEngine.GUI var color = TextColor; if (!enabled) color *= 0.6f; - Render2D.DrawText(font, _text, color, ref _layout, TextMaterial); + if (Render2D.Fallbacks != null) + { + Render2D.DrawText(font, Render2D.Fallbacks, _text, color, ref _layout, TextMaterial); + } + else + { + Render2D.DrawText(font, _text, color, ref _layout, TextMaterial); + } } else if (!string.IsNullOrEmpty(_watermarkText) && !IsFocused) { - Render2D.DrawText(font, _watermarkText, WatermarkTextColor, ref _layout, TextMaterial); + if (Render2D.Fallbacks != null) + { + Render2D.DrawText(font, Render2D.Fallbacks, _watermarkText, WatermarkTextColor, ref _layout, TextMaterial); + } + else + { + Render2D.DrawText(font, _watermarkText, WatermarkTextColor, ref _layout, TextMaterial); + } } // Caret diff --git a/Source/Engine/UI/GUI/Panels/DropPanel.cs b/Source/Engine/UI/GUI/Panels/DropPanel.cs index 06256c9f8..ed620ab98 100644 --- a/Source/Engine/UI/GUI/Panels/DropPanel.cs +++ b/Source/Engine/UI/GUI/Panels/DropPanel.cs @@ -130,7 +130,7 @@ namespace FlaxEngine.GUI /// Gets or sets the font used to render panel header text. /// [EditorDisplay("Header Text Style"), EditorOrder(2020), ExpandGroups] - public MultiFontReference HeaderTextFont { get; set; } + public FontReference HeaderTextFont { get; set; } /// /// Gets or sets the custom material used to render the text. It must has domain set to GUI and have a public texture parameter named Font used to sample font atlas texture with font characters data. @@ -238,7 +238,7 @@ namespace FlaxEngine.GUI var style = Style.Current; HeaderColor = style.BackgroundNormal; HeaderColorMouseOver = style.BackgroundHighlighted; - HeaderTextFont = new MultiFontReference(style.FontMedium); + HeaderTextFont = new FontReference(style.FontMedium); HeaderTextColor = style.Foreground; ArrowImageOpened = new SpriteBrush(style.ArrowDown); ArrowImageClosed = new SpriteBrush(style.ArrowRight); @@ -375,7 +375,7 @@ namespace FlaxEngine.GUI textColor *= 0.6f; } - Render2D.DrawText(HeaderTextFont.GetMultiFont(), HeaderTextMaterial, HeaderText, textRect, textColor, TextAlignment.Near, TextAlignment.Center); + Render2D.DrawText(HeaderTextFont.GetFont(), HeaderTextMaterial, HeaderText, textRect, textColor, TextAlignment.Near, TextAlignment.Center); if (!_isClosed && EnableContainmentLines) { diff --git a/Source/Engine/UI/GUI/Style.cs b/Source/Engine/UI/GUI/Style.cs index a8b87fb3a..c4b51b085 100644 --- a/Source/Engine/UI/GUI/Style.cs +++ b/Source/Engine/UI/GUI/Style.cs @@ -15,61 +15,67 @@ namespace FlaxEngine.GUI public static Style Current { get; set; } [Serialize] - private MultiFontReference _fontTitle; + private FontReference _fontTitle; /// /// The font title. /// [NoSerialize] [EditorOrder(10)] - public MultiFont FontTitle + public Font FontTitle { - get => _fontTitle?.GetMultiFont(); - set => _fontTitle = new MultiFontReference(value); + get => _fontTitle?.GetFont(); + set => _fontTitle = new FontReference(value); } [Serialize] - private MultiFontReference _fontLarge; + private FontReference _fontLarge; /// /// The font large. /// [NoSerialize] [EditorOrder(20)] - public MultiFont FontLarge + public Font FontLarge { - get => _fontLarge?.GetMultiFont(); - set => _fontLarge = new MultiFontReference(value); + get => _fontLarge?.GetFont(); + set => _fontLarge = new FontReference(value); } [Serialize] - private MultiFontReference _fontMedium; + private FontReference _fontMedium; /// /// The font medium. /// [NoSerialize] [EditorOrder(30)] - public MultiFont FontMedium + public Font FontMedium { - get => _fontMedium?.GetMultiFont(); - set => _fontMedium = new MultiFontReference(value); + get => _fontMedium?.GetFont(); + set => _fontMedium = new FontReference(value); } [Serialize] - private MultiFontReference _fontSmall; + private FontReference _fontSmall; /// /// The font small. /// [NoSerialize] [EditorOrder(40)] - public MultiFont FontSmall + public Font FontSmall { - get => _fontSmall?.GetMultiFont(); - set => _fontSmall = new MultiFontReference(value); + get => _fontSmall?.GetFont(); + set => _fontSmall = new FontReference(value); } + /// + /// The fallback fonts to use if the primary font can't render the char. + /// + [EditorOrder(50)] + public FallbackFonts Fallbacks; + /// /// The background color. /// From 623f478b4466bb248db1f6f7448124a1b338668b Mon Sep 17 00:00:00 2001 From: ExMatics HydrogenC <33123710+HydrogenC@users.noreply.github.com> Date: Thu, 30 Nov 2023 23:22:11 +0800 Subject: [PATCH 042/139] Move fallback rendering to new class --- .../CustomEditors/Dedicated/ScriptsEditor.cs | 2 +- .../Dedicated/UIControlEditor.cs | 2 +- .../Editors/ActorTransformEditor.cs | 2 +- .../Editor/CustomEditors/Editors/TagEditor.cs | 2 +- .../GUI/ContextMenu/ContextMenuButton.cs | 4 +- Source/Editor/GUI/Docking/DockWindow.cs | 2 +- Source/Editor/GUI/MainMenuButton.cs | 2 +- Source/Editor/GUI/NavigationButton.cs | 2 +- .../Editor/GUI/Timeline/Tracks/MemberTrack.cs | 2 +- Source/Editor/GUI/ToolStripButton.cs | 2 +- Source/Editor/GUI/Tree/TreeNode.cs | 2 +- .../Archetypes/Animation.StateMachine.cs | 4 +- Source/Editor/Surface/Archetypes/Animation.cs | 2 +- .../Editor/Surface/Archetypes/BehaviorTree.cs | 4 +- .../Surface/ContextMenu/VisjectCMItem.cs | 2 +- Source/Editor/Surface/Elements/InputBox.cs | 4 +- Source/Editor/Surface/SurfaceNode.cs | 6 +- Source/Editor/Tools/Foliage/FoliageTab.cs | 2 +- Source/Editor/Tools/Terrain/CarveTab.cs | 2 +- Source/Editor/Utilities/TextRenderUtils.cs | 108 --------------- Source/Editor/Viewport/EditorViewport.cs | 6 +- .../Viewport/Widgets/ViewportWidgetButton.cs | 2 +- Source/Editor/Windows/AboutDialog.cs | 2 +- Source/Editor/Windows/PluginsWindow.cs | 2 +- Source/Editor/Windows/Profiler/Timeline.cs | 2 +- Source/Engine/Render2D/FallbackTextUtils.cs | 126 ++++++++++++++++++ Source/Engine/Render2D/Render2D.cs | 78 +---------- Source/Engine/UI/GUI/Common/Dropdown.cs | 2 +- Source/Engine/UI/GUI/Common/Label.cs | 2 +- Source/Engine/UI/GUI/Common/TextBox.cs | 2 +- 30 files changed, 165 insertions(+), 217 deletions(-) delete mode 100644 Source/Editor/Utilities/TextRenderUtils.cs create mode 100644 Source/Engine/Render2D/FallbackTextUtils.cs diff --git a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs index 90ae9ae54..ab949508e 100644 --- a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs @@ -43,7 +43,7 @@ namespace FlaxEditor.CustomEditors.Dedicated // Add script button var buttonText = "Add script"; - var textSize = Render2D.MeasureText(Style.Current.FontMedium, buttonText); + var textSize = FallbackTextUtils.MeasureText(Style.Current.FontMedium, buttonText); float addScriptButtonWidth = (textSize.X < 60.0f) ? 60.0f : textSize.X + 4; var buttonHeight = (textSize.Y < 18) ? 18 : textSize.Y + 4; _addScriptsButton = new Button diff --git a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs index b8250918f..4efbee259 100644 --- a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs @@ -423,7 +423,7 @@ namespace FlaxEditor.CustomEditors.Dedicated // Set control type button var space = layout.Space(20); var buttonText = "Set Type"; - var textSize = Render2D.MeasureText(FlaxEngine.GUI.Style.Current.FontMedium, buttonText); + var textSize = FallbackTextUtils.MeasureText(FlaxEngine.GUI.Style.Current.FontMedium, buttonText); float setTypeButtonWidth = (textSize.X < 60.0f) ? 60.0f : textSize.X + 4; var setTypeButton = new Button { diff --git a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs index e84bf5914..8889a765a 100644 --- a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs @@ -101,7 +101,7 @@ namespace FlaxEditor.CustomEditors.Editors _linkButton.Clicked += ToggleLink; ToggleEnabled(); SetLinkStyle(); - var textSize = Render2D.MeasureText(FlaxEngine.GUI.Style.Current.FontMedium, LinkedLabel.Text.Value); + var textSize = FallbackTextUtils.MeasureText(FlaxEngine.GUI.Style.Current.FontMedium, LinkedLabel.Text.Value); _linkButton.LocalX += textSize.X + 10; LinkedLabel.SetupContextMenu += (label, menu, editor) => { diff --git a/Source/Editor/CustomEditors/Editors/TagEditor.cs b/Source/Editor/CustomEditors/Editors/TagEditor.cs index 49ac9d937..bbece7e04 100644 --- a/Source/Editor/CustomEditors/Editors/TagEditor.cs +++ b/Source/Editor/CustomEditors/Editors/TagEditor.cs @@ -631,7 +631,7 @@ namespace FlaxEditor.CustomEditors.Editors TooltipText = "Edit...", Parent = _label, }; - var textSize = Render2D.MeasureText(FlaxEngine.GUI.Style.Current.FontMedium, buttonText); + var textSize = FallbackTextUtils.MeasureText(FlaxEngine.GUI.Style.Current.FontMedium, buttonText); if (textSize.Y > button.Width) button.Width = textSize.Y + 2; diff --git a/Source/Editor/GUI/ContextMenu/ContextMenuButton.cs b/Source/Editor/GUI/ContextMenu/ContextMenuButton.cs index 3137de240..d8c492e46 100644 --- a/Source/Editor/GUI/ContextMenu/ContextMenuButton.cs +++ b/Source/Editor/GUI/ContextMenu/ContextMenuButton.cs @@ -236,9 +236,9 @@ namespace FlaxEditor.GUI.ContextMenu float width = 20; if (style.FontMedium) { - width += Render2D.MeasureText(style.FontMedium, Text).X; + width += FallbackTextUtils.MeasureText(style.FontMedium, Text).X; if (!string.IsNullOrEmpty(ShortKeys)) - width += 40 + Render2D.MeasureText(style.FontMedium, ShortKeys).X; + width += 40 + FallbackTextUtils.MeasureText(style.FontMedium, ShortKeys).X; } return Mathf.Max(width, base.MinimumWidth); diff --git a/Source/Editor/GUI/Docking/DockWindow.cs b/Source/Editor/GUI/Docking/DockWindow.cs index 36a4c112e..b7c12287f 100644 --- a/Source/Editor/GUI/Docking/DockWindow.cs +++ b/Source/Editor/GUI/Docking/DockWindow.cs @@ -489,7 +489,7 @@ namespace FlaxEditor.GUI.Docking { var style = Style.Current; if (style?.FontMedium != null) - _titleSize = Render2D.MeasureText(style.FontMedium, _title); + _titleSize = FallbackTextUtils.MeasureText(style.FontMedium, _title); } base.PerformLayoutBeforeChildren(); diff --git a/Source/Editor/GUI/MainMenuButton.cs b/Source/Editor/GUI/MainMenuButton.cs index 3440996aa..6fe8b98d3 100644 --- a/Source/Editor/GUI/MainMenuButton.cs +++ b/Source/Editor/GUI/MainMenuButton.cs @@ -103,7 +103,7 @@ namespace FlaxEditor.GUI float width = 18; if (style.FontMedium) - width += Render2D.MeasureText(style.FontMedium, Text).X; + width += FallbackTextUtils.MeasureText(style.FontMedium, Text).X; Width = width; } diff --git a/Source/Editor/GUI/NavigationButton.cs b/Source/Editor/GUI/NavigationButton.cs index 6face21ef..5d399da02 100644 --- a/Source/Editor/GUI/NavigationButton.cs +++ b/Source/Editor/GUI/NavigationButton.cs @@ -68,7 +68,7 @@ namespace FlaxEditor.GUI if (style.FontMedium) { - Width = Render2D.MeasureText(style.FontMedium, Text).X + 2 * DefaultMargin; + Width = FallbackTextUtils.MeasureText(style.FontMedium, Text).X + 2 * DefaultMargin; } } } diff --git a/Source/Editor/GUI/Timeline/Tracks/MemberTrack.cs b/Source/Editor/GUI/Timeline/Tracks/MemberTrack.cs index b4433622e..dd79922ac 100644 --- a/Source/Editor/GUI/Timeline/Tracks/MemberTrack.cs +++ b/Source/Editor/GUI/Timeline/Tracks/MemberTrack.cs @@ -345,7 +345,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks if (_previewValue != null) { // Based on Track.Draw for track text placement - var left = _xOffset + 16 + Render2D.MeasureText(Style.Current.FontSmall, Title ?? Name).X; + var left = _xOffset + 16 + FallbackTextUtils.MeasureText(Style.Current.FontSmall, Title ?? Name).X; if (Icon.IsValid) left += 18; if (IsExpanded) diff --git a/Source/Editor/GUI/ToolStripButton.cs b/Source/Editor/GUI/ToolStripButton.cs index cf34fd36b..50dce78b4 100644 --- a/Source/Editor/GUI/ToolStripButton.cs +++ b/Source/Editor/GUI/ToolStripButton.cs @@ -152,7 +152,7 @@ namespace FlaxEditor.GUI if (hasSprite) width += iconSize; if (!string.IsNullOrEmpty(_text) && style.FontMedium) - width += Render2D.MeasureText(style.FontMedium, _text).X + (hasSprite ? DefaultMargin : 0); + width += FallbackTextUtils.MeasureText(style.FontMedium, _text).X + (hasSprite ? DefaultMargin : 0); Width = width; } diff --git a/Source/Editor/GUI/Tree/TreeNode.cs b/Source/Editor/GUI/Tree/TreeNode.cs index 7b47e5eb6..700ba6da1 100644 --- a/Source/Editor/GUI/Tree/TreeNode.cs +++ b/Source/Editor/GUI/Tree/TreeNode.cs @@ -576,7 +576,7 @@ namespace FlaxEditor.GUI.Tree var font = TextFont.GetFont(); if (font) { - _textWidth = Render2D.MeasureText(font, _text).X; + _textWidth = FallbackTextUtils.MeasureText(font, _text).X; _textChanged = false; } } diff --git a/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs b/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs index 19a995b1e..078b89c56 100644 --- a/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs +++ b/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs @@ -337,7 +337,7 @@ namespace FlaxEditor.Surface.Archetypes _textRect = new Rectangle(Float2.Zero, Size); var style = Style.Current; - var titleSize = Render2D.MeasureText(style.FontLarge, Title); + var titleSize = FallbackTextUtils.MeasureText(style.FontLarge, Title); var width = Mathf.Max(100, titleSize.X + 50); Resize(width, 0); titleSize.X += 8.0f; @@ -1402,7 +1402,7 @@ namespace FlaxEditor.Surface.Archetypes { Title = StateTitle; var style = Style.Current; - var titleSize = Render2D.MeasureText(style.FontLarge, Title); + var titleSize = FallbackTextUtils.MeasureText(style.FontLarge, Title); var width = Mathf.Max(100, titleSize.X + 50); Resize(width, 0); titleSize.X += 8.0f; diff --git a/Source/Editor/Surface/Archetypes/Animation.cs b/Source/Editor/Surface/Archetypes/Animation.cs index dcf4a9689..dae16b425 100644 --- a/Source/Editor/Surface/Archetypes/Animation.cs +++ b/Source/Editor/Surface/Archetypes/Animation.cs @@ -77,7 +77,7 @@ namespace FlaxEditor.Surface.Archetypes Title = asset?.ShortName ?? "Animation"; var style = Style.Current; - Resize(Mathf.Max(230, Render2D.MeasureText(style.FontLarge, Title).X + 30), 160); + Resize(Mathf.Max(230, FallbackTextUtils.MeasureText(style.FontLarge, Title).X + 30), 160); } /// diff --git a/Source/Editor/Surface/Archetypes/BehaviorTree.cs b/Source/Editor/Surface/Archetypes/BehaviorTree.cs index 364dfa1ef..5f3c0b0a2 100644 --- a/Source/Editor/Surface/Archetypes/BehaviorTree.cs +++ b/Source/Editor/Surface/Archetypes/BehaviorTree.cs @@ -101,7 +101,7 @@ namespace FlaxEditor.Surface.Archetypes _debugRelevant = Behavior.GetNodeDebugRelevancy(instance, behavior); _debugInfo = Behavior.GetNodeDebugInfo(instance, behavior); if (!string.IsNullOrEmpty(_debugInfo)) - _debugInfoSize = Render2D.MeasureText(Style.Current.FontSmall, _debugInfo); + _debugInfoSize = FallbackTextUtils.MeasureText(Style.Current.FontSmall, _debugInfo); } } @@ -488,7 +488,7 @@ namespace FlaxEditor.Surface.Archetypes var height = 0.0f; var titleLabelFont = Style.Current.FontLarge; width = Mathf.Max(width, 100.0f); - width = Mathf.Max(width, Render2D.MeasureText(titleLabelFont, Title).X + 30); + width = Mathf.Max(width, FallbackTextUtils.MeasureText(titleLabelFont, Title).X + 30); if (_debugInfoSize.X > 0) { width = Mathf.Max(width, _debugInfoSize.X + 8.0f); diff --git a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs index fd9f0c63b..019be78b6 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs @@ -286,7 +286,7 @@ namespace FlaxEditor.Surface.ContextMenu Render2D.DrawText(style.FontSmall, _archetype.Title, textRect, Enabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center); if (_archetype.SubTitle != null) { - var titleLength = Render2D.MeasureText(style.FontSmall, _archetype.Title).X; + var titleLength = FallbackTextUtils.MeasureText(style.FontSmall, _archetype.Title).X; var subTitleRect = new Rectangle(textRect.X + titleLength, textRect.Y, textRect.Width - titleLength, textRect.Height); Render2D.DrawText(style.FontSmall, _archetype.SubTitle, subTitleRect, style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center); } diff --git a/Source/Editor/Surface/Elements/InputBox.cs b/Source/Editor/Surface/Elements/InputBox.cs index 2ab286bd9..526d85d15 100644 --- a/Source/Editor/Surface/Elements/InputBox.cs +++ b/Source/Editor/Surface/Elements/InputBox.cs @@ -1428,7 +1428,7 @@ namespace FlaxEditor.Surface.Elements if (_defaultValueEditor != null) { - _defaultValueEditor.Location = new Float2(X + Width + 8 + Render2D.MeasureText(Style.Current.FontSmall, Text).X, Y); + _defaultValueEditor.Location = new Float2(X + Width + 8 + FallbackTextUtils.MeasureText(Style.Current.FontSmall, Text).X, Y); } } @@ -1635,7 +1635,7 @@ namespace FlaxEditor.Surface.Elements { if (DefaultValueEditors[i].CanUse(this, ref _currentType)) { - var bounds = new Rectangle(X + Width + 8 + Render2D.MeasureText(Style.Current.FontSmall, Text).X, Y, 90, Height); + var bounds = new Rectangle(X + Width + 8 + FallbackTextUtils.MeasureText(Style.Current.FontSmall, Text).X, Y, 90, Height); _editor = DefaultValueEditors[i]; try { diff --git a/Source/Editor/Surface/SurfaceNode.cs b/Source/Editor/Surface/SurfaceNode.cs index 60d776102..3c2381192 100644 --- a/Source/Editor/Surface/SurfaceNode.cs +++ b/Source/Editor/Surface/SurfaceNode.cs @@ -200,7 +200,7 @@ namespace FlaxEditor.Surface continue; if (child is InputBox inputBox) { - var boxWidth = Render2D.MeasureText(boxLabelFont, inputBox.Text).X + 20; + var boxWidth = FallbackTextUtils.MeasureText(boxLabelFont, inputBox.Text).X + 20; if (inputBox.DefaultValueEditor != null) boxWidth += inputBox.DefaultValueEditor.Width + 4; leftWidth = Mathf.Max(leftWidth, boxWidth); @@ -208,7 +208,7 @@ namespace FlaxEditor.Surface } else if (child is OutputBox outputBox) { - rightWidth = Mathf.Max(rightWidth, Render2D.MeasureText(boxLabelFont, outputBox.Text).X + 20); + rightWidth = Mathf.Max(rightWidth, FallbackTextUtils.MeasureText(boxLabelFont, outputBox.Text).X + 20); rightHeight = Mathf.Max(rightHeight, outputBox.Archetype.Position.Y - Constants.NodeMarginY - Constants.NodeHeaderSize + 20.0f); } else if (child is Control control) @@ -226,7 +226,7 @@ namespace FlaxEditor.Surface } } width = Mathf.Max(width, leftWidth + rightWidth + 10); - width = Mathf.Max(width, Render2D.MeasureText(titleLabelFont, Title).X + 30); + width = Mathf.Max(width, FallbackTextUtils.MeasureText(titleLabelFont, Title).X + 30); height = Mathf.Max(height, Mathf.Max(leftHeight, rightHeight)); Resize(width, height); } diff --git a/Source/Editor/Tools/Foliage/FoliageTab.cs b/Source/Editor/Tools/Foliage/FoliageTab.cs index 58cf6f204..c2b1fbe68 100644 --- a/Source/Editor/Tools/Foliage/FoliageTab.cs +++ b/Source/Editor/Tools/Foliage/FoliageTab.cs @@ -148,7 +148,7 @@ namespace FlaxEditor.Tools.Foliage Parent = _noFoliagePanel, Enabled = false }; - var textSize = Render2D.MeasureText(Style.Current.FontMedium, buttonText); + var textSize = FallbackTextUtils.MeasureText(Style.Current.FontMedium, buttonText); if (_createNewFoliage.Width < textSize.X) { _createNewFoliage.LocalX -= (textSize.X - _createNewFoliage.Width) / 2; diff --git a/Source/Editor/Tools/Terrain/CarveTab.cs b/Source/Editor/Tools/Terrain/CarveTab.cs index 12c9fd148..0a86aab6f 100644 --- a/Source/Editor/Tools/Terrain/CarveTab.cs +++ b/Source/Editor/Tools/Terrain/CarveTab.cs @@ -106,7 +106,7 @@ namespace FlaxEditor.Tools.Terrain Parent = _noTerrainPanel, Enabled = false }; - var textSize = Render2D.MeasureText(Style.Current.FontMedium, buttonText); + var textSize = FallbackTextUtils.MeasureText(Style.Current.FontMedium, buttonText); if (_createTerrainButton.Width < textSize.X) { _createTerrainButton.LocalX -= (textSize.X - _createTerrainButton.Width) / 2; diff --git a/Source/Editor/Utilities/TextRenderUtils.cs b/Source/Editor/Utilities/TextRenderUtils.cs deleted file mode 100644 index d7b79ae81..000000000 --- a/Source/Editor/Utilities/TextRenderUtils.cs +++ /dev/null @@ -1,108 +0,0 @@ -using FlaxEngine.GUI; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace FlaxEngine -{ - public static class TextRenderUtils - { - /// - /// Draws a text using the global fallback defined in styles. - /// - /// The font to use. - /// The text to render. - /// The size and position of the area in which the text is drawn. - /// The text color. - /// The horizontal alignment of the text in a layout rectangle. - /// The vertical alignment of the text in a layout rectangle. - /// Describes how wrap text inside a layout rectangle. - /// The scale for distance one baseline from another. Default is 1. - /// The text drawing scale. Default is 1. - public static void DrawTextWithFallback(Font font, string text, Rectangle layoutRect, Color color, TextAlignment horizontalAlignment = TextAlignment.Near, TextAlignment verticalAlignment = TextAlignment.Near, TextWrapping textWrapping = TextWrapping.NoWrap, float baseLinesGapScale = 1.0f, float scale = 1.0f) - { - var layout = new TextLayoutOptions - { - Bounds = layoutRect, - HorizontalAlignment = horizontalAlignment, - VerticalAlignment = verticalAlignment, - TextWrapping = textWrapping, - Scale = scale, - BaseLinesGapScale = baseLinesGapScale, - }; - - - Render2D.DrawText(font, Style.Current.Fallbacks, text, color, ref layout); - } - - /// - /// Draws a text using the global fallback defined in styles. Given material must have GUI domain and a public parameter named Font (texture parameter used for a font atlas sampling). - /// - /// The font to use. - /// Custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture. - /// The text to render. - /// The size and position of the area in which the text is drawn. - /// The text color. - /// The horizontal alignment of the text in a layout rectangle. - /// The vertical alignment of the text in a layout rectangle. - /// Describes how wrap text inside a layout rectangle. - /// The scale for distance one baseline from another. Default is 1. - /// The text drawing scale. Default is 1. - public static void DrawTextWithFallback(Font font, MaterialBase customMaterial, string text, Rectangle layoutRect, Color color, TextAlignment horizontalAlignment = TextAlignment.Near, TextAlignment verticalAlignment = TextAlignment.Near, TextWrapping textWrapping = TextWrapping.NoWrap, float baseLinesGapScale = 1.0f, float scale = 1.0f) - { - var layout = new TextLayoutOptions - { - Bounds = layoutRect, - HorizontalAlignment = horizontalAlignment, - VerticalAlignment = verticalAlignment, - TextWrapping = textWrapping, - Scale = scale, - BaseLinesGapScale = baseLinesGapScale, - }; - - Render2D.DrawText(font, Style.Current.Fallbacks, text, color, ref layout, customMaterial); - } - - public static Float2 MeasureTextWithFallback(Font font, string text) - { - return font.MeasureText(Style.Current.Fallbacks, text); - } - - public static Float2 MeasureTextWithFallback(Font font, string text, ref TextRange textRange) - { - return font.MeasureText(Style.Current.Fallbacks, text, ref textRange); - } - - public static Float2 MeasureTextWithFallback(Font font, string text, ref TextLayoutOptions layout) - { - return font.MeasureText(Style.Current.Fallbacks, text, ref layout); - } - - public static Float2 MeasureTextWithFallback(Font font, string text, ref TextRange textRange, ref TextLayoutOptions layout) - { - return font.MeasureText(Style.Current.Fallbacks, text, ref textRange, ref layout); - } - - public static Float2 GetCharPositionWithFallback(Font font, string text, int index) - { - return font.GetCharPosition(Style.Current.Fallbacks, text, index); - } - - public static Float2 GetCharPositionWithFallback(Font font, string text, ref TextRange textRange, int index) - { - return font.GetCharPosition(Style.Current.Fallbacks, text, ref textRange, index); - } - - public static Float2 GetCharPositionWithFallback(Font font, string text, int index, ref TextLayoutOptions layout) - { - return font.GetCharPosition(Style.Current.Fallbacks, text, index, ref layout); - } - - public static Float2 GetCharPositionWithFallback(Font font, string text, ref TextRange textRange, int index, ref TextLayoutOptions layout) - { - return font.GetCharPosition(Style.Current.Fallbacks, text, ref textRange, index, ref layout); - } - } -} diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index 77a677c0c..0633d0ba9 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -548,9 +548,9 @@ namespace FlaxEditor.Viewport #region Camera settings widget var largestText = "Relative Panning"; - var textSize = Render2D.MeasureText(Style.Current.FontMedium, largestText); + var textSize = FallbackTextUtils.MeasureText(Style.Current.FontMedium, largestText); var xLocationForExtras = textSize.X + 5; - var cameraSpeedTextWidth = Render2D.MeasureText(Style.Current.FontMedium, "0.00").X; + var cameraSpeedTextWidth = FallbackTextUtils.MeasureText(Style.Current.FontMedium, "0.00").X; // Camera Settings Widget _cameraWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); @@ -801,7 +801,7 @@ namespace FlaxEditor.Viewport #region View mode widget largestText = "Brightness"; - textSize = Render2D.MeasureText(Style.Current.FontMedium, largestText); + textSize = FallbackTextUtils.MeasureText(Style.Current.FontMedium, largestText); xLocationForExtras = textSize.X + 5; var viewMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperLeft); diff --git a/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs b/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs index 250790621..598f4ee32 100644 --- a/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs +++ b/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs @@ -164,7 +164,7 @@ namespace FlaxEditor.Viewport.Widgets var style = Style.Current; if (style != null && style.FontMedium) - Width = CalculateButtonWidth(_forcedTextWidth > 0.0f ? _forcedTextWidth : Render2D.MeasureText(style.FontMedium, _text).X, Icon.IsValid); + Width = CalculateButtonWidth(_forcedTextWidth > 0.0f ? _forcedTextWidth : FallbackTextUtils.MeasureText(style.FontMedium, _text).X, Icon.IsValid); } } } diff --git a/Source/Editor/Windows/AboutDialog.cs b/Source/Editor/Windows/AboutDialog.cs index ac887dadf..02aad16ec 100644 --- a/Source/Editor/Windows/AboutDialog.cs +++ b/Source/Editor/Windows/AboutDialog.cs @@ -54,7 +54,7 @@ namespace FlaxEditor.Windows Parent = this }; var buttonText = "Copy version info"; - var fontSize = Render2D.MeasureText(Style.Current.FontMedium, buttonText); + var fontSize = FallbackTextUtils.MeasureText(Style.Current.FontMedium, buttonText); var copyVersionButton = new Button(Width - fontSize.X - 8, 6, fontSize.X + 4, 20) { Text = buttonText, diff --git a/Source/Editor/Windows/PluginsWindow.cs b/Source/Editor/Windows/PluginsWindow.cs index 4dfa0ed08..03beab2fa 100644 --- a/Source/Editor/Windows/PluginsWindow.cs +++ b/Source/Editor/Windows/PluginsWindow.cs @@ -122,7 +122,7 @@ namespace FlaxEditor.Windows url = desc.RepositoryUrl; versionLabel.Font.Font.WaitForLoaded(); var font = versionLabel.Font.GetFont(); - var authorWidth = Render2D.MeasureText(font, desc.Author).X + 8; + var authorWidth = FallbackTextUtils.MeasureText(font, desc.Author).X + 8; var authorLabel = new ClickableLabel { HorizontalAlignment = TextAlignment.Far, diff --git a/Source/Editor/Windows/Profiler/Timeline.cs b/Source/Editor/Windows/Profiler/Timeline.cs index 00365f74a..780b31a3b 100644 --- a/Source/Editor/Windows/Profiler/Timeline.cs +++ b/Source/Editor/Windows/Profiler/Timeline.cs @@ -86,7 +86,7 @@ namespace FlaxEditor.Windows.Profiler Render2D.DrawRectangle(bounds, color * 0.5f); if (_nameLength < 0 && style.FontMedium) - _nameLength = Render2D.MeasureText(style.FontMedium, _name).X; + _nameLength = FallbackTextUtils.MeasureText(style.FontMedium, _name).X; if (_nameLength < bounds.Width + 4) { diff --git a/Source/Engine/Render2D/FallbackTextUtils.cs b/Source/Engine/Render2D/FallbackTextUtils.cs new file mode 100644 index 000000000..eb676f0a9 --- /dev/null +++ b/Source/Engine/Render2D/FallbackTextUtils.cs @@ -0,0 +1,126 @@ + +namespace FlaxEngine +{ + /// + /// A collection of functions to handle text rendering with fallback font + /// + public static class FallbackTextUtils + { + public static FallbackFonts Fallbacks + { + get; set; + } = null; + + public static void DrawText(Font font, string text, Rectangle layoutRect, Color color, TextAlignment horizontalAlignment = TextAlignment.Near, TextAlignment verticalAlignment = TextAlignment.Near, TextWrapping textWrapping = TextWrapping.NoWrap, float baseLinesGapScale = 1.0f, float scale = 1.0f) + { + var layout = new TextLayoutOptions + { + Bounds = layoutRect, + HorizontalAlignment = horizontalAlignment, + VerticalAlignment = verticalAlignment, + TextWrapping = textWrapping, + Scale = scale, + BaseLinesGapScale = baseLinesGapScale, + }; + + if (Fallbacks != null) + { + Render2D.DrawText(font, Fallbacks, text, color, ref layout); + } + else + { + Render2D.DrawText(font, text, color, ref layout); + } + } + + public static void DrawText(Font font, MaterialBase customMaterial, string text, Rectangle layoutRect, Color color, TextAlignment horizontalAlignment = TextAlignment.Near, TextAlignment verticalAlignment = TextAlignment.Near, TextWrapping textWrapping = TextWrapping.NoWrap, float baseLinesGapScale = 1.0f, float scale = 1.0f) + { + var layout = new TextLayoutOptions + { + Bounds = layoutRect, + HorizontalAlignment = horizontalAlignment, + VerticalAlignment = verticalAlignment, + TextWrapping = textWrapping, + Scale = scale, + BaseLinesGapScale = baseLinesGapScale, + }; + + if (Fallbacks != null) + { + Render2D.DrawText(font, Fallbacks, text, color, ref layout, customMaterial); + } + else + { + Render2D.DrawText(font, text, color, ref layout, customMaterial); + } + } + + public static Float2 MeasureText(Font font, string text) + { + if (Fallbacks != null) + { + return font.MeasureText(Fallbacks, text); + } + else + { + return font.MeasureText(text); + } + } + + public static Float2 MeasureText(Font font, string text, ref TextRange textRange) + { + if (Fallbacks != null) + { + return font.MeasureText(Fallbacks, text, ref textRange); + } + else + { + return font.MeasureText(text, ref textRange); + } + } + + public static Float2 MeasureText(Font font, string text, ref TextLayoutOptions layout) + { + if (Fallbacks != null) + { + return font.MeasureText(Fallbacks, text, ref layout); + } + else + { + return font.MeasureText(text, ref layout); + } + } + + public static Float2 MeasureText(Font font, string text, ref TextRange textRange, ref TextLayoutOptions layout) + { + if (Fallbacks != null) + { + return font.MeasureText(Fallbacks, text, ref textRange, ref layout); + } + else + { + return font.MeasureText(text, ref textRange, ref layout); + } + } + + public static Float2 GetCharPosition(Font font, string text, int index) + { + return font.GetCharPosition(Style.Current.Fallbacks, text, index); + } + + public static Float2 GetCharPosition(Font font, string text, ref TextRange textRange, int index) + { + return font.GetCharPosition(Style.Current.Fallbacks, text, ref textRange, index); + } + + public static Float2 GetCharPosition(Font font, string text, int index, ref TextLayoutOptions layout) + { + return font.GetCharPosition(Style.Current.Fallbacks, text, index, ref layout); + } + + public static Float2 GetCharPosition(Font font, string text, ref TextRange textRange, int index, ref TextLayoutOptions layout) + { + return font.GetCharPosition(Style.Current.Fallbacks, text, ref textRange, index, ref layout); + } + } +} diff --git a/Source/Engine/Render2D/Render2D.cs b/Source/Engine/Render2D/Render2D.cs index b7158ccc7..6e4f1dc1d 100644 --- a/Source/Engine/Render2D/Render2D.cs +++ b/Source/Engine/Render2D/Render2D.cs @@ -7,10 +7,6 @@ namespace FlaxEngine { partial class Render2D { - public static FallbackFonts Fallbacks - { - get; set; - } = null; /// /// Pushes transformation layer. @@ -117,8 +113,7 @@ namespace FlaxEngine /// Describes how wrap text inside a layout rectangle. /// The scale for distance one baseline from another. Default is 1. /// The text drawing scale. Default is 1. - /// Whether to use fallback fonts for chars not renderable by the font. - public static void DrawText(Font font, string text, Rectangle layoutRect, Color color, TextAlignment horizontalAlignment = TextAlignment.Near, TextAlignment verticalAlignment = TextAlignment.Near, TextWrapping textWrapping = TextWrapping.NoWrap, float baseLinesGapScale = 1.0f, float scale = 1.0f, bool useFallback = true) + public static void DrawText(Font font, string text, Rectangle layoutRect, Color color, TextAlignment horizontalAlignment = TextAlignment.Near, TextAlignment verticalAlignment = TextAlignment.Near, TextWrapping textWrapping = TextWrapping.NoWrap, float baseLinesGapScale = 1.0f, float scale = 1.0f) { var layout = new TextLayoutOptions { @@ -129,15 +124,7 @@ namespace FlaxEngine Scale = scale, BaseLinesGapScale = baseLinesGapScale, }; - - if (useFallback && Fallbacks != null) - { - DrawText(font, Fallbacks, text, color, ref layout); - } - else - { - DrawText(font, text, color, ref layout); - } + DrawText(font, text, color, ref layout); } /// @@ -153,8 +140,7 @@ namespace FlaxEngine /// Describes how wrap text inside a layout rectangle. /// The scale for distance one baseline from another. Default is 1. /// The text drawing scale. Default is 1. - /// Whether to use fallback fonts for chars not renderable by the font. - public static void DrawText(Font font, MaterialBase customMaterial, string text, Rectangle layoutRect, Color color, TextAlignment horizontalAlignment = TextAlignment.Near, TextAlignment verticalAlignment = TextAlignment.Near, TextWrapping textWrapping = TextWrapping.NoWrap, float baseLinesGapScale = 1.0f, float scale = 1.0f, bool useFallback = true) + public static void DrawText(Font font, MaterialBase customMaterial, string text, Rectangle layoutRect, Color color, TextAlignment horizontalAlignment = TextAlignment.Near, TextAlignment verticalAlignment = TextAlignment.Near, TextWrapping textWrapping = TextWrapping.NoWrap, float baseLinesGapScale = 1.0f, float scale = 1.0f) { var layout = new TextLayoutOptions { @@ -165,63 +151,7 @@ namespace FlaxEngine Scale = scale, BaseLinesGapScale = baseLinesGapScale, }; - - if (useFallback && Fallbacks != null) - { - DrawText(font, Fallbacks, text, color, ref layout, customMaterial); - } - else - { - DrawText(font, text, color, ref layout, customMaterial); - } - } - - public static Float2 MeasureText(Font font, string text, bool useFallback = true) - { - if (useFallback && Fallbacks != null) - { - return font.MeasureText(Style.Current.Fallbacks, text); - } - else - { - return font.MeasureText(text); - } - } - - public static Float2 MeasureText(Font font, string text, ref TextRange textRange, bool useFallback = true) - { - if (useFallback && Fallbacks != null) - { - return font.MeasureText(Style.Current.Fallbacks, text, ref textRange); - } - else - { - return font.MeasureText(text, ref textRange); - } - } - - public static Float2 MeasureText(Font font, string text, ref TextLayoutOptions layout, bool useFallback = true) - { - if (useFallback && Fallbacks != null) - { - return font.MeasureText(Style.Current.Fallbacks, text, ref layout); - } - else - { - return font.MeasureText(text, ref layout); - } - } - - public static Float2 MeasureText(Font font, string text, ref TextRange textRange, ref TextLayoutOptions layout, bool useFallback = true) - { - if (useFallback && Fallbacks != null) - { - return font.MeasureText(Style.Current.Fallbacks, text, ref textRange, ref layout); - } - else - { - return font.MeasureText(text, ref textRange, ref layout); - } + DrawText(font, text, color, ref layout, customMaterial); } /// diff --git a/Source/Engine/UI/GUI/Common/Dropdown.cs b/Source/Engine/UI/GUI/Common/Dropdown.cs index c9fef131c..46f05f516 100644 --- a/Source/Engine/UI/GUI/Common/Dropdown.cs +++ b/Source/Engine/UI/GUI/Common/Dropdown.cs @@ -476,7 +476,7 @@ namespace FlaxEngine.GUI var font = Font.GetFont(); for (int i = 0; i < _items.Count; i++) { - itemsWidth = Mathf.Max(itemsWidth, itemsMargin + 4 + Render2D.MeasureText(font, _items[i]).X); + itemsWidth = Mathf.Max(itemsWidth, itemsMargin + 4 + FallbackTextUtils.MeasureText(font, _items[i]).X); } */ var itemsWidth = Width; diff --git a/Source/Engine/UI/GUI/Common/Label.cs b/Source/Engine/UI/GUI/Common/Label.cs index d7147d3ab..4e26c1934 100644 --- a/Source/Engine/UI/GUI/Common/Label.cs +++ b/Source/Engine/UI/GUI/Common/Label.cs @@ -256,7 +256,7 @@ namespace FlaxEngine.GUI layout.Bounds.Size.X = Width - Margin.Width; else if (_autoWidth && !_autoHeight) layout.Bounds.Size.Y = Height - Margin.Height; - _textSize = Render2D.MeasureText(font, _text, ref layout); + _textSize = FallbackTextUtils.MeasureText(font, _text, ref layout); _textSize.Y *= BaseLinesGapScale; // Check if size is controlled via text diff --git a/Source/Engine/UI/GUI/Common/TextBox.cs b/Source/Engine/UI/GUI/Common/TextBox.cs index a32f3a76c..ea7523f48 100644 --- a/Source/Engine/UI/GUI/Common/TextBox.cs +++ b/Source/Engine/UI/GUI/Common/TextBox.cs @@ -106,7 +106,7 @@ namespace FlaxEngine.GUI return Float2.Zero; } - return Render2D.MeasureText(font, _text, ref _layout); + return FallbackTextUtils.MeasureText(font, _text, ref _layout); } /// From 3d139a241ab4720e64ea03aae082de284bd5d26b Mon Sep 17 00:00:00 2001 From: ExMatics HydrogenC <33123710+HydrogenC@users.noreply.github.com> Date: Fri, 1 Dec 2023 09:28:29 +0800 Subject: [PATCH 043/139] Fix C# editor --- Source/Editor/Content/GUI/ContentView.cs | 2 +- Source/Editor/Content/Items/ContentItem.cs | 2 +- Source/Editor/Content/Tree/ContentTreeNode.cs | 5 +- .../Dedicated/MeshReferenceEditor.cs | 4 +- .../CustomEditors/Dedicated/ScriptsEditor.cs | 3 +- .../CustomEditors/Dedicated/SplineEditor.cs | 2 +- .../Editors/ActorTransformEditor.cs | 1 - .../Editors/FlaxObjectRefEditor.cs | 4 +- .../CustomEditors/Editors/TypeEditor.cs | 4 +- Source/Editor/GUI/AssetPicker.cs | 6 +- Source/Editor/GUI/ComboBox.cs | 2 +- .../GUI/ContextMenu/ContextMenuButton.cs | 5 +- Source/Editor/GUI/CurveEditor.cs | 2 +- .../Editor/GUI/Dialogs/ColorPickerDialog.cs | 22 +- Source/Editor/GUI/Docking/DockPanelProxy.cs | 4 +- Source/Editor/GUI/Docking/DockWindow.cs | 1 - Source/Editor/GUI/ItemsListContextMenu.cs | 7 +- Source/Editor/GUI/MainMenuButton.cs | 3 +- Source/Editor/GUI/NavigationButton.cs | 3 +- Source/Editor/GUI/Row.cs | 3 +- Source/Editor/GUI/StatusBar.cs | 2 +- Source/Editor/GUI/StyleValueEditor.cs | 2 +- Source/Editor/GUI/Table.cs | 3 +- Source/Editor/GUI/Tabs/Tabs.cs | 2 +- Source/Editor/GUI/Timeline/GUI/Background.cs | 2 +- .../Editor/GUI/Timeline/GUI/PositionHandle.cs | 1 - Source/Editor/GUI/Timeline/Track.cs | 2 +- Source/Editor/GUI/ToolStripButton.cs | 3 +- Source/Editor/GUI/Tree/TreeNode.cs | 3 +- Source/Editor/Options/OptionsModule.cs | 2 +- Source/Editor/SceneGraph/GUI/ActorTreeNode.cs | 4 +- .../Archetypes/Animation.StateMachine.cs | 4 +- .../Editor/Surface/Archetypes/BehaviorTree.cs | 3 +- .../Surface/Archetypes/ParticleModules.cs | 2 +- Source/Editor/Surface/Archetypes/Particles.cs | 4 +- .../Editor/Surface/ContextMenu/VisjectCM.cs | 2 +- .../Surface/ContextMenu/VisjectCMItem.cs | 18 +- Source/Editor/Surface/Elements/InputBox.cs | 2 +- Source/Editor/Surface/Elements/OutputBox.cs | 2 +- Source/Editor/Surface/Elements/TextView.cs | 2 +- Source/Editor/Surface/SurfaceComment.cs | 2 +- Source/Editor/Surface/SurfaceNode.cs | 3 +- Source/Editor/Tools/Foliage/FoliageTab.cs | 1 - Source/Editor/Tools/Terrain/CarveTab.cs | 1 - Source/Editor/Viewport/EditorViewport.cs | 6 +- .../Viewport/Previews/AnimationPreview.cs | 4 +- .../Editor/Viewport/Previews/ModelPreview.cs | 4 +- .../Previews/ParticleSystemPreview.cs | 2 +- .../Viewport/Previews/SkinnedModelPreview.cs | 4 +- .../Viewport/Previews/TexturePreview.cs | 2 +- .../Viewport/Widgets/ViewportWidgetButton.cs | 3 +- Source/Editor/Windows/AboutDialog.cs | 1 - .../Windows/Assets/AnimationGraphWindow.cs | 2 +- .../Editor/Windows/Assets/AnimationWindow.cs | 2 +- Source/Editor/Windows/Assets/ModelWindow.cs | 4 +- .../Windows/Assets/SkinnedModelWindow.cs | 4 +- Source/Editor/Windows/ContentWindow.Search.cs | 2 +- Source/Editor/Windows/DebugLogWindow.cs | 4 +- Source/Editor/Windows/GameWindow.cs | 8 +- Source/Editor/Windows/Profiler/SingleChart.cs | 4 +- Source/Editor/Windows/Profiler/Timeline.cs | 5 +- Source/Editor/Windows/SceneTreeWindow.cs | 2 +- Source/Editor/Windows/ToolboxWindow.cs | 5 +- Source/Engine/Render2D/FallbackTextUtils.cs | 205 +++++++++++++----- Source/Engine/UI/GUI/Common/Button.cs | 3 +- Source/Engine/UI/GUI/Common/Dropdown.cs | 3 +- Source/Engine/UI/GUI/Common/Label.cs | 4 +- .../Engine/UI/GUI/Common/RichTextBox.Tags.cs | 1 - Source/Engine/UI/GUI/Common/RichTextBox.cs | 1 - .../Engine/UI/GUI/Common/RichTextBoxBase.cs | 8 +- Source/Engine/UI/GUI/Common/TextBox.cs | 28 +-- Source/Engine/UI/GUI/Panels/DropPanel.cs | 3 +- Source/Engine/UI/GUI/Style.cs | 1 - Source/Engine/UI/GUI/Tooltip.cs | 3 +- 74 files changed, 272 insertions(+), 213 deletions(-) diff --git a/Source/Editor/Content/GUI/ContentView.cs b/Source/Editor/Content/GUI/ContentView.cs index 259be104b..1091c9cef 100644 --- a/Source/Editor/Content/GUI/ContentView.cs +++ b/Source/Editor/Content/GUI/ContentView.cs @@ -598,7 +598,7 @@ namespace FlaxEditor.Content.GUI // Check if it's an empty thing if (_items.Count == 0) { - Render2D.DrawText(style.FontSmall, IsSearching ? "No results" : "Empty", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontSmall, IsSearching ? "No results" : "Empty", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); } } diff --git a/Source/Editor/Content/Items/ContentItem.cs b/Source/Editor/Content/Items/ContentItem.cs index 604caa704..0842dfff7 100644 --- a/Source/Editor/Content/Items/ContentItem.cs +++ b/Source/Editor/Content/Items/ContentItem.cs @@ -745,7 +745,7 @@ namespace FlaxEditor.Content // Draw short name Render2D.PushClip(ref textRect); - Render2D.DrawText(style.FontMedium, ShowFileExtension || view.ShowFileExtensions ? FileName : ShortName, textRect, style.Foreground, nameAlignment, TextAlignment.Center, TextWrapping.WrapWords, 1f, 0.95f); + FallbackTextUtils.DrawText(style.FontMedium, ShowFileExtension || view.ShowFileExtensions ? FileName : ShortName, textRect, style.Foreground, nameAlignment, TextAlignment.Center, TextWrapping.WrapWords, 1f, 0.95f); Render2D.PopClip(); } diff --git a/Source/Editor/Content/Tree/ContentTreeNode.cs b/Source/Editor/Content/Tree/ContentTreeNode.cs index ee9b463f7..2f378e7f5 100644 --- a/Source/Editor/Content/Tree/ContentTreeNode.cs +++ b/Source/Editor/Content/Tree/ContentTreeNode.cs @@ -1,7 +1,6 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System.Collections.Generic; -using System.Linq; using FlaxEditor.GUI; using FlaxEditor.GUI.Drag; using FlaxEditor.GUI.Tree; @@ -151,8 +150,8 @@ namespace FlaxEditor.Content var textRect = TextRect; for (int i = 0; i < ranges.Length; i++) { - var start = font.GetCharPosition(text, ranges[i].StartIndex); - var end = font.GetCharPosition(text, ranges[i].EndIndex); + var start = FallbackTextUtils.GetCharPosition(font, text, ranges[i].StartIndex); + var end = FallbackTextUtils.GetCharPosition(font, text, ranges[i].EndIndex); _highlights.Add(new Rectangle(start.X + textRect.X, textRect.Y, end.X - start.X, textRect.Height)); } isThisVisible = true; diff --git a/Source/Editor/CustomEditors/Dedicated/MeshReferenceEditor.cs b/Source/Editor/CustomEditors/Dedicated/MeshReferenceEditor.cs index 4099e5aee..66c1c791f 100644 --- a/Source/Editor/CustomEditors/Dedicated/MeshReferenceEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/MeshReferenceEditor.cs @@ -109,7 +109,7 @@ namespace FlaxEditor.CustomEditors.Dedicated { // Draw name Render2D.PushClip(nameRect); - Render2D.DrawText(style.FontMedium, _valueName, nameRect, isEnabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontMedium, _valueName, nameRect, isEnabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center); Render2D.PopClip(); // Draw deselect button @@ -118,7 +118,7 @@ namespace FlaxEditor.CustomEditors.Dedicated else { // Draw info - Render2D.DrawText(style.FontMedium, "-", nameRect, isEnabled ? Color.OrangeRed : Color.DarkOrange, TextAlignment.Near, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontMedium, "-", nameRect, isEnabled ? Color.OrangeRed : Color.DarkOrange, TextAlignment.Near, TextAlignment.Center); } // Draw picker button diff --git a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs index ab949508e..017fd7655 100644 --- a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs @@ -3,7 +3,6 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Linq; using FlaxEditor.Actions; using FlaxEditor.Content; using FlaxEditor.GUI; @@ -87,7 +86,7 @@ namespace FlaxEditor.CustomEditors.Dedicated var size = Size; // Info - Render2D.DrawText(style.FontSmall, "Drag scripts here", new Rectangle(2, _addScriptsButton.Height + 4, size.X - 4, size.Y - 4 - 20), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontSmall, "Drag scripts here", new Rectangle(2, _addScriptsButton.Height + 4, size.X - 4, size.Y - 4 - 20), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); // Check if drag is over if (IsDragOver && _dragHandlers != null && _dragHandlers.HasValidDrag) diff --git a/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs b/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs index 7b9b65c5c..fa4379db1 100644 --- a/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs @@ -226,7 +226,7 @@ namespace FlaxEditor.CustomEditors.Dedicated if (!enabled) color *= 0.6f; Render2D.DrawSprite(tab._customIcon, iconRect, color); - Render2D.DrawText(style.FontMedium, tab._customText, new Rectangle(0, iconSize, size.X, textHeight), color, TextAlignment.Center, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontMedium, tab._customText, new Rectangle(0, iconSize, size.X, textHeight), color, TextAlignment.Center, TextAlignment.Center); } } diff --git a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs index 8889a765a..d8d16027b 100644 --- a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs @@ -2,7 +2,6 @@ using FlaxEngine; using FlaxEngine.GUI; -using System.Linq; namespace FlaxEditor.CustomEditors.Editors { diff --git a/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs b/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs index 731da3817..2f64ea159 100644 --- a/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs +++ b/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs @@ -199,7 +199,7 @@ namespace FlaxEditor.CustomEditors.Editors { // Draw name Render2D.PushClip(nameRect); - Render2D.DrawText(style.FontMedium, _valueName, nameRect, isEnabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontMedium, _valueName, nameRect, isEnabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center); Render2D.PopClip(); // Draw deselect button @@ -208,7 +208,7 @@ namespace FlaxEditor.CustomEditors.Editors else { // Draw info - Render2D.DrawText(style.FontMedium, "-", nameRect, isEnabled ? Color.OrangeRed : Color.DarkOrange, TextAlignment.Near, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontMedium, "-", nameRect, isEnabled ? Color.OrangeRed : Color.DarkOrange, TextAlignment.Near, TextAlignment.Center); } // Draw picker button diff --git a/Source/Editor/CustomEditors/Editors/TypeEditor.cs b/Source/Editor/CustomEditors/Editors/TypeEditor.cs index 38800a738..4d0b90383 100644 --- a/Source/Editor/CustomEditors/Editors/TypeEditor.cs +++ b/Source/Editor/CustomEditors/Editors/TypeEditor.cs @@ -160,13 +160,13 @@ namespace FlaxEditor.CustomEditors.Editors // Draw name Render2D.PushClip(nameRect); - Render2D.DrawText(style.FontMedium, _valueName, nameRect, style.Foreground, TextAlignment.Near, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontMedium, _valueName, nameRect, style.Foreground, TextAlignment.Near, TextAlignment.Center); Render2D.PopClip(); } else { // Draw info - Render2D.DrawText(style.FontMedium, "-", nameRect, Color.OrangeRed, TextAlignment.Near, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontMedium, "-", nameRect, Color.OrangeRed, TextAlignment.Near, TextAlignment.Center); } // Draw picker button diff --git a/Source/Editor/GUI/AssetPicker.cs b/Source/Editor/GUI/AssetPicker.cs index 84f58daf1..2abe5db38 100644 --- a/Source/Editor/GUI/AssetPicker.cs +++ b/Source/Editor/GUI/AssetPicker.cs @@ -139,7 +139,7 @@ namespace FlaxEditor.GUI float sizeForTextLeft = Width - button1Rect.Right; if (sizeForTextLeft > 30) { - Render2D.DrawText( + FallbackTextUtils.DrawText( style.FontSmall, Validator.SelectedItem.ShortName, new Rectangle(button1Rect.Right + 2, 0, sizeForTextLeft, ButtonsSize), @@ -161,7 +161,7 @@ namespace FlaxEditor.GUI var name = Validator.SelectedAsset.GetType().Name; if (Validator.SelectedAsset.IsVirtual) name += " (virtual)"; - Render2D.DrawText( + FallbackTextUtils.DrawText( style.FontSmall, name, new Rectangle(button1Rect.Right + 2, 0, sizeForTextLeft, ButtonsSize), @@ -174,7 +174,7 @@ namespace FlaxEditor.GUI { // No element selected Render2D.FillRectangle(iconRect, style.BackgroundNormal); - Render2D.DrawText(style.FontMedium, "No asset\nselected", iconRect, Color.Orange, TextAlignment.Center, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, Height / DefaultIconSize); + FallbackTextUtils.DrawText(style.FontMedium, "No asset\nselected", iconRect, Color.Orange, TextAlignment.Center, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, Height / DefaultIconSize); } // Check if drag is over diff --git a/Source/Editor/GUI/ComboBox.cs b/Source/Editor/GUI/ComboBox.cs index 0417cc7e3..8f3a7cb4b 100644 --- a/Source/Editor/GUI/ComboBox.cs +++ b/Source/Editor/GUI/ComboBox.cs @@ -554,7 +554,7 @@ namespace FlaxEditor.GUI var textRect = new Rectangle(margin, 0, clientRect.Width - boxSize - 2.0f * margin, clientRect.Height); Render2D.PushClip(textRect); var textColor = TextColor; - Render2D.DrawText(Font.GetFont(), text, textRect, enabled ? textColor : textColor * 0.5f, TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, textScale); + FallbackTextUtils.DrawText(Font.GetFont(), text, textRect, enabled ? textColor : textColor * 0.5f, TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, textScale); Render2D.PopClip(); } diff --git a/Source/Editor/GUI/ContextMenu/ContextMenuButton.cs b/Source/Editor/GUI/ContextMenu/ContextMenuButton.cs index d8c492e46..035e997e9 100644 --- a/Source/Editor/GUI/ContextMenu/ContextMenuButton.cs +++ b/Source/Editor/GUI/ContextMenu/ContextMenuButton.cs @@ -1,7 +1,6 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; -using System.Linq; using FlaxEngine; using FlaxEngine.GUI; @@ -129,12 +128,12 @@ namespace FlaxEditor.GUI.ContextMenu base.Draw(); // Draw text - Render2D.DrawText(style.FontMedium, Text, textRect, textColor, TextAlignment.Near, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontMedium, Text, textRect, textColor, TextAlignment.Near, TextAlignment.Center); if (!string.IsNullOrEmpty(ShortKeys)) { // Draw short keys - Render2D.DrawText(style.FontMedium, ShortKeys, textRect, textColor, TextAlignment.Far, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontMedium, ShortKeys, textRect, textColor, TextAlignment.Far, TextAlignment.Center); } // Draw icon diff --git a/Source/Editor/GUI/CurveEditor.cs b/Source/Editor/GUI/CurveEditor.cs index 22deec120..f4839acd5 100644 --- a/Source/Editor/GUI/CurveEditor.cs +++ b/Source/Editor/GUI/CurveEditor.cs @@ -832,7 +832,7 @@ namespace FlaxEditor.GUI 50, LabelsSize ); - Render2D.DrawText(_labelsFont, label, labelRect, _labelsColor.AlphaMultiplied(strength), TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, 0.7f); + FallbackTextUtils.DrawText(_labelsFont, label, labelRect, _labelsColor.AlphaMultiplied(strength), TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, 0.7f); } } } diff --git a/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs b/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs index 27878a763..8b1881faf 100644 --- a/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs +++ b/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs @@ -281,33 +281,33 @@ namespace FlaxEditor.GUI.Dialogs // RGBA var rgbaR = new Rectangle(_cRed.Left - ChannelTextWidth, _cRed.Y, 10000, _cRed.Height); - Render2D.DrawText(style.FontMedium, "R", rgbaR, textColor, TextAlignment.Near, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontMedium, "R", rgbaR, textColor, TextAlignment.Near, TextAlignment.Center); rgbaR.Location.Y = _cGreen.Y; - Render2D.DrawText(style.FontMedium, "G", rgbaR, textColor, TextAlignment.Near, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontMedium, "G", rgbaR, textColor, TextAlignment.Near, TextAlignment.Center); rgbaR.Location.Y = _cBlue.Y; - Render2D.DrawText(style.FontMedium, "B", rgbaR, textColor, TextAlignment.Near, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontMedium, "B", rgbaR, textColor, TextAlignment.Near, TextAlignment.Center); rgbaR.Location.Y = _cAlpha.Y; - Render2D.DrawText(style.FontMedium, "A", rgbaR, textColor, TextAlignment.Near, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontMedium, "A", rgbaR, textColor, TextAlignment.Near, TextAlignment.Center); // HSV left var hsvHl = new Rectangle(_cHue.Left - ChannelTextWidth, _cHue.Y, 10000, _cHue.Height); - Render2D.DrawText(style.FontMedium, "H", hsvHl, textColor, TextAlignment.Near, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontMedium, "H", hsvHl, textColor, TextAlignment.Near, TextAlignment.Center); hsvHl.Location.Y = _cSaturation.Y; - Render2D.DrawText(style.FontMedium, "S", hsvHl, textColor, TextAlignment.Near, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontMedium, "S", hsvHl, textColor, TextAlignment.Near, TextAlignment.Center); hsvHl.Location.Y = _cValue.Y; - Render2D.DrawText(style.FontMedium, "V", hsvHl, textColor, TextAlignment.Near, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontMedium, "V", hsvHl, textColor, TextAlignment.Near, TextAlignment.Center); // HSV right var hsvHr = new Rectangle(_cHue.Right + 2, _cHue.Y, 10000, _cHue.Height); - Render2D.DrawText(style.FontMedium, "°", hsvHr, textColor, TextAlignment.Near, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontMedium, "°", hsvHr, textColor, TextAlignment.Near, TextAlignment.Center); hsvHr.Location.Y = _cSaturation.Y; - Render2D.DrawText(style.FontMedium, "%", hsvHr, textColor, TextAlignment.Near, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontMedium, "%", hsvHr, textColor, TextAlignment.Near, TextAlignment.Center); hsvHr.Location.Y = _cValue.Y; - Render2D.DrawText(style.FontMedium, "%", hsvHr, textColor, TextAlignment.Near, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontMedium, "%", hsvHr, textColor, TextAlignment.Near, TextAlignment.Center); // Hex var hex = new Rectangle(_cHex.Left - 26, _cHex.Y, 10000, _cHex.Height); - Render2D.DrawText(style.FontMedium, "Hex", hex, textColor, TextAlignment.Near, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontMedium, "Hex", hex, textColor, TextAlignment.Near, TextAlignment.Center); // Color difference var newRect = new Rectangle(_cOK.X, _cHex.Bottom + PickerMargin, _cCancel.Right - _cOK.Left, 0); diff --git a/Source/Editor/GUI/Docking/DockPanelProxy.cs b/Source/Editor/GUI/Docking/DockPanelProxy.cs index e6e57de8e..0b36b85fa 100644 --- a/Source/Editor/GUI/Docking/DockPanelProxy.cs +++ b/Source/Editor/GUI/Docking/DockPanelProxy.cs @@ -208,7 +208,7 @@ namespace FlaxEditor.GUI.Docking } // Draw text - Render2D.DrawText( + FallbackTextUtils.DrawText( style.FontMedium, tab.Title, new Rectangle(DockPanel.DefaultLeftTextMargin + iconWidth, 0, Width - DockPanel.DefaultLeftTextMargin - DockPanel.DefaultButtonsSize - 2 * DockPanel.DefaultButtonsMargin, DockPanel.DefaultHeaderHeight), @@ -271,7 +271,7 @@ namespace FlaxEditor.GUI.Docking } // Draw text - Render2D.DrawText( + FallbackTextUtils.DrawText( style.FontMedium, tab.Title, new Rectangle(x + DockPanel.DefaultLeftTextMargin + iconWidth, 0, 10000, DockPanel.DefaultHeaderHeight), diff --git a/Source/Editor/GUI/Docking/DockWindow.cs b/Source/Editor/GUI/Docking/DockWindow.cs index b7c12287f..561c898a0 100644 --- a/Source/Editor/GUI/Docking/DockWindow.cs +++ b/Source/Editor/GUI/Docking/DockWindow.cs @@ -6,7 +6,6 @@ using FlaxEngine; using FlaxEngine.Assertions; using FlaxEngine.GUI; using FlaxEditor.Options; -using System.Linq; namespace FlaxEditor.GUI.Docking { diff --git a/Source/Editor/GUI/ItemsListContextMenu.cs b/Source/Editor/GUI/ItemsListContextMenu.cs index b823cc907..d3c433a47 100644 --- a/Source/Editor/GUI/ItemsListContextMenu.cs +++ b/Source/Editor/GUI/ItemsListContextMenu.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; -using System.Linq; using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.Input; using FlaxEditor.Utilities; @@ -87,8 +86,8 @@ namespace FlaxEditor.GUI var font = style.FontSmall; for (int i = 0; i < ranges.Length; i++) { - var start = font.GetCharPosition(Name, ranges[i].StartIndex); - var end = font.GetCharPosition(Name, ranges[i].EndIndex); + var start = FallbackTextUtils.GetCharPosition(font, Name, ranges[i].StartIndex); + var end = FallbackTextUtils.GetCharPosition(font, Name, ranges[i].EndIndex); _highlights.Add(new Rectangle(start.X + 2, 0, end.X - start.X, Height)); } Visible = true; @@ -137,7 +136,7 @@ namespace FlaxEditor.GUI } // Draw name - Render2D.DrawText(style.FontSmall, Name, textRect, TintColor * (Enabled ? style.Foreground : style.ForegroundDisabled), TextAlignment.Near, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontSmall, Name, textRect, TintColor * (Enabled ? style.Foreground : style.ForegroundDisabled), TextAlignment.Near, TextAlignment.Center); } /// diff --git a/Source/Editor/GUI/MainMenuButton.cs b/Source/Editor/GUI/MainMenuButton.cs index 6fe8b98d3..43849f4ab 100644 --- a/Source/Editor/GUI/MainMenuButton.cs +++ b/Source/Editor/GUI/MainMenuButton.cs @@ -2,7 +2,6 @@ using FlaxEngine; using FlaxEngine.GUI; -using System.Linq; namespace FlaxEditor.GUI { @@ -73,7 +72,7 @@ namespace FlaxEditor.GUI } // Draw text - Render2D.DrawText(style.FontMedium, Text, clientRect, enabled && hasChildItems ? style.Foreground : style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontMedium, Text, clientRect, enabled && hasChildItems ? style.Foreground : style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); } /// diff --git a/Source/Editor/GUI/NavigationButton.cs b/Source/Editor/GUI/NavigationButton.cs index 5d399da02..8dedf72fd 100644 --- a/Source/Editor/GUI/NavigationButton.cs +++ b/Source/Editor/GUI/NavigationButton.cs @@ -2,7 +2,6 @@ using FlaxEngine; using FlaxEngine.GUI; -using System.Linq; namespace FlaxEditor.GUI { @@ -58,7 +57,7 @@ namespace FlaxEditor.GUI } // Draw text - Render2D.DrawText(style.FontMedium, Text, textRect, EnabledInHierarchy ? style.Foreground : style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontMedium, Text, textRect, EnabledInHierarchy ? style.Foreground : style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center); } /// diff --git a/Source/Editor/GUI/Row.cs b/Source/Editor/GUI/Row.cs index ab74cbe96..c7be48626 100644 --- a/Source/Editor/GUI/Row.cs +++ b/Source/Editor/GUI/Row.cs @@ -1,7 +1,6 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; -using System.Linq; using FlaxEngine; using FlaxEngine.GUI; @@ -99,7 +98,7 @@ namespace FlaxEditor.GUI rect.Width -= leftDepthMargin; Render2D.PushClip(rect); - Render2D.DrawText(style.FontMedium, text, rect, style.Foreground, column.CellAlignment, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontMedium, text, rect, style.Foreground, column.CellAlignment, TextAlignment.Center); Render2D.PopClip(); x += width; diff --git a/Source/Editor/GUI/StatusBar.cs b/Source/Editor/GUI/StatusBar.cs index f8f7ae839..7770af9a1 100644 --- a/Source/Editor/GUI/StatusBar.cs +++ b/Source/Editor/GUI/StatusBar.cs @@ -56,7 +56,7 @@ namespace FlaxEditor.GUI Render2D.DrawSprite(style.StatusBarSizeGrip, new Rectangle(Width - 12, 10, 12, 12), style.Foreground); // Draw status text - Render2D.DrawText(style.FontSmall, Text, new Rectangle(4, 0, Width - 20, Height), TextColor, TextAlignment.Near, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontSmall, Text, new Rectangle(4, 0, Width - 20, Height), TextColor, TextAlignment.Near, TextAlignment.Center); } } } diff --git a/Source/Editor/GUI/StyleValueEditor.cs b/Source/Editor/GUI/StyleValueEditor.cs index a89902177..db6696152 100644 --- a/Source/Editor/GUI/StyleValueEditor.cs +++ b/Source/Editor/GUI/StyleValueEditor.cs @@ -157,7 +157,7 @@ namespace FlaxEditor.GUI { Rectangle textRectangle = r; textRectangle.X = 4; - Render2D.DrawText(style.FontMedium, "No Style", textRectangle, style.Foreground); + FallbackTextUtils.DrawText(style.FontMedium, "No Style", textRectangle, style.Foreground); } } diff --git a/Source/Editor/GUI/Table.cs b/Source/Editor/GUI/Table.cs index 54656a6c2..2358557c3 100644 --- a/Source/Editor/GUI/Table.cs +++ b/Source/Editor/GUI/Table.cs @@ -1,7 +1,6 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; -using System.Linq; using System.Runtime.CompilerServices; using FlaxEngine; using FlaxEngine.GUI; @@ -131,7 +130,7 @@ namespace FlaxEditor.GUI var style = Style.Current; var font = column.TitleFont ?? style.FontMedium; - Render2D.DrawText(font, column.Title, rect, column.TitleColor, TextAlignment.Center, TextAlignment.Center); + FallbackTextUtils.DrawText(font, column.Title, rect, column.TitleColor, TextAlignment.Center, TextAlignment.Center); if (columnIndex < _columns.Length - 1) { diff --git a/Source/Editor/GUI/Tabs/Tabs.cs b/Source/Editor/GUI/Tabs/Tabs.cs index 3c70363e7..5995c7ef5 100644 --- a/Source/Editor/GUI/Tabs/Tabs.cs +++ b/Source/Editor/GUI/Tabs/Tabs.cs @@ -98,7 +98,7 @@ namespace FlaxEditor.GUI.Tabs // Draw text if (!string.IsNullOrEmpty(Tab.Text)) { - Render2D.DrawText(style.FontMedium, Tab.Text, new Rectangle(tabRect.X + textOffset, tabRect.Y, tabRect.Width - textOffset, tabRect.Height), style.Foreground, Tabs.TabsTextHorizontalAlignment, Tabs.TabsTextVerticalAlignment); + FallbackTextUtils.DrawText(style.FontMedium, Tab.Text, new Rectangle(tabRect.X + textOffset, tabRect.Y, tabRect.Width - textOffset, tabRect.Height), style.Foreground, Tabs.TabsTextHorizontalAlignment, Tabs.TabsTextVerticalAlignment); } } } diff --git a/Source/Editor/GUI/Timeline/GUI/Background.cs b/Source/Editor/GUI/Timeline/GUI/Background.cs index b9eff562a..05c1121c0 100644 --- a/Source/Editor/GUI/Timeline/GUI/Background.cs +++ b/Source/Editor/GUI/Timeline/GUI/Background.cs @@ -301,7 +301,7 @@ namespace FlaxEditor.GUI.Timeline.GUI default: throw new ArgumentOutOfRangeException(); } var labelRect = new Rectangle(x + 2, -verticalLinesHeaderExtend * 0.8f + timeAxisHeaderOffset, 50, verticalLinesHeaderExtend); - Render2D.DrawText(style.FontSmall, labelText, labelRect, labelColor, TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, 0.8f); + FallbackTextUtils.DrawText(style.FontSmall, labelText, labelRect, labelColor, TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, 0.8f); } } } diff --git a/Source/Editor/GUI/Timeline/GUI/PositionHandle.cs b/Source/Editor/GUI/Timeline/GUI/PositionHandle.cs index 791fb7133..3f835a35f 100644 --- a/Source/Editor/GUI/Timeline/GUI/PositionHandle.cs +++ b/Source/Editor/GUI/Timeline/GUI/PositionHandle.cs @@ -2,7 +2,6 @@ using System; using System.Globalization; -using System.Linq; using FlaxEngine; using FlaxEngine.GUI; diff --git a/Source/Editor/GUI/Timeline/Track.cs b/Source/Editor/GUI/Timeline/Track.cs index f38917aa6..20be8f0c2 100644 --- a/Source/Editor/GUI/Timeline/Track.cs +++ b/Source/Editor/GUI/Timeline/Track.cs @@ -965,7 +965,7 @@ namespace FlaxEditor.GUI.Timeline } // Draw text - Render2D.DrawText(style.FontSmall, Title ?? Name, textRect, textColor, TextAlignment.Near, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontSmall, Title ?? Name, textRect, textColor, TextAlignment.Near, TextAlignment.Center); // Disabled overlay DrawDisabled = Mute || (ParentTrack != null && ParentTrack.DrawDisabled); diff --git a/Source/Editor/GUI/ToolStripButton.cs b/Source/Editor/GUI/ToolStripButton.cs index 50dce78b4..c4f4603e2 100644 --- a/Source/Editor/GUI/ToolStripButton.cs +++ b/Source/Editor/GUI/ToolStripButton.cs @@ -1,7 +1,6 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; -using System.Linq; using FlaxEngine; using FlaxEngine.GUI; @@ -137,7 +136,7 @@ namespace FlaxEditor.GUI if (!string.IsNullOrEmpty(_text)) { textRect.Size.X = Width - DefaultMargin - textRect.Left; - Render2D.DrawText(style.FontMedium, _text, textRect, enabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontMedium, _text, textRect, enabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center); } } diff --git a/Source/Editor/GUI/Tree/TreeNode.cs b/Source/Editor/GUI/Tree/TreeNode.cs index 700ba6da1..6680e3608 100644 --- a/Source/Editor/GUI/Tree/TreeNode.cs +++ b/Source/Editor/GUI/Tree/TreeNode.cs @@ -1,7 +1,6 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; -using System.Linq; using FlaxEngine; using FlaxEngine.GUI; @@ -657,7 +656,7 @@ namespace FlaxEditor.GUI.Tree } // Draw text - Render2D.DrawText(TextFont.GetFont(), _text, textRect, _cachedTextColor, TextAlignment.Near, TextAlignment.Center); + FallbackTextUtils.DrawText(TextFont.GetFont(), _text, textRect, _cachedTextColor, TextAlignment.Near, TextAlignment.Center); // Draw drag and drop effect if (IsDragOver && _tree.DraggedOverNode == this) diff --git a/Source/Editor/Options/OptionsModule.cs b/Source/Editor/Options/OptionsModule.cs index b13548016..dc376a0a2 100644 --- a/Source/Editor/Options/OptionsModule.cs +++ b/Source/Editor/Options/OptionsModule.cs @@ -224,7 +224,7 @@ namespace FlaxEditor.Options } } - Render2D.Fallbacks = FallbackFonts.Create(Options.Interface.Fallbacks); + FallbackTextUtils.Fallbacks = FallbackFonts.Create(Options.Interface.Fallbacks); } /// diff --git a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs index f64e46385..eefd7f4dd 100644 --- a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs +++ b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs @@ -142,8 +142,8 @@ namespace FlaxEditor.SceneGraph.GUI var textRect = TextRect; for (int i = 0; i < ranges.Length; i++) { - var start = font.GetCharPosition(text, ranges[i].StartIndex); - var end = font.GetCharPosition(text, ranges[i].EndIndex); + var start = FallbackTextUtils.GetCharPosition(font, text, ranges[i].StartIndex); + var end = FallbackTextUtils.GetCharPosition(font, text, ranges[i].EndIndex); _highlights.Add(new Rectangle(start.X + textRect.X, textRect.Y, end.X - start.X, textRect.Height)); } isThisVisible = true; diff --git a/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs b/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs index 078b89c56..be9a96a7d 100644 --- a/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs +++ b/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs @@ -379,7 +379,7 @@ namespace FlaxEditor.Surface.Archetypes } // Name - Render2D.DrawText(style.FontLarge, Title, _textRect, style.Foreground, TextAlignment.Center, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontLarge, Title, _textRect, style.Foreground, TextAlignment.Center, TextAlignment.Center); } /// @@ -1128,7 +1128,7 @@ namespace FlaxEditor.Surface.Archetypes } // Name - Render2D.DrawText(style.FontLarge, Title, _textRect, style.Foreground, TextAlignment.Center, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontLarge, Title, _textRect, style.Foreground, TextAlignment.Center, TextAlignment.Center); // Close button Render2D.DrawSprite(style.Cross, _closeButtonRect, _closeButtonRect.Contains(_mousePosition) ? style.Foreground : style.ForegroundGrey); diff --git a/Source/Editor/Surface/Archetypes/BehaviorTree.cs b/Source/Editor/Surface/Archetypes/BehaviorTree.cs index 5f3c0b0a2..b240685cc 100644 --- a/Source/Editor/Surface/Archetypes/BehaviorTree.cs +++ b/Source/Editor/Surface/Archetypes/BehaviorTree.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; -using System.Linq; using FlaxEditor.CustomEditors.Dedicated; using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.Drag; @@ -185,7 +184,7 @@ namespace FlaxEditor.Surface.Archetypes if (!string.IsNullOrEmpty(_debugInfo)) { var style = Style.Current; - Render2D.DrawText(style.FontSmall, _debugInfo, new Rectangle(4, _headerRect.Bottom + 4, _debugInfoSize), style.Foreground); + FallbackTextUtils.DrawText(style.FontSmall, _debugInfo, new Rectangle(4, _headerRect.Bottom + 4, _debugInfoSize), style.Foreground); } // Debug relevancy outline diff --git a/Source/Editor/Surface/Archetypes/ParticleModules.cs b/Source/Editor/Surface/Archetypes/ParticleModules.cs index e5be3d35d..6082e212e 100644 --- a/Source/Editor/Surface/Archetypes/ParticleModules.cs +++ b/Source/Editor/Surface/Archetypes/ParticleModules.cs @@ -113,7 +113,7 @@ namespace FlaxEditor.Surface.Archetypes var idx = (int)ModuleType; var headerRect = new Rectangle(0, 0, Width, 16.0f); //Render2D.FillRectangle(headerRect, Color.Red); - Render2D.DrawText(style.FontMedium, Title, headerRect, style.Foreground, TextAlignment.Center, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontMedium, Title, headerRect, style.Foreground, TextAlignment.Center, TextAlignment.Center); DrawChildren(); diff --git a/Source/Editor/Surface/Archetypes/Particles.cs b/Source/Editor/Surface/Archetypes/Particles.cs index 40b38f7ce..bf7828960 100644 --- a/Source/Editor/Surface/Archetypes/Particles.cs +++ b/Source/Editor/Surface/Archetypes/Particles.cs @@ -154,7 +154,7 @@ namespace FlaxEditor.Surface.Archetypes if (headerRect.Contains(mousePosition)) headerColor *= 1.07f; Render2D.FillRectangle(headerRect, headerColor); - Render2D.DrawText(style.FontLarge, Names[idx], headerRect, style.Foreground, TextAlignment.Center, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontLarge, Names[idx], headerRect, style.Foreground, TextAlignment.Center, TextAlignment.Center); DrawChildren(); } @@ -194,7 +194,7 @@ namespace FlaxEditor.Surface.Archetypes if (_headerRect.Contains(ref _mousePosition)) headerColor *= 1.07f; Render2D.FillRectangle(_headerRect, headerColor); - Render2D.DrawText(style.FontLarge, Title, _headerRect, style.Foreground, TextAlignment.Center, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontLarge, Title, _headerRect, style.Foreground, TextAlignment.Center, TextAlignment.Center); DrawChildren(); diff --git a/Source/Editor/Surface/ContextMenu/VisjectCM.cs b/Source/Editor/Surface/ContextMenu/VisjectCM.cs index 5256b3cb7..930741807 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCM.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCM.cs @@ -141,7 +141,7 @@ namespace FlaxEditor.Surface.ContextMenu }; // Title bar - var titleFontReference = new FontReference(Style.Current.FontLarge); + var titleFontReference = new FontReference(Style.Current.FontLarge.Asset, 10); var titleLabel = new Label { Width = Width * 0.5f - 8f, diff --git a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs index 019be78b6..f4976be67 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs @@ -200,8 +200,8 @@ namespace FlaxEditor.Surface.ContextMenu var font = style.FontSmall; for (int i = 0; i < ranges.Length; i++) { - var start = font.GetCharPosition(_archetype.Title, ranges[i].StartIndex); - var end = font.GetCharPosition(_archetype.Title, ranges[i].EndIndex); + var start = FallbackTextUtils.GetCharPosition(font, _archetype.Title, ranges[i].StartIndex); + var end = FallbackTextUtils.GetCharPosition(font, _archetype.Title, ranges[i].EndIndex); _highlights.Add(new Rectangle(start.X + textRect.X, 0, end.X - start.X, Height)); if (ranges[i].StartIndex <= 0) @@ -222,8 +222,8 @@ namespace FlaxEditor.Surface.ContextMenu _highlights.Clear(); var style = Style.Current; var font = style.FontSmall; - var start = font.GetCharPosition(_archetype.Title, 0); - var end = font.GetCharPosition(_archetype.Title, _archetype.Title.Length - 1); + var start = FallbackTextUtils.GetCharPosition(font, _archetype.Title, 0); + var end = FallbackTextUtils.GetCharPosition(font, _archetype.Title, _archetype.Title.Length - 1); _highlights.Add(new Rectangle(start.X + textRect.X, 0, end.X - start.X, Height)); _isFullMatch = true; Visible = true; @@ -237,8 +237,8 @@ namespace FlaxEditor.Surface.ContextMenu _highlights.Clear(); var style = Style.Current; var font = style.FontSmall; - var start = font.GetCharPosition(_archetype.Title, 0); - var end = font.GetCharPosition(_archetype.Title, _archetype.Title.Length - 1); + var start = FallbackTextUtils.GetCharPosition(font, _archetype.Title, 0); + var end = FallbackTextUtils.GetCharPosition(font, _archetype.Title, _archetype.Title.Length - 1); _highlights.Add(new Rectangle(start.X + textRect.X, 0, end.X - start.X, Height)); Visible = true; @@ -283,19 +283,19 @@ namespace FlaxEditor.Surface.ContextMenu } // Draw name - Render2D.DrawText(style.FontSmall, _archetype.Title, textRect, Enabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontSmall, _archetype.Title, textRect, Enabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center); if (_archetype.SubTitle != null) { var titleLength = FallbackTextUtils.MeasureText(style.FontSmall, _archetype.Title).X; var subTitleRect = new Rectangle(textRect.X + titleLength, textRect.Y, textRect.Width - titleLength, textRect.Height); - Render2D.DrawText(style.FontSmall, _archetype.SubTitle, subTitleRect, style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontSmall, _archetype.SubTitle, subTitleRect, style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center); } // Reset transform and draw score mark if (showScoreHit) { Render2D.PopTransform(); - Render2D.DrawText(style.FontSmall, "> ", textRect, Enabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontSmall, "> ", textRect, Enabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center); } } diff --git a/Source/Editor/Surface/Elements/InputBox.cs b/Source/Editor/Surface/Elements/InputBox.cs index 526d85d15..a5a99b932 100644 --- a/Source/Editor/Surface/Elements/InputBox.cs +++ b/Source/Editor/Surface/Elements/InputBox.cs @@ -1443,7 +1443,7 @@ namespace FlaxEditor.Surface.Elements // Draw text var style = Style.Current; var rect = new Rectangle(Width + 4, 0, 1410, Height); - Render2D.DrawText(style.FontSmall, Text, rect, Enabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontSmall, Text, rect, Enabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center); } /// diff --git a/Source/Editor/Surface/Elements/OutputBox.cs b/Source/Editor/Surface/Elements/OutputBox.cs index 497e2001a..5fb123700 100644 --- a/Source/Editor/Surface/Elements/OutputBox.cs +++ b/Source/Editor/Surface/Elements/OutputBox.cs @@ -189,7 +189,7 @@ namespace FlaxEditor.Surface.Elements // Draw text var style = Style.Current; var rect = new Rectangle(-100, 0, 100 - 2, Height); - Render2D.DrawText(style.FontSmall, Text, rect, Enabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Far, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontSmall, Text, rect, Enabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Far, TextAlignment.Center); } } } diff --git a/Source/Editor/Surface/Elements/TextView.cs b/Source/Editor/Surface/Elements/TextView.cs index 284497396..c52667fe3 100644 --- a/Source/Editor/Surface/Elements/TextView.cs +++ b/Source/Editor/Surface/Elements/TextView.cs @@ -25,7 +25,7 @@ namespace FlaxEditor.Surface.Elements var style = Style.Current; var color = Enabled ? style.Foreground : style.ForegroundDisabled; - Render2D.DrawText(style.FontSmall, Archetype.Text, new Rectangle(Float2.Zero, Size), color, TextAlignment.Near, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontSmall, Archetype.Text, new Rectangle(Float2.Zero, Size), color, TextAlignment.Near, TextAlignment.Center); } } } diff --git a/Source/Editor/Surface/SurfaceComment.cs b/Source/Editor/Surface/SurfaceComment.cs index aad45190e..26cc912ab 100644 --- a/Source/Editor/Surface/SurfaceComment.cs +++ b/Source/Editor/Surface/SurfaceComment.cs @@ -169,7 +169,7 @@ namespace FlaxEditor.Surface // Header Render2D.FillRectangle(_headerRect, headerColor); - Render2D.DrawText(style.FontLarge, Title, _headerRect, style.Foreground, TextAlignment.Center, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontLarge, Title, _headerRect, style.Foreground, TextAlignment.Center, TextAlignment.Center); // Close button Render2D.DrawSprite(style.Cross, _closeButtonRect, _closeButtonRect.Contains(_mousePosition) && Surface.CanEdit ? style.Foreground : style.ForegroundGrey); diff --git a/Source/Editor/Surface/SurfaceNode.cs b/Source/Editor/Surface/SurfaceNode.cs index 3c2381192..df706faa6 100644 --- a/Source/Editor/Surface/SurfaceNode.cs +++ b/Source/Editor/Surface/SurfaceNode.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; -using System.Linq; using FlaxEditor.Scripting; using FlaxEditor.Surface.Elements; using FlaxEditor.Surface.Undo; @@ -1028,7 +1027,7 @@ namespace FlaxEditor.Surface if (_headerRect.Contains(ref _mousePosition)) headerColor *= 1.07f; Render2D.FillRectangle(_headerRect, headerColor); - Render2D.DrawText(style.FontLarge, Title, _headerRect, style.Foreground, TextAlignment.Center, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontLarge, Title, _headerRect, style.Foreground, TextAlignment.Center, TextAlignment.Center); // Close button if ((Archetype.Flags & NodeFlags.NoCloseButton) == 0 && Surface.CanEdit) diff --git a/Source/Editor/Tools/Foliage/FoliageTab.cs b/Source/Editor/Tools/Foliage/FoliageTab.cs index c2b1fbe68..d36c3b6ed 100644 --- a/Source/Editor/Tools/Foliage/FoliageTab.cs +++ b/Source/Editor/Tools/Foliage/FoliageTab.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; -using System.Linq; using FlaxEditor.GUI.Tabs; using FlaxEditor.Modules; using FlaxEditor.SceneGraph.Actors; diff --git a/Source/Editor/Tools/Terrain/CarveTab.cs b/Source/Editor/Tools/Terrain/CarveTab.cs index 0a86aab6f..e64a364f0 100644 --- a/Source/Editor/Tools/Terrain/CarveTab.cs +++ b/Source/Editor/Tools/Terrain/CarveTab.cs @@ -1,7 +1,6 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; -using System.Linq; using FlaxEditor.GUI.Tabs; using FlaxEditor.Modules; using FlaxEditor.SceneGraph.Actors; diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index 0633d0ba9..515514131 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -1233,8 +1233,8 @@ namespace FlaxEditor.Viewport color = Color.Yellow; var text = string.Format("FPS: {0}", fps); var font = Style.Current.FontMedium; - Render2D.DrawText(font, text, new Rectangle(Float2.One, Size), Color.Black); - Render2D.DrawText(font, text, new Rectangle(Float2.Zero, Size), color); + FallbackTextUtils.DrawText(font, text, new Rectangle(Float2.One, Size), Color.Black); + FallbackTextUtils.DrawText(font, text, new Rectangle(Float2.Zero, Size), color); } } @@ -1814,7 +1814,7 @@ namespace FlaxEditor.Viewport { var bounds = new Rectangle(Float2.Zero, Size); Render2D.FillRectangle(bounds, new Color(0.0f, 0.0f, 0.0f, 0.2f)); - Render2D.DrawText(Style.Current.FontLarge, "Debugger breakpoint hit...", bounds, Color.White, TextAlignment.Center, TextAlignment.Center); + FallbackTextUtils.DrawText(Style.Current.FontLarge, "Debugger breakpoint hit...", bounds, Color.White, TextAlignment.Center, TextAlignment.Center); } } diff --git a/Source/Editor/Viewport/Previews/AnimationPreview.cs b/Source/Editor/Viewport/Previews/AnimationPreview.cs index 1679a36bc..c4ada83c4 100644 --- a/Source/Editor/Viewport/Previews/AnimationPreview.cs +++ b/Source/Editor/Viewport/Previews/AnimationPreview.cs @@ -96,11 +96,11 @@ namespace FlaxEditor.Viewport.Previews var skinnedModel = SkinnedModel; if (skinnedModel == null) { - Render2D.DrawText(style.FontLarge, "Missing Base Model", new Rectangle(Float2.Zero, Size), Color.Red, TextAlignment.Center, TextAlignment.Center, TextWrapping.WrapWords); + FallbackTextUtils.DrawText(style.FontLarge, "Missing Base Model", new Rectangle(Float2.Zero, Size), Color.Red, TextAlignment.Center, TextAlignment.Center, TextWrapping.WrapWords); } else if (!skinnedModel.IsLoaded) { - Render2D.DrawText(style.FontLarge, "Loading...", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontLarge, "Loading...", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); } } diff --git a/Source/Editor/Viewport/Previews/ModelPreview.cs b/Source/Editor/Viewport/Previews/ModelPreview.cs index a6496aafe..b31776bf6 100644 --- a/Source/Editor/Viewport/Previews/ModelPreview.cs +++ b/Source/Editor/Viewport/Previews/ModelPreview.cs @@ -409,8 +409,8 @@ namespace FlaxEditor.Viewport.Previews } var font = Style.Current.FontMedium; var pos = new Float2(10, 50); - Render2D.DrawText(font, text, new Rectangle(pos + Float2.One, Size), Color.Black); - Render2D.DrawText(font, text, new Rectangle(pos, Size), Color.White); + FallbackTextUtils.DrawText(font, text, new Rectangle(pos + Float2.One, Size), Color.Black); + FallbackTextUtils.DrawText(font, text, new Rectangle(pos, Size), Color.White); } } diff --git a/Source/Editor/Viewport/Previews/ParticleSystemPreview.cs b/Source/Editor/Viewport/Previews/ParticleSystemPreview.cs index c6ce5a7b5..a6eb721ce 100644 --- a/Source/Editor/Viewport/Previews/ParticleSystemPreview.cs +++ b/Source/Editor/Viewport/Previews/ParticleSystemPreview.cs @@ -254,7 +254,7 @@ namespace FlaxEditor.Viewport.Previews if (_showParticlesCounter) { var count = _previewEffect.ParticlesCount; - Render2D.DrawText( + FallbackTextUtils.DrawText( Style.Current.FontSmall, "Particles: " + count, new Rectangle(Float2.Zero, Size), diff --git a/Source/Editor/Viewport/Previews/SkinnedModelPreview.cs b/Source/Editor/Viewport/Previews/SkinnedModelPreview.cs index 8bcc506d9..6c02c019a 100644 --- a/Source/Editor/Viewport/Previews/SkinnedModelPreview.cs +++ b/Source/Editor/Viewport/Previews/SkinnedModelPreview.cs @@ -161,8 +161,8 @@ namespace FlaxEditor.Viewport.Previews } var font = Style.Current.FontMedium; var pos = new Float2(10, 50); - Render2D.DrawText(font, text, new Rectangle(pos + Float2.One, Size), Color.Black); - Render2D.DrawText(font, text, new Rectangle(pos, Size), Color.White); + FallbackTextUtils.DrawText(font, text, new Rectangle(pos + Float2.One, Size), Color.Black); + FallbackTextUtils.DrawText(font, text, new Rectangle(pos, Size), Color.White); } } diff --git a/Source/Editor/Viewport/Previews/TexturePreview.cs b/Source/Editor/Viewport/Previews/TexturePreview.cs index ec10bb019..5e7594e9c 100644 --- a/Source/Editor/Viewport/Previews/TexturePreview.cs +++ b/Source/Editor/Viewport/Previews/TexturePreview.cs @@ -103,7 +103,7 @@ namespace FlaxEditor.Viewport.Previews { var bounds = new Rectangle(Float2.Zero, Size); Render2D.FillRectangle(bounds, new Color(0.0f, 0.0f, 0.0f, 0.2f)); - Render2D.DrawText(Style.Current.FontLarge, "Debugger breakpoint hit...", bounds, Color.White, TextAlignment.Center, TextAlignment.Center); + FallbackTextUtils.DrawText(Style.Current.FontLarge, "Debugger breakpoint hit...", bounds, Color.White, TextAlignment.Center, TextAlignment.Center); } Render2D.PopClip(); diff --git a/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs b/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs index 598f4ee32..43f990afb 100644 --- a/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs +++ b/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs @@ -1,7 +1,6 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; -using System.Linq; using FlaxEditor.GUI.ContextMenu; using FlaxEngine; using FlaxEngine.GUI; @@ -124,7 +123,7 @@ namespace FlaxEditor.Viewport.Widgets } // Draw text - Render2D.DrawText(style.FontMedium, _text, textRect, style.ForegroundViewport * (IsMouseOver ? 1.0f : 0.9f), TextAlignment.Center, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontMedium, _text, textRect, style.ForegroundViewport * (IsMouseOver ? 1.0f : 0.9f), TextAlignment.Center, TextAlignment.Center); } /// diff --git a/Source/Editor/Windows/AboutDialog.cs b/Source/Editor/Windows/AboutDialog.cs index 02aad16ec..8c38ed2a9 100644 --- a/Source/Editor/Windows/AboutDialog.cs +++ b/Source/Editor/Windows/AboutDialog.cs @@ -2,7 +2,6 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System.Collections.Generic; -using System.Linq; using FlaxEditor.GUI.Dialogs; using FlaxEngine; using FlaxEngine.GUI; diff --git a/Source/Editor/Windows/Assets/AnimationGraphWindow.cs b/Source/Editor/Windows/Assets/AnimationGraphWindow.cs index 12eac22b5..eb4198fd8 100644 --- a/Source/Editor/Windows/Assets/AnimationGraphWindow.cs +++ b/Source/Editor/Windows/Assets/AnimationGraphWindow.cs @@ -51,7 +51,7 @@ namespace FlaxEditor.Windows.Assets var style = Style.Current; if (_window.Asset == null || !_window.Asset.IsLoaded) { - Render2D.DrawText(style.FontLarge, "Loading...", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontLarge, "Loading...", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); } } } diff --git a/Source/Editor/Windows/Assets/AnimationWindow.cs b/Source/Editor/Windows/Assets/AnimationWindow.cs index 8f88d93f6..042cf2b37 100644 --- a/Source/Editor/Windows/Assets/AnimationWindow.cs +++ b/Source/Editor/Windows/Assets/AnimationWindow.cs @@ -61,7 +61,7 @@ namespace FlaxEditor.Windows.Assets var animation = _window.Asset; if (animation == null || !animation.IsLoaded) { - Render2D.DrawText(style.FontLarge, "Loading...", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontLarge, "Loading...", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); } } diff --git a/Source/Editor/Windows/Assets/ModelWindow.cs b/Source/Editor/Windows/Assets/ModelWindow.cs index c2764a5a6..d48966043 100644 --- a/Source/Editor/Windows/Assets/ModelWindow.cs +++ b/Source/Editor/Windows/Assets/ModelWindow.cs @@ -48,7 +48,7 @@ namespace FlaxEditor.Windows.Assets var asset = _window.Asset; if (asset == null || !asset.IsLoaded) { - Render2D.DrawText(style.FontLarge, "Loading...", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontLarge, "Loading...", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); } } } @@ -645,7 +645,7 @@ namespace FlaxEditor.Windows.Assets if (!Proxy.Window._meshData.RequestMeshData(Proxy.Window._asset)) { Invalidate(); - Render2D.DrawText(Style.Current.FontMedium, "Loading...", new Rectangle(Float2.Zero, size), Color.White, TextAlignment.Center, TextAlignment.Center); + FallbackTextUtils.DrawText(Style.Current.FontMedium, "Loading...", new Rectangle(Float2.Zero, size), Color.White, TextAlignment.Center, TextAlignment.Center); return; } diff --git a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs index 95827240c..1e7192d9f 100644 --- a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs +++ b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs @@ -50,7 +50,7 @@ namespace FlaxEditor.Windows.Assets var asset = _window.Asset; if (asset == null || !asset.IsLoaded) { - Render2D.DrawText(style.FontLarge, "Loading...", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontLarge, "Loading...", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); } } } @@ -715,7 +715,7 @@ namespace FlaxEditor.Windows.Assets if (!Proxy.Window.RequestMeshData()) { Invalidate(); - Render2D.DrawText(Style.Current.FontMedium, "Loading...", new Rectangle(Float2.Zero, size), Color.White, TextAlignment.Center, TextAlignment.Center); + FallbackTextUtils.DrawText(Style.Current.FontMedium, "Loading...", new Rectangle(Float2.Zero, size), Color.White, TextAlignment.Center, TextAlignment.Center); return; } diff --git a/Source/Editor/Windows/ContentWindow.Search.cs b/Source/Editor/Windows/ContentWindow.Search.cs index a1072d158..67b7deddd 100644 --- a/Source/Editor/Windows/ContentWindow.Search.cs +++ b/Source/Editor/Windows/ContentWindow.Search.cs @@ -57,7 +57,7 @@ namespace FlaxEditor.Windows var textRect = new Rectangle(margin, 0, clientRect.Width - boxSize - 2.0f * margin, clientRect.Height); Render2D.PushClip(textRect); var textColor = TextColor; - Render2D.DrawText(Font.GetFont(), "View", textRect, enabled ? textColor : textColor * 0.5f, TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, textScale); + FallbackTextUtils.DrawText(Font.GetFont(), "View", textRect, enabled ? textColor : textColor * 0.5f, TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, textScale); Render2D.PopClip(); // Arrow diff --git a/Source/Editor/Windows/DebugLogWindow.cs b/Source/Editor/Windows/DebugLogWindow.cs index ab0c07ec5..91d4f9d8c 100644 --- a/Source/Editor/Windows/DebugLogWindow.cs +++ b/Source/Editor/Windows/DebugLogWindow.cs @@ -140,11 +140,11 @@ namespace FlaxEditor.Windows Render2D.PushClip(ref clientRect); if (LogCount == 1) { - Render2D.DrawText(style.FontMedium, Desc.Title, textRect, style.Foreground); + FallbackTextUtils.DrawText(style.FontMedium, Desc.Title, textRect, style.Foreground); } else if (LogCount > 1) { - Render2D.DrawText(style.FontMedium, $"{Desc.Title} ({LogCount})", textRect, style.Foreground); + FallbackTextUtils.DrawText(style.FontMedium, $"{Desc.Title} ({LogCount})", textRect, style.Foreground); } Render2D.PopClip(); } diff --git a/Source/Editor/Windows/GameWindow.cs b/Source/Editor/Windows/GameWindow.cs index 4a8c11176..2c0364995 100644 --- a/Source/Editor/Windows/GameWindow.cs +++ b/Source/Editor/Windows/GameWindow.cs @@ -827,7 +827,7 @@ namespace FlaxEditor.Windows if (Camera.MainCamera == null) { var style = Style.Current; - Render2D.DrawText(style.FontLarge, "No camera", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontLarge, "No camera", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); } // Selected UI controls outline @@ -866,8 +866,8 @@ namespace FlaxEditor.Windows var alpha = Mathf.Saturate(-animTime / fadeOutTime); var rect = new Rectangle(new Float2(6), Size - 12); var text = "Press Shift+F11 to unlock the mouse"; - Render2D.DrawText(style.FontSmall, text, rect + new Float2(1.0f), style.Background * alpha, TextAlignment.Near, TextAlignment.Far); - Render2D.DrawText(style.FontSmall, text, rect, style.Foreground * alpha, TextAlignment.Near, TextAlignment.Far); + FallbackTextUtils.DrawText(style.FontSmall, text, rect + new Float2(1.0f), style.Background * alpha, TextAlignment.Near, TextAlignment.Far); + FallbackTextUtils.DrawText(style.FontSmall, text, rect, style.Foreground * alpha, TextAlignment.Near, TextAlignment.Far); } timeout = 1.0f; @@ -884,7 +884,7 @@ namespace FlaxEditor.Windows { var bounds = new Rectangle(Float2.Zero, Size); Render2D.FillRectangle(bounds, new Color(0.0f, 0.0f, 0.0f, 0.2f)); - Render2D.DrawText(Style.Current.FontLarge, "Debugger breakpoint hit...", bounds, Color.White, TextAlignment.Center, TextAlignment.Center); + FallbackTextUtils.DrawText(Style.Current.FontLarge, "Debugger breakpoint hit...", bounds, Color.White, TextAlignment.Center, TextAlignment.Center); } } } diff --git a/Source/Editor/Windows/Profiler/SingleChart.cs b/Source/Editor/Windows/Profiler/SingleChart.cs index 4f36692e5..25241b4c6 100644 --- a/Source/Editor/Windows/Profiler/SingleChart.cs +++ b/Source/Editor/Windows/Profiler/SingleChart.cs @@ -138,8 +138,8 @@ namespace FlaxEditor.Windows.Profiler var headerRect = new Rectangle(0, chartHeight, Width, TitleHeight); var headerTextRect = new Rectangle(2, chartHeight, Width - 4, TitleHeight); Render2D.FillRectangle(headerRect, style.BackgroundNormal); - Render2D.DrawText(style.FontMedium, Title, headerTextRect, style.ForegroundGrey, TextAlignment.Near, TextAlignment.Center); - Render2D.DrawText(style.FontMedium, _sample, headerTextRect, style.Foreground, TextAlignment.Far, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontMedium, Title, headerTextRect, style.ForegroundGrey, TextAlignment.Near, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontMedium, _sample, headerTextRect, style.Foreground, TextAlignment.Far, TextAlignment.Center); } private void OnClick(ref Float2 location) diff --git a/Source/Editor/Windows/Profiler/Timeline.cs b/Source/Editor/Windows/Profiler/Timeline.cs index 780b31a3b..f87efdace 100644 --- a/Source/Editor/Windows/Profiler/Timeline.cs +++ b/Source/Editor/Windows/Profiler/Timeline.cs @@ -2,7 +2,6 @@ using FlaxEngine; using FlaxEngine.GUI; -using System.Linq; namespace FlaxEditor.Windows.Profiler { @@ -91,7 +90,7 @@ namespace FlaxEditor.Windows.Profiler if (_nameLength < bounds.Width + 4) { Render2D.PushClip(bounds); - Render2D.DrawText(style.FontMedium, _name, bounds, Style.Current.Foreground, TextAlignment.Center, TextAlignment.Center); + FallbackTextUtils.DrawText(style.FontMedium, _name, bounds, Style.Current.Foreground, TextAlignment.Center, TextAlignment.Center); Render2D.PopClip(); } } @@ -116,7 +115,7 @@ namespace FlaxEditor.Windows.Profiler var style = Style.Current; var rect = new Rectangle(Float2.Zero, Size); Render2D.PushClip(rect); - Render2D.DrawText(style.FontMedium, Name, rect, Style.Current.Foreground, TextAlignment.Center, TextAlignment.Center, TextWrapping.WrapChars); + FallbackTextUtils.DrawText(style.FontMedium, Name, rect, Style.Current.Foreground, TextAlignment.Center, TextAlignment.Center, TextWrapping.WrapChars); Render2D.PopClip(); } } diff --git a/Source/Editor/Windows/SceneTreeWindow.cs b/Source/Editor/Windows/SceneTreeWindow.cs index 63ba7b960..2fd990861 100644 --- a/Source/Editor/Windows/SceneTreeWindow.cs +++ b/Source/Editor/Windows/SceneTreeWindow.cs @@ -297,7 +297,7 @@ namespace FlaxEditor.Windows } if (overlayText != null) { - Render2D.DrawText(style.FontLarge, overlayText, GetClientArea(), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center, textWrap); + FallbackTextUtils.DrawText(style.FontLarge, overlayText, GetClientArea(), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center, textWrap); } base.Draw(); diff --git a/Source/Editor/Windows/ToolboxWindow.cs b/Source/Editor/Windows/ToolboxWindow.cs index 16b9165e1..f86341df3 100644 --- a/Source/Editor/Windows/ToolboxWindow.cs +++ b/Source/Editor/Windows/ToolboxWindow.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; -using System.Linq; using FlaxEditor.GUI.Input; using FlaxEditor.GUI.Tabs; using FlaxEditor.GUI.Tree; @@ -273,8 +272,8 @@ namespace FlaxEditor.Windows var textRect = item.TextRect; for (int i = 0; i < ranges.Length; i++) { - var start = font.GetCharPosition(text, ranges[i].StartIndex); - var end = font.GetCharPosition(text, ranges[i].EndIndex); + var start = FallbackTextUtils.GetCharPosition(font, text, ranges[i].StartIndex); + var end = FallbackTextUtils.GetCharPosition(font, text, ranges[i].EndIndex); highlights.Add(new Rectangle(start.X + textRect.X, textRect.Y, end.X - start.X, textRect.Height)); } item.SetHighlights(highlights); diff --git a/Source/Engine/Render2D/FallbackTextUtils.cs b/Source/Engine/Render2D/FallbackTextUtils.cs index eb676f0a9..b6d8770e2 100644 --- a/Source/Engine/Render2D/FallbackTextUtils.cs +++ b/Source/Engine/Render2D/FallbackTextUtils.cs @@ -1,4 +1,6 @@ +using System.Runtime.CompilerServices; + namespace FlaxEngine { /// @@ -11,41 +13,10 @@ namespace FlaxEngine get; set; } = null; - public static void DrawText(Font font, string text, Rectangle layoutRect, Color color, TextAlignment horizontalAlignment = TextAlignment.Near, TextAlignment verticalAlignment = TextAlignment.Near, TextWrapping textWrapping = TextWrapping.NoWrap, float baseLinesGapScale = 1.0f, float scale = 1.0f) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void DrawText(Font font, string text, Color color, ref TextLayoutOptions layout, MaterialBase customMaterial = null, bool useFallback = true) { - var layout = new TextLayoutOptions - { - Bounds = layoutRect, - HorizontalAlignment = horizontalAlignment, - VerticalAlignment = verticalAlignment, - TextWrapping = textWrapping, - Scale = scale, - BaseLinesGapScale = baseLinesGapScale, - }; - - if (Fallbacks != null) - { - Render2D.DrawText(font, Fallbacks, text, color, ref layout); - } - else - { - Render2D.DrawText(font, text, color, ref layout); - } - } - - public static void DrawText(Font font, MaterialBase customMaterial, string text, Rectangle layoutRect, Color color, TextAlignment horizontalAlignment = TextAlignment.Near, TextAlignment verticalAlignment = TextAlignment.Near, TextWrapping textWrapping = TextWrapping.NoWrap, float baseLinesGapScale = 1.0f, float scale = 1.0f) - { - var layout = new TextLayoutOptions - { - Bounds = layoutRect, - HorizontalAlignment = horizontalAlignment, - VerticalAlignment = verticalAlignment, - TextWrapping = textWrapping, - Scale = scale, - BaseLinesGapScale = baseLinesGapScale, - }; - - if (Fallbacks != null) + if (Fallbacks != null && useFallback) { Render2D.DrawText(font, Fallbacks, text, color, ref layout, customMaterial); } @@ -55,9 +26,56 @@ namespace FlaxEngine } } - public static Float2 MeasureText(Font font, string text) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void DrawText(Font font, string text, Rectangle layoutRect, Color color, TextAlignment horizontalAlignment = TextAlignment.Near, TextAlignment verticalAlignment = TextAlignment.Near, TextWrapping textWrapping = TextWrapping.NoWrap, float baseLinesGapScale = 1.0f, float scale = 1.0f, bool useFallback = true) { - if (Fallbacks != null) + var layout = new TextLayoutOptions + { + Bounds = layoutRect, + HorizontalAlignment = horizontalAlignment, + VerticalAlignment = verticalAlignment, + TextWrapping = textWrapping, + Scale = scale, + BaseLinesGapScale = baseLinesGapScale, + }; + + if (Fallbacks != null && useFallback) + { + Render2D.DrawText(font, Fallbacks, text, color, ref layout); + } + else + { + Render2D.DrawText(font, text, color, ref layout); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void DrawText(Font font, MaterialBase customMaterial, string text, Rectangle layoutRect, Color color, TextAlignment horizontalAlignment = TextAlignment.Near, TextAlignment verticalAlignment = TextAlignment.Near, TextWrapping textWrapping = TextWrapping.NoWrap, float baseLinesGapScale = 1.0f, float scale = 1.0f, bool useFallback = true) + { + var layout = new TextLayoutOptions + { + Bounds = layoutRect, + HorizontalAlignment = horizontalAlignment, + VerticalAlignment = verticalAlignment, + TextWrapping = textWrapping, + Scale = scale, + BaseLinesGapScale = baseLinesGapScale, + }; + + if (Fallbacks != null && useFallback) + { + Render2D.DrawText(font, Fallbacks, text, color, ref layout, customMaterial); + } + else + { + Render2D.DrawText(font, text, color, ref layout, customMaterial); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Float2 MeasureText(Font font, string text, bool useFallback = true) + { + if (Fallbacks != null && useFallback) { return font.MeasureText(Fallbacks, text); } @@ -67,9 +85,10 @@ namespace FlaxEngine } } - public static Float2 MeasureText(Font font, string text, ref TextRange textRange) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Float2 MeasureText(Font font, string text, ref TextRange textRange, bool useFallback = true) { - if (Fallbacks != null) + if (Fallbacks != null && useFallback) { return font.MeasureText(Fallbacks, text, ref textRange); } @@ -79,9 +98,10 @@ namespace FlaxEngine } } - public static Float2 MeasureText(Font font, string text, ref TextLayoutOptions layout) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Float2 MeasureText(Font font, string text, ref TextLayoutOptions layout, bool useFallback = true) { - if (Fallbacks != null) + if (Fallbacks != null && useFallback) { return font.MeasureText(Fallbacks, text, ref layout); } @@ -91,9 +111,10 @@ namespace FlaxEngine } } - public static Float2 MeasureText(Font font, string text, ref TextRange textRange, ref TextLayoutOptions layout) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Float2 MeasureText(Font font, string text, ref TextRange textRange, ref TextLayoutOptions layout, bool useFallback = true) { - if (Fallbacks != null) + if (Fallbacks != null && useFallback) { return font.MeasureText(Fallbacks, text, ref textRange, ref layout); } @@ -103,24 +124,108 @@ namespace FlaxEngine } } - public static Float2 GetCharPosition(Font font, string text, int index) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int HitTestText(Font font, string text, Float2 location, bool useFallback = true) { - return font.GetCharPosition(Style.Current.Fallbacks, text, index); + if (Fallbacks != null && useFallback) + { + return font.HitTestText(Fallbacks, text, location); + } + else + { + return font.HitTestText(text, location); + } } - public static Float2 GetCharPosition(Font font, string text, ref TextRange textRange, int index) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int HitTestText(Font font, string text, ref TextRange textRange, Float2 location, bool useFallback = true) { - return font.GetCharPosition(Style.Current.Fallbacks, text, ref textRange, index); + if (Fallbacks != null && useFallback) + { + return font.HitTestText(Fallbacks, text, ref textRange, location); + } + else + { + return font.HitTestText(text, ref textRange, location); + } } - public static Float2 GetCharPosition(Font font, string text, int index, ref TextLayoutOptions layout) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int HitTestText(Font font, string text, Float2 location, ref TextLayoutOptions layout, bool useFallback = true) { - return font.GetCharPosition(Style.Current.Fallbacks, text, index, ref layout); + if (Fallbacks != null && useFallback) + { + return font.HitTestText(Fallbacks, text, location, ref layout); + } + else + { + return font.HitTestText(text, location, ref layout); + } } - public static Float2 GetCharPosition(Font font, string text, ref TextRange textRange, int index, ref TextLayoutOptions layout) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int HitTestText(Font font, string text, ref TextRange textRange, Float2 location, ref TextLayoutOptions layout, bool useFallback = true) { - return font.GetCharPosition(Style.Current.Fallbacks, text, ref textRange, index, ref layout); + if (Fallbacks != null && useFallback) + { + return font.HitTestText(Fallbacks, text, ref textRange, location, ref layout); + } + else + { + return font.HitTestText(text, ref textRange, location, ref layout); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Float2 GetCharPosition(Font font, string text, int index, bool useFallback = true) + { + if (Fallbacks != null && useFallback) + { + return font.GetCharPosition(Fallbacks, text, index); + } + else + { + return font.GetCharPosition(text, index); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Float2 GetCharPosition(Font font, string text, ref TextRange textRange, int index, bool useFallback = true) + { + if (Fallbacks != null && useFallback) + { + return font.GetCharPosition(Fallbacks, text, ref textRange, index); + } + else + { + return font.GetCharPosition(text, ref textRange, index); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Float2 GetCharPosition(Font font, string text, int index, ref TextLayoutOptions layout, bool useFallback = true) + { + if (Fallbacks != null && useFallback) + { + return font.GetCharPosition(Fallbacks, text, index, ref layout); + } + else + { + return font.GetCharPosition(text, index, ref layout); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Float2 GetCharPosition(Font font, string text, ref TextRange textRange, int index, ref TextLayoutOptions layout, bool useFallback = true) + { + if (Fallbacks != null && useFallback) + { + return font.GetCharPosition(Fallbacks, text, ref textRange, index, ref layout); + } + else + { + return font.GetCharPosition(text, ref textRange, index, ref layout); + } } } } diff --git a/Source/Engine/UI/GUI/Common/Button.cs b/Source/Engine/UI/GUI/Common/Button.cs index 756501c5e..feb34418b 100644 --- a/Source/Engine/UI/GUI/Common/Button.cs +++ b/Source/Engine/UI/GUI/Common/Button.cs @@ -1,7 +1,6 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; -using System.Linq; namespace FlaxEngine.GUI { @@ -262,7 +261,7 @@ namespace FlaxEngine.GUI Render2D.DrawRectangle(clientRect, borderColor, BorderThickness); // Draw text - Render2D.DrawText(_font?.GetFont(), TextMaterial, _text, clientRect, textColor, TextAlignment.Center, TextAlignment.Center); + FallbackTextUtils.DrawText(_font?.GetFont(), TextMaterial, _text, clientRect, textColor, TextAlignment.Center, TextAlignment.Center); } /// diff --git a/Source/Engine/UI/GUI/Common/Dropdown.cs b/Source/Engine/UI/GUI/Common/Dropdown.cs index 46f05f516..3b298b136 100644 --- a/Source/Engine/UI/GUI/Common/Dropdown.cs +++ b/Source/Engine/UI/GUI/Common/Dropdown.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; -using System.Linq; namespace FlaxEngine.GUI { @@ -674,7 +673,7 @@ namespace FlaxEngine.GUI var textRect = new Rectangle(margin, 0, clientRect.Width - boxSize - 2.0f * margin, clientRect.Height); Render2D.PushClip(textRect); var textColor = TextColor; - Render2D.DrawText(Font.GetFont(), FontMaterial, _items[_selectedIndex], textRect, enabled ? textColor : textColor * 0.5f, TextAlignment.Near, TextAlignment.Center); + FallbackTextUtils.DrawText(Font.GetFont(), FontMaterial, _items[_selectedIndex], textRect, enabled ? textColor : textColor * 0.5f, TextAlignment.Near, TextAlignment.Center); Render2D.PopClip(); } diff --git a/Source/Engine/UI/GUI/Common/Label.cs b/Source/Engine/UI/GUI/Common/Label.cs index 4e26c1934..3d1caad28 100644 --- a/Source/Engine/UI/GUI/Common/Label.cs +++ b/Source/Engine/UI/GUI/Common/Label.cs @@ -1,8 +1,6 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. -using FlaxEditor.Options; using System.ComponentModel; -using System.Linq; namespace FlaxEngine.GUI { @@ -235,7 +233,7 @@ namespace FlaxEngine.GUI } } - Render2D.DrawText(_font.GetFont(), Material, _text, rect, color, hAlignment, wAlignment, Wrapping, BaseLinesGapScale, scale); + FallbackTextUtils.DrawText(_font.GetFont(), Material, _text, rect, color, hAlignment, wAlignment, Wrapping, BaseLinesGapScale, scale); if (ClipText) Render2D.PopClip(); diff --git a/Source/Engine/UI/GUI/Common/RichTextBox.Tags.cs b/Source/Engine/UI/GUI/Common/RichTextBox.Tags.cs index d3c2feefe..df8e0be7c 100644 --- a/Source/Engine/UI/GUI/Common/RichTextBox.Tags.cs +++ b/Source/Engine/UI/GUI/Common/RichTextBox.Tags.cs @@ -1,7 +1,6 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using FlaxEngine.Utilities; -using System.Linq; namespace FlaxEngine.GUI { diff --git a/Source/Engine/UI/GUI/Common/RichTextBox.cs b/Source/Engine/UI/GUI/Common/RichTextBox.cs index 514878b37..ab922d93d 100644 --- a/Source/Engine/UI/GUI/Common/RichTextBox.cs +++ b/Source/Engine/UI/GUI/Common/RichTextBox.cs @@ -1,7 +1,6 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System.Collections.Generic; -using System.Linq; namespace FlaxEngine.GUI { diff --git a/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs b/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs index 438a7e3d8..a30d8e603 100644 --- a/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs +++ b/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs @@ -154,7 +154,7 @@ namespace FlaxEngine.GUI if (!font) break; height = font.Height / DpiScale; - return textBlock.Bounds.Location + font.GetCharPosition(_text, ref textBlock.Range, index - textBlock.Range.StartIndex); + return textBlock.Bounds.Location + FallbackTextUtils.GetCharPosition(font, _text, ref textBlock.Range, index - textBlock.Range.StartIndex); } } @@ -196,7 +196,7 @@ namespace FlaxEngine.GUI var font = textBlock.Style.Font.GetFont(); if (!font && textBlock.Range.Length > 0) break; - return font.HitTestText(_text, ref textBlock.Range, location - textBlock.Bounds.Location) + textBlock.Range.StartIndex; + return FallbackTextUtils.HitTestText(font, _text, ref textBlock.Range, location - textBlock.Bounds.Location) + textBlock.Range.StartIndex; } } @@ -288,8 +288,8 @@ namespace FlaxEngine.GUI // Selection if (hasSelection && textBlock.Style.BackgroundSelectedBrush != null && textBlock.Range.Intersect(ref selection)) { - var leftEdge = selection.StartIndex <= textBlock.Range.StartIndex ? textBlock.Bounds.UpperLeft : font.GetCharPosition(_text, selection.StartIndex); - var rightEdge = selection.EndIndex >= textBlock.Range.EndIndex ? textBlock.Bounds.UpperRight : font.GetCharPosition(_text, selection.EndIndex); + var leftEdge = selection.StartIndex <= textBlock.Range.StartIndex ? textBlock.Bounds.UpperLeft : FallbackTextUtils.GetCharPosition(font, _text, selection.StartIndex); + var rightEdge = selection.EndIndex >= textBlock.Range.EndIndex ? textBlock.Bounds.UpperRight : FallbackTextUtils.GetCharPosition(font, _text, selection.EndIndex); float height = font.Height / DpiScale; float alpha = Mathf.Min(1.0f, Mathf.Cos(_animateTime * BackgroundSelectedFlashSpeed) * 0.5f + 1.3f); alpha *= alpha; diff --git a/Source/Engine/UI/GUI/Common/TextBox.cs b/Source/Engine/UI/GUI/Common/TextBox.cs index ea7523f48..70a153faf 100644 --- a/Source/Engine/UI/GUI/Common/TextBox.cs +++ b/Source/Engine/UI/GUI/Common/TextBox.cs @@ -1,7 +1,5 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. -using System.Drawing; -using System.Linq; namespace FlaxEngine.GUI { @@ -120,7 +118,7 @@ namespace FlaxEngine.GUI } height = font.Height / DpiScale; - return font.GetCharPosition(_text, index, ref _layout); + return FallbackTextUtils.GetCharPosition(font, _text, index, ref _layout); } /// @@ -132,7 +130,7 @@ namespace FlaxEngine.GUI return 0; } - return font.HitTestText(_text, location, ref _layout); + return FallbackTextUtils.HitTestText(font, _text, location, ref _layout); } /// @@ -171,8 +169,8 @@ namespace FlaxEngine.GUI // Check if sth is selected to draw selection if (HasSelection) { - var leftEdge = font.GetCharPosition(_text, SelectionLeft, ref _layout); - var rightEdge = font.GetCharPosition(_text, SelectionRight, ref _layout); + var leftEdge = FallbackTextUtils.GetCharPosition(font, _text, SelectionLeft, ref _layout); + var rightEdge = FallbackTextUtils.GetCharPosition(font, _text, SelectionRight, ref _layout); float fontHeight = font.Height / DpiScale; // Draw selection background @@ -213,25 +211,11 @@ namespace FlaxEngine.GUI var color = TextColor; if (!enabled) color *= 0.6f; - if (Render2D.Fallbacks != null) - { - Render2D.DrawText(font, Render2D.Fallbacks, _text, color, ref _layout, TextMaterial); - } - else - { - Render2D.DrawText(font, _text, color, ref _layout, TextMaterial); - } + FallbackTextUtils.DrawText(font, _text, color, ref _layout, TextMaterial); } else if (!string.IsNullOrEmpty(_watermarkText) && !IsFocused) { - if (Render2D.Fallbacks != null) - { - Render2D.DrawText(font, Render2D.Fallbacks, _watermarkText, WatermarkTextColor, ref _layout, TextMaterial); - } - else - { - Render2D.DrawText(font, _watermarkText, WatermarkTextColor, ref _layout, TextMaterial); - } + FallbackTextUtils.DrawText(font, _watermarkText, WatermarkTextColor, ref _layout, TextMaterial); } // Caret diff --git a/Source/Engine/UI/GUI/Panels/DropPanel.cs b/Source/Engine/UI/GUI/Panels/DropPanel.cs index ed620ab98..123e0f034 100644 --- a/Source/Engine/UI/GUI/Panels/DropPanel.cs +++ b/Source/Engine/UI/GUI/Panels/DropPanel.cs @@ -1,7 +1,6 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; -using System.Linq; namespace FlaxEngine.GUI { @@ -375,7 +374,7 @@ namespace FlaxEngine.GUI textColor *= 0.6f; } - Render2D.DrawText(HeaderTextFont.GetFont(), HeaderTextMaterial, HeaderText, textRect, textColor, TextAlignment.Near, TextAlignment.Center); + FallbackTextUtils.DrawText(HeaderTextFont.GetFont(), HeaderTextMaterial, HeaderText, textRect, textColor, TextAlignment.Near, TextAlignment.Center); if (!_isClosed && EnableContainmentLines) { diff --git a/Source/Engine/UI/GUI/Style.cs b/Source/Engine/UI/GUI/Style.cs index c4b51b085..f4fca13eb 100644 --- a/Source/Engine/UI/GUI/Style.cs +++ b/Source/Engine/UI/GUI/Style.cs @@ -1,6 +1,5 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. -using System.Linq; namespace FlaxEngine.GUI { diff --git a/Source/Engine/UI/GUI/Tooltip.cs b/Source/Engine/UI/GUI/Tooltip.cs index 7a1026e55..41d06b017 100644 --- a/Source/Engine/UI/GUI/Tooltip.cs +++ b/Source/Engine/UI/GUI/Tooltip.cs @@ -1,7 +1,6 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; -using System.Linq; namespace FlaxEngine.GUI { @@ -235,7 +234,7 @@ namespace FlaxEngine.GUI Render2D.FillRectangle(new Rectangle(1.1f, 1.1f, Width - 2, Height - 2), style.Background); // Tooltip text - Render2D.DrawText( + FallbackTextUtils.DrawText( style.FontMedium, _currentText, GetClientArea(), From 3c5035d3e93540fa718ca97accd5ce94e7afa7a4 Mon Sep 17 00:00:00 2001 From: ExMatics HydrogenC <33123710+HydrogenC@users.noreply.github.com> Date: Fri, 1 Dec 2023 18:06:25 +0800 Subject: [PATCH 044/139] Fix merge problem --- Source/Editor/Windows/PluginsWindow.cs | 55 +++++++++++++++++++++----- 1 file changed, 45 insertions(+), 10 deletions(-) diff --git a/Source/Editor/Windows/PluginsWindow.cs b/Source/Editor/Windows/PluginsWindow.cs index 03beab2fa..f43fb0342 100644 --- a/Source/Editor/Windows/PluginsWindow.cs +++ b/Source/Editor/Windows/PluginsWindow.cs @@ -14,7 +14,6 @@ using FlaxEditor.GUI.Tabs; using FlaxEngine; using FlaxEngine.GUI; using FlaxEngine.Json; -using FlaxEngine.Utilities; namespace FlaxEditor.Windows { @@ -122,7 +121,7 @@ namespace FlaxEditor.Windows url = desc.RepositoryUrl; versionLabel.Font.Font.WaitForLoaded(); var font = versionLabel.Font.GetFont(); - var authorWidth = FallbackTextUtils.MeasureText(font, desc.Author).X + 8; + var authorWidth = font.MeasureText(desc.Author).X + 8; var authorLabel = new ClickableLabel { HorizontalAlignment = TextAlignment.Far, @@ -392,6 +391,7 @@ namespace FlaxEditor.Windows } Editor.Log("Plugin project has been cloned."); + try { // Start git submodule clone @@ -412,24 +412,28 @@ namespace FlaxEditor.Windows } // Find project config file. Could be different then what the user named the folder. - var files = Directory.GetFiles(clonePath); string pluginProjectName = ""; - foreach (var file in files) + foreach (var file in Directory.GetFiles(clonePath)) { if (file.Contains(".flaxproj", StringComparison.OrdinalIgnoreCase)) { pluginProjectName = Path.GetFileNameWithoutExtension(file); - Debug.Log(pluginProjectName); + break; } } - if (string.IsNullOrEmpty(pluginProjectName)) - Editor.LogError("Failed to find plugin project file to add to Project config. Please add manually."); - else { - await AddReferenceToProject(pluginName, pluginProjectName); - MessageBox.Show($"{pluginName} has been successfully cloned. Restart editor for changes to take effect.", "Plugin Project Created", MessageBoxButtons.OK); + Editor.LogError("Failed to find plugin project file to add to Project config. Please add manually."); + return; } + + await AddModuleReferencesInGameModule(clonePath); + await AddReferenceToProject(pluginName, pluginProjectName); + + if (Editor.Options.Options.SourceCode.AutoGenerateScriptsProjectFiles) + Editor.ProgressReporting.GenerateScriptsProjectFiles.RunAsync(); + + MessageBox.Show($"{pluginName} has been successfully cloned. Restart editor for changes to take effect.", "Plugin Project Created", MessageBoxButtons.OK); } private void OnAddButtonClicked() @@ -749,6 +753,37 @@ namespace FlaxEditor.Windows MessageBox.Show($"{pluginName} has been successfully created. Restart editor for changes to take effect.", "Plugin Project Created", MessageBoxButtons.OK); } + private async Task AddModuleReferencesInGameModule(string pluginFolderPath) + { + // Common game build script location + var gameScript = Path.Combine(Globals.ProjectFolder, "Source/Game/Game.Build.cs"); + if (File.Exists(gameScript)) + { + var gameScriptContents = await File.ReadAllTextAsync(gameScript); + var insertLocation = gameScriptContents.IndexOf("base.Setup(options);", StringComparison.Ordinal); + if (insertLocation != -1) + { + insertLocation += 20; + var modifiedAny = false; + + // Find all code modules in a plugin to auto-reference them in game build script + foreach (var subDir in Directory.GetDirectories(Path.Combine(pluginFolderPath, "Source"))) + { + var pluginModuleName = Path.GetFileName(subDir); + var pluginModuleScriptPath = Path.Combine(subDir, pluginModuleName + ".Build.cs"); + if (File.Exists(pluginModuleScriptPath)) + { + gameScriptContents = gameScriptContents.Insert(insertLocation, $"\n options.PublicDependencies.Add(\"{pluginModuleName}\");"); + modifiedAny = true; + } + } + + if (modifiedAny) + await File.WriteAllTextAsync(gameScript, gameScriptContents, Encoding.UTF8); + } + } + } + private async Task AddReferenceToProject(string pluginFolderName, string pluginName) { // Project flax config file From 95f5e31e4852ab6e2a25177f3f587ba0840b03a4 Mon Sep 17 00:00:00 2001 From: ExMatics HydrogenC <33123710+HydrogenC@users.noreply.github.com> Date: Fri, 1 Dec 2023 19:38:15 +0800 Subject: [PATCH 045/139] Fix textbox height Fix build error under non-windows platforms --- Source/Editor/GUI/Row.cs | 5 ++- Source/Engine/Render2D/FallbackFonts.h | 35 +++++++++++++++--- Source/Engine/Render2D/FallbackTextUtils.cs | 37 +++++++++++++++++++ Source/Engine/Render2D/Font.h | 7 ++-- Source/Engine/Render2D/FontAsset.cpp | 7 ++++ Source/Engine/Render2D/FontAsset.h | 2 +- .../Engine/UI/GUI/Common/RichTextBoxBase.cs | 1 + Source/Engine/UI/GUI/Common/TextBox.cs | 4 +- 8 files changed, 85 insertions(+), 13 deletions(-) diff --git a/Source/Editor/GUI/Row.cs b/Source/Editor/GUI/Row.cs index c7be48626..9868ab456 100644 --- a/Source/Editor/GUI/Row.cs +++ b/Source/Editor/GUI/Row.cs @@ -38,8 +38,9 @@ namespace FlaxEditor.GUI { Depth = -1; - if (Height < Style.Current.FontMedium.Height) - Height = Style.Current.FontMedium.Height + 4; + var mediumHeight = FallbackTextUtils.GetMaxHeight(Style.Current.FontMedium); + if (Height < mediumHeight) + Height = mediumHeight + 4; } /// diff --git a/Source/Engine/Render2D/FallbackFonts.h b/Source/Engine/Render2D/FallbackFonts.h index 5861354fc..040ca8087 100644 --- a/Source/Engine/Render2D/FallbackFonts.h +++ b/Source/Engine/Render2D/FallbackFonts.h @@ -9,36 +9,55 @@ struct TextRange; class Font; class FontAsset; +/// +/// Defines a list of fonts that can be used as a fallback, ordered by priority. +/// API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API FallbackFonts : public ManagedScriptingObject { DECLARE_SCRIPTING_TYPE_NO_SPAWN(FallbackFonts); private: - /// - /// The list of fallback fonts, ordered by priority. - /// The first element is reserved for the primary font, fallback fonts starts from the second element. - /// Array _fontAssets; + // Cache fallback fonts of various sizes Dictionary*> _cache; public: + /// + /// Initializes a new instance of the class. + /// + /// The fallback font assets. FallbackFonts(const Array& fonts); + /// + /// Initializes a new instance of the class, exposed for C#. + /// + /// The fallback font assets. + /// The new instance. API_FUNCTION() FORCE_INLINE static FallbackFonts* Create(const Array& fonts) { return New(fonts); } + /// + /// Get the parent assets of fallback fonts. + /// + /// The font assets. API_PROPERTY() FORCE_INLINE Array& GetFonts() { return _fontAssets; } + /// + /// Set the fallback fonts. + /// + /// The parent assets of the new fonts. API_PROPERTY() FORCE_INLINE void SetFonts(const Array& val) { _fontAssets = val; } /// - /// Combine the primary fonts with the fallback fonts to get a font list + /// Gets the fallback fonts with the given size. /// + /// The size. + /// The generated fonts. API_FUNCTION() FORCE_INLINE Array& GetFontList(float size) { Array* result; if (_cache.TryGet(size, result)) { @@ -61,6 +80,8 @@ public: /// Gets the index of the fallback font that should be used to render the char /// /// The char. + /// The primary font. + /// The number to return if none of the fonts can render. /// -1 if char can be rendered with primary font, index if it matches a fallback font. API_FUNCTION() FORCE_INLINE int32 GetCharFallbackIndex(Char c, Font* primaryFont = nullptr, int32 missing = -1) { if (primaryFont && primaryFont->GetAsset()->ContainsChar(c)) { @@ -81,6 +102,10 @@ public: return missing; } + /// + /// Checks if every font is properly loaded. + /// + /// True if every font asset is non-null, otherwise false. API_FUNCTION() FORCE_INLINE bool Verify() { for (int32 i = 0; i < _fontAssets.Count(); i++) { diff --git a/Source/Engine/Render2D/FallbackTextUtils.cs b/Source/Engine/Render2D/FallbackTextUtils.cs index b6d8770e2..486a354bf 100644 --- a/Source/Engine/Render2D/FallbackTextUtils.cs +++ b/Source/Engine/Render2D/FallbackTextUtils.cs @@ -227,5 +227,42 @@ namespace FlaxEngine return font.GetCharPosition(text, ref textRange, index, ref layout); } } + + /// + /// Gets the max font height among the font and all fallback fonts of the same size. + /// + /// The primary font to use. + /// The fallback fonts. + /// The max height. + public static float GetMaxHeight(Font font, FallbackFonts fallbacks) + { + float height = font.Height; + + var fallbackFonts = fallbacks.GetFontList(font.Size); + foreach (var item in fallbackFonts) + { + height = Mathf.Max(height, item.Height); + } + + return height; + } + + /// + /// Gets the max font height among the font and all fallback fonts of the same size. + /// + /// The primary font to use. + /// Whether to enable fallback fonts, uses if true. + /// The max height. + public static float GetMaxHeight(Font font, bool useFallback = true) + { + if(Fallbacks != null && useFallback) + { + return GetMaxHeight(font, Fallbacks); + } + else + { + return font.Height; + } + } } } diff --git a/Source/Engine/Render2D/Font.h b/Source/Engine/Render2D/Font.h index d932888fb..110a8fe70 100644 --- a/Source/Engine/Render2D/Font.h +++ b/Source/Engine/Render2D/Font.h @@ -122,7 +122,8 @@ struct TIsPODType }; /// -/// The font block info generated during text processing. +/// The font block info generated during text processing. +/// A block means a range of text that belongs to the same line and can be rendered with the same font. /// API_STRUCT(NoDefault) struct FontBlockCache { @@ -134,7 +135,7 @@ API_STRUCT(NoDefault) struct FontBlockCache API_FIELD() Float2 Location; /// - /// The height of the current block + /// The size of the current block /// API_FIELD() Float2 Size; @@ -183,7 +184,7 @@ API_STRUCT(NoDefault) struct BlockedTextLineCache API_FIELD() float MaxAscender; /// - /// The index of the font to render with + /// The blocks that belongs to this line /// API_FIELD() Array Blocks; }; diff --git a/Source/Engine/Render2D/FontAsset.cpp b/Source/Engine/Render2D/FontAsset.cpp index 1e94b19b1..ea629367f 100644 --- a/Source/Engine/Render2D/FontAsset.cpp +++ b/Source/Engine/Render2D/FontAsset.cpp @@ -199,6 +199,13 @@ bool FontAsset::Save(const StringView& path) #endif + +/// +/// Check if the font contains the glyph of a char +/// +/// The char to test. +/// True if the font contains the glyph of the char, otherwise false. + bool FontAsset::ContainsChar(Char c) const { return FT_Get_Char_Index(GetFTFace(), c) > 0; } diff --git a/Source/Engine/Render2D/FontAsset.h b/Source/Engine/Render2D/FontAsset.h index a773a8ad6..f3c909911 100644 --- a/Source/Engine/Render2D/FontAsset.h +++ b/Source/Engine/Render2D/FontAsset.h @@ -179,7 +179,7 @@ public: /// /// The char to test. /// True if the font contains the glyph of the char, otherwise false. - API_FUNCTION() FORCE_INLINE bool ContainsChar(Char c) const; + API_FUNCTION() bool ContainsChar(Char c) const; /// /// Invalidates all cached dynamic font atlases using this font. Can be used to reload font characters after changing font asset options. diff --git a/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs b/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs index a30d8e603..8d7858af0 100644 --- a/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs +++ b/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs @@ -316,6 +316,7 @@ namespace FlaxEngine.GUI color = textBlock.Style.ShadowColor; if (!enabled) color *= 0.6f; + // We don't need font fallbacks for rich text since the font is user-selected Render2D.DrawText(font, _text, ref textBlock.Range, color, textBlock.Bounds.Location + textBlock.Style.ShadowOffset, textBlock.Style.CustomMaterial); } diff --git a/Source/Engine/UI/GUI/Common/TextBox.cs b/Source/Engine/UI/GUI/Common/TextBox.cs index 70a153faf..b1861df56 100644 --- a/Source/Engine/UI/GUI/Common/TextBox.cs +++ b/Source/Engine/UI/GUI/Common/TextBox.cs @@ -117,7 +117,7 @@ namespace FlaxEngine.GUI return Float2.Zero; } - height = font.Height / DpiScale; + height = FallbackTextUtils.GetMaxHeight(font) / DpiScale; return FallbackTextUtils.GetCharPosition(font, _text, index, ref _layout); } @@ -171,7 +171,7 @@ namespace FlaxEngine.GUI { var leftEdge = FallbackTextUtils.GetCharPosition(font, _text, SelectionLeft, ref _layout); var rightEdge = FallbackTextUtils.GetCharPosition(font, _text, SelectionRight, ref _layout); - float fontHeight = font.Height / DpiScale; + float fontHeight = FallbackTextUtils.GetMaxHeight(font) / DpiScale; // Draw selection background float alpha = Mathf.Min(1.0f, Mathf.Cos(_animateTime * BackgroundSelectedFlashSpeed) * 0.5f + 1.3f); From 29eb3954c5bc0da8c7ce0b2e997cc797c2b1754c Mon Sep 17 00:00:00 2001 From: ExMatics HydrogenC <33123710+HydrogenC@users.noreply.github.com> Date: Sat, 2 Dec 2023 17:48:51 +0800 Subject: [PATCH 046/139] Create global settings for font fallback --- Source/Editor/Windows/SplashScreen.cpp | 6 +- Source/Engine/Core/Config/GameSettings.cs | 2 +- Source/Engine/Core/Config/GameSettings.h | 11 +++ .../Platform/Win32/IncludeWindowsHeaders.h | 2 +- Source/Engine/Render2D/FallbackFonts.cpp | 2 +- Source/Engine/Render2D/FallbackFonts.h | 14 ++-- Source/Engine/Render2D/Font.cpp | 8 +- Source/Engine/Render2D/Font.h | 36 ++++----- Source/Engine/Render2D/Render2D.cpp | 24 +++--- Source/Engine/Render2D/Render2D.h | 81 +++++++++++++++---- 10 files changed, 122 insertions(+), 64 deletions(-) diff --git a/Source/Editor/Windows/SplashScreen.cpp b/Source/Editor/Windows/SplashScreen.cpp index 92894b537..cbb51ae3c 100644 --- a/Source/Editor/Windows/SplashScreen.cpp +++ b/Source/Editor/Windows/SplashScreen.cpp @@ -264,7 +264,7 @@ void SplashScreen::OnDraw() layout.HorizontalAlignment = TextAlignment::Near; layout.VerticalAlignment = TextAlignment::Near; layout.Scale = Math::Min((width - 20 * s) / titleLength.X, 1.0f); - Render2D::DrawText(_titleFont, GetTitle(), Color::White, layout); + Render2D::DrawTextInternal(_titleFont, GetTitle(), Color::White, layout); // Subtitle String subtitle(_quote); @@ -279,14 +279,14 @@ void SplashScreen::OnDraw() layout.Scale = 1.0f; layout.HorizontalAlignment = TextAlignment::Far; layout.VerticalAlignment = TextAlignment::Far; - Render2D::DrawText(_subtitleFont, subtitle, Color::FromRGB(0x8C8C8C), layout); + Render2D::DrawTextInternal(_subtitleFont, subtitle, Color::FromRGB(0x8C8C8C), layout); // Additional info const float infoMargin = 6 * s; layout.Bounds = Rectangle(infoMargin, lightBarHeight + infoMargin, width - (2 * infoMargin), height - lightBarHeight - (2 * infoMargin)); layout.HorizontalAlignment = TextAlignment::Near; layout.VerticalAlignment = TextAlignment::Center; - Render2D::DrawText(_subtitleFont, _infoText, Color::FromRGB(0xFFFFFF) * 0.9f, layout); + Render2D::DrawTextInternal(_subtitleFont, _infoText, Color::FromRGB(0xFFFFFF) * 0.9f, layout); } bool SplashScreen::HasLoadedFonts() const diff --git a/Source/Engine/Core/Config/GameSettings.cs b/Source/Engine/Core/Config/GameSettings.cs index 40d9f5dbc..76c968968 100644 --- a/Source/Engine/Core/Config/GameSettings.cs +++ b/Source/Engine/Core/Config/GameSettings.cs @@ -119,7 +119,7 @@ namespace FlaxEditor.Content.Settings /// /// The custom settings to use with a game. Can be specified by the user to define game-specific options and be used by the external plugins (used as key-value pair). /// - [EditorOrder(1100), EditorDisplay("Other Settings"), Tooltip("The custom settings to use with a game. Can be specified by the user to define game-specific options and be used by the external plugins (used as key-value pair).")] + [EditorOrder(1500), EditorDisplay("Other Settings"), Tooltip("The custom settings to use with a game. Can be specified by the user to define game-specific options and be used by the external plugins (used as key-value pair).")] public Dictionary CustomSettings; #if FLAX_EDITOR || PLATFORM_WINDOWS diff --git a/Source/Engine/Core/Config/GameSettings.h b/Source/Engine/Core/Config/GameSettings.h index 54ad29a7b..6813f3ee2 100644 --- a/Source/Engine/Core/Config/GameSettings.h +++ b/Source/Engine/Core/Config/GameSettings.h @@ -7,6 +7,8 @@ #include "Engine/Core/Types/String.h" #include "Engine/Core/Collections/Dictionary.h" +class FontFallbackList; + /// /// The main game engine configuration service. Loads and applies game configuration. /// @@ -33,6 +35,15 @@ public: API_FIELD(Attributes="EditorOrder(15), EditorDisplay(\"General\")") String CopyrightNotice; + /// + /// The copyright note used for content signing (eg. source code header). + /// + API_FIELD(Attributes = "EditorOrder(1200), EditorDisplay(\"Other Settings\")") + bool EnableFontFallback; + + API_FIELD(Attributes = "EditorOrder(1205), EditorDisplay(\"Other Settings\")") + FontFallbackList* FontFallbacks; + /// /// The default application icon. /// diff --git a/Source/Engine/Platform/Win32/IncludeWindowsHeaders.h b/Source/Engine/Platform/Win32/IncludeWindowsHeaders.h index 3f2b8d36e..66ed97a20 100644 --- a/Source/Engine/Platform/Win32/IncludeWindowsHeaders.h +++ b/Source/Engine/Platform/Win32/IncludeWindowsHeaders.h @@ -57,7 +57,7 @@ #undef CreateWindow #undef CreateProcess #undef SetWindowText -#undef DrawText +#undef DrawTextInternal #undef CreateFont #undef IsMinimized #undef IsMaximized diff --git a/Source/Engine/Render2D/FallbackFonts.cpp b/Source/Engine/Render2D/FallbackFonts.cpp index b79a0db4e..7f81a42bb 100644 --- a/Source/Engine/Render2D/FallbackFonts.cpp +++ b/Source/Engine/Render2D/FallbackFonts.cpp @@ -2,7 +2,7 @@ #include "FontManager.h" #include "Engine/Core/Math/Math.h" -FallbackFonts::FallbackFonts(const Array& fonts) +FontFallbackList::FontFallbackList(const Array& fonts) : ManagedScriptingObject(SpawnParams(Guid::New(), Font::TypeInitializer)), _fontAssets(fonts) { diff --git a/Source/Engine/Render2D/FallbackFonts.h b/Source/Engine/Render2D/FallbackFonts.h index 040ca8087..fb247e4e2 100644 --- a/Source/Engine/Render2D/FallbackFonts.h +++ b/Source/Engine/Render2D/FallbackFonts.h @@ -12,9 +12,9 @@ class FontAsset; /// /// Defines a list of fonts that can be used as a fallback, ordered by priority. /// -API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API FallbackFonts : public ManagedScriptingObject +API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API FontFallbackList : public ManagedScriptingObject { - DECLARE_SCRIPTING_TYPE_NO_SPAWN(FallbackFonts); + DECLARE_SCRIPTING_TYPE_NO_SPAWN(FontFallbackList); private: Array _fontAssets; @@ -23,18 +23,18 @@ private: public: /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The fallback font assets. - FallbackFonts(const Array& fonts); + FontFallbackList(const Array& fonts); /// - /// Initializes a new instance of the class, exposed for C#. + /// Initializes a new instance of the class, exposed for C#. /// /// The fallback font assets. /// The new instance. - API_FUNCTION() FORCE_INLINE static FallbackFonts* Create(const Array& fonts) { - return New(fonts); + API_FUNCTION() FORCE_INLINE static FontFallbackList* Create(const Array& fonts) { + return New(fonts); } /// diff --git a/Source/Engine/Render2D/Font.cpp b/Source/Engine/Render2D/Font.cpp index 4cf1f73a2..5d029ca5e 100644 --- a/Source/Engine/Render2D/Font.cpp +++ b/Source/Engine/Render2D/Font.cpp @@ -293,7 +293,7 @@ void Font::ProcessText(const StringView& text, Array& outputLines } } -void Font::ProcessText(FallbackFonts* fallbacks, const StringView& text, Array& outputLines, API_PARAM(Ref) const TextLayoutOptions& layout) +void Font::ProcessText(FontFallbackList* fallbacks, const StringView& text, Array& outputLines, API_PARAM(Ref) const TextLayoutOptions& layout) { const Array& fallbackFonts = fallbacks->GetFontList(GetSize()); float cursorX = 0; @@ -569,7 +569,7 @@ Float2 Font::MeasureText(const StringView& text, const TextLayoutOptions& layout return max; } -Float2 Font::MeasureText(FallbackFonts* fallbacks, const StringView& text, const TextLayoutOptions& layout) +Float2 Font::MeasureText(FontFallbackList* fallbacks, const StringView& text, const TextLayoutOptions& layout) { // Check if there is no need to do anything if (text.IsEmpty()) @@ -664,7 +664,7 @@ int32 Font::HitTestText(const StringView& text, const Float2& location, const Te return smallestIndex; } -int32 Font::HitTestText(FallbackFonts* fallbacks, const StringView& text, const Float2& location, const TextLayoutOptions& layout) +int32 Font::HitTestText(FontFallbackList* fallbacks, const StringView& text, const Float2& location, const TextLayoutOptions& layout) { // Check if there is no need to do anything if (text.Length() <= 0) @@ -818,7 +818,7 @@ Float2 Font::GetCharPosition(const StringView& text, int32 index, const TextLayo return rootOffset + Float2(lines.Last().Location.X + lines.Last().Size.X, static_cast((lines.Count() - 1) * baseLinesDistance)); } -Float2 Font::GetCharPosition(FallbackFonts* fallbacks, const StringView& text, int32 index, const TextLayoutOptions& layout) +Float2 Font::GetCharPosition(FontFallbackList* fallbacks, const StringView& text, int32 index, const TextLayoutOptions& layout) { // Check if there is no need to do anything if (text.IsEmpty()) diff --git a/Source/Engine/Render2D/Font.h b/Source/Engine/Render2D/Font.h index 110a8fe70..6bb2849e2 100644 --- a/Source/Engine/Render2D/Font.h +++ b/Source/Engine/Render2D/Font.h @@ -10,7 +10,7 @@ #include "TextLayoutOptions.h" class FontAsset; -class FallbackFonts; +class FontFallbackList; struct FontTextureAtlasSlot; struct BlockedTextLineCache; @@ -463,7 +463,7 @@ public: /// The input text. /// The layout properties. /// The output lines list. - void ProcessText(FallbackFonts* fallbacks, const StringView& text, Array& outputLines, API_PARAM(Ref) const TextLayoutOptions& layout); + void ProcessText(FontFallbackList* fallbacks, const StringView& text, Array& outputLines, API_PARAM(Ref) const TextLayoutOptions& layout); /// /// Processes text to get cached lines for rendering. @@ -471,7 +471,7 @@ public: /// The input text. /// The layout properties. /// The output lines list. - API_FUNCTION() Array ProcessText(FallbackFonts* fallbacks, const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout) + API_FUNCTION() Array ProcessText(FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout) { Array lines; ProcessText(fallbacks, text, lines, layout); @@ -485,7 +485,7 @@ public: /// The input text range (substring range of the input text parameter). /// The layout properties. /// The output lines list. - API_FUNCTION() Array ProcessText(FallbackFonts* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange, API_PARAM(Ref) const TextLayoutOptions& layout) + API_FUNCTION() Array ProcessText(FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange, API_PARAM(Ref) const TextLayoutOptions& layout) { Array lines; ProcessText(fallbacks, textRange.Substring(text), lines, layout); @@ -497,7 +497,7 @@ public: /// /// The input text. /// The output lines list. - API_FUNCTION() FORCE_INLINE Array ProcessText(FallbackFonts* fallbacks, const StringView& text) + API_FUNCTION() FORCE_INLINE Array ProcessText(FontFallbackList* fallbacks, const StringView& text) { return ProcessText(fallbacks, text, TextLayoutOptions()); } @@ -508,7 +508,7 @@ public: /// The input text. /// The input text range (substring range of the input text parameter). /// The output lines list. - API_FUNCTION() FORCE_INLINE Array ProcessText(FallbackFonts* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange) + API_FUNCTION() FORCE_INLINE Array ProcessText(FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange) { return ProcessText(fallbacks, textRange.Substring(text), TextLayoutOptions()); } @@ -560,7 +560,7 @@ public: /// The input text to test. /// The layout properties. /// The minimum size for that text and fot to render properly. - API_FUNCTION() Float2 MeasureText(FallbackFonts* fallbacks, const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout); + API_FUNCTION() Float2 MeasureText(FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout); /// /// Measures minimum size of the rectangle that will be needed to draw given text. @@ -569,7 +569,7 @@ public: /// The input text range (substring range of the input text parameter). /// The layout properties. /// The minimum size for that text and fot to render properly. - API_FUNCTION() Float2 MeasureText(FallbackFonts* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange, API_PARAM(Ref) const TextLayoutOptions& layout) + API_FUNCTION() Float2 MeasureText(FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange, API_PARAM(Ref) const TextLayoutOptions& layout) { return MeasureText(fallbacks, textRange.Substring(text), layout); } @@ -579,7 +579,7 @@ public: /// . /// The input text to test. /// The minimum size for that text and fot to render properly. - API_FUNCTION() FORCE_INLINE Float2 MeasureText(FallbackFonts* fallbacks, const StringView& text) + API_FUNCTION() FORCE_INLINE Float2 MeasureText(FontFallbackList* fallbacks, const StringView& text) { return MeasureText(fallbacks, text, TextLayoutOptions()); } @@ -590,7 +590,7 @@ public: /// The input text to test. /// The input text range (substring range of the input text parameter). /// The minimum size for that text and fot to render properly. - API_FUNCTION() FORCE_INLINE Float2 MeasureText(FallbackFonts* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange) + API_FUNCTION() FORCE_INLINE Float2 MeasureText(FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange) { return MeasureText(fallbacks, textRange.Substring(text), TextLayoutOptions()); } @@ -647,7 +647,7 @@ public: /// The input location to test. /// The text layout properties. /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). - API_FUNCTION() int32 HitTestText(FallbackFonts* fallbacks, const StringView& text, const Float2& location, API_PARAM(Ref) const TextLayoutOptions& layout); + API_FUNCTION() int32 HitTestText(FontFallbackList* fallbacks, const StringView& text, const Float2& location, API_PARAM(Ref) const TextLayoutOptions& layout); /// /// Calculates hit character index at given location. @@ -657,7 +657,7 @@ public: /// The input location to test. /// The text layout properties. /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). - API_FUNCTION() int32 HitTestText(FallbackFonts* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Float2& location, API_PARAM(Ref) const TextLayoutOptions& layout) + API_FUNCTION() int32 HitTestText(FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Float2& location, API_PARAM(Ref) const TextLayoutOptions& layout) { return HitTestText(fallbacks, textRange.Substring(text), location, layout); } @@ -668,7 +668,7 @@ public: /// The input text to test. /// The input location to test. /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). - API_FUNCTION() FORCE_INLINE int32 HitTestText(FallbackFonts* fallbacks, const StringView& text, const Float2& location) + API_FUNCTION() FORCE_INLINE int32 HitTestText(FontFallbackList* fallbacks, const StringView& text, const Float2& location) { return HitTestText(fallbacks, text, location, TextLayoutOptions()); } @@ -680,7 +680,7 @@ public: /// The input text range (substring range of the input text parameter). /// The input location to test. /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). - API_FUNCTION() FORCE_INLINE int32 HitTestText(FallbackFonts* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Float2& location) + API_FUNCTION() FORCE_INLINE int32 HitTestText(FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Float2& location) { return HitTestText(fallbacks, textRange.Substring(text), location, TextLayoutOptions()); } @@ -737,7 +737,7 @@ public: /// The text position to get coordinates of. /// The text layout properties. /// The character position (upper left corner which can be used for a caret position). - API_FUNCTION() Float2 GetCharPosition(FallbackFonts* fallbacks, const StringView& text, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout); + API_FUNCTION() Float2 GetCharPosition(FontFallbackList* fallbacks, const StringView& text, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout); /// /// Calculates character position for given text and character index. @@ -747,7 +747,7 @@ public: /// The text position to get coordinates of. /// The text layout properties. /// The character position (upper left corner which can be used for a caret position). - API_FUNCTION() Float2 GetCharPosition(FallbackFonts* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout) + API_FUNCTION() Float2 GetCharPosition(FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout) { return GetCharPosition(fallbacks, textRange.Substring(text), index, layout); } @@ -758,7 +758,7 @@ public: /// The input text to test. /// The text position to get coordinates of. /// The character position (upper left corner which can be used for a caret position). - API_FUNCTION() FORCE_INLINE Float2 GetCharPosition(FallbackFonts* fallbacks, const StringView& text, int32 index) + API_FUNCTION() FORCE_INLINE Float2 GetCharPosition(FontFallbackList* fallbacks, const StringView& text, int32 index) { return GetCharPosition(fallbacks, text, index, TextLayoutOptions()); } @@ -770,7 +770,7 @@ public: /// The input text range (substring range of the input text parameter). /// The text position to get coordinates of. /// The character position (upper left corner which can be used for a caret position). - API_FUNCTION() FORCE_INLINE Float2 GetCharPosition(FallbackFonts* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange, int32 index) + API_FUNCTION() FORCE_INLINE Float2 GetCharPosition(FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange, int32 index) { return GetCharPosition(fallbacks, textRange.Substring(text), index, TextLayoutOptions()); } diff --git a/Source/Engine/Render2D/Render2D.cpp b/Source/Engine/Render2D/Render2D.cpp index 4e0cacb44..351bb3700 100644 --- a/Source/Engine/Render2D/Render2D.cpp +++ b/Source/Engine/Render2D/Render2D.cpp @@ -1144,7 +1144,7 @@ void DrawBatch(int32 startIndex, int32 count) Context->DrawIndexed(countIb, 0, d.StartIB); } -void Render2D::DrawText(Font* font, const StringView& text, const Color& color, const Float2& location, MaterialBase* customMaterial) +void Render2D::DrawTextInternal(Font* font, const StringView& text, const Color& color, const Float2& location, MaterialBase* customMaterial) { RENDER2D_CHECK_RENDERING_STATE; @@ -1252,12 +1252,12 @@ void Render2D::DrawText(Font* font, const StringView& text, const Color& color, } } -void Render2D::DrawText(Font* font, const StringView& text, const TextRange& textRange, const Color& color, const Float2& location, MaterialBase* customMaterial) +void Render2D::DrawTextInternal(Font* font, const StringView& text, const TextRange& textRange, const Color& color, const Float2& location, MaterialBase* customMaterial) { - DrawText(font, textRange.Substring(text), color, location, customMaterial); + DrawTextInternal(font, textRange.Substring(text), color, location, customMaterial); } -void Render2D::DrawText(Font* font, const StringView& text, const Color& color, const TextLayoutOptions& layout, MaterialBase* customMaterial) +void Render2D::DrawTextInternal(Font* font, const StringView& text, const Color& color, const TextLayoutOptions& layout, MaterialBase* customMaterial) { RENDER2D_CHECK_RENDERING_STATE; @@ -1365,12 +1365,12 @@ void Render2D::DrawText(Font* font, const StringView& text, const Color& color, } } -void Render2D::DrawText(Font* font, const StringView& text, const TextRange& textRange, const Color& color, const TextLayoutOptions& layout, MaterialBase* customMaterial) +void Render2D::DrawTextInternal(Font* font, const StringView& text, const TextRange& textRange, const Color& color, const TextLayoutOptions& layout, MaterialBase* customMaterial) { - DrawText(font, textRange.Substring(text), color, layout, customMaterial); + DrawTextInternal(font, textRange.Substring(text), color, layout, customMaterial); } -void Render2D::DrawText(Font* font, FallbackFonts* fallbacks, const StringView& text, const Color& color, const Float2& location, MaterialBase* customMaterial) +void Render2D::DrawTextInternal(Font* font, FontFallbackList* fallbacks, const StringView& text, const Color& color, const Float2& location, MaterialBase* customMaterial) { RENDER2D_CHECK_RENDERING_STATE; @@ -1549,12 +1549,12 @@ void Render2D::DrawText(Font* font, FallbackFonts* fallbacks, const StringView& } } -void Render2D::DrawText(Font* font, FallbackFonts* fallbacks, const StringView& text, const TextRange& textRange, const Color& color, const Float2& location, MaterialBase* customMaterial) +void Render2D::DrawTextInternal(Font* font, FontFallbackList* fallbacks, const StringView& text, const TextRange& textRange, const Color& color, const Float2& location, MaterialBase* customMaterial) { - DrawText(font, fallbacks, textRange.Substring(text), color, location, customMaterial); + DrawTextInternal(font, fallbacks, textRange.Substring(text), color, location, customMaterial); } -void Render2D::DrawText(Font* font, FallbackFonts* fallbacks, const StringView& text, const Color& color, const TextLayoutOptions& layout, MaterialBase* customMaterial) +void Render2D::DrawTextInternal(Font* font, FontFallbackList* fallbacks, const StringView& text, const Color& color, const TextLayoutOptions& layout, MaterialBase* customMaterial) { RENDER2D_CHECK_RENDERING_STATE; @@ -1673,9 +1673,9 @@ void Render2D::DrawText(Font* font, FallbackFonts* fallbacks, const StringView& } } -void Render2D::DrawText(Font* font, FallbackFonts* fallbacks, const StringView& text, const TextRange& textRange, const Color& color, const TextLayoutOptions& layout, MaterialBase* customMaterial) +void Render2D::DrawTextInternal(Font* font, FontFallbackList* fallbacks, const StringView& text, const TextRange& textRange, const Color& color, const TextLayoutOptions& layout, MaterialBase* customMaterial) { - DrawText(font, fallbacks, textRange.Substring(text), color, layout, customMaterial); + DrawTextInternal(font, fallbacks, textRange.Substring(text), color, layout, customMaterial); } FORCE_INLINE bool NeedAlphaWithTint(const Color& color) diff --git a/Source/Engine/Render2D/Render2D.h b/Source/Engine/Render2D/Render2D.h index c99d38104..fa59b1d79 100644 --- a/Source/Engine/Render2D/Render2D.h +++ b/Source/Engine/Render2D/Render2D.h @@ -15,7 +15,7 @@ struct Matrix3x3; struct Viewport; struct TextRange; class Font; -class FallbackFonts; +class FontFallbackList; class GPUPipelineState; class GPUTexture; class GPUTextureView; @@ -54,6 +54,9 @@ API_CLASS(Static) class FLAXENGINE_API Render2D }; public: + API_FIELD() static bool EnableFontFallback; + API_FIELD() static FontFallbackList* FallbackFonts; + /// /// Checks if interface is during rendering phrase (Draw calls may be performed without failing). /// @@ -175,17 +178,17 @@ public: public: /// - /// Draws a text. + /// Draws a text, with font fallbacking disabled. /// /// The font to use. /// The text to render. /// The text color. /// The text location. /// The custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture. - API_FUNCTION() static void DrawText(Font* font, const StringView& text, const Color& color, const Float2& location, MaterialBase* customMaterial = nullptr); + API_FUNCTION() static void DrawTextInternal(Font* font, const StringView& text, const Color& color, const Float2& location, MaterialBase* customMaterial = nullptr); /// - /// Draws a text. + /// Draws a text, with font fallbacking disabled. /// /// The font to use. /// The text to render. @@ -193,20 +196,20 @@ public: /// The text color. /// The text location. /// The custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture. - API_FUNCTION() static void DrawText(Font* font, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Color& color, const Float2& location, MaterialBase* customMaterial = nullptr); + API_FUNCTION() static void DrawTextInternal(Font* font, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Color& color, const Float2& location, MaterialBase* customMaterial = nullptr); /// - /// Draws a text with formatting. + /// Draws a text with formatting, with font fallbacking disabled. /// /// The font to use. /// The text to render. /// The text color. /// The text layout properties. /// The custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture. - API_FUNCTION() static void DrawText(Font* font, const StringView& text, const Color& color, API_PARAM(Ref) const TextLayoutOptions& layout, MaterialBase* customMaterial = nullptr); + API_FUNCTION() static void DrawTextInternal(Font* font, const StringView& text, const Color& color, API_PARAM(Ref) const TextLayoutOptions& layout, MaterialBase* customMaterial = nullptr); /// - /// Draws a text with formatting. + /// Draws a text with formatting, with font fallbacking disabled. /// /// The font to use. /// The text to render. @@ -214,10 +217,10 @@ public: /// The text color. /// The text layout properties. /// The custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture. - API_FUNCTION() static void DrawText(Font* font, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Color& color, API_PARAM(Ref) const TextLayoutOptions& layout, MaterialBase* customMaterial = nullptr); + API_FUNCTION() static void DrawTextInternal(Font* font, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Color& color, API_PARAM(Ref) const TextLayoutOptions& layout, MaterialBase* customMaterial = nullptr); /// - /// Draws a text. + /// Draws a text, using custom fallback options. /// /// The fonts to use, ordered by priority. /// The text to render. @@ -225,20 +228,20 @@ public: /// The text color. /// The text location. /// The custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture. - API_FUNCTION() static void DrawText(Font* font, FallbackFonts* fallbacks, const StringView& text, const Color& color, const Float2& location, MaterialBase* customMaterial = nullptr); + API_FUNCTION() static void DrawTextInternal(Font* font, FontFallbackList* fallbacks, const StringView& text, const Color& color, const Float2& location, MaterialBase* customMaterial = nullptr); /// - /// Draws a text with formatting. + /// Draws a text with formatting, using custom fallback options. /// /// The fonts to use, ordered by priority. /// The text to render. /// The text color. /// The text layout properties. /// The custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture. - API_FUNCTION() static void DrawText(Font* font, FallbackFonts* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Color& color, const Float2& location, MaterialBase* customMaterial = nullptr); + API_FUNCTION() static void DrawTextInternal(Font* font, FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Color& color, const Float2& location, MaterialBase* customMaterial = nullptr); /// - /// Draws a text with formatting. + /// Draws a text with formatting, using custom fallback options. /// /// The fonts to use, ordered by priority. /// The text to render. @@ -246,10 +249,10 @@ public: /// The text color. /// The text layout properties. /// The custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture. - API_FUNCTION() static void DrawText(Font* font, FallbackFonts* fallbacks, const StringView& text, const Color& color, API_PARAM(Ref) const TextLayoutOptions& layout, MaterialBase* customMaterial = nullptr); + API_FUNCTION() static void DrawTextInternal(Font* font, FontFallbackList* fallbacks, const StringView& text, const Color& color, API_PARAM(Ref) const TextLayoutOptions& layout, MaterialBase* customMaterial = nullptr); /// - /// Draws a text with formatting. + /// Draws a text with formatting, using custom fallback options. /// /// The fonts to use, ordered by priority. /// The text to render. @@ -257,7 +260,51 @@ public: /// The text color. /// The text layout properties. /// The custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture. - API_FUNCTION() static void DrawText(Font* font, FallbackFonts* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Color& color, API_PARAM(Ref) const TextLayoutOptions& layout, MaterialBase* customMaterial = nullptr); + API_FUNCTION() static void DrawTextInternal(Font* font, FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Color& color, API_PARAM(Ref) const TextLayoutOptions& layout, MaterialBase* customMaterial = nullptr); + + /// + /// Draws a text, follows the fallback settings defined in . + /// + /// The font to use. + /// The text to render. + /// The text color. + /// The text location. + /// The custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture. + API_FUNCTION() FORCE_INLINE static void DrawText(Font* font, const StringView& text, const Color& color, const Float2& location, MaterialBase* customMaterial = nullptr) { + if (EnableFontFallback && FallbackFonts) { + DrawTextInternal(font, FallbackFonts, text, color, location, customMaterial); + } + else { + DrawTextInternal(font, text, color, location, customMaterial); + } + } + + API_FUNCTION() FORCE_INLINE static void DrawText(Font* font, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Color& color, const Float2& location, MaterialBase* customMaterial = nullptr) { + if (EnableFontFallback && FallbackFonts) { + DrawTextInternal(font, FallbackFonts, text, textRange, color, location, customMaterial); + } + else { + DrawTextInternal(font, text, textRange, color, location, customMaterial); + } + } + + API_FUNCTION() FORCE_INLINE static void DrawText(Font* font, const StringView& text, const Color& color, API_PARAM(Ref) const TextLayoutOptions& layout, MaterialBase* customMaterial = nullptr) { + if (EnableFontFallback && FallbackFonts) { + DrawTextInternal(font, FallbackFonts, text, color, layout, customMaterial); + } + else { + DrawTextInternal(font, text, color, layout, customMaterial); + } + } + + API_FUNCTION() FORCE_INLINE static void DrawText(Font* font, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Color& color, API_PARAM(Ref) const TextLayoutOptions& layout, MaterialBase* customMaterial = nullptr) { + if (EnableFontFallback && FallbackFonts) { + DrawTextInternal(font, FallbackFonts, text, textRange, color, layout, customMaterial); + } + else { + DrawTextInternal(font, text, textRange, color, layout, customMaterial); + } + } /// /// Fills a rectangle area. From 2f019d4264fc7dfd57777297a8b592ab4064bf86 Mon Sep 17 00:00:00 2001 From: ExMatics HydrogenC <33123710+HydrogenC@users.noreply.github.com> Date: Sat, 2 Dec 2023 17:51:32 +0800 Subject: [PATCH 047/139] Fix unintended unname --- Source/Engine/Platform/Win32/IncludeWindowsHeaders.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Platform/Win32/IncludeWindowsHeaders.h b/Source/Engine/Platform/Win32/IncludeWindowsHeaders.h index 66ed97a20..3f2b8d36e 100644 --- a/Source/Engine/Platform/Win32/IncludeWindowsHeaders.h +++ b/Source/Engine/Platform/Win32/IncludeWindowsHeaders.h @@ -57,7 +57,7 @@ #undef CreateWindow #undef CreateProcess #undef SetWindowText -#undef DrawTextInternal +#undef DrawText #undef CreateFont #undef IsMinimized #undef IsMaximized From 23b71e7d3e825a3cb7d44300aa4e65c4fb684405 Mon Sep 17 00:00:00 2001 From: ExMatics HydrogenC <33123710+HydrogenC@users.noreply.github.com> Date: Sun, 3 Dec 2023 15:11:47 +0800 Subject: [PATCH 048/139] Make font fallbacking the default option --- Source/Editor/Content/GUI/ContentView.cs | 2 +- Source/Editor/Content/Items/ContentItem.cs | 2 +- Source/Editor/Content/Tree/ContentTreeNode.cs | 4 +- .../Dedicated/MeshReferenceEditor.cs | 4 +- .../CustomEditors/Dedicated/ScriptsEditor.cs | 4 +- .../CustomEditors/Dedicated/SplineEditor.cs | 2 +- .../Dedicated/UIControlEditor.cs | 2 +- .../Editors/ActorTransformEditor.cs | 2 +- .../Editors/FlaxObjectRefEditor.cs | 4 +- .../Editor/CustomEditors/Editors/TagEditor.cs | 2 +- .../CustomEditors/Editors/TypeEditor.cs | 4 +- Source/Editor/GUI/AssetPicker.cs | 6 +- Source/Editor/GUI/ComboBox.cs | 2 +- .../GUI/ContextMenu/ContextMenuButton.cs | 8 +- Source/Editor/GUI/CurveEditor.cs | 2 +- .../Editor/GUI/Dialogs/ColorPickerDialog.cs | 22 +- Source/Editor/GUI/Docking/DockPanelProxy.cs | 4 +- Source/Editor/GUI/Docking/DockWindow.cs | 2 +- Source/Editor/GUI/ItemsListContextMenu.cs | 6 +- Source/Editor/GUI/MainMenuButton.cs | 4 +- Source/Editor/GUI/NavigationButton.cs | 4 +- Source/Editor/GUI/Row.cs | 4 +- Source/Editor/GUI/StatusBar.cs | 2 +- Source/Editor/GUI/StyleValueEditor.cs | 2 +- Source/Editor/GUI/Table.cs | 2 +- Source/Editor/GUI/Tabs/Tabs.cs | 2 +- Source/Editor/GUI/Timeline/GUI/Background.cs | 2 +- Source/Editor/GUI/Timeline/Track.cs | 2 +- .../Editor/GUI/Timeline/Tracks/MemberTrack.cs | 2 +- Source/Editor/GUI/ToolStripButton.cs | 4 +- Source/Editor/GUI/Tree/TreeNode.cs | 4 +- Source/Editor/Options/OptionsModule.cs | 7 +- Source/Editor/SceneGraph/GUI/ActorTreeNode.cs | 4 +- .../Archetypes/Animation.StateMachine.cs | 8 +- Source/Editor/Surface/Archetypes/Animation.cs | 2 +- .../Editor/Surface/Archetypes/BehaviorTree.cs | 6 +- .../Surface/Archetypes/ParticleModules.cs | 2 +- Source/Editor/Surface/Archetypes/Particles.cs | 4 +- .../Surface/ContextMenu/VisjectCMItem.cs | 20 +- Source/Editor/Surface/Elements/InputBox.cs | 6 +- Source/Editor/Surface/Elements/OutputBox.cs | 2 +- Source/Editor/Surface/Elements/TextView.cs | 2 +- Source/Editor/Surface/SurfaceComment.cs | 2 +- Source/Editor/Surface/SurfaceNode.cs | 8 +- Source/Editor/Tools/Foliage/FoliageTab.cs | 2 +- Source/Editor/Tools/Terrain/CarveTab.cs | 2 +- Source/Editor/Viewport/EditorViewport.cs | 12 +- .../Viewport/Previews/AnimationPreview.cs | 4 +- .../Editor/Viewport/Previews/ModelPreview.cs | 4 +- .../Previews/ParticleSystemPreview.cs | 2 +- .../Viewport/Previews/SkinnedModelPreview.cs | 4 +- .../Viewport/Previews/TexturePreview.cs | 2 +- .../Viewport/Widgets/ViewportWidgetButton.cs | 4 +- Source/Editor/Windows/AboutDialog.cs | 2 +- .../Windows/Assets/AnimationGraphWindow.cs | 2 +- .../Editor/Windows/Assets/AnimationWindow.cs | 2 +- Source/Editor/Windows/Assets/ModelWindow.cs | 4 +- .../Windows/Assets/SkinnedModelWindow.cs | 4 +- Source/Editor/Windows/ContentWindow.Search.cs | 2 +- Source/Editor/Windows/DebugLogWindow.cs | 4 +- Source/Editor/Windows/GameWindow.cs | 8 +- Source/Editor/Windows/Profiler/SingleChart.cs | 4 +- Source/Editor/Windows/Profiler/Timeline.cs | 6 +- Source/Editor/Windows/SceneTreeWindow.cs | 2 +- Source/Editor/Windows/SplashScreen.cpp | 2 +- Source/Editor/Windows/ToolboxWindow.cs | 4 +- Source/Engine/Core/Config/GameSettings.h | 9 - Source/Engine/Core/Config/GraphicsSettings.h | 14 + Source/Engine/Graphics/Graphics.cpp | 4 + Source/Engine/Render2D/FallbackTextUtils.cs | 268 --------- Source/Engine/Render2D/Font.cpp | 24 +- Source/Engine/Render2D/Font.h | 567 ++++++++++++------ Source/Engine/Render2D/Render2D.cpp | 2 + Source/Engine/Render2D/Render2D.cs | 4 +- Source/Engine/Render2D/Render2D.h | 6 +- Source/Engine/UI/GUI/Common/Button.cs | 2 +- Source/Engine/UI/GUI/Common/Dropdown.cs | 4 +- Source/Engine/UI/GUI/Common/Label.cs | 4 +- .../Engine/UI/GUI/Common/RichTextBoxBase.cs | 8 +- Source/Engine/UI/GUI/Common/TextBox.cs | 18 +- Source/Engine/UI/GUI/Panels/DropPanel.cs | 2 +- Source/Engine/UI/GUI/Style.cs | 6 - Source/Engine/UI/GUI/Tooltip.cs | 2 +- 83 files changed, 598 insertions(+), 619 deletions(-) delete mode 100644 Source/Engine/Render2D/FallbackTextUtils.cs diff --git a/Source/Editor/Content/GUI/ContentView.cs b/Source/Editor/Content/GUI/ContentView.cs index 1091c9cef..259be104b 100644 --- a/Source/Editor/Content/GUI/ContentView.cs +++ b/Source/Editor/Content/GUI/ContentView.cs @@ -598,7 +598,7 @@ namespace FlaxEditor.Content.GUI // Check if it's an empty thing if (_items.Count == 0) { - FallbackTextUtils.DrawText(style.FontSmall, IsSearching ? "No results" : "Empty", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); + Render2D.DrawText(style.FontSmall, IsSearching ? "No results" : "Empty", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); } } diff --git a/Source/Editor/Content/Items/ContentItem.cs b/Source/Editor/Content/Items/ContentItem.cs index 0842dfff7..604caa704 100644 --- a/Source/Editor/Content/Items/ContentItem.cs +++ b/Source/Editor/Content/Items/ContentItem.cs @@ -745,7 +745,7 @@ namespace FlaxEditor.Content // Draw short name Render2D.PushClip(ref textRect); - FallbackTextUtils.DrawText(style.FontMedium, ShowFileExtension || view.ShowFileExtensions ? FileName : ShortName, textRect, style.Foreground, nameAlignment, TextAlignment.Center, TextWrapping.WrapWords, 1f, 0.95f); + Render2D.DrawText(style.FontMedium, ShowFileExtension || view.ShowFileExtensions ? FileName : ShortName, textRect, style.Foreground, nameAlignment, TextAlignment.Center, TextWrapping.WrapWords, 1f, 0.95f); Render2D.PopClip(); } diff --git a/Source/Editor/Content/Tree/ContentTreeNode.cs b/Source/Editor/Content/Tree/ContentTreeNode.cs index 2f378e7f5..e2cd1e771 100644 --- a/Source/Editor/Content/Tree/ContentTreeNode.cs +++ b/Source/Editor/Content/Tree/ContentTreeNode.cs @@ -150,8 +150,8 @@ namespace FlaxEditor.Content var textRect = TextRect; for (int i = 0; i < ranges.Length; i++) { - var start = FallbackTextUtils.GetCharPosition(font, text, ranges[i].StartIndex); - var end = FallbackTextUtils.GetCharPosition(font, text, ranges[i].EndIndex); + var start = font.GetCharPosition(text, ranges[i].StartIndex); + var end = font.GetCharPosition(text, ranges[i].EndIndex); _highlights.Add(new Rectangle(start.X + textRect.X, textRect.Y, end.X - start.X, textRect.Height)); } isThisVisible = true; diff --git a/Source/Editor/CustomEditors/Dedicated/MeshReferenceEditor.cs b/Source/Editor/CustomEditors/Dedicated/MeshReferenceEditor.cs index 66c1c791f..4099e5aee 100644 --- a/Source/Editor/CustomEditors/Dedicated/MeshReferenceEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/MeshReferenceEditor.cs @@ -109,7 +109,7 @@ namespace FlaxEditor.CustomEditors.Dedicated { // Draw name Render2D.PushClip(nameRect); - FallbackTextUtils.DrawText(style.FontMedium, _valueName, nameRect, isEnabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center); + Render2D.DrawText(style.FontMedium, _valueName, nameRect, isEnabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center); Render2D.PopClip(); // Draw deselect button @@ -118,7 +118,7 @@ namespace FlaxEditor.CustomEditors.Dedicated else { // Draw info - FallbackTextUtils.DrawText(style.FontMedium, "-", nameRect, isEnabled ? Color.OrangeRed : Color.DarkOrange, TextAlignment.Near, TextAlignment.Center); + Render2D.DrawText(style.FontMedium, "-", nameRect, isEnabled ? Color.OrangeRed : Color.DarkOrange, TextAlignment.Near, TextAlignment.Center); } // Draw picker button diff --git a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs index 017fd7655..d7bfbbad7 100644 --- a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs @@ -42,7 +42,7 @@ namespace FlaxEditor.CustomEditors.Dedicated // Add script button var buttonText = "Add script"; - var textSize = FallbackTextUtils.MeasureText(Style.Current.FontMedium, buttonText); + var textSize = Style.Current.FontMedium.MeasureText(buttonText); float addScriptButtonWidth = (textSize.X < 60.0f) ? 60.0f : textSize.X + 4; var buttonHeight = (textSize.Y < 18) ? 18 : textSize.Y + 4; _addScriptsButton = new Button @@ -86,7 +86,7 @@ namespace FlaxEditor.CustomEditors.Dedicated var size = Size; // Info - FallbackTextUtils.DrawText(style.FontSmall, "Drag scripts here", new Rectangle(2, _addScriptsButton.Height + 4, size.X - 4, size.Y - 4 - 20), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); + Render2D.DrawText(style.FontSmall, "Drag scripts here", new Rectangle(2, _addScriptsButton.Height + 4, size.X - 4, size.Y - 4 - 20), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); // Check if drag is over if (IsDragOver && _dragHandlers != null && _dragHandlers.HasValidDrag) diff --git a/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs b/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs index fa4379db1..7b9b65c5c 100644 --- a/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs @@ -226,7 +226,7 @@ namespace FlaxEditor.CustomEditors.Dedicated if (!enabled) color *= 0.6f; Render2D.DrawSprite(tab._customIcon, iconRect, color); - FallbackTextUtils.DrawText(style.FontMedium, tab._customText, new Rectangle(0, iconSize, size.X, textHeight), color, TextAlignment.Center, TextAlignment.Center); + Render2D.DrawText(style.FontMedium, tab._customText, new Rectangle(0, iconSize, size.X, textHeight), color, TextAlignment.Center, TextAlignment.Center); } } diff --git a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs index 4efbee259..296560507 100644 --- a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs @@ -423,7 +423,7 @@ namespace FlaxEditor.CustomEditors.Dedicated // Set control type button var space = layout.Space(20); var buttonText = "Set Type"; - var textSize = FallbackTextUtils.MeasureText(FlaxEngine.GUI.Style.Current.FontMedium, buttonText); + var textSize = FlaxEngine.GUI.Style.Current.FontMedium.MeasureText(buttonText); float setTypeButtonWidth = (textSize.X < 60.0f) ? 60.0f : textSize.X + 4; var setTypeButton = new Button { diff --git a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs index d8d16027b..4c153e759 100644 --- a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs @@ -100,7 +100,7 @@ namespace FlaxEditor.CustomEditors.Editors _linkButton.Clicked += ToggleLink; ToggleEnabled(); SetLinkStyle(); - var textSize = FallbackTextUtils.MeasureText(FlaxEngine.GUI.Style.Current.FontMedium, LinkedLabel.Text.Value); + var textSize = FlaxEngine.GUI.Style.Current.FontMedium.MeasureText(LinkedLabel.Text.Value); _linkButton.LocalX += textSize.X + 10; LinkedLabel.SetupContextMenu += (label, menu, editor) => { diff --git a/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs b/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs index 2f64ea159..731da3817 100644 --- a/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs +++ b/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs @@ -199,7 +199,7 @@ namespace FlaxEditor.CustomEditors.Editors { // Draw name Render2D.PushClip(nameRect); - FallbackTextUtils.DrawText(style.FontMedium, _valueName, nameRect, isEnabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center); + Render2D.DrawText(style.FontMedium, _valueName, nameRect, isEnabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center); Render2D.PopClip(); // Draw deselect button @@ -208,7 +208,7 @@ namespace FlaxEditor.CustomEditors.Editors else { // Draw info - FallbackTextUtils.DrawText(style.FontMedium, "-", nameRect, isEnabled ? Color.OrangeRed : Color.DarkOrange, TextAlignment.Near, TextAlignment.Center); + Render2D.DrawText(style.FontMedium, "-", nameRect, isEnabled ? Color.OrangeRed : Color.DarkOrange, TextAlignment.Near, TextAlignment.Center); } // Draw picker button diff --git a/Source/Editor/CustomEditors/Editors/TagEditor.cs b/Source/Editor/CustomEditors/Editors/TagEditor.cs index bbece7e04..dbd5d124c 100644 --- a/Source/Editor/CustomEditors/Editors/TagEditor.cs +++ b/Source/Editor/CustomEditors/Editors/TagEditor.cs @@ -631,7 +631,7 @@ namespace FlaxEditor.CustomEditors.Editors TooltipText = "Edit...", Parent = _label, }; - var textSize = FallbackTextUtils.MeasureText(FlaxEngine.GUI.Style.Current.FontMedium, buttonText); + var textSize = FlaxEngine.GUI.Style.Current.FontMedium.MeasureText(buttonText); if (textSize.Y > button.Width) button.Width = textSize.Y + 2; diff --git a/Source/Editor/CustomEditors/Editors/TypeEditor.cs b/Source/Editor/CustomEditors/Editors/TypeEditor.cs index 4d0b90383..38800a738 100644 --- a/Source/Editor/CustomEditors/Editors/TypeEditor.cs +++ b/Source/Editor/CustomEditors/Editors/TypeEditor.cs @@ -160,13 +160,13 @@ namespace FlaxEditor.CustomEditors.Editors // Draw name Render2D.PushClip(nameRect); - FallbackTextUtils.DrawText(style.FontMedium, _valueName, nameRect, style.Foreground, TextAlignment.Near, TextAlignment.Center); + Render2D.DrawText(style.FontMedium, _valueName, nameRect, style.Foreground, TextAlignment.Near, TextAlignment.Center); Render2D.PopClip(); } else { // Draw info - FallbackTextUtils.DrawText(style.FontMedium, "-", nameRect, Color.OrangeRed, TextAlignment.Near, TextAlignment.Center); + Render2D.DrawText(style.FontMedium, "-", nameRect, Color.OrangeRed, TextAlignment.Near, TextAlignment.Center); } // Draw picker button diff --git a/Source/Editor/GUI/AssetPicker.cs b/Source/Editor/GUI/AssetPicker.cs index 2abe5db38..84f58daf1 100644 --- a/Source/Editor/GUI/AssetPicker.cs +++ b/Source/Editor/GUI/AssetPicker.cs @@ -139,7 +139,7 @@ namespace FlaxEditor.GUI float sizeForTextLeft = Width - button1Rect.Right; if (sizeForTextLeft > 30) { - FallbackTextUtils.DrawText( + Render2D.DrawText( style.FontSmall, Validator.SelectedItem.ShortName, new Rectangle(button1Rect.Right + 2, 0, sizeForTextLeft, ButtonsSize), @@ -161,7 +161,7 @@ namespace FlaxEditor.GUI var name = Validator.SelectedAsset.GetType().Name; if (Validator.SelectedAsset.IsVirtual) name += " (virtual)"; - FallbackTextUtils.DrawText( + Render2D.DrawText( style.FontSmall, name, new Rectangle(button1Rect.Right + 2, 0, sizeForTextLeft, ButtonsSize), @@ -174,7 +174,7 @@ namespace FlaxEditor.GUI { // No element selected Render2D.FillRectangle(iconRect, style.BackgroundNormal); - FallbackTextUtils.DrawText(style.FontMedium, "No asset\nselected", iconRect, Color.Orange, TextAlignment.Center, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, Height / DefaultIconSize); + Render2D.DrawText(style.FontMedium, "No asset\nselected", iconRect, Color.Orange, TextAlignment.Center, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, Height / DefaultIconSize); } // Check if drag is over diff --git a/Source/Editor/GUI/ComboBox.cs b/Source/Editor/GUI/ComboBox.cs index 8f3a7cb4b..0417cc7e3 100644 --- a/Source/Editor/GUI/ComboBox.cs +++ b/Source/Editor/GUI/ComboBox.cs @@ -554,7 +554,7 @@ namespace FlaxEditor.GUI var textRect = new Rectangle(margin, 0, clientRect.Width - boxSize - 2.0f * margin, clientRect.Height); Render2D.PushClip(textRect); var textColor = TextColor; - FallbackTextUtils.DrawText(Font.GetFont(), text, textRect, enabled ? textColor : textColor * 0.5f, TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, textScale); + Render2D.DrawText(Font.GetFont(), text, textRect, enabled ? textColor : textColor * 0.5f, TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, textScale); Render2D.PopClip(); } diff --git a/Source/Editor/GUI/ContextMenu/ContextMenuButton.cs b/Source/Editor/GUI/ContextMenu/ContextMenuButton.cs index 035e997e9..e371f7c4b 100644 --- a/Source/Editor/GUI/ContextMenu/ContextMenuButton.cs +++ b/Source/Editor/GUI/ContextMenu/ContextMenuButton.cs @@ -128,12 +128,12 @@ namespace FlaxEditor.GUI.ContextMenu base.Draw(); // Draw text - FallbackTextUtils.DrawText(style.FontMedium, Text, textRect, textColor, TextAlignment.Near, TextAlignment.Center); + Render2D.DrawText(style.FontMedium, Text, textRect, textColor, TextAlignment.Near, TextAlignment.Center); if (!string.IsNullOrEmpty(ShortKeys)) { // Draw short keys - FallbackTextUtils.DrawText(style.FontMedium, ShortKeys, textRect, textColor, TextAlignment.Far, TextAlignment.Center); + Render2D.DrawText(style.FontMedium, ShortKeys, textRect, textColor, TextAlignment.Far, TextAlignment.Center); } // Draw icon @@ -235,9 +235,9 @@ namespace FlaxEditor.GUI.ContextMenu float width = 20; if (style.FontMedium) { - width += FallbackTextUtils.MeasureText(style.FontMedium, Text).X; + width += style.FontMedium.MeasureText(Text).X; if (!string.IsNullOrEmpty(ShortKeys)) - width += 40 + FallbackTextUtils.MeasureText(style.FontMedium, ShortKeys).X; + width += 40 + style.FontMedium.MeasureText(ShortKeys).X; } return Mathf.Max(width, base.MinimumWidth); diff --git a/Source/Editor/GUI/CurveEditor.cs b/Source/Editor/GUI/CurveEditor.cs index f4839acd5..22deec120 100644 --- a/Source/Editor/GUI/CurveEditor.cs +++ b/Source/Editor/GUI/CurveEditor.cs @@ -832,7 +832,7 @@ namespace FlaxEditor.GUI 50, LabelsSize ); - FallbackTextUtils.DrawText(_labelsFont, label, labelRect, _labelsColor.AlphaMultiplied(strength), TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, 0.7f); + Render2D.DrawText(_labelsFont, label, labelRect, _labelsColor.AlphaMultiplied(strength), TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, 0.7f); } } } diff --git a/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs b/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs index 8b1881faf..27878a763 100644 --- a/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs +++ b/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs @@ -281,33 +281,33 @@ namespace FlaxEditor.GUI.Dialogs // RGBA var rgbaR = new Rectangle(_cRed.Left - ChannelTextWidth, _cRed.Y, 10000, _cRed.Height); - FallbackTextUtils.DrawText(style.FontMedium, "R", rgbaR, textColor, TextAlignment.Near, TextAlignment.Center); + Render2D.DrawText(style.FontMedium, "R", rgbaR, textColor, TextAlignment.Near, TextAlignment.Center); rgbaR.Location.Y = _cGreen.Y; - FallbackTextUtils.DrawText(style.FontMedium, "G", rgbaR, textColor, TextAlignment.Near, TextAlignment.Center); + Render2D.DrawText(style.FontMedium, "G", rgbaR, textColor, TextAlignment.Near, TextAlignment.Center); rgbaR.Location.Y = _cBlue.Y; - FallbackTextUtils.DrawText(style.FontMedium, "B", rgbaR, textColor, TextAlignment.Near, TextAlignment.Center); + Render2D.DrawText(style.FontMedium, "B", rgbaR, textColor, TextAlignment.Near, TextAlignment.Center); rgbaR.Location.Y = _cAlpha.Y; - FallbackTextUtils.DrawText(style.FontMedium, "A", rgbaR, textColor, TextAlignment.Near, TextAlignment.Center); + Render2D.DrawText(style.FontMedium, "A", rgbaR, textColor, TextAlignment.Near, TextAlignment.Center); // HSV left var hsvHl = new Rectangle(_cHue.Left - ChannelTextWidth, _cHue.Y, 10000, _cHue.Height); - FallbackTextUtils.DrawText(style.FontMedium, "H", hsvHl, textColor, TextAlignment.Near, TextAlignment.Center); + Render2D.DrawText(style.FontMedium, "H", hsvHl, textColor, TextAlignment.Near, TextAlignment.Center); hsvHl.Location.Y = _cSaturation.Y; - FallbackTextUtils.DrawText(style.FontMedium, "S", hsvHl, textColor, TextAlignment.Near, TextAlignment.Center); + Render2D.DrawText(style.FontMedium, "S", hsvHl, textColor, TextAlignment.Near, TextAlignment.Center); hsvHl.Location.Y = _cValue.Y; - FallbackTextUtils.DrawText(style.FontMedium, "V", hsvHl, textColor, TextAlignment.Near, TextAlignment.Center); + Render2D.DrawText(style.FontMedium, "V", hsvHl, textColor, TextAlignment.Near, TextAlignment.Center); // HSV right var hsvHr = new Rectangle(_cHue.Right + 2, _cHue.Y, 10000, _cHue.Height); - FallbackTextUtils.DrawText(style.FontMedium, "°", hsvHr, textColor, TextAlignment.Near, TextAlignment.Center); + Render2D.DrawText(style.FontMedium, "°", hsvHr, textColor, TextAlignment.Near, TextAlignment.Center); hsvHr.Location.Y = _cSaturation.Y; - FallbackTextUtils.DrawText(style.FontMedium, "%", hsvHr, textColor, TextAlignment.Near, TextAlignment.Center); + Render2D.DrawText(style.FontMedium, "%", hsvHr, textColor, TextAlignment.Near, TextAlignment.Center); hsvHr.Location.Y = _cValue.Y; - FallbackTextUtils.DrawText(style.FontMedium, "%", hsvHr, textColor, TextAlignment.Near, TextAlignment.Center); + Render2D.DrawText(style.FontMedium, "%", hsvHr, textColor, TextAlignment.Near, TextAlignment.Center); // Hex var hex = new Rectangle(_cHex.Left - 26, _cHex.Y, 10000, _cHex.Height); - FallbackTextUtils.DrawText(style.FontMedium, "Hex", hex, textColor, TextAlignment.Near, TextAlignment.Center); + Render2D.DrawText(style.FontMedium, "Hex", hex, textColor, TextAlignment.Near, TextAlignment.Center); // Color difference var newRect = new Rectangle(_cOK.X, _cHex.Bottom + PickerMargin, _cCancel.Right - _cOK.Left, 0); diff --git a/Source/Editor/GUI/Docking/DockPanelProxy.cs b/Source/Editor/GUI/Docking/DockPanelProxy.cs index 0b36b85fa..e6e57de8e 100644 --- a/Source/Editor/GUI/Docking/DockPanelProxy.cs +++ b/Source/Editor/GUI/Docking/DockPanelProxy.cs @@ -208,7 +208,7 @@ namespace FlaxEditor.GUI.Docking } // Draw text - FallbackTextUtils.DrawText( + Render2D.DrawText( style.FontMedium, tab.Title, new Rectangle(DockPanel.DefaultLeftTextMargin + iconWidth, 0, Width - DockPanel.DefaultLeftTextMargin - DockPanel.DefaultButtonsSize - 2 * DockPanel.DefaultButtonsMargin, DockPanel.DefaultHeaderHeight), @@ -271,7 +271,7 @@ namespace FlaxEditor.GUI.Docking } // Draw text - FallbackTextUtils.DrawText( + Render2D.DrawText( style.FontMedium, tab.Title, new Rectangle(x + DockPanel.DefaultLeftTextMargin + iconWidth, 0, 10000, DockPanel.DefaultHeaderHeight), diff --git a/Source/Editor/GUI/Docking/DockWindow.cs b/Source/Editor/GUI/Docking/DockWindow.cs index 561c898a0..dd4e39ce2 100644 --- a/Source/Editor/GUI/Docking/DockWindow.cs +++ b/Source/Editor/GUI/Docking/DockWindow.cs @@ -488,7 +488,7 @@ namespace FlaxEditor.GUI.Docking { var style = Style.Current; if (style?.FontMedium != null) - _titleSize = FallbackTextUtils.MeasureText(style.FontMedium, _title); + _titleSize = style.FontMedium.MeasureText(_title); } base.PerformLayoutBeforeChildren(); diff --git a/Source/Editor/GUI/ItemsListContextMenu.cs b/Source/Editor/GUI/ItemsListContextMenu.cs index d3c433a47..42d236991 100644 --- a/Source/Editor/GUI/ItemsListContextMenu.cs +++ b/Source/Editor/GUI/ItemsListContextMenu.cs @@ -86,8 +86,8 @@ namespace FlaxEditor.GUI var font = style.FontSmall; for (int i = 0; i < ranges.Length; i++) { - var start = FallbackTextUtils.GetCharPosition(font, Name, ranges[i].StartIndex); - var end = FallbackTextUtils.GetCharPosition(font, Name, ranges[i].EndIndex); + var start = font.GetCharPosition(Name, ranges[i].StartIndex); + var end = font.GetCharPosition(Name, ranges[i].EndIndex); _highlights.Add(new Rectangle(start.X + 2, 0, end.X - start.X, Height)); } Visible = true; @@ -136,7 +136,7 @@ namespace FlaxEditor.GUI } // Draw name - FallbackTextUtils.DrawText(style.FontSmall, Name, textRect, TintColor * (Enabled ? style.Foreground : style.ForegroundDisabled), TextAlignment.Near, TextAlignment.Center); + Render2D.DrawText(style.FontSmall, Name, textRect, TintColor * (Enabled ? style.Foreground : style.ForegroundDisabled), TextAlignment.Near, TextAlignment.Center); } /// diff --git a/Source/Editor/GUI/MainMenuButton.cs b/Source/Editor/GUI/MainMenuButton.cs index 43849f4ab..117922361 100644 --- a/Source/Editor/GUI/MainMenuButton.cs +++ b/Source/Editor/GUI/MainMenuButton.cs @@ -72,7 +72,7 @@ namespace FlaxEditor.GUI } // Draw text - FallbackTextUtils.DrawText(style.FontMedium, Text, clientRect, enabled && hasChildItems ? style.Foreground : style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); + Render2D.DrawText(style.FontMedium, Text, clientRect, enabled && hasChildItems ? style.Foreground : style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); } /// @@ -102,7 +102,7 @@ namespace FlaxEditor.GUI float width = 18; if (style.FontMedium) - width += FallbackTextUtils.MeasureText(style.FontMedium, Text).X; + width += style.FontMedium.MeasureText(Text).X; Width = width; } diff --git a/Source/Editor/GUI/NavigationButton.cs b/Source/Editor/GUI/NavigationButton.cs index 8dedf72fd..18e862304 100644 --- a/Source/Editor/GUI/NavigationButton.cs +++ b/Source/Editor/GUI/NavigationButton.cs @@ -57,7 +57,7 @@ namespace FlaxEditor.GUI } // Draw text - FallbackTextUtils.DrawText(style.FontMedium, Text, textRect, EnabledInHierarchy ? style.Foreground : style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center); + Render2D.DrawText(style.FontMedium, Text, textRect, EnabledInHierarchy ? style.Foreground : style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center); } /// @@ -67,7 +67,7 @@ namespace FlaxEditor.GUI if (style.FontMedium) { - Width = FallbackTextUtils.MeasureText(style.FontMedium, Text).X + 2 * DefaultMargin; + Width = style.FontMedium.MeasureText(Text).X + 2 * DefaultMargin; } } } diff --git a/Source/Editor/GUI/Row.cs b/Source/Editor/GUI/Row.cs index 9868ab456..43785c735 100644 --- a/Source/Editor/GUI/Row.cs +++ b/Source/Editor/GUI/Row.cs @@ -38,7 +38,7 @@ namespace FlaxEditor.GUI { Depth = -1; - var mediumHeight = FallbackTextUtils.GetMaxHeight(Style.Current.FontMedium); + var mediumHeight = Style.Current.FontMedium.GetMaxHeight(); if (Height < mediumHeight) Height = mediumHeight + 4; } @@ -99,7 +99,7 @@ namespace FlaxEditor.GUI rect.Width -= leftDepthMargin; Render2D.PushClip(rect); - FallbackTextUtils.DrawText(style.FontMedium, text, rect, style.Foreground, column.CellAlignment, TextAlignment.Center); + Render2D.DrawText(style.FontMedium, text, rect, style.Foreground, column.CellAlignment, TextAlignment.Center); Render2D.PopClip(); x += width; diff --git a/Source/Editor/GUI/StatusBar.cs b/Source/Editor/GUI/StatusBar.cs index 7770af9a1..f8f7ae839 100644 --- a/Source/Editor/GUI/StatusBar.cs +++ b/Source/Editor/GUI/StatusBar.cs @@ -56,7 +56,7 @@ namespace FlaxEditor.GUI Render2D.DrawSprite(style.StatusBarSizeGrip, new Rectangle(Width - 12, 10, 12, 12), style.Foreground); // Draw status text - FallbackTextUtils.DrawText(style.FontSmall, Text, new Rectangle(4, 0, Width - 20, Height), TextColor, TextAlignment.Near, TextAlignment.Center); + Render2D.DrawText(style.FontSmall, Text, new Rectangle(4, 0, Width - 20, Height), TextColor, TextAlignment.Near, TextAlignment.Center); } } } diff --git a/Source/Editor/GUI/StyleValueEditor.cs b/Source/Editor/GUI/StyleValueEditor.cs index db6696152..a89902177 100644 --- a/Source/Editor/GUI/StyleValueEditor.cs +++ b/Source/Editor/GUI/StyleValueEditor.cs @@ -157,7 +157,7 @@ namespace FlaxEditor.GUI { Rectangle textRectangle = r; textRectangle.X = 4; - FallbackTextUtils.DrawText(style.FontMedium, "No Style", textRectangle, style.Foreground); + Render2D.DrawText(style.FontMedium, "No Style", textRectangle, style.Foreground); } } diff --git a/Source/Editor/GUI/Table.cs b/Source/Editor/GUI/Table.cs index 2358557c3..1d22ddb15 100644 --- a/Source/Editor/GUI/Table.cs +++ b/Source/Editor/GUI/Table.cs @@ -130,7 +130,7 @@ namespace FlaxEditor.GUI var style = Style.Current; var font = column.TitleFont ?? style.FontMedium; - FallbackTextUtils.DrawText(font, column.Title, rect, column.TitleColor, TextAlignment.Center, TextAlignment.Center); + Render2D.DrawText(font, column.Title, rect, column.TitleColor, TextAlignment.Center, TextAlignment.Center); if (columnIndex < _columns.Length - 1) { diff --git a/Source/Editor/GUI/Tabs/Tabs.cs b/Source/Editor/GUI/Tabs/Tabs.cs index 5995c7ef5..3c70363e7 100644 --- a/Source/Editor/GUI/Tabs/Tabs.cs +++ b/Source/Editor/GUI/Tabs/Tabs.cs @@ -98,7 +98,7 @@ namespace FlaxEditor.GUI.Tabs // Draw text if (!string.IsNullOrEmpty(Tab.Text)) { - FallbackTextUtils.DrawText(style.FontMedium, Tab.Text, new Rectangle(tabRect.X + textOffset, tabRect.Y, tabRect.Width - textOffset, tabRect.Height), style.Foreground, Tabs.TabsTextHorizontalAlignment, Tabs.TabsTextVerticalAlignment); + Render2D.DrawText(style.FontMedium, Tab.Text, new Rectangle(tabRect.X + textOffset, tabRect.Y, tabRect.Width - textOffset, tabRect.Height), style.Foreground, Tabs.TabsTextHorizontalAlignment, Tabs.TabsTextVerticalAlignment); } } } diff --git a/Source/Editor/GUI/Timeline/GUI/Background.cs b/Source/Editor/GUI/Timeline/GUI/Background.cs index 05c1121c0..b9eff562a 100644 --- a/Source/Editor/GUI/Timeline/GUI/Background.cs +++ b/Source/Editor/GUI/Timeline/GUI/Background.cs @@ -301,7 +301,7 @@ namespace FlaxEditor.GUI.Timeline.GUI default: throw new ArgumentOutOfRangeException(); } var labelRect = new Rectangle(x + 2, -verticalLinesHeaderExtend * 0.8f + timeAxisHeaderOffset, 50, verticalLinesHeaderExtend); - FallbackTextUtils.DrawText(style.FontSmall, labelText, labelRect, labelColor, TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, 0.8f); + Render2D.DrawText(style.FontSmall, labelText, labelRect, labelColor, TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, 0.8f); } } } diff --git a/Source/Editor/GUI/Timeline/Track.cs b/Source/Editor/GUI/Timeline/Track.cs index 20be8f0c2..f38917aa6 100644 --- a/Source/Editor/GUI/Timeline/Track.cs +++ b/Source/Editor/GUI/Timeline/Track.cs @@ -965,7 +965,7 @@ namespace FlaxEditor.GUI.Timeline } // Draw text - FallbackTextUtils.DrawText(style.FontSmall, Title ?? Name, textRect, textColor, TextAlignment.Near, TextAlignment.Center); + Render2D.DrawText(style.FontSmall, Title ?? Name, textRect, textColor, TextAlignment.Near, TextAlignment.Center); // Disabled overlay DrawDisabled = Mute || (ParentTrack != null && ParentTrack.DrawDisabled); diff --git a/Source/Editor/GUI/Timeline/Tracks/MemberTrack.cs b/Source/Editor/GUI/Timeline/Tracks/MemberTrack.cs index dd79922ac..63787df2c 100644 --- a/Source/Editor/GUI/Timeline/Tracks/MemberTrack.cs +++ b/Source/Editor/GUI/Timeline/Tracks/MemberTrack.cs @@ -345,7 +345,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks if (_previewValue != null) { // Based on Track.Draw for track text placement - var left = _xOffset + 16 + FallbackTextUtils.MeasureText(Style.Current.FontSmall, Title ?? Name).X; + var left = _xOffset + 16 + Style.Current.FontSmall.MeasureText(Title ?? Name).X; if (Icon.IsValid) left += 18; if (IsExpanded) diff --git a/Source/Editor/GUI/ToolStripButton.cs b/Source/Editor/GUI/ToolStripButton.cs index c4f4603e2..e839a7356 100644 --- a/Source/Editor/GUI/ToolStripButton.cs +++ b/Source/Editor/GUI/ToolStripButton.cs @@ -136,7 +136,7 @@ namespace FlaxEditor.GUI if (!string.IsNullOrEmpty(_text)) { textRect.Size.X = Width - DefaultMargin - textRect.Left; - FallbackTextUtils.DrawText(style.FontMedium, _text, textRect, enabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center); + Render2D.DrawText(style.FontMedium, _text, textRect, enabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center); } } @@ -151,7 +151,7 @@ namespace FlaxEditor.GUI if (hasSprite) width += iconSize; if (!string.IsNullOrEmpty(_text) && style.FontMedium) - width += FallbackTextUtils.MeasureText(style.FontMedium, _text).X + (hasSprite ? DefaultMargin : 0); + width += style.FontMedium.MeasureText(_text).X + (hasSprite ? DefaultMargin : 0); Width = width; } diff --git a/Source/Editor/GUI/Tree/TreeNode.cs b/Source/Editor/GUI/Tree/TreeNode.cs index 6680e3608..703079469 100644 --- a/Source/Editor/GUI/Tree/TreeNode.cs +++ b/Source/Editor/GUI/Tree/TreeNode.cs @@ -575,7 +575,7 @@ namespace FlaxEditor.GUI.Tree var font = TextFont.GetFont(); if (font) { - _textWidth = FallbackTextUtils.MeasureText(font, _text).X; + _textWidth = font.MeasureText(_text).X; _textChanged = false; } } @@ -656,7 +656,7 @@ namespace FlaxEditor.GUI.Tree } // Draw text - FallbackTextUtils.DrawText(TextFont.GetFont(), _text, textRect, _cachedTextColor, TextAlignment.Near, TextAlignment.Center); + Render2D.DrawText(TextFont.GetFont(), _text, textRect, _cachedTextColor, TextAlignment.Near, TextAlignment.Center); // Draw drag and drop effect if (IsDragOver && _tree.DraggedOverNode == this) diff --git a/Source/Editor/Options/OptionsModule.cs b/Source/Editor/Options/OptionsModule.cs index dc376a0a2..bf35105a5 100644 --- a/Source/Editor/Options/OptionsModule.cs +++ b/Source/Editor/Options/OptionsModule.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.IO; +using FlaxEditor.Content.Settings; using FlaxEditor.Modules; using FlaxEngine; using FlaxEngine.GUI; @@ -224,7 +225,11 @@ namespace FlaxEditor.Options } } - FallbackTextUtils.Fallbacks = FallbackFonts.Create(Options.Interface.Fallbacks); + var graphicsSetttings = GameSettings.Load(); + if (graphicsSetttings.EnableFontFallback && graphicsSetttings.FallbackFonts == null) + { + Render2D.FallbackFonts = graphicsSetttings.FallbackFonts = FontFallbackList.Create(Options.Interface.Fallbacks); + } } /// diff --git a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs index eefd7f4dd..f64e46385 100644 --- a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs +++ b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs @@ -142,8 +142,8 @@ namespace FlaxEditor.SceneGraph.GUI var textRect = TextRect; for (int i = 0; i < ranges.Length; i++) { - var start = FallbackTextUtils.GetCharPosition(font, text, ranges[i].StartIndex); - var end = FallbackTextUtils.GetCharPosition(font, text, ranges[i].EndIndex); + var start = font.GetCharPosition(text, ranges[i].StartIndex); + var end = font.GetCharPosition(text, ranges[i].EndIndex); _highlights.Add(new Rectangle(start.X + textRect.X, textRect.Y, end.X - start.X, textRect.Height)); } isThisVisible = true; diff --git a/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs b/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs index be9a96a7d..621c7c25e 100644 --- a/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs +++ b/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs @@ -337,7 +337,7 @@ namespace FlaxEditor.Surface.Archetypes _textRect = new Rectangle(Float2.Zero, Size); var style = Style.Current; - var titleSize = FallbackTextUtils.MeasureText(style.FontLarge, Title); + var titleSize = style.FontLarge.MeasureText(Title); var width = Mathf.Max(100, titleSize.X + 50); Resize(width, 0); titleSize.X += 8.0f; @@ -379,7 +379,7 @@ namespace FlaxEditor.Surface.Archetypes } // Name - FallbackTextUtils.DrawText(style.FontLarge, Title, _textRect, style.Foreground, TextAlignment.Center, TextAlignment.Center); + Render2D.DrawText(style.FontLarge, Title, _textRect, style.Foreground, TextAlignment.Center, TextAlignment.Center); } /// @@ -1128,7 +1128,7 @@ namespace FlaxEditor.Surface.Archetypes } // Name - FallbackTextUtils.DrawText(style.FontLarge, Title, _textRect, style.Foreground, TextAlignment.Center, TextAlignment.Center); + Render2D.DrawText(style.FontLarge, Title, _textRect, style.Foreground, TextAlignment.Center, TextAlignment.Center); // Close button Render2D.DrawSprite(style.Cross, _closeButtonRect, _closeButtonRect.Contains(_mousePosition) ? style.Foreground : style.ForegroundGrey); @@ -1402,7 +1402,7 @@ namespace FlaxEditor.Surface.Archetypes { Title = StateTitle; var style = Style.Current; - var titleSize = FallbackTextUtils.MeasureText(style.FontLarge, Title); + var titleSize = style.FontLarge.MeasureText(Title); var width = Mathf.Max(100, titleSize.X + 50); Resize(width, 0); titleSize.X += 8.0f; diff --git a/Source/Editor/Surface/Archetypes/Animation.cs b/Source/Editor/Surface/Archetypes/Animation.cs index dae16b425..40a3d2a63 100644 --- a/Source/Editor/Surface/Archetypes/Animation.cs +++ b/Source/Editor/Surface/Archetypes/Animation.cs @@ -77,7 +77,7 @@ namespace FlaxEditor.Surface.Archetypes Title = asset?.ShortName ?? "Animation"; var style = Style.Current; - Resize(Mathf.Max(230, FallbackTextUtils.MeasureText(style.FontLarge, Title).X + 30), 160); + Resize(Mathf.Max(230, style.FontLarge.MeasureText(Title).X + 30), 160); } /// diff --git a/Source/Editor/Surface/Archetypes/BehaviorTree.cs b/Source/Editor/Surface/Archetypes/BehaviorTree.cs index b240685cc..f8cd7bb5a 100644 --- a/Source/Editor/Surface/Archetypes/BehaviorTree.cs +++ b/Source/Editor/Surface/Archetypes/BehaviorTree.cs @@ -100,7 +100,7 @@ namespace FlaxEditor.Surface.Archetypes _debugRelevant = Behavior.GetNodeDebugRelevancy(instance, behavior); _debugInfo = Behavior.GetNodeDebugInfo(instance, behavior); if (!string.IsNullOrEmpty(_debugInfo)) - _debugInfoSize = FallbackTextUtils.MeasureText(Style.Current.FontSmall, _debugInfo); + _debugInfoSize = Style.Current.FontSmall.MeasureText(_debugInfo); } } @@ -184,7 +184,7 @@ namespace FlaxEditor.Surface.Archetypes if (!string.IsNullOrEmpty(_debugInfo)) { var style = Style.Current; - FallbackTextUtils.DrawText(style.FontSmall, _debugInfo, new Rectangle(4, _headerRect.Bottom + 4, _debugInfoSize), style.Foreground); + Render2D.DrawText(style.FontSmall, _debugInfo, new Rectangle(4, _headerRect.Bottom + 4, _debugInfoSize), style.Foreground); } // Debug relevancy outline @@ -487,7 +487,7 @@ namespace FlaxEditor.Surface.Archetypes var height = 0.0f; var titleLabelFont = Style.Current.FontLarge; width = Mathf.Max(width, 100.0f); - width = Mathf.Max(width, FallbackTextUtils.MeasureText(titleLabelFont, Title).X + 30); + width = Mathf.Max(width, titleLabelFont.MeasureText(Title).X + 30); if (_debugInfoSize.X > 0) { width = Mathf.Max(width, _debugInfoSize.X + 8.0f); diff --git a/Source/Editor/Surface/Archetypes/ParticleModules.cs b/Source/Editor/Surface/Archetypes/ParticleModules.cs index 6082e212e..e5be3d35d 100644 --- a/Source/Editor/Surface/Archetypes/ParticleModules.cs +++ b/Source/Editor/Surface/Archetypes/ParticleModules.cs @@ -113,7 +113,7 @@ namespace FlaxEditor.Surface.Archetypes var idx = (int)ModuleType; var headerRect = new Rectangle(0, 0, Width, 16.0f); //Render2D.FillRectangle(headerRect, Color.Red); - FallbackTextUtils.DrawText(style.FontMedium, Title, headerRect, style.Foreground, TextAlignment.Center, TextAlignment.Center); + Render2D.DrawText(style.FontMedium, Title, headerRect, style.Foreground, TextAlignment.Center, TextAlignment.Center); DrawChildren(); diff --git a/Source/Editor/Surface/Archetypes/Particles.cs b/Source/Editor/Surface/Archetypes/Particles.cs index bf7828960..40b38f7ce 100644 --- a/Source/Editor/Surface/Archetypes/Particles.cs +++ b/Source/Editor/Surface/Archetypes/Particles.cs @@ -154,7 +154,7 @@ namespace FlaxEditor.Surface.Archetypes if (headerRect.Contains(mousePosition)) headerColor *= 1.07f; Render2D.FillRectangle(headerRect, headerColor); - FallbackTextUtils.DrawText(style.FontLarge, Names[idx], headerRect, style.Foreground, TextAlignment.Center, TextAlignment.Center); + Render2D.DrawText(style.FontLarge, Names[idx], headerRect, style.Foreground, TextAlignment.Center, TextAlignment.Center); DrawChildren(); } @@ -194,7 +194,7 @@ namespace FlaxEditor.Surface.Archetypes if (_headerRect.Contains(ref _mousePosition)) headerColor *= 1.07f; Render2D.FillRectangle(_headerRect, headerColor); - FallbackTextUtils.DrawText(style.FontLarge, Title, _headerRect, style.Foreground, TextAlignment.Center, TextAlignment.Center); + Render2D.DrawText(style.FontLarge, Title, _headerRect, style.Foreground, TextAlignment.Center, TextAlignment.Center); DrawChildren(); diff --git a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs index f4976be67..207875a92 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs @@ -200,8 +200,8 @@ namespace FlaxEditor.Surface.ContextMenu var font = style.FontSmall; for (int i = 0; i < ranges.Length; i++) { - var start = FallbackTextUtils.GetCharPosition(font, _archetype.Title, ranges[i].StartIndex); - var end = FallbackTextUtils.GetCharPosition(font, _archetype.Title, ranges[i].EndIndex); + var start = font.GetCharPosition(_archetype.Title, ranges[i].StartIndex); + var end = font.GetCharPosition(_archetype.Title, ranges[i].EndIndex); _highlights.Add(new Rectangle(start.X + textRect.X, 0, end.X - start.X, Height)); if (ranges[i].StartIndex <= 0) @@ -222,8 +222,8 @@ namespace FlaxEditor.Surface.ContextMenu _highlights.Clear(); var style = Style.Current; var font = style.FontSmall; - var start = FallbackTextUtils.GetCharPosition(font, _archetype.Title, 0); - var end = FallbackTextUtils.GetCharPosition(font, _archetype.Title, _archetype.Title.Length - 1); + var start = font.GetCharPosition(_archetype.Title, 0); + var end = font.GetCharPosition(_archetype.Title, _archetype.Title.Length - 1); _highlights.Add(new Rectangle(start.X + textRect.X, 0, end.X - start.X, Height)); _isFullMatch = true; Visible = true; @@ -237,8 +237,8 @@ namespace FlaxEditor.Surface.ContextMenu _highlights.Clear(); var style = Style.Current; var font = style.FontSmall; - var start = FallbackTextUtils.GetCharPosition(font, _archetype.Title, 0); - var end = FallbackTextUtils.GetCharPosition(font, _archetype.Title, _archetype.Title.Length - 1); + var start = font.GetCharPosition(_archetype.Title, 0); + var end = font.GetCharPosition(_archetype.Title, _archetype.Title.Length - 1); _highlights.Add(new Rectangle(start.X + textRect.X, 0, end.X - start.X, Height)); Visible = true; @@ -283,19 +283,19 @@ namespace FlaxEditor.Surface.ContextMenu } // Draw name - FallbackTextUtils.DrawText(style.FontSmall, _archetype.Title, textRect, Enabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center); + Render2D.DrawText(style.FontSmall, _archetype.Title, textRect, Enabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center); if (_archetype.SubTitle != null) { - var titleLength = FallbackTextUtils.MeasureText(style.FontSmall, _archetype.Title).X; + var titleLength = style.FontSmall.MeasureText(_archetype.Title).X; var subTitleRect = new Rectangle(textRect.X + titleLength, textRect.Y, textRect.Width - titleLength, textRect.Height); - FallbackTextUtils.DrawText(style.FontSmall, _archetype.SubTitle, subTitleRect, style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center); + Render2D.DrawText(style.FontSmall, _archetype.SubTitle, subTitleRect, style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center); } // Reset transform and draw score mark if (showScoreHit) { Render2D.PopTransform(); - FallbackTextUtils.DrawText(style.FontSmall, "> ", textRect, Enabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center); + Render2D.DrawText(style.FontSmall, "> ", textRect, Enabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center); } } diff --git a/Source/Editor/Surface/Elements/InputBox.cs b/Source/Editor/Surface/Elements/InputBox.cs index a5a99b932..2047bcd1a 100644 --- a/Source/Editor/Surface/Elements/InputBox.cs +++ b/Source/Editor/Surface/Elements/InputBox.cs @@ -1428,7 +1428,7 @@ namespace FlaxEditor.Surface.Elements if (_defaultValueEditor != null) { - _defaultValueEditor.Location = new Float2(X + Width + 8 + FallbackTextUtils.MeasureText(Style.Current.FontSmall, Text).X, Y); + _defaultValueEditor.Location = new Float2(X + Width + 8 + Style.Current.FontSmall.MeasureText(Text).X, Y); } } @@ -1443,7 +1443,7 @@ namespace FlaxEditor.Surface.Elements // Draw text var style = Style.Current; var rect = new Rectangle(Width + 4, 0, 1410, Height); - FallbackTextUtils.DrawText(style.FontSmall, Text, rect, Enabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center); + Render2D.DrawText(style.FontSmall, Text, rect, Enabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center); } /// @@ -1635,7 +1635,7 @@ namespace FlaxEditor.Surface.Elements { if (DefaultValueEditors[i].CanUse(this, ref _currentType)) { - var bounds = new Rectangle(X + Width + 8 + FallbackTextUtils.MeasureText(Style.Current.FontSmall, Text).X, Y, 90, Height); + var bounds = new Rectangle(X + Width + 8 + Style.Current.FontSmall.MeasureText(Text).X, Y, 90, Height); _editor = DefaultValueEditors[i]; try { diff --git a/Source/Editor/Surface/Elements/OutputBox.cs b/Source/Editor/Surface/Elements/OutputBox.cs index 5fb123700..497e2001a 100644 --- a/Source/Editor/Surface/Elements/OutputBox.cs +++ b/Source/Editor/Surface/Elements/OutputBox.cs @@ -189,7 +189,7 @@ namespace FlaxEditor.Surface.Elements // Draw text var style = Style.Current; var rect = new Rectangle(-100, 0, 100 - 2, Height); - FallbackTextUtils.DrawText(style.FontSmall, Text, rect, Enabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Far, TextAlignment.Center); + Render2D.DrawText(style.FontSmall, Text, rect, Enabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Far, TextAlignment.Center); } } } diff --git a/Source/Editor/Surface/Elements/TextView.cs b/Source/Editor/Surface/Elements/TextView.cs index c52667fe3..284497396 100644 --- a/Source/Editor/Surface/Elements/TextView.cs +++ b/Source/Editor/Surface/Elements/TextView.cs @@ -25,7 +25,7 @@ namespace FlaxEditor.Surface.Elements var style = Style.Current; var color = Enabled ? style.Foreground : style.ForegroundDisabled; - FallbackTextUtils.DrawText(style.FontSmall, Archetype.Text, new Rectangle(Float2.Zero, Size), color, TextAlignment.Near, TextAlignment.Center); + Render2D.DrawText(style.FontSmall, Archetype.Text, new Rectangle(Float2.Zero, Size), color, TextAlignment.Near, TextAlignment.Center); } } } diff --git a/Source/Editor/Surface/SurfaceComment.cs b/Source/Editor/Surface/SurfaceComment.cs index 26cc912ab..aad45190e 100644 --- a/Source/Editor/Surface/SurfaceComment.cs +++ b/Source/Editor/Surface/SurfaceComment.cs @@ -169,7 +169,7 @@ namespace FlaxEditor.Surface // Header Render2D.FillRectangle(_headerRect, headerColor); - FallbackTextUtils.DrawText(style.FontLarge, Title, _headerRect, style.Foreground, TextAlignment.Center, TextAlignment.Center); + Render2D.DrawText(style.FontLarge, Title, _headerRect, style.Foreground, TextAlignment.Center, TextAlignment.Center); // Close button Render2D.DrawSprite(style.Cross, _closeButtonRect, _closeButtonRect.Contains(_mousePosition) && Surface.CanEdit ? style.Foreground : style.ForegroundGrey); diff --git a/Source/Editor/Surface/SurfaceNode.cs b/Source/Editor/Surface/SurfaceNode.cs index df706faa6..780ef81f0 100644 --- a/Source/Editor/Surface/SurfaceNode.cs +++ b/Source/Editor/Surface/SurfaceNode.cs @@ -199,7 +199,7 @@ namespace FlaxEditor.Surface continue; if (child is InputBox inputBox) { - var boxWidth = FallbackTextUtils.MeasureText(boxLabelFont, inputBox.Text).X + 20; + var boxWidth = boxLabelFont.MeasureText(inputBox.Text).X + 20; if (inputBox.DefaultValueEditor != null) boxWidth += inputBox.DefaultValueEditor.Width + 4; leftWidth = Mathf.Max(leftWidth, boxWidth); @@ -207,7 +207,7 @@ namespace FlaxEditor.Surface } else if (child is OutputBox outputBox) { - rightWidth = Mathf.Max(rightWidth, FallbackTextUtils.MeasureText(boxLabelFont, outputBox.Text).X + 20); + rightWidth = Mathf.Max(rightWidth, boxLabelFont.MeasureText(outputBox.Text).X + 20); rightHeight = Mathf.Max(rightHeight, outputBox.Archetype.Position.Y - Constants.NodeMarginY - Constants.NodeHeaderSize + 20.0f); } else if (child is Control control) @@ -225,7 +225,7 @@ namespace FlaxEditor.Surface } } width = Mathf.Max(width, leftWidth + rightWidth + 10); - width = Mathf.Max(width, FallbackTextUtils.MeasureText(titleLabelFont, Title).X + 30); + width = Mathf.Max(width, titleLabelFont.MeasureText(Title).X + 30); height = Mathf.Max(height, Mathf.Max(leftHeight, rightHeight)); Resize(width, height); } @@ -1027,7 +1027,7 @@ namespace FlaxEditor.Surface if (_headerRect.Contains(ref _mousePosition)) headerColor *= 1.07f; Render2D.FillRectangle(_headerRect, headerColor); - FallbackTextUtils.DrawText(style.FontLarge, Title, _headerRect, style.Foreground, TextAlignment.Center, TextAlignment.Center); + Render2D.DrawText(style.FontLarge, Title, _headerRect, style.Foreground, TextAlignment.Center, TextAlignment.Center); // Close button if ((Archetype.Flags & NodeFlags.NoCloseButton) == 0 && Surface.CanEdit) diff --git a/Source/Editor/Tools/Foliage/FoliageTab.cs b/Source/Editor/Tools/Foliage/FoliageTab.cs index d36c3b6ed..1b46a42be 100644 --- a/Source/Editor/Tools/Foliage/FoliageTab.cs +++ b/Source/Editor/Tools/Foliage/FoliageTab.cs @@ -147,7 +147,7 @@ namespace FlaxEditor.Tools.Foliage Parent = _noFoliagePanel, Enabled = false }; - var textSize = FallbackTextUtils.MeasureText(Style.Current.FontMedium, buttonText); + var textSize = Style.Current.FontMedium.MeasureText(buttonText); if (_createNewFoliage.Width < textSize.X) { _createNewFoliage.LocalX -= (textSize.X - _createNewFoliage.Width) / 2; diff --git a/Source/Editor/Tools/Terrain/CarveTab.cs b/Source/Editor/Tools/Terrain/CarveTab.cs index e64a364f0..d51915acf 100644 --- a/Source/Editor/Tools/Terrain/CarveTab.cs +++ b/Source/Editor/Tools/Terrain/CarveTab.cs @@ -105,7 +105,7 @@ namespace FlaxEditor.Tools.Terrain Parent = _noTerrainPanel, Enabled = false }; - var textSize = FallbackTextUtils.MeasureText(Style.Current.FontMedium, buttonText); + var textSize = Style.Current.FontMedium.MeasureText(buttonText); if (_createTerrainButton.Width < textSize.X) { _createTerrainButton.LocalX -= (textSize.X - _createTerrainButton.Width) / 2; diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index 515514131..c49392d01 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -548,9 +548,9 @@ namespace FlaxEditor.Viewport #region Camera settings widget var largestText = "Relative Panning"; - var textSize = FallbackTextUtils.MeasureText(Style.Current.FontMedium, largestText); + var textSize = Style.Current.FontMedium.MeasureText(largestText); var xLocationForExtras = textSize.X + 5; - var cameraSpeedTextWidth = FallbackTextUtils.MeasureText(Style.Current.FontMedium, "0.00").X; + var cameraSpeedTextWidth = Style.Current.FontMedium.MeasureText("0.00").X; // Camera Settings Widget _cameraWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); @@ -801,7 +801,7 @@ namespace FlaxEditor.Viewport #region View mode widget largestText = "Brightness"; - textSize = FallbackTextUtils.MeasureText(Style.Current.FontMedium, largestText); + textSize = Style.Current.FontMedium.MeasureText(largestText); xLocationForExtras = textSize.X + 5; var viewMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperLeft); @@ -1233,8 +1233,8 @@ namespace FlaxEditor.Viewport color = Color.Yellow; var text = string.Format("FPS: {0}", fps); var font = Style.Current.FontMedium; - FallbackTextUtils.DrawText(font, text, new Rectangle(Float2.One, Size), Color.Black); - FallbackTextUtils.DrawText(font, text, new Rectangle(Float2.Zero, Size), color); + Render2D.DrawText(font, text, new Rectangle(Float2.One, Size), Color.Black); + Render2D.DrawText(font, text, new Rectangle(Float2.Zero, Size), color); } } @@ -1814,7 +1814,7 @@ namespace FlaxEditor.Viewport { var bounds = new Rectangle(Float2.Zero, Size); Render2D.FillRectangle(bounds, new Color(0.0f, 0.0f, 0.0f, 0.2f)); - FallbackTextUtils.DrawText(Style.Current.FontLarge, "Debugger breakpoint hit...", bounds, Color.White, TextAlignment.Center, TextAlignment.Center); + Render2D.DrawText(Style.Current.FontLarge, "Debugger breakpoint hit...", bounds, Color.White, TextAlignment.Center, TextAlignment.Center); } } diff --git a/Source/Editor/Viewport/Previews/AnimationPreview.cs b/Source/Editor/Viewport/Previews/AnimationPreview.cs index c4ada83c4..1679a36bc 100644 --- a/Source/Editor/Viewport/Previews/AnimationPreview.cs +++ b/Source/Editor/Viewport/Previews/AnimationPreview.cs @@ -96,11 +96,11 @@ namespace FlaxEditor.Viewport.Previews var skinnedModel = SkinnedModel; if (skinnedModel == null) { - FallbackTextUtils.DrawText(style.FontLarge, "Missing Base Model", new Rectangle(Float2.Zero, Size), Color.Red, TextAlignment.Center, TextAlignment.Center, TextWrapping.WrapWords); + Render2D.DrawText(style.FontLarge, "Missing Base Model", new Rectangle(Float2.Zero, Size), Color.Red, TextAlignment.Center, TextAlignment.Center, TextWrapping.WrapWords); } else if (!skinnedModel.IsLoaded) { - FallbackTextUtils.DrawText(style.FontLarge, "Loading...", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); + Render2D.DrawText(style.FontLarge, "Loading...", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); } } diff --git a/Source/Editor/Viewport/Previews/ModelPreview.cs b/Source/Editor/Viewport/Previews/ModelPreview.cs index b31776bf6..a6496aafe 100644 --- a/Source/Editor/Viewport/Previews/ModelPreview.cs +++ b/Source/Editor/Viewport/Previews/ModelPreview.cs @@ -409,8 +409,8 @@ namespace FlaxEditor.Viewport.Previews } var font = Style.Current.FontMedium; var pos = new Float2(10, 50); - FallbackTextUtils.DrawText(font, text, new Rectangle(pos + Float2.One, Size), Color.Black); - FallbackTextUtils.DrawText(font, text, new Rectangle(pos, Size), Color.White); + Render2D.DrawText(font, text, new Rectangle(pos + Float2.One, Size), Color.Black); + Render2D.DrawText(font, text, new Rectangle(pos, Size), Color.White); } } diff --git a/Source/Editor/Viewport/Previews/ParticleSystemPreview.cs b/Source/Editor/Viewport/Previews/ParticleSystemPreview.cs index a6eb721ce..c6ce5a7b5 100644 --- a/Source/Editor/Viewport/Previews/ParticleSystemPreview.cs +++ b/Source/Editor/Viewport/Previews/ParticleSystemPreview.cs @@ -254,7 +254,7 @@ namespace FlaxEditor.Viewport.Previews if (_showParticlesCounter) { var count = _previewEffect.ParticlesCount; - FallbackTextUtils.DrawText( + Render2D.DrawText( Style.Current.FontSmall, "Particles: " + count, new Rectangle(Float2.Zero, Size), diff --git a/Source/Editor/Viewport/Previews/SkinnedModelPreview.cs b/Source/Editor/Viewport/Previews/SkinnedModelPreview.cs index 6c02c019a..8bcc506d9 100644 --- a/Source/Editor/Viewport/Previews/SkinnedModelPreview.cs +++ b/Source/Editor/Viewport/Previews/SkinnedModelPreview.cs @@ -161,8 +161,8 @@ namespace FlaxEditor.Viewport.Previews } var font = Style.Current.FontMedium; var pos = new Float2(10, 50); - FallbackTextUtils.DrawText(font, text, new Rectangle(pos + Float2.One, Size), Color.Black); - FallbackTextUtils.DrawText(font, text, new Rectangle(pos, Size), Color.White); + Render2D.DrawText(font, text, new Rectangle(pos + Float2.One, Size), Color.Black); + Render2D.DrawText(font, text, new Rectangle(pos, Size), Color.White); } } diff --git a/Source/Editor/Viewport/Previews/TexturePreview.cs b/Source/Editor/Viewport/Previews/TexturePreview.cs index 5e7594e9c..ec10bb019 100644 --- a/Source/Editor/Viewport/Previews/TexturePreview.cs +++ b/Source/Editor/Viewport/Previews/TexturePreview.cs @@ -103,7 +103,7 @@ namespace FlaxEditor.Viewport.Previews { var bounds = new Rectangle(Float2.Zero, Size); Render2D.FillRectangle(bounds, new Color(0.0f, 0.0f, 0.0f, 0.2f)); - FallbackTextUtils.DrawText(Style.Current.FontLarge, "Debugger breakpoint hit...", bounds, Color.White, TextAlignment.Center, TextAlignment.Center); + Render2D.DrawText(Style.Current.FontLarge, "Debugger breakpoint hit...", bounds, Color.White, TextAlignment.Center, TextAlignment.Center); } Render2D.PopClip(); diff --git a/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs b/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs index 43f990afb..77e94ae53 100644 --- a/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs +++ b/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs @@ -123,7 +123,7 @@ namespace FlaxEditor.Viewport.Widgets } // Draw text - FallbackTextUtils.DrawText(style.FontMedium, _text, textRect, style.ForegroundViewport * (IsMouseOver ? 1.0f : 0.9f), TextAlignment.Center, TextAlignment.Center); + Render2D.DrawText(style.FontMedium, _text, textRect, style.ForegroundViewport * (IsMouseOver ? 1.0f : 0.9f), TextAlignment.Center, TextAlignment.Center); } /// @@ -163,7 +163,7 @@ namespace FlaxEditor.Viewport.Widgets var style = Style.Current; if (style != null && style.FontMedium) - Width = CalculateButtonWidth(_forcedTextWidth > 0.0f ? _forcedTextWidth : FallbackTextUtils.MeasureText(style.FontMedium, _text).X, Icon.IsValid); + Width = CalculateButtonWidth(_forcedTextWidth > 0.0f ? _forcedTextWidth : style.FontMedium.MeasureText(_text).X, Icon.IsValid); } } } diff --git a/Source/Editor/Windows/AboutDialog.cs b/Source/Editor/Windows/AboutDialog.cs index 8c38ed2a9..b059dadcb 100644 --- a/Source/Editor/Windows/AboutDialog.cs +++ b/Source/Editor/Windows/AboutDialog.cs @@ -53,7 +53,7 @@ namespace FlaxEditor.Windows Parent = this }; var buttonText = "Copy version info"; - var fontSize = FallbackTextUtils.MeasureText(Style.Current.FontMedium, buttonText); + var fontSize = Style.Current.FontMedium.MeasureText(buttonText); var copyVersionButton = new Button(Width - fontSize.X - 8, 6, fontSize.X + 4, 20) { Text = buttonText, diff --git a/Source/Editor/Windows/Assets/AnimationGraphWindow.cs b/Source/Editor/Windows/Assets/AnimationGraphWindow.cs index eb4198fd8..12eac22b5 100644 --- a/Source/Editor/Windows/Assets/AnimationGraphWindow.cs +++ b/Source/Editor/Windows/Assets/AnimationGraphWindow.cs @@ -51,7 +51,7 @@ namespace FlaxEditor.Windows.Assets var style = Style.Current; if (_window.Asset == null || !_window.Asset.IsLoaded) { - FallbackTextUtils.DrawText(style.FontLarge, "Loading...", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); + Render2D.DrawText(style.FontLarge, "Loading...", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); } } } diff --git a/Source/Editor/Windows/Assets/AnimationWindow.cs b/Source/Editor/Windows/Assets/AnimationWindow.cs index 042cf2b37..8f88d93f6 100644 --- a/Source/Editor/Windows/Assets/AnimationWindow.cs +++ b/Source/Editor/Windows/Assets/AnimationWindow.cs @@ -61,7 +61,7 @@ namespace FlaxEditor.Windows.Assets var animation = _window.Asset; if (animation == null || !animation.IsLoaded) { - FallbackTextUtils.DrawText(style.FontLarge, "Loading...", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); + Render2D.DrawText(style.FontLarge, "Loading...", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); } } diff --git a/Source/Editor/Windows/Assets/ModelWindow.cs b/Source/Editor/Windows/Assets/ModelWindow.cs index d48966043..c2764a5a6 100644 --- a/Source/Editor/Windows/Assets/ModelWindow.cs +++ b/Source/Editor/Windows/Assets/ModelWindow.cs @@ -48,7 +48,7 @@ namespace FlaxEditor.Windows.Assets var asset = _window.Asset; if (asset == null || !asset.IsLoaded) { - FallbackTextUtils.DrawText(style.FontLarge, "Loading...", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); + Render2D.DrawText(style.FontLarge, "Loading...", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); } } } @@ -645,7 +645,7 @@ namespace FlaxEditor.Windows.Assets if (!Proxy.Window._meshData.RequestMeshData(Proxy.Window._asset)) { Invalidate(); - FallbackTextUtils.DrawText(Style.Current.FontMedium, "Loading...", new Rectangle(Float2.Zero, size), Color.White, TextAlignment.Center, TextAlignment.Center); + Render2D.DrawText(Style.Current.FontMedium, "Loading...", new Rectangle(Float2.Zero, size), Color.White, TextAlignment.Center, TextAlignment.Center); return; } diff --git a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs index 1e7192d9f..95827240c 100644 --- a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs +++ b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs @@ -50,7 +50,7 @@ namespace FlaxEditor.Windows.Assets var asset = _window.Asset; if (asset == null || !asset.IsLoaded) { - FallbackTextUtils.DrawText(style.FontLarge, "Loading...", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); + Render2D.DrawText(style.FontLarge, "Loading...", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); } } } @@ -715,7 +715,7 @@ namespace FlaxEditor.Windows.Assets if (!Proxy.Window.RequestMeshData()) { Invalidate(); - FallbackTextUtils.DrawText(Style.Current.FontMedium, "Loading...", new Rectangle(Float2.Zero, size), Color.White, TextAlignment.Center, TextAlignment.Center); + Render2D.DrawText(Style.Current.FontMedium, "Loading...", new Rectangle(Float2.Zero, size), Color.White, TextAlignment.Center, TextAlignment.Center); return; } diff --git a/Source/Editor/Windows/ContentWindow.Search.cs b/Source/Editor/Windows/ContentWindow.Search.cs index 67b7deddd..a1072d158 100644 --- a/Source/Editor/Windows/ContentWindow.Search.cs +++ b/Source/Editor/Windows/ContentWindow.Search.cs @@ -57,7 +57,7 @@ namespace FlaxEditor.Windows var textRect = new Rectangle(margin, 0, clientRect.Width - boxSize - 2.0f * margin, clientRect.Height); Render2D.PushClip(textRect); var textColor = TextColor; - FallbackTextUtils.DrawText(Font.GetFont(), "View", textRect, enabled ? textColor : textColor * 0.5f, TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, textScale); + Render2D.DrawText(Font.GetFont(), "View", textRect, enabled ? textColor : textColor * 0.5f, TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, textScale); Render2D.PopClip(); // Arrow diff --git a/Source/Editor/Windows/DebugLogWindow.cs b/Source/Editor/Windows/DebugLogWindow.cs index 91d4f9d8c..ab0c07ec5 100644 --- a/Source/Editor/Windows/DebugLogWindow.cs +++ b/Source/Editor/Windows/DebugLogWindow.cs @@ -140,11 +140,11 @@ namespace FlaxEditor.Windows Render2D.PushClip(ref clientRect); if (LogCount == 1) { - FallbackTextUtils.DrawText(style.FontMedium, Desc.Title, textRect, style.Foreground); + Render2D.DrawText(style.FontMedium, Desc.Title, textRect, style.Foreground); } else if (LogCount > 1) { - FallbackTextUtils.DrawText(style.FontMedium, $"{Desc.Title} ({LogCount})", textRect, style.Foreground); + Render2D.DrawText(style.FontMedium, $"{Desc.Title} ({LogCount})", textRect, style.Foreground); } Render2D.PopClip(); } diff --git a/Source/Editor/Windows/GameWindow.cs b/Source/Editor/Windows/GameWindow.cs index 2c0364995..4a8c11176 100644 --- a/Source/Editor/Windows/GameWindow.cs +++ b/Source/Editor/Windows/GameWindow.cs @@ -827,7 +827,7 @@ namespace FlaxEditor.Windows if (Camera.MainCamera == null) { var style = Style.Current; - FallbackTextUtils.DrawText(style.FontLarge, "No camera", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); + Render2D.DrawText(style.FontLarge, "No camera", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); } // Selected UI controls outline @@ -866,8 +866,8 @@ namespace FlaxEditor.Windows var alpha = Mathf.Saturate(-animTime / fadeOutTime); var rect = new Rectangle(new Float2(6), Size - 12); var text = "Press Shift+F11 to unlock the mouse"; - FallbackTextUtils.DrawText(style.FontSmall, text, rect + new Float2(1.0f), style.Background * alpha, TextAlignment.Near, TextAlignment.Far); - FallbackTextUtils.DrawText(style.FontSmall, text, rect, style.Foreground * alpha, TextAlignment.Near, TextAlignment.Far); + Render2D.DrawText(style.FontSmall, text, rect + new Float2(1.0f), style.Background * alpha, TextAlignment.Near, TextAlignment.Far); + Render2D.DrawText(style.FontSmall, text, rect, style.Foreground * alpha, TextAlignment.Near, TextAlignment.Far); } timeout = 1.0f; @@ -884,7 +884,7 @@ namespace FlaxEditor.Windows { var bounds = new Rectangle(Float2.Zero, Size); Render2D.FillRectangle(bounds, new Color(0.0f, 0.0f, 0.0f, 0.2f)); - FallbackTextUtils.DrawText(Style.Current.FontLarge, "Debugger breakpoint hit...", bounds, Color.White, TextAlignment.Center, TextAlignment.Center); + Render2D.DrawText(Style.Current.FontLarge, "Debugger breakpoint hit...", bounds, Color.White, TextAlignment.Center, TextAlignment.Center); } } } diff --git a/Source/Editor/Windows/Profiler/SingleChart.cs b/Source/Editor/Windows/Profiler/SingleChart.cs index 25241b4c6..4f36692e5 100644 --- a/Source/Editor/Windows/Profiler/SingleChart.cs +++ b/Source/Editor/Windows/Profiler/SingleChart.cs @@ -138,8 +138,8 @@ namespace FlaxEditor.Windows.Profiler var headerRect = new Rectangle(0, chartHeight, Width, TitleHeight); var headerTextRect = new Rectangle(2, chartHeight, Width - 4, TitleHeight); Render2D.FillRectangle(headerRect, style.BackgroundNormal); - FallbackTextUtils.DrawText(style.FontMedium, Title, headerTextRect, style.ForegroundGrey, TextAlignment.Near, TextAlignment.Center); - FallbackTextUtils.DrawText(style.FontMedium, _sample, headerTextRect, style.Foreground, TextAlignment.Far, TextAlignment.Center); + Render2D.DrawText(style.FontMedium, Title, headerTextRect, style.ForegroundGrey, TextAlignment.Near, TextAlignment.Center); + Render2D.DrawText(style.FontMedium, _sample, headerTextRect, style.Foreground, TextAlignment.Far, TextAlignment.Center); } private void OnClick(ref Float2 location) diff --git a/Source/Editor/Windows/Profiler/Timeline.cs b/Source/Editor/Windows/Profiler/Timeline.cs index f87efdace..59a7a0e26 100644 --- a/Source/Editor/Windows/Profiler/Timeline.cs +++ b/Source/Editor/Windows/Profiler/Timeline.cs @@ -85,12 +85,12 @@ namespace FlaxEditor.Windows.Profiler Render2D.DrawRectangle(bounds, color * 0.5f); if (_nameLength < 0 && style.FontMedium) - _nameLength = FallbackTextUtils.MeasureText(style.FontMedium, _name).X; + _nameLength = style.FontMedium.MeasureText(_name).X; if (_nameLength < bounds.Width + 4) { Render2D.PushClip(bounds); - FallbackTextUtils.DrawText(style.FontMedium, _name, bounds, Style.Current.Foreground, TextAlignment.Center, TextAlignment.Center); + Render2D.DrawText(style.FontMedium, _name, bounds, Style.Current.Foreground, TextAlignment.Center, TextAlignment.Center); Render2D.PopClip(); } } @@ -115,7 +115,7 @@ namespace FlaxEditor.Windows.Profiler var style = Style.Current; var rect = new Rectangle(Float2.Zero, Size); Render2D.PushClip(rect); - FallbackTextUtils.DrawText(style.FontMedium, Name, rect, Style.Current.Foreground, TextAlignment.Center, TextAlignment.Center, TextWrapping.WrapChars); + Render2D.DrawText(style.FontMedium, Name, rect, Style.Current.Foreground, TextAlignment.Center, TextAlignment.Center, TextWrapping.WrapChars); Render2D.PopClip(); } } diff --git a/Source/Editor/Windows/SceneTreeWindow.cs b/Source/Editor/Windows/SceneTreeWindow.cs index 2fd990861..63ba7b960 100644 --- a/Source/Editor/Windows/SceneTreeWindow.cs +++ b/Source/Editor/Windows/SceneTreeWindow.cs @@ -297,7 +297,7 @@ namespace FlaxEditor.Windows } if (overlayText != null) { - FallbackTextUtils.DrawText(style.FontLarge, overlayText, GetClientArea(), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center, textWrap); + Render2D.DrawText(style.FontLarge, overlayText, GetClientArea(), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center, textWrap); } base.Draw(); diff --git a/Source/Editor/Windows/SplashScreen.cpp b/Source/Editor/Windows/SplashScreen.cpp index cbb51ae3c..cba59191e 100644 --- a/Source/Editor/Windows/SplashScreen.cpp +++ b/Source/Editor/Windows/SplashScreen.cpp @@ -258,7 +258,7 @@ void SplashScreen::OnDraw() return; // Title - const auto titleLength = _titleFont->MeasureText(GetTitle()); + const auto titleLength = _titleFont->MeasureTextInternal(GetTitle()); TextLayoutOptions layout; layout.Bounds = Rectangle(10 * s, 10 * s, width - 10 * s, 50 * s); layout.HorizontalAlignment = TextAlignment::Near; diff --git a/Source/Editor/Windows/ToolboxWindow.cs b/Source/Editor/Windows/ToolboxWindow.cs index f86341df3..e2b04669b 100644 --- a/Source/Editor/Windows/ToolboxWindow.cs +++ b/Source/Editor/Windows/ToolboxWindow.cs @@ -272,8 +272,8 @@ namespace FlaxEditor.Windows var textRect = item.TextRect; for (int i = 0; i < ranges.Length; i++) { - var start = FallbackTextUtils.GetCharPosition(font, text, ranges[i].StartIndex); - var end = FallbackTextUtils.GetCharPosition(font, text, ranges[i].EndIndex); + var start = font.GetCharPosition(text, ranges[i].StartIndex); + var end = font.GetCharPosition(text, ranges[i].EndIndex); highlights.Add(new Rectangle(start.X + textRect.X, textRect.Y, end.X - start.X, textRect.Height)); } item.SetHighlights(highlights); diff --git a/Source/Engine/Core/Config/GameSettings.h b/Source/Engine/Core/Config/GameSettings.h index 6813f3ee2..675c26bd1 100644 --- a/Source/Engine/Core/Config/GameSettings.h +++ b/Source/Engine/Core/Config/GameSettings.h @@ -35,15 +35,6 @@ public: API_FIELD(Attributes="EditorOrder(15), EditorDisplay(\"General\")") String CopyrightNotice; - /// - /// The copyright note used for content signing (eg. source code header). - /// - API_FIELD(Attributes = "EditorOrder(1200), EditorDisplay(\"Other Settings\")") - bool EnableFontFallback; - - API_FIELD(Attributes = "EditorOrder(1205), EditorDisplay(\"Other Settings\")") - FontFallbackList* FontFallbacks; - /// /// The default application icon. /// diff --git a/Source/Engine/Core/Config/GraphicsSettings.h b/Source/Engine/Core/Config/GraphicsSettings.h index 81d80cb35..7abfbb048 100644 --- a/Source/Engine/Core/Config/GraphicsSettings.h +++ b/Source/Engine/Core/Config/GraphicsSettings.h @@ -6,6 +6,8 @@ #include "Engine/Graphics/Enums.h" #include "Engine/Graphics/PostProcessSettings.h" +class FontFallbackList; + /// /// Graphics rendering settings. /// @@ -118,6 +120,18 @@ public: API_FIELD(Attributes="EditorOrder(10000), EditorDisplay(\"Post Process Settings\", EditorDisplayAttribute.InlineStyle)") PostProcessSettings PostProcessSettings; + /// + /// + /// + API_FIELD(Attributes = "EditorOrder(12000), EditorDisplay(\"Text Render Settings\", EditorDisplayAttribute.InlineStyle)") + bool EnableFontFallback = true; + + /// + /// + /// + API_FIELD(Attributes = "EditorOrder(12005), EditorDisplay(\"Text Render Settings\", EditorDisplayAttribute.InlineStyle)") + FontFallbackList* FallbackFonts; + private: /// /// Renamed UeeHDRProbes into UseHDRProbes diff --git a/Source/Engine/Graphics/Graphics.cpp b/Source/Engine/Graphics/Graphics.cpp index f91c58cea..20f29288b 100644 --- a/Source/Engine/Graphics/Graphics.cpp +++ b/Source/Engine/Graphics/Graphics.cpp @@ -8,6 +8,7 @@ #include "Engine/Core/Config/GraphicsSettings.h" #include "Engine/Engine/CommandLine.h" #include "Engine/Engine/EngineService.h" +#include "Engine/Render2D/Render2D.h" bool Graphics::UseVSync = false; Quality Graphics::AAQuality = Quality::Medium; @@ -69,6 +70,9 @@ void GraphicsSettings::Apply() Graphics::GIQuality = GIQuality; Graphics::PostProcessSettings = ::PostProcessSettings(); Graphics::PostProcessSettings.BlendWith(PostProcessSettings, 1.0f); + + Render2D::EnableFontFallback = EnableFontFallback; + Render2D::FallbackFonts = FallbackFonts; } void Graphics::DisposeDevice() diff --git a/Source/Engine/Render2D/FallbackTextUtils.cs b/Source/Engine/Render2D/FallbackTextUtils.cs deleted file mode 100644 index 486a354bf..000000000 --- a/Source/Engine/Render2D/FallbackTextUtils.cs +++ /dev/null @@ -1,268 +0,0 @@ - -using System.Runtime.CompilerServices; - -namespace FlaxEngine -{ - /// - /// A collection of functions to handle text rendering with fallback font - /// - public static class FallbackTextUtils - { - public static FallbackFonts Fallbacks - { - get; set; - } = null; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void DrawText(Font font, string text, Color color, ref TextLayoutOptions layout, MaterialBase customMaterial = null, bool useFallback = true) - { - if (Fallbacks != null && useFallback) - { - Render2D.DrawText(font, Fallbacks, text, color, ref layout, customMaterial); - } - else - { - Render2D.DrawText(font, text, color, ref layout, customMaterial); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void DrawText(Font font, string text, Rectangle layoutRect, Color color, TextAlignment horizontalAlignment = TextAlignment.Near, TextAlignment verticalAlignment = TextAlignment.Near, TextWrapping textWrapping = TextWrapping.NoWrap, float baseLinesGapScale = 1.0f, float scale = 1.0f, bool useFallback = true) - { - var layout = new TextLayoutOptions - { - Bounds = layoutRect, - HorizontalAlignment = horizontalAlignment, - VerticalAlignment = verticalAlignment, - TextWrapping = textWrapping, - Scale = scale, - BaseLinesGapScale = baseLinesGapScale, - }; - - if (Fallbacks != null && useFallback) - { - Render2D.DrawText(font, Fallbacks, text, color, ref layout); - } - else - { - Render2D.DrawText(font, text, color, ref layout); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void DrawText(Font font, MaterialBase customMaterial, string text, Rectangle layoutRect, Color color, TextAlignment horizontalAlignment = TextAlignment.Near, TextAlignment verticalAlignment = TextAlignment.Near, TextWrapping textWrapping = TextWrapping.NoWrap, float baseLinesGapScale = 1.0f, float scale = 1.0f, bool useFallback = true) - { - var layout = new TextLayoutOptions - { - Bounds = layoutRect, - HorizontalAlignment = horizontalAlignment, - VerticalAlignment = verticalAlignment, - TextWrapping = textWrapping, - Scale = scale, - BaseLinesGapScale = baseLinesGapScale, - }; - - if (Fallbacks != null && useFallback) - { - Render2D.DrawText(font, Fallbacks, text, color, ref layout, customMaterial); - } - else - { - Render2D.DrawText(font, text, color, ref layout, customMaterial); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Float2 MeasureText(Font font, string text, bool useFallback = true) - { - if (Fallbacks != null && useFallback) - { - return font.MeasureText(Fallbacks, text); - } - else - { - return font.MeasureText(text); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Float2 MeasureText(Font font, string text, ref TextRange textRange, bool useFallback = true) - { - if (Fallbacks != null && useFallback) - { - return font.MeasureText(Fallbacks, text, ref textRange); - } - else - { - return font.MeasureText(text, ref textRange); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Float2 MeasureText(Font font, string text, ref TextLayoutOptions layout, bool useFallback = true) - { - if (Fallbacks != null && useFallback) - { - return font.MeasureText(Fallbacks, text, ref layout); - } - else - { - return font.MeasureText(text, ref layout); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Float2 MeasureText(Font font, string text, ref TextRange textRange, ref TextLayoutOptions layout, bool useFallback = true) - { - if (Fallbacks != null && useFallback) - { - return font.MeasureText(Fallbacks, text, ref textRange, ref layout); - } - else - { - return font.MeasureText(text, ref textRange, ref layout); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int HitTestText(Font font, string text, Float2 location, bool useFallback = true) - { - if (Fallbacks != null && useFallback) - { - return font.HitTestText(Fallbacks, text, location); - } - else - { - return font.HitTestText(text, location); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int HitTestText(Font font, string text, ref TextRange textRange, Float2 location, bool useFallback = true) - { - if (Fallbacks != null && useFallback) - { - return font.HitTestText(Fallbacks, text, ref textRange, location); - } - else - { - return font.HitTestText(text, ref textRange, location); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int HitTestText(Font font, string text, Float2 location, ref TextLayoutOptions layout, bool useFallback = true) - { - if (Fallbacks != null && useFallback) - { - return font.HitTestText(Fallbacks, text, location, ref layout); - } - else - { - return font.HitTestText(text, location, ref layout); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int HitTestText(Font font, string text, ref TextRange textRange, Float2 location, ref TextLayoutOptions layout, bool useFallback = true) - { - if (Fallbacks != null && useFallback) - { - return font.HitTestText(Fallbacks, text, ref textRange, location, ref layout); - } - else - { - return font.HitTestText(text, ref textRange, location, ref layout); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Float2 GetCharPosition(Font font, string text, int index, bool useFallback = true) - { - if (Fallbacks != null && useFallback) - { - return font.GetCharPosition(Fallbacks, text, index); - } - else - { - return font.GetCharPosition(text, index); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Float2 GetCharPosition(Font font, string text, ref TextRange textRange, int index, bool useFallback = true) - { - if (Fallbacks != null && useFallback) - { - return font.GetCharPosition(Fallbacks, text, ref textRange, index); - } - else - { - return font.GetCharPosition(text, ref textRange, index); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Float2 GetCharPosition(Font font, string text, int index, ref TextLayoutOptions layout, bool useFallback = true) - { - if (Fallbacks != null && useFallback) - { - return font.GetCharPosition(Fallbacks, text, index, ref layout); - } - else - { - return font.GetCharPosition(text, index, ref layout); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Float2 GetCharPosition(Font font, string text, ref TextRange textRange, int index, ref TextLayoutOptions layout, bool useFallback = true) - { - if (Fallbacks != null && useFallback) - { - return font.GetCharPosition(Fallbacks, text, ref textRange, index, ref layout); - } - else - { - return font.GetCharPosition(text, ref textRange, index, ref layout); - } - } - - /// - /// Gets the max font height among the font and all fallback fonts of the same size. - /// - /// The primary font to use. - /// The fallback fonts. - /// The max height. - public static float GetMaxHeight(Font font, FallbackFonts fallbacks) - { - float height = font.Height; - - var fallbackFonts = fallbacks.GetFontList(font.Size); - foreach (var item in fallbackFonts) - { - height = Mathf.Max(height, item.Height); - } - - return height; - } - - /// - /// Gets the max font height among the font and all fallback fonts of the same size. - /// - /// The primary font to use. - /// Whether to enable fallback fonts, uses if true. - /// The max height. - public static float GetMaxHeight(Font font, bool useFallback = true) - { - if(Fallbacks != null && useFallback) - { - return GetMaxHeight(font, Fallbacks); - } - else - { - return font.Height; - } - } - } -} diff --git a/Source/Engine/Render2D/Font.cpp b/Source/Engine/Render2D/Font.cpp index 5d029ca5e..9f28aac01 100644 --- a/Source/Engine/Render2D/Font.cpp +++ b/Source/Engine/Render2D/Font.cpp @@ -103,6 +103,18 @@ void Font::Invalidate() _characters.Clear(); } +inline API_FUNCTION() float Font::GetMaxHeight(FontFallbackList* fallbacks) const +{ + float height = GetHeight(); + auto& fallbackFonts = fallbacks->GetFontList(GetSize()); + for (int32 i = 0; i < fallbackFonts.Count(); i++) + { + height = Math::Max(height, static_cast(fallbackFonts[i]->GetHeight())); + } + + return height; +} + void Font::ProcessText(const StringView& text, Array& outputLines, const TextLayoutOptions& layout) { float cursorX = 0; @@ -548,7 +560,7 @@ void Font::ProcessText(FontFallbackList* fallbacks, const StringView& text, Arra } } -Float2 Font::MeasureText(const StringView& text, const TextLayoutOptions& layout) +Float2 Font::MeasureTextInternal(const StringView& text, const TextLayoutOptions& layout) { // Check if there is no need to do anything if (text.IsEmpty()) @@ -569,7 +581,7 @@ Float2 Font::MeasureText(const StringView& text, const TextLayoutOptions& layout return max; } -Float2 Font::MeasureText(FontFallbackList* fallbacks, const StringView& text, const TextLayoutOptions& layout) +Float2 Font::MeasureTextInternal(FontFallbackList* fallbacks, const StringView& text, const TextLayoutOptions& layout) { // Check if there is no need to do anything if (text.IsEmpty()) @@ -590,7 +602,7 @@ Float2 Font::MeasureText(FontFallbackList* fallbacks, const StringView& text, co return max; } -int32 Font::HitTestText(const StringView& text, const Float2& location, const TextLayoutOptions& layout) +int32 Font::HitTestTextInternal(const StringView& text, const Float2& location, const TextLayoutOptions& layout) { // Check if there is no need to do anything if (text.Length() <= 0) @@ -664,7 +676,7 @@ int32 Font::HitTestText(const StringView& text, const Float2& location, const Te return smallestIndex; } -int32 Font::HitTestText(FontFallbackList* fallbacks, const StringView& text, const Float2& location, const TextLayoutOptions& layout) +int32 Font::HitTestTextInternal(FontFallbackList* fallbacks, const StringView& text, const Float2& location, const TextLayoutOptions& layout) { // Check if there is no need to do anything if (text.Length() <= 0) @@ -764,7 +776,7 @@ int32 Font::HitTestText(FontFallbackList* fallbacks, const StringView& text, con return smallestIndex; } -Float2 Font::GetCharPosition(const StringView& text, int32 index, const TextLayoutOptions& layout) +Float2 Font::GetCharPositionInternal(const StringView& text, int32 index, const TextLayoutOptions& layout) { // Check if there is no need to do anything if (text.IsEmpty()) @@ -818,7 +830,7 @@ Float2 Font::GetCharPosition(const StringView& text, int32 index, const TextLayo return rootOffset + Float2(lines.Last().Location.X + lines.Last().Size.X, static_cast((lines.Count() - 1) * baseLinesDistance)); } -Float2 Font::GetCharPosition(FontFallbackList* fallbacks, const StringView& text, int32 index, const TextLayoutOptions& layout) +Float2 Font::GetCharPositionInternal(FontFallbackList* fallbacks, const StringView& text, int32 index, const TextLayoutOptions& layout) { // Check if there is no need to do anything if (text.IsEmpty()) diff --git a/Source/Engine/Render2D/Font.h b/Source/Engine/Render2D/Font.h index 6bb2849e2..1fb41d032 100644 --- a/Source/Engine/Render2D/Font.h +++ b/Source/Engine/Render2D/Font.h @@ -8,6 +8,7 @@ #include "Engine/Content/AssetReference.h" #include "Engine/Scripting/ScriptingObject.h" #include "TextLayoutOptions.h" +#include "Render2D.h" class FontAsset; class FontFallbackList; @@ -402,7 +403,31 @@ public: public: /// - /// Processes text to get cached lines for rendering. + /// Gets the maximum height among the font and the fallback fonts. + /// + /// The fallback fonts. + /// The maximum height. + API_FUNCTION() float GetMaxHeight(FontFallbackList* fallbacks) const; + + /// + /// Gets the maximum height among the font and the fallback fonts, uses the default font defined in . + /// + /// The fallback fonts. + /// The maximum height. + API_FUNCTION() FORCE_INLINE float GetMaxHeight() const + { + if (Render2D::EnableFontFallback && Render2D::FallbackFonts) + { + return GetMaxHeight(Render2D::FallbackFonts); + } + else + { + return GetHeight(); + } + } + + /// + /// Processes text to get cached lines for rendering, with font fallbacking disabled. /// /// The input text. /// The layout properties. @@ -410,12 +435,12 @@ public: void ProcessText(const StringView& text, Array& outputLines, API_PARAM(Ref) const TextLayoutOptions& layout); /// - /// Processes text to get cached lines for rendering. + /// Processes text to get cached lines for rendering, with font fallbacking disabled. /// /// The input text. /// The layout properties. /// The output lines list. - API_FUNCTION() Array ProcessText(const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout) + API_FUNCTION() FORCE_INLINE Array ProcessText(const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout) { Array lines; ProcessText(text, lines, layout); @@ -423,13 +448,13 @@ public: } /// - /// Processes text to get cached lines for rendering. + /// Processes text to get cached lines for rendering, with font fallbacking disabled. /// /// The input text. /// The input text range (substring range of the input text parameter). /// The layout properties. /// The output lines list. - API_FUNCTION() Array ProcessText(const StringView& text, API_PARAM(Ref) const TextRange& textRange, API_PARAM(Ref) const TextLayoutOptions& layout) + API_FUNCTION() FORCE_INLINE Array ProcessText(const StringView& text, API_PARAM(Ref) const TextRange& textRange, API_PARAM(Ref) const TextLayoutOptions& layout) { Array lines; ProcessText(textRange.Substring(text), lines, layout); @@ -437,7 +462,7 @@ public: } /// - /// Processes text to get cached lines for rendering. + /// Processes text to get cached lines for rendering, with font fallbacking disabled. /// /// The input text. /// The output lines list. @@ -447,7 +472,7 @@ public: } /// - /// Processes text to get cached lines for rendering. + /// Processes text to get cached lines for rendering, with font fallbacking disabled. /// /// The input text. /// The input text range (substring range of the input text parameter). @@ -458,7 +483,7 @@ public: } /// - /// Processes text to get cached lines for rendering. + /// Processes text to get cached lines for rendering, using custom fallback options. /// /// The input text. /// The layout properties. @@ -466,12 +491,12 @@ public: void ProcessText(FontFallbackList* fallbacks, const StringView& text, Array& outputLines, API_PARAM(Ref) const TextLayoutOptions& layout); /// - /// Processes text to get cached lines for rendering. + /// Processes text to get cached lines for rendering, using custom fallback options. /// /// The input text. /// The layout properties. /// The output lines list. - API_FUNCTION() Array ProcessText(FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout) + API_FUNCTION() FORCE_INLINE Array ProcessText(FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout) { Array lines; ProcessText(fallbacks, text, lines, layout); @@ -479,13 +504,13 @@ public: } /// - /// Processes text to get cached lines for rendering. + /// Processes text to get cached lines for rendering, using custom fallback options. /// /// The input text. /// The input text range (substring range of the input text parameter). /// The layout properties. /// The output lines list. - API_FUNCTION() Array ProcessText(FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange, API_PARAM(Ref) const TextLayoutOptions& layout) + API_FUNCTION() FORCE_INLINE Array ProcessText(FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange, API_PARAM(Ref) const TextLayoutOptions& layout) { Array lines; ProcessText(fallbacks, textRange.Substring(text), lines, layout); @@ -493,7 +518,7 @@ public: } /// - /// Processes text to get cached lines for rendering. + /// Processes text to get cached lines for rendering, using custom fallback options. /// /// The input text. /// The output lines list. @@ -503,7 +528,7 @@ public: } /// - /// Processes text to get cached lines for rendering. + /// Processes text to get cached lines for rendering, using custom fallback options. /// /// The input text. /// The input text range (substring range of the input text parameter). @@ -514,122 +539,293 @@ public: } /// - /// Measures minimum size of the rectangle that will be needed to draw given text. + /// Measures minimum size of the rectangle that will be needed to draw given text, with font fallbacking disabled. /// /// The input text to test. /// The layout properties. /// The minimum size for that text and fot to render properly. - API_FUNCTION() Float2 MeasureText(const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout); + API_FUNCTION() Float2 MeasureTextInternal(const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout); /// - /// Measures minimum size of the rectangle that will be needed to draw given text. + /// Measures minimum size of the rectangle that will be needed to draw given text, with font fallbacking disabled. /// /// The input text to test. /// The input text range (substring range of the input text parameter). /// The layout properties. /// The minimum size for that text and fot to render properly. - API_FUNCTION() Float2 MeasureText(const StringView& text, API_PARAM(Ref) const TextRange& textRange, API_PARAM(Ref) const TextLayoutOptions& layout) + API_FUNCTION() FORCE_INLINE Float2 MeasureTextInternal(const StringView& text, API_PARAM(Ref) const TextRange& textRange, API_PARAM(Ref) const TextLayoutOptions& layout) { - return MeasureText(textRange.Substring(text), layout); + return MeasureTextInternal(textRange.Substring(text), layout); } /// - /// Measures minimum size of the rectangle that will be needed to draw given text + /// Measures minimum size of the rectangle that will be needed to draw given text, with font fallbacking disabled. + /// . + /// The input text to test. + /// The minimum size for that text and fot to render properly. + API_FUNCTION() FORCE_INLINE Float2 MeasureTextInternal(const StringView& text) + { + return MeasureTextInternal(text, TextLayoutOptions()); + } + + /// + /// Measures minimum size of the rectangle that will be needed to draw given text, with font fallbacking disabled. + /// . + /// The input text to test. + /// The input text range (substring range of the input text parameter). + /// The minimum size for that text and fot to render properly. + API_FUNCTION() FORCE_INLINE Float2 MeasureTextInternal(const StringView& text, API_PARAM(Ref) const TextRange& textRange) + { + return MeasureTextInternal(textRange.Substring(text), TextLayoutOptions()); + } + + /// + /// Measures minimum size of the rectangle that will be needed to draw given text, using custom fallback options. + /// + /// The input text to test. + /// The layout properties. + /// The minimum size for that text and fot to render properly. + API_FUNCTION() Float2 MeasureTextInternal(FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout); + + /// + /// Measures minimum size of the rectangle that will be needed to draw given text, using custom fallback options. + /// + /// The input text to test. + /// The input text range (substring range of the input text parameter). + /// The layout properties. + /// The minimum size for that text and fot to render properly. + API_FUNCTION() FORCE_INLINE Float2 MeasureTextInternal(FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange, API_PARAM(Ref) const TextLayoutOptions& layout) + { + return MeasureTextInternal(fallbacks, textRange.Substring(text), layout); + } + + /// + /// Measures minimum size of the rectangle that will be needed to draw given text, using custom fallback options. + /// . + /// The input text to test. + /// The minimum size for that text and fot to render properly. + API_FUNCTION() FORCE_INLINE Float2 MeasureTextInternal(FontFallbackList* fallbacks, const StringView& text) + { + return MeasureTextInternal(fallbacks, text, TextLayoutOptions()); + } + + /// + /// Measures minimum size of the rectangle that will be needed to draw given text, using custom fallback options. + /// . + /// The input text to test. + /// The input text range (substring range of the input text parameter). + /// The minimum size for that text and fot to render properly. + API_FUNCTION() FORCE_INLINE Float2 MeasureTextInternal(FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange) + { + return MeasureTextInternal(fallbacks, textRange.Substring(text), TextLayoutOptions()); + } + + /// + /// Measures minimum size of the rectangle that will be needed to draw given text, follows the fallback settings defined in . + /// + /// The input text to test. + /// The layout properties. + /// The minimum size for that text and fot to render properly. + API_FUNCTION() FORCE_INLINE Float2 MeasureText(const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout) { + if (Render2D::EnableFontFallback && Render2D::FallbackFonts) { + return MeasureTextInternal(Render2D::FallbackFonts, text, layout); + } + else { + return MeasureTextInternal(text, layout); + } + } + + /// + /// Measures minimum size of the rectangle that will be needed to draw given text, follows the fallback settings defined in . + /// + /// The input text to test. + /// The input text range (substring range of the input text parameter). + /// The layout properties. + /// The minimum size for that text and fot to render properly. + API_FUNCTION() FORCE_INLINE Float2 MeasureText(const StringView& text, API_PARAM(Ref) const TextRange& textRange, API_PARAM(Ref) const TextLayoutOptions& layout) + { + if (Render2D::EnableFontFallback && Render2D::FallbackFonts) { + return MeasureTextInternal(Render2D::FallbackFonts, textRange.Substring(text), layout); + } + else { + return MeasureTextInternal(textRange.Substring(text), layout); + } + } + + /// + /// Measures minimum size of the rectangle that will be needed to draw given text, follows the fallback settings defined in . /// . /// The input text to test. /// The minimum size for that text and fot to render properly. API_FUNCTION() FORCE_INLINE Float2 MeasureText(const StringView& text) { - return MeasureText(text, TextLayoutOptions()); + if (Render2D::EnableFontFallback && Render2D::FallbackFonts) { + return MeasureTextInternal(Render2D::FallbackFonts, text, TextLayoutOptions()); + } + else { + return MeasureTextInternal(text, TextLayoutOptions()); + } } /// - /// Measures minimum size of the rectangle that will be needed to draw given text + /// Measures minimum size of the rectangle that will be needed to draw given text, follows the fallback settings defined in . /// . /// The input text to test. /// The input text range (substring range of the input text parameter). /// The minimum size for that text and fot to render properly. API_FUNCTION() FORCE_INLINE Float2 MeasureText(const StringView& text, API_PARAM(Ref) const TextRange& textRange) { - return MeasureText(textRange.Substring(text), TextLayoutOptions()); + if (Render2D::EnableFontFallback && Render2D::FallbackFonts) { + return MeasureTextInternal(Render2D::FallbackFonts, textRange.Substring(text), TextLayoutOptions()); + } + else { + return MeasureTextInternal(textRange.Substring(text), TextLayoutOptions()); + } } /// - /// Measures minimum size of the rectangle that will be needed to draw given text. - /// - /// The input text to test. - /// The layout properties. - /// The minimum size for that text and fot to render properly. - API_FUNCTION() Float2 MeasureText(FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout); - - /// - /// Measures minimum size of the rectangle that will be needed to draw given text. - /// - /// The input text to test. - /// The input text range (substring range of the input text parameter). - /// The layout properties. - /// The minimum size for that text and fot to render properly. - API_FUNCTION() Float2 MeasureText(FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange, API_PARAM(Ref) const TextLayoutOptions& layout) - { - return MeasureText(fallbacks, textRange.Substring(text), layout); - } - - /// - /// Measures minimum size of the rectangle that will be needed to draw given text - /// . - /// The input text to test. - /// The minimum size for that text and fot to render properly. - API_FUNCTION() FORCE_INLINE Float2 MeasureText(FontFallbackList* fallbacks, const StringView& text) - { - return MeasureText(fallbacks, text, TextLayoutOptions()); - } - - /// - /// Measures minimum size of the rectangle that will be needed to draw given text - /// . - /// The input text to test. - /// The input text range (substring range of the input text parameter). - /// The minimum size for that text and fot to render properly. - API_FUNCTION() FORCE_INLINE Float2 MeasureText(FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange) - { - return MeasureText(fallbacks, textRange.Substring(text), TextLayoutOptions()); - } - - /// - /// Calculates hit character index at given location. + /// Calculates hit character index at given location, with font fallbacking disabled. /// /// The input text to test. /// The input location to test. /// The text layout properties. /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). - API_FUNCTION() int32 HitTestText(const StringView& text, const Float2& location, API_PARAM(Ref) const TextLayoutOptions& layout); + API_FUNCTION() int32 HitTestTextInternal(const StringView& text, const Float2& location, API_PARAM(Ref) const TextLayoutOptions& layout); /// - /// Calculates hit character index at given location. + /// Calculates hit character index at given location, with font fallbacking disabled. /// /// The input text to test. /// The input text range (substring range of the input text parameter). /// The input location to test. /// The text layout properties. /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). - API_FUNCTION() int32 HitTestText(const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Float2& location, API_PARAM(Ref) const TextLayoutOptions& layout) + API_FUNCTION() FORCE_INLINE int32 HitTestTextInternal(const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Float2& location, API_PARAM(Ref) const TextLayoutOptions& layout) { - return HitTestText(textRange.Substring(text), location, layout); + return HitTestTextInternal(textRange.Substring(text), location, layout); } /// - /// Calculates hit character index at given location. + /// Calculates hit character index at given location, with font fallbacking disabled. + /// + /// The input text to test. + /// The input location to test. + /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). + API_FUNCTION() FORCE_INLINE int32 HitTestTextInternal(const StringView& text, const Float2& location) + { + return HitTestTextInternal(text, location, TextLayoutOptions()); + } + + /// + /// Calculates hit character index at given location, with font fallbacking disabled. + /// + /// The input text to test. + /// The input text range (substring range of the input text parameter). + /// The input location to test. + /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). + API_FUNCTION() FORCE_INLINE int32 HitTestTextInternal(const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Float2& location) + { + return HitTestTextInternal(textRange.Substring(text), location, TextLayoutOptions()); + } + + /// + /// Calculates hit character index at given location, using custom fallback options. + /// + /// The input text to test. + /// The input location to test. + /// The text layout properties. + /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). + API_FUNCTION() int32 HitTestTextInternal(FontFallbackList* fallbacks, const StringView& text, const Float2& location, API_PARAM(Ref) const TextLayoutOptions& layout); + + /// + /// Calculates hit character index at given location, using custom fallback options. + /// + /// The input text to test. + /// The input text range (substring range of the input text parameter). + /// The input location to test. + /// The text layout properties. + /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). + API_FUNCTION() FORCE_INLINE int32 HitTestTextInternal(FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Float2& location, API_PARAM(Ref) const TextLayoutOptions& layout) + { + return HitTestTextInternal(fallbacks, textRange.Substring(text), location, layout); + } + + /// + /// Calculates hit character index at given location, using custom fallback options. + /// + /// The input text to test. + /// The input location to test. + /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). + API_FUNCTION() FORCE_INLINE int32 HitTestTextInternal(FontFallbackList* fallbacks, const StringView& text, const Float2& location) + { + return HitTestTextInternal(fallbacks, text, location, TextLayoutOptions()); + } + + /// + /// Calculates hit character index at given location, using custom fallback options. + /// + /// The input text to test. + /// The input text range (substring range of the input text parameter). + /// The input location to test. + /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). + API_FUNCTION() FORCE_INLINE int32 HitTestTextInternal(FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Float2& location) + { + return HitTestTextInternal(fallbacks, textRange.Substring(text), location, TextLayoutOptions()); + } + + /// + /// Calculates hit character index at given location, follows the fallback settings defined in . + /// + /// The input text to test. + /// The input location to test. + /// The text layout properties. + /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). + API_FUNCTION() FORCE_INLINE int32 HitTestText(const StringView& text, const Float2& location, API_PARAM(Ref) const TextLayoutOptions& layout) { + if (Render2D::EnableFontFallback && Render2D::FallbackFonts) { + return HitTestTextInternal(Render2D::FallbackFonts, text, location, layout); + } + else { + return HitTestTextInternal(text, location, layout); + } + } + + /// + /// Calculates hit character index at given location, follows the fallback settings defined in . + /// + /// The input text to test. + /// The input text range (substring range of the input text parameter). + /// The input location to test. + /// The text layout properties. + /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). + API_FUNCTION() FORCE_INLINE int32 HitTestText(const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Float2& location, API_PARAM(Ref) const TextLayoutOptions& layout) + { + if (Render2D::EnableFontFallback && Render2D::FallbackFonts) { + return HitTestTextInternal(Render2D::FallbackFonts, textRange.Substring(text), location, layout); + } + else { + return HitTestTextInternal(textRange.Substring(text), location, layout); + } + + } + + /// + /// Calculates hit character index at given location, follows the fallback settings defined in . /// /// The input text to test. /// The input location to test. /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). API_FUNCTION() FORCE_INLINE int32 HitTestText(const StringView& text, const Float2& location) { - return HitTestText(text, location, TextLayoutOptions()); + if (Render2D::EnableFontFallback && Render2D::FallbackFonts) { + return HitTestTextInternal(Render2D::FallbackFonts, text, location, TextLayoutOptions()); + } + else { + return HitTestTextInternal(text, location, TextLayoutOptions()); + } } /// - /// Calculates hit character index at given location. + /// Calculates hit character index at given location, follows the fallback settings defined in . /// /// The input text to test. /// The input text range (substring range of the input text parameter). @@ -637,89 +833,156 @@ public: /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). API_FUNCTION() FORCE_INLINE int32 HitTestText(const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Float2& location) { - return HitTestText(textRange.Substring(text), location, TextLayoutOptions()); + if (Render2D::EnableFontFallback && Render2D::FallbackFonts) { + return HitTestTextInternal(Render2D::FallbackFonts, textRange.Substring(text), location, TextLayoutOptions()); + } + else { + return HitTestTextInternal(textRange.Substring(text), location, TextLayoutOptions()); + } } /// - /// Calculates hit character index at given location. - /// - /// The input text to test. - /// The input location to test. - /// The text layout properties. - /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). - API_FUNCTION() int32 HitTestText(FontFallbackList* fallbacks, const StringView& text, const Float2& location, API_PARAM(Ref) const TextLayoutOptions& layout); - - /// - /// Calculates hit character index at given location. - /// - /// The input text to test. - /// The input text range (substring range of the input text parameter). - /// The input location to test. - /// The text layout properties. - /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). - API_FUNCTION() int32 HitTestText(FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Float2& location, API_PARAM(Ref) const TextLayoutOptions& layout) - { - return HitTestText(fallbacks, textRange.Substring(text), location, layout); - } - - /// - /// Calculates hit character index at given location. - /// - /// The input text to test. - /// The input location to test. - /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). - API_FUNCTION() FORCE_INLINE int32 HitTestText(FontFallbackList* fallbacks, const StringView& text, const Float2& location) - { - return HitTestText(fallbacks, text, location, TextLayoutOptions()); - } - - /// - /// Calculates hit character index at given location. - /// - /// The input text to test. - /// The input text range (substring range of the input text parameter). - /// The input location to test. - /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). - API_FUNCTION() FORCE_INLINE int32 HitTestText(FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Float2& location) - { - return HitTestText(fallbacks, textRange.Substring(text), location, TextLayoutOptions()); - } - - /// - /// Calculates character position for given text and character index. + /// Calculates character position for given text and character index, with font fallbacking disabled. /// /// The input text to test. /// The text position to get coordinates of. /// The text layout properties. /// The character position (upper left corner which can be used for a caret position). - API_FUNCTION() Float2 GetCharPosition(const StringView& text, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout); + API_FUNCTION() Float2 GetCharPositionInternal(const StringView& text, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout); /// - /// Calculates character position for given text and character index. + /// Calculates character position for given text and character index, with font fallbacking disabled. /// /// The input text to test. /// The input text range (substring range of the input text parameter). /// The text position to get coordinates of. /// The text layout properties. /// The character position (upper left corner which can be used for a caret position). - API_FUNCTION() Float2 GetCharPosition(const StringView& text, API_PARAM(Ref) const TextRange& textRange, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout) + API_FUNCTION() FORCE_INLINE Float2 GetCharPositionInternal(const StringView& text, API_PARAM(Ref) const TextRange& textRange, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout) { - return GetCharPosition(textRange.Substring(text), index, layout); + return GetCharPositionInternal(textRange.Substring(text), index, layout); } /// - /// Calculates character position for given text and character index + /// Calculates character position for given text and character index, with font fallbacking disabled. + /// + /// The input text to test. + /// The text position to get coordinates of. + /// The character position (upper left corner which can be used for a caret position). + API_FUNCTION() FORCE_INLINE Float2 GetCharPositionInternal(const StringView& text, int32 index) + { + return GetCharPositionInternal(text, index, TextLayoutOptions()); + } + + /// + /// Calculates character position for given text and character index, with font fallbacking disabled. + /// + /// The input text to test. + /// The input text range (substring range of the input text parameter). + /// The text position to get coordinates of. + /// The character position (upper left corner which can be used for a caret position). + API_FUNCTION() FORCE_INLINE Float2 GetCharPositionInternal(const StringView& text, API_PARAM(Ref) const TextRange& textRange, int32 index) + { + return GetCharPositionInternal(textRange.Substring(text), index, TextLayoutOptions()); + } + + /// + /// Calculates character position for given text and character index, using custom fallback options. + /// + /// The input text to test. + /// The text position to get coordinates of. + /// The text layout properties. + /// The character position (upper left corner which can be used for a caret position). + API_FUNCTION() Float2 GetCharPositionInternal(FontFallbackList* fallbacks, const StringView& text, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout); + + /// + /// Calculates character position for given text and character index, using custom fallback options. + /// + /// The input text to test. + /// The input text range (substring range of the input text parameter). + /// The text position to get coordinates of. + /// The text layout properties. + /// The character position (upper left corner which can be used for a caret position). + API_FUNCTION() FORCE_INLINE Float2 GetCharPositionInternal(FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout) + { + return GetCharPositionInternal(fallbacks, textRange.Substring(text), index, layout); + } + + /// + /// Calculates character position for given text and character index, using custom fallback options. + /// + /// The input text to test. + /// The text position to get coordinates of. + /// The character position (upper left corner which can be used for a caret position). + API_FUNCTION() FORCE_INLINE Float2 GetCharPositionInternal(FontFallbackList* fallbacks, const StringView& text, int32 index) + { + return GetCharPositionInternal(fallbacks, text, index, TextLayoutOptions()); + } + + /// + /// Calculates character position for given text and character index, using custom fallback options. + /// + /// The input text to test. + /// The input text range (substring range of the input text parameter). + /// The text position to get coordinates of. + /// The character position (upper left corner which can be used for a caret position). + API_FUNCTION() FORCE_INLINE Float2 GetCharPositionInternal(FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange, int32 index) + { + return GetCharPositionInternal(fallbacks, textRange.Substring(text), index, TextLayoutOptions()); + } + + /// + /// Calculates character position for given text and character index, follows the fallback settings defined in . + /// + /// The input text to test. + /// The text position to get coordinates of. + /// The text layout properties. + /// The character position (upper left corner which can be used for a caret position). + API_FUNCTION() FORCE_INLINE Float2 GetCharPosition(const StringView& text, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout) { + if (Render2D::EnableFontFallback && Render2D::FallbackFonts) { + return GetCharPositionInternal(Render2D::FallbackFonts, text, index, layout); + } + else { + return GetCharPositionInternal(text, index, layout); + } + } + + /// + /// Calculates character position for given text and character index, follows the fallback settings defined in . + /// + /// The input text to test. + /// The input text range (substring range of the input text parameter). + /// The text position to get coordinates of. + /// The text layout properties. + /// The character position (upper left corner which can be used for a caret position). + API_FUNCTION() FORCE_INLINE Float2 GetCharPosition(const StringView& text, API_PARAM(Ref) const TextRange& textRange, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout) + { + if (Render2D::EnableFontFallback && Render2D::FallbackFonts) { + return GetCharPositionInternal(Render2D::FallbackFonts, textRange.Substring(text), index, layout); + } + else { + return GetCharPositionInternal(textRange.Substring(text), index, layout); + } + } + + /// + /// Calculates character position for given text and character index, follows the fallback settings defined in . /// /// The input text to test. /// The text position to get coordinates of. /// The character position (upper left corner which can be used for a caret position). API_FUNCTION() FORCE_INLINE Float2 GetCharPosition(const StringView& text, int32 index) { - return GetCharPosition(text, index, TextLayoutOptions()); + if (Render2D::EnableFontFallback && Render2D::FallbackFonts) { + return GetCharPositionInternal(Render2D::FallbackFonts, text, index, TextLayoutOptions()); + } + else { + return GetCharPositionInternal(text, index, TextLayoutOptions()); + } } /// - /// Calculates character position for given text and character index + /// Calculates character position for given text and character index, follows the fallback settings defined in . /// /// The input text to test. /// The input text range (substring range of the input text parameter). @@ -727,52 +990,12 @@ public: /// The character position (upper left corner which can be used for a caret position). API_FUNCTION() FORCE_INLINE Float2 GetCharPosition(const StringView& text, API_PARAM(Ref) const TextRange& textRange, int32 index) { - return GetCharPosition(textRange.Substring(text), index, TextLayoutOptions()); - } - - /// - /// Calculates character position for given text and character index. - /// - /// The input text to test. - /// The text position to get coordinates of. - /// The text layout properties. - /// The character position (upper left corner which can be used for a caret position). - API_FUNCTION() Float2 GetCharPosition(FontFallbackList* fallbacks, const StringView& text, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout); - - /// - /// Calculates character position for given text and character index. - /// - /// The input text to test. - /// The input text range (substring range of the input text parameter). - /// The text position to get coordinates of. - /// The text layout properties. - /// The character position (upper left corner which can be used for a caret position). - API_FUNCTION() Float2 GetCharPosition(FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout) - { - return GetCharPosition(fallbacks, textRange.Substring(text), index, layout); - } - - /// - /// Calculates character position for given text and character index - /// - /// The input text to test. - /// The text position to get coordinates of. - /// The character position (upper left corner which can be used for a caret position). - API_FUNCTION() FORCE_INLINE Float2 GetCharPosition(FontFallbackList* fallbacks, const StringView& text, int32 index) - { - return GetCharPosition(fallbacks, text, index, TextLayoutOptions()); - } - - /// - /// Calculates character position for given text and character index - /// - /// The input text to test. - /// The input text range (substring range of the input text parameter). - /// The text position to get coordinates of. - /// The character position (upper left corner which can be used for a caret position). - API_FUNCTION() FORCE_INLINE Float2 GetCharPosition(FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange, int32 index) - { - return GetCharPosition(fallbacks, textRange.Substring(text), index, TextLayoutOptions()); + if (Render2D::EnableFontFallback && Render2D::FallbackFonts) { + return GetCharPositionInternal(Render2D::FallbackFonts, textRange.Substring(text), index, TextLayoutOptions()); + } + else { + return GetCharPositionInternal(textRange.Substring(text), index, TextLayoutOptions()); + } } /// diff --git a/Source/Engine/Render2D/Render2D.cpp b/Source/Engine/Render2D/Render2D.cpp index 351bb3700..fa52baff0 100644 --- a/Source/Engine/Render2D/Render2D.cpp +++ b/Source/Engine/Render2D/Render2D.cpp @@ -182,6 +182,8 @@ struct ClipMask }; Render2D::RenderingFeatures Render2D::Features = RenderingFeatures::VertexSnapping; +bool Render2D::EnableFontFallback = true; +FontFallbackList* Render2D::FallbackFonts = nullptr; namespace { diff --git a/Source/Engine/Render2D/Render2D.cs b/Source/Engine/Render2D/Render2D.cs index 6e4f1dc1d..b36f155df 100644 --- a/Source/Engine/Render2D/Render2D.cs +++ b/Source/Engine/Render2D/Render2D.cs @@ -102,7 +102,7 @@ namespace FlaxEngine } /// - /// Draws a text. + /// Draws a text, follows the font fallback settings defined in . /// /// The font to use. /// The text to render. @@ -128,7 +128,7 @@ namespace FlaxEngine } /// - /// Draws a text using a custom material shader. Given material must have GUI domain and a public parameter named Font (texture parameter used for a font atlas sampling). + /// Draws a text using a custom material shader. Given material must have GUI domain and a public parameter named Font (texture parameter used for a font atlas sampling). Follows the font fallback settings defined in . /// /// The font to use. /// Custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture. diff --git a/Source/Engine/Render2D/Render2D.h b/Source/Engine/Render2D/Render2D.h index fa59b1d79..5813d6f87 100644 --- a/Source/Engine/Render2D/Render2D.h +++ b/Source/Engine/Render2D/Render2D.h @@ -54,8 +54,6 @@ API_CLASS(Static) class FLAXENGINE_API Render2D }; public: - API_FIELD() static bool EnableFontFallback; - API_FIELD() static FontFallbackList* FallbackFonts; /// /// Checks if interface is during rendering phrase (Draw calls may be performed without failing). @@ -72,6 +70,10 @@ public: /// API_FIELD() static RenderingFeatures Features; + API_FIELD() static bool EnableFontFallback; + + API_FIELD() static FontFallbackList* FallbackFonts; + /// /// Called when frame rendering begins by the graphics device. /// diff --git a/Source/Engine/UI/GUI/Common/Button.cs b/Source/Engine/UI/GUI/Common/Button.cs index feb34418b..02337411b 100644 --- a/Source/Engine/UI/GUI/Common/Button.cs +++ b/Source/Engine/UI/GUI/Common/Button.cs @@ -261,7 +261,7 @@ namespace FlaxEngine.GUI Render2D.DrawRectangle(clientRect, borderColor, BorderThickness); // Draw text - FallbackTextUtils.DrawText(_font?.GetFont(), TextMaterial, _text, clientRect, textColor, TextAlignment.Center, TextAlignment.Center); + Render2D.DrawText(_font?.GetFont(), TextMaterial, _text, clientRect, textColor, TextAlignment.Center, TextAlignment.Center); } /// diff --git a/Source/Engine/UI/GUI/Common/Dropdown.cs b/Source/Engine/UI/GUI/Common/Dropdown.cs index 3b298b136..ecca2978f 100644 --- a/Source/Engine/UI/GUI/Common/Dropdown.cs +++ b/Source/Engine/UI/GUI/Common/Dropdown.cs @@ -475,7 +475,7 @@ namespace FlaxEngine.GUI var font = Font.GetFont(); for (int i = 0; i < _items.Count; i++) { - itemsWidth = Mathf.Max(itemsWidth, itemsMargin + 4 + FallbackTextUtils.MeasureText(font, _items[i]).X); + itemsWidth = Mathf.Max(itemsWidth, itemsMargin + 4 + font.MeasureText(_items[i]).X); } */ var itemsWidth = Width; @@ -673,7 +673,7 @@ namespace FlaxEngine.GUI var textRect = new Rectangle(margin, 0, clientRect.Width - boxSize - 2.0f * margin, clientRect.Height); Render2D.PushClip(textRect); var textColor = TextColor; - FallbackTextUtils.DrawText(Font.GetFont(), FontMaterial, _items[_selectedIndex], textRect, enabled ? textColor : textColor * 0.5f, TextAlignment.Near, TextAlignment.Center); + Render2D.DrawText(Font.GetFont(), FontMaterial, _items[_selectedIndex], textRect, enabled ? textColor : textColor * 0.5f, TextAlignment.Near, TextAlignment.Center); Render2D.PopClip(); } diff --git a/Source/Engine/UI/GUI/Common/Label.cs b/Source/Engine/UI/GUI/Common/Label.cs index 3d1caad28..3c7c04fb2 100644 --- a/Source/Engine/UI/GUI/Common/Label.cs +++ b/Source/Engine/UI/GUI/Common/Label.cs @@ -233,7 +233,7 @@ namespace FlaxEngine.GUI } } - FallbackTextUtils.DrawText(_font.GetFont(), Material, _text, rect, color, hAlignment, wAlignment, Wrapping, BaseLinesGapScale, scale); + Render2D.DrawText(_font.GetFont(), Material, _text, rect, color, hAlignment, wAlignment, Wrapping, BaseLinesGapScale, scale); if (ClipText) Render2D.PopClip(); @@ -254,7 +254,7 @@ namespace FlaxEngine.GUI layout.Bounds.Size.X = Width - Margin.Width; else if (_autoWidth && !_autoHeight) layout.Bounds.Size.Y = Height - Margin.Height; - _textSize = FallbackTextUtils.MeasureText(font, _text, ref layout); + _textSize = font.MeasureText(_text, ref layout); _textSize.Y *= BaseLinesGapScale; // Check if size is controlled via text diff --git a/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs b/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs index 8d7858af0..46f0fb1ad 100644 --- a/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs +++ b/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs @@ -154,7 +154,7 @@ namespace FlaxEngine.GUI if (!font) break; height = font.Height / DpiScale; - return textBlock.Bounds.Location + FallbackTextUtils.GetCharPosition(font, _text, ref textBlock.Range, index - textBlock.Range.StartIndex); + return textBlock.Bounds.Location + font.GetCharPosition(_text, ref textBlock.Range, index - textBlock.Range.StartIndex); } } @@ -196,7 +196,7 @@ namespace FlaxEngine.GUI var font = textBlock.Style.Font.GetFont(); if (!font && textBlock.Range.Length > 0) break; - return FallbackTextUtils.HitTestText(font, _text, ref textBlock.Range, location - textBlock.Bounds.Location) + textBlock.Range.StartIndex; + return font.HitTestText(_text, ref textBlock.Range, location - textBlock.Bounds.Location) + textBlock.Range.StartIndex; } } @@ -288,8 +288,8 @@ namespace FlaxEngine.GUI // Selection if (hasSelection && textBlock.Style.BackgroundSelectedBrush != null && textBlock.Range.Intersect(ref selection)) { - var leftEdge = selection.StartIndex <= textBlock.Range.StartIndex ? textBlock.Bounds.UpperLeft : FallbackTextUtils.GetCharPosition(font, _text, selection.StartIndex); - var rightEdge = selection.EndIndex >= textBlock.Range.EndIndex ? textBlock.Bounds.UpperRight : FallbackTextUtils.GetCharPosition(font, _text, selection.EndIndex); + var leftEdge = selection.StartIndex <= textBlock.Range.StartIndex ? textBlock.Bounds.UpperLeft : font.GetCharPosition(_text, selection.StartIndex); + var rightEdge = selection.EndIndex >= textBlock.Range.EndIndex ? textBlock.Bounds.UpperRight : font.GetCharPosition(_text, selection.EndIndex); float height = font.Height / DpiScale; float alpha = Mathf.Min(1.0f, Mathf.Cos(_animateTime * BackgroundSelectedFlashSpeed) * 0.5f + 1.3f); alpha *= alpha; diff --git a/Source/Engine/UI/GUI/Common/TextBox.cs b/Source/Engine/UI/GUI/Common/TextBox.cs index b1861df56..3c0d55008 100644 --- a/Source/Engine/UI/GUI/Common/TextBox.cs +++ b/Source/Engine/UI/GUI/Common/TextBox.cs @@ -104,7 +104,7 @@ namespace FlaxEngine.GUI return Float2.Zero; } - return FallbackTextUtils.MeasureText(font, _text, ref _layout); + return font.MeasureText(_text, ref _layout); } /// @@ -117,8 +117,8 @@ namespace FlaxEngine.GUI return Float2.Zero; } - height = FallbackTextUtils.GetMaxHeight(font) / DpiScale; - return FallbackTextUtils.GetCharPosition(font, _text, index, ref _layout); + height = font.GetMaxHeight() / DpiScale; + return font.GetCharPosition(_text, index, ref _layout); } /// @@ -130,7 +130,7 @@ namespace FlaxEngine.GUI return 0; } - return FallbackTextUtils.HitTestText(font, _text, location, ref _layout); + return font.HitTestText(_text, location, ref _layout); } /// @@ -169,9 +169,9 @@ namespace FlaxEngine.GUI // Check if sth is selected to draw selection if (HasSelection) { - var leftEdge = FallbackTextUtils.GetCharPosition(font, _text, SelectionLeft, ref _layout); - var rightEdge = FallbackTextUtils.GetCharPosition(font, _text, SelectionRight, ref _layout); - float fontHeight = FallbackTextUtils.GetMaxHeight(font) / DpiScale; + var leftEdge = font.GetCharPosition(_text, SelectionLeft, ref _layout); + var rightEdge = font.GetCharPosition(_text, SelectionRight, ref _layout); + float fontHeight = font.GetMaxHeight() / DpiScale; // Draw selection background float alpha = Mathf.Min(1.0f, Mathf.Cos(_animateTime * BackgroundSelectedFlashSpeed) * 0.5f + 1.3f); @@ -211,11 +211,11 @@ namespace FlaxEngine.GUI var color = TextColor; if (!enabled) color *= 0.6f; - FallbackTextUtils.DrawText(font, _text, color, ref _layout, TextMaterial); + Render2D.DrawText(font, _text, color, ref _layout, TextMaterial); } else if (!string.IsNullOrEmpty(_watermarkText) && !IsFocused) { - FallbackTextUtils.DrawText(font, _watermarkText, WatermarkTextColor, ref _layout, TextMaterial); + Render2D.DrawText(font, _watermarkText, WatermarkTextColor, ref _layout, TextMaterial); } // Caret diff --git a/Source/Engine/UI/GUI/Panels/DropPanel.cs b/Source/Engine/UI/GUI/Panels/DropPanel.cs index 123e0f034..66e7413eb 100644 --- a/Source/Engine/UI/GUI/Panels/DropPanel.cs +++ b/Source/Engine/UI/GUI/Panels/DropPanel.cs @@ -374,7 +374,7 @@ namespace FlaxEngine.GUI textColor *= 0.6f; } - FallbackTextUtils.DrawText(HeaderTextFont.GetFont(), HeaderTextMaterial, HeaderText, textRect, textColor, TextAlignment.Near, TextAlignment.Center); + Render2D.DrawText(HeaderTextFont.GetFont(), HeaderTextMaterial, HeaderText, textRect, textColor, TextAlignment.Near, TextAlignment.Center); if (!_isClosed && EnableContainmentLines) { diff --git a/Source/Engine/UI/GUI/Style.cs b/Source/Engine/UI/GUI/Style.cs index f4fca13eb..6092f0f72 100644 --- a/Source/Engine/UI/GUI/Style.cs +++ b/Source/Engine/UI/GUI/Style.cs @@ -69,12 +69,6 @@ namespace FlaxEngine.GUI set => _fontSmall = new FontReference(value); } - /// - /// The fallback fonts to use if the primary font can't render the char. - /// - [EditorOrder(50)] - public FallbackFonts Fallbacks; - /// /// The background color. /// diff --git a/Source/Engine/UI/GUI/Tooltip.cs b/Source/Engine/UI/GUI/Tooltip.cs index 41d06b017..8d35c21b9 100644 --- a/Source/Engine/UI/GUI/Tooltip.cs +++ b/Source/Engine/UI/GUI/Tooltip.cs @@ -234,7 +234,7 @@ namespace FlaxEngine.GUI Render2D.FillRectangle(new Rectangle(1.1f, 1.1f, Width - 2, Height - 2), style.Background); // Tooltip text - FallbackTextUtils.DrawText( + Render2D.DrawText( style.FontMedium, _currentText, GetClientArea(), From 6ab1663a1429591d0678bab1790e3e0b4a9628ca Mon Sep 17 00:00:00 2001 From: ExMatics HydrogenC <33123710+HydrogenC@users.noreply.github.com> Date: Sun, 3 Dec 2023 15:18:27 +0800 Subject: [PATCH 049/139] Add missing xml annotations --- Source/Engine/Core/Config/GraphicsSettings.h | 4 +-- Source/Engine/Render2D/Render2D.h | 26 ++++++++++++++++++++ Source/Engine/UI/GUI/Style.cs | 1 - Source/Engine/UI/GUI/Tooltip.cs | 1 - 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/Source/Engine/Core/Config/GraphicsSettings.h b/Source/Engine/Core/Config/GraphicsSettings.h index 7abfbb048..e109a55e4 100644 --- a/Source/Engine/Core/Config/GraphicsSettings.h +++ b/Source/Engine/Core/Config/GraphicsSettings.h @@ -121,13 +121,13 @@ public: PostProcessSettings PostProcessSettings; /// - /// + /// Whether to enable font fallbacking globally. /// API_FIELD(Attributes = "EditorOrder(12000), EditorDisplay(\"Text Render Settings\", EditorDisplayAttribute.InlineStyle)") bool EnableFontFallback = true; /// - /// + /// The fallback fonts used for text rendering, ignored if null. /// API_FIELD(Attributes = "EditorOrder(12005), EditorDisplay(\"Text Render Settings\", EditorDisplayAttribute.InlineStyle)") FontFallbackList* FallbackFonts; diff --git a/Source/Engine/Render2D/Render2D.h b/Source/Engine/Render2D/Render2D.h index 5813d6f87..6657d8542 100644 --- a/Source/Engine/Render2D/Render2D.h +++ b/Source/Engine/Render2D/Render2D.h @@ -281,6 +281,15 @@ public: } } + /// + /// Draws a text, follows the fallback settings defined in . + /// + /// The font to use. + /// The text to render. + /// The input text range (substring range of the input text parameter). + /// The text color. + /// The text location. + /// The custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture. API_FUNCTION() FORCE_INLINE static void DrawText(Font* font, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Color& color, const Float2& location, MaterialBase* customMaterial = nullptr) { if (EnableFontFallback && FallbackFonts) { DrawTextInternal(font, FallbackFonts, text, textRange, color, location, customMaterial); @@ -290,6 +299,14 @@ public: } } + /// + /// Draws a text with formatting, follows the fallback settings defined in . + /// + /// The font to use. + /// The text to render. + /// The text color. + /// The text layout properties. + /// The custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture. API_FUNCTION() FORCE_INLINE static void DrawText(Font* font, const StringView& text, const Color& color, API_PARAM(Ref) const TextLayoutOptions& layout, MaterialBase* customMaterial = nullptr) { if (EnableFontFallback && FallbackFonts) { DrawTextInternal(font, FallbackFonts, text, color, layout, customMaterial); @@ -299,6 +316,15 @@ public: } } + /// + /// Draws a text with formatting, follows the fallback settings defined in . + /// + /// The font to use. + /// The text to render. + /// The input text range (substring range of the input text parameter). + /// The text color. + /// The text layout properties. + /// The custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture. API_FUNCTION() FORCE_INLINE static void DrawText(Font* font, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Color& color, API_PARAM(Ref) const TextLayoutOptions& layout, MaterialBase* customMaterial = nullptr) { if (EnableFontFallback && FallbackFonts) { DrawTextInternal(font, FallbackFonts, text, textRange, color, layout, customMaterial); diff --git a/Source/Engine/UI/GUI/Style.cs b/Source/Engine/UI/GUI/Style.cs index 6092f0f72..22b8f52af 100644 --- a/Source/Engine/UI/GUI/Style.cs +++ b/Source/Engine/UI/GUI/Style.cs @@ -1,6 +1,5 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. - namespace FlaxEngine.GUI { /// diff --git a/Source/Engine/UI/GUI/Tooltip.cs b/Source/Engine/UI/GUI/Tooltip.cs index 8d35c21b9..734fb078f 100644 --- a/Source/Engine/UI/GUI/Tooltip.cs +++ b/Source/Engine/UI/GUI/Tooltip.cs @@ -243,7 +243,6 @@ namespace FlaxEngine.GUI TextAlignment.Center, TextWrapping.WrapWords ); - } /// From 360c75355c014bdb7be5051ea41460a777781ba4 Mon Sep 17 00:00:00 2001 From: ExMatics HydrogenC <33123710+HydrogenC@users.noreply.github.com> Date: Sun, 3 Dec 2023 16:04:50 +0800 Subject: [PATCH 050/139] Fix build error under non-windows platforms --- Source/Engine/Render2D/Font.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Render2D/Font.cpp b/Source/Engine/Render2D/Font.cpp index 9f28aac01..9a2037b3f 100644 --- a/Source/Engine/Render2D/Font.cpp +++ b/Source/Engine/Render2D/Font.cpp @@ -103,7 +103,7 @@ void Font::Invalidate() _characters.Clear(); } -inline API_FUNCTION() float Font::GetMaxHeight(FontFallbackList* fallbacks) const +float Font::GetMaxHeight(FontFallbackList* fallbacks) const { float height = GetHeight(); auto& fallbackFonts = fallbacks->GetFontList(GetSize()); From 3c19262574bc83a7a523951f4a929325063056fd Mon Sep 17 00:00:00 2001 From: "Mr. Capybara" Date: Sun, 17 Dec 2023 10:15:41 -0400 Subject: [PATCH 051/139] Add parameter to change arrow cap size --- Source/Editor/SceneGraph/Actors/DirectionalLightNode.cs | 2 +- Source/Editor/SceneGraph/Actors/SpotLightNode.cs | 2 +- Source/Editor/Viewport/ViewportDraggingHelper.cs | 2 +- Source/Engine/Debug/DebugDraw.cpp | 6 +++--- Source/Engine/Debug/DebugDraw.cs | 3 ++- Source/Engine/Debug/DebugDraw.h | 7 ++++--- Source/Engine/Physics/Joints/D6Joint.cpp | 3 ++- Source/Engine/Physics/Joints/HingeJoint.cpp | 5 +++-- Source/Engine/Physics/Joints/SphericalJoint.cpp | 3 ++- 9 files changed, 19 insertions(+), 14 deletions(-) diff --git a/Source/Editor/SceneGraph/Actors/DirectionalLightNode.cs b/Source/Editor/SceneGraph/Actors/DirectionalLightNode.cs index 5363d1f55..1eabc946d 100644 --- a/Source/Editor/SceneGraph/Actors/DirectionalLightNode.cs +++ b/Source/Editor/SceneGraph/Actors/DirectionalLightNode.cs @@ -23,7 +23,7 @@ namespace FlaxEditor.SceneGraph.Actors base.OnDebugDraw(data); var transform = Actor.Transform; - DebugDraw.DrawWireArrow(transform.Translation, transform.Orientation, 1.0f, Color.Red, 0.0f, false); + DebugDraw.DrawWireArrow(transform.Translation, transform.Orientation, 1.0f, 0.5f, Color.Red, 0.0f, false); } } } diff --git a/Source/Editor/SceneGraph/Actors/SpotLightNode.cs b/Source/Editor/SceneGraph/Actors/SpotLightNode.cs index b961ce205..567e8e430 100644 --- a/Source/Editor/SceneGraph/Actors/SpotLightNode.cs +++ b/Source/Editor/SceneGraph/Actors/SpotLightNode.cs @@ -23,7 +23,7 @@ namespace FlaxEditor.SceneGraph.Actors base.OnDebugDraw(data); var transform = Actor.Transform; - DebugDraw.DrawWireArrow(transform.Translation, transform.Orientation, 0.3f, Color.Red, 0.0f, false); + DebugDraw.DrawWireArrow(transform.Translation, transform.Orientation, 0.3f, 0.15f, Color.Red, 0.0f, false); } } } diff --git a/Source/Editor/Viewport/ViewportDraggingHelper.cs b/Source/Editor/Viewport/ViewportDraggingHelper.cs index 8a1b4f183..8cbbc5a4f 100644 --- a/Source/Editor/Viewport/ViewportDraggingHelper.cs +++ b/Source/Editor/Viewport/ViewportDraggingHelper.cs @@ -218,7 +218,7 @@ namespace FlaxEditor.Viewport LocalOrientation = RootNode.RaycastNormalRotation(ref hitNormal), Name = item.ShortName }; - DebugDraw.DrawWireArrow(PostProcessSpawnedActorLocation(actor, ref hitNormal), actor.LocalOrientation, 1.0f, Color.Red, 1000000); + DebugDraw.DrawWireArrow(PostProcessSpawnedActorLocation(actor, ref hitNormal), actor.LocalOrientation, 1.0f, 0.5f, Color.Red, 1000000); Spawn(actor, ref hitLocation, ref hitNormal); } else if (hit is StaticModelNode staticModelNode) diff --git a/Source/Engine/Debug/DebugDraw.cpp b/Source/Engine/Debug/DebugDraw.cpp index 059ebbd5d..bd7a218a7 100644 --- a/Source/Engine/Debug/DebugDraw.cpp +++ b/Source/Engine/Debug/DebugDraw.cpp @@ -1940,15 +1940,15 @@ void DebugDraw::DrawWireArc(const Vector3& position, const Quaternion& orientati DrawLine(prevPos, world.GetTranslation(), color, duration, depthTest); } -void DebugDraw::DrawWireArrow(const Vector3& position, const Quaternion& orientation, float scale, const Color& color, float duration, bool depthTest) +void DebugDraw::DrawWireArrow(const Vector3& position, const Quaternion& orientation, float scale, float capScale, const Color& color, float duration, bool depthTest) { Float3 direction, up, right; Float3::Transform(Float3::Forward, orientation, direction); Float3::Transform(Float3::Up, orientation, up); Float3::Transform(Float3::Right, orientation, right); const Vector3 end = position + direction * (100.0f * scale); - const Vector3 capEnd = position + direction * (70.0f * scale); - const float arrowSidesRatio = scale * 30.0f; + const Vector3 capEnd = end - (direction * (100 * Math::Min(capScale, scale * 0.5f))); + const float arrowSidesRatio = Math::Min(capScale, scale * 0.5f) * 30.0f; DrawLine(position, end, color, duration, depthTest); DrawLine(end, capEnd + up * arrowSidesRatio, color, duration, depthTest); diff --git a/Source/Engine/Debug/DebugDraw.cs b/Source/Engine/Debug/DebugDraw.cs index bc000e03d..8ff04d072 100644 --- a/Source/Engine/Debug/DebugDraw.cs +++ b/Source/Engine/Debug/DebugDraw.cs @@ -218,10 +218,11 @@ namespace FlaxEngine /// The arrow origin position. /// The orientation (defines the arrow direction). /// The arrow scale (used to adjust the arrow size). + /// The arrow cap scale. /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - public static void DrawWireArrow(Vector3 position, Quaternion orientation, float scale, Color color, float duration = 0.0f, bool depthTest = true) + public static void DrawWireArrow(Vector3 position, Quaternion orientation, float scale, float capScale, Color color, float duration = 0.0f, bool depthTest = true) { } diff --git a/Source/Engine/Debug/DebugDraw.h b/Source/Engine/Debug/DebugDraw.h index b02b94475..30feb4440 100644 --- a/Source/Engine/Debug/DebugDraw.h +++ b/Source/Engine/Debug/DebugDraw.h @@ -570,10 +570,11 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The arrow origin position. /// The orientation (defines the arrow direction). /// The arrow scale (used to adjust the arrow size). + /// The arrow cap scale. /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawWireArrow(const Vector3& position, const Quaternion& orientation, float scale, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawWireArrow(const Vector3& position, const Quaternion& orientation, float scale, float capScale, const Color& color, float duration = 0.0f, bool depthTest = true); /// /// Draws the box. @@ -650,7 +651,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw #define DEBUG_DRAW_WIRE_CYLINDER(position, orientation, radius, height, color, duration, depthTest) DebugDraw::DrawWireCylinder(position, orientation, radius, height, color, duration, depthTest) #define DEBUG_DRAW_WIRE_CONE(position, orientation, radius, angleXY, angleXZ, color, duration, depthTest) DebugDraw::DrawWireCone(position, orientation, radius, angleXY, angleXZ, color, duration, depthTest) #define DEBUG_DRAW_WIRE_ARC(position, orientation, radius, angle, color, duration, depthTest) DebugDraw::DrawWireArc(position, orientation, radius, angle, color, duration, depthTest) -#define DEBUG_DRAW_WIRE_ARROW(position, orientation, scale, color, duration, depthTest) DebugDraw::DrawWireArrow(position, orientation, scale, color, duration, depthTest) +#define DEBUG_DRAW_WIRE_ARROW(position, orientation, scale, capScale, color, duration, depthTest) DebugDraw::DrawWireArrow(position, orientation, scale, capScale, color, duration, depthTest) #define DEBUG_DRAW_TEXT(text, position, color, size, duration) DebugDraw::DrawText(text, position, color, size, duration) #else @@ -679,7 +680,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw #define DEBUG_DRAW_WIRE_CYLINDER(position, orientation, radius, height, color, duration, depthTest) #define DEBUG_DRAW_WIRE_CONE(position, orientation, radius, angleXY, angleXZ, color, duration, depthTest) #define DEBUG_DRAW_WIRE_ARC(position, orientation, radius, angle, color, duration, depthTest) -#define DEBUG_DRAW_WIRE_ARROW(position, orientation, scale, color, duration, depthTest) +#define DEBUG_DRAW_WIRE_ARROW(position, orientation, scale, capScale, color, duration, depthTest) #define DEBUG_DRAW_TEXT(text, position, color, size, duration) #endif diff --git a/Source/Engine/Physics/Joints/D6Joint.cpp b/Source/Engine/Physics/Joints/D6Joint.cpp index a5ef035a3..685fa760a 100644 --- a/Source/Engine/Physics/Joints/D6Joint.cpp +++ b/Source/Engine/Physics/Joints/D6Joint.cpp @@ -159,7 +159,8 @@ void D6Joint::OnDebugDrawSelected() const float twistSize = 9.0f; const Color swingColor = Color::Green.AlphaMultiplied(0.6f); const Color twistColor = Color::Yellow.AlphaMultiplied(0.5f); - DEBUG_DRAW_WIRE_ARROW(target, targetRotation, swingSize / 100.0f * 0.5f, Color::Red, 0, false); + const float arrowSize = swingSize / 100.0f * 0.5f; + DEBUG_DRAW_WIRE_ARROW(target, targetRotation, arrowSize, arrowSize * 0.5f, Color::Red, 0, false); if (_motion[(int32)D6JointAxis::SwingY] == D6JointMotion::Locked && _motion[(int32)D6JointAxis::SwingZ] == D6JointMotion::Locked) { // Swing is locked diff --git a/Source/Engine/Physics/Joints/HingeJoint.cpp b/Source/Engine/Physics/Joints/HingeJoint.cpp index 7f85941ef..6952711e8 100644 --- a/Source/Engine/Physics/Joints/HingeJoint.cpp +++ b/Source/Engine/Physics/Joints/HingeJoint.cpp @@ -63,8 +63,9 @@ void HingeJoint::OnDebugDrawSelected() const Quaternion targetRotation = GetTargetOrientation() * xRotation; const float size = 15.0f; const Color color = Color::Green.AlphaMultiplied(0.6f); - DEBUG_DRAW_WIRE_ARROW(source, sourceRotation, size / 100.0f * 0.5f, Color::Red, 0, false); - DEBUG_DRAW_WIRE_ARROW(target, targetRotation, size / 100.0f * 0.5f, Color::Blue, 0, false); + const float arrowSize = size / 100.0f * 0.5f; + DEBUG_DRAW_WIRE_ARROW(source, sourceRotation, arrowSize, arrowSize * 0.5f, Color::Red, 0, false); + DEBUG_DRAW_WIRE_ARROW(target, targetRotation, arrowSize, arrowSize * 0.5f, Color::Blue, 0, false); if (EnumHasAnyFlags(_flags, HingeJointFlag::Limit)) { const float upper = Math::Max(_limit.Upper, _limit.Lower); diff --git a/Source/Engine/Physics/Joints/SphericalJoint.cpp b/Source/Engine/Physics/Joints/SphericalJoint.cpp index c44d4bc6e..84ab9d6e9 100644 --- a/Source/Engine/Physics/Joints/SphericalJoint.cpp +++ b/Source/Engine/Physics/Joints/SphericalJoint.cpp @@ -38,8 +38,9 @@ void SphericalJoint::OnDebugDrawSelected() const Vector3 source = GetPosition(); const Vector3 target = GetTargetPosition(); const float size = 15.0f; + const float arrowSize = size / 100.0f * 0.5f; const Color color = Color::Green.AlphaMultiplied(0.6f); - DEBUG_DRAW_WIRE_ARROW(source, GetOrientation(), size / 100.0f * 0.5f, Color::Red, 0, false); + DEBUG_DRAW_WIRE_ARROW(source, GetOrientation(), arrowSize, arrowSize * 0.5f, Color::Red, 0, false); if (EnumHasAnyFlags(_flags, SphericalJointFlag::Limit)) { DEBUG_DRAW_CONE(source, GetOrientation(), size, _limit.YLimitAngle * DegreesToRadians, _limit.ZLimitAngle * DegreesToRadians, color, 0, false); From 4497b2ca7d5ee86f67d8ae19cd1db3220fe1464a Mon Sep 17 00:00:00 2001 From: ExMatics HydrogenC <33123710+HydrogenC@users.noreply.github.com> Date: Tue, 19 Dec 2023 12:36:12 +0800 Subject: [PATCH 052/139] Add fallback settings for control --- Source/Engine/UI/GUI/Common/Label.cs | 27 ++++++++++++++++-- Source/Engine/UI/GUI/Common/TextBox.cs | 39 ++++++++++++++++++++------ 2 files changed, 56 insertions(+), 10 deletions(-) diff --git a/Source/Engine/UI/GUI/Common/Label.cs b/Source/Engine/UI/GUI/Common/Label.cs index 3c7c04fb2..8de54fda3 100644 --- a/Source/Engine/UI/GUI/Common/Label.cs +++ b/Source/Engine/UI/GUI/Common/Label.cs @@ -182,6 +182,12 @@ namespace FlaxEngine.GUI set => _autoFitTextRange = value; } + /// + /// Gets or sets whether to fallback when the primary font cannot render a char. + /// + [EditorOrder(120), DefaultValue(true), Tooltip("Whether to fallback when the font cannot render a char.")] + public bool EnableFontFallback { get; set; } = true; + /// /// Initializes a new instance of the class. /// @@ -233,7 +239,23 @@ namespace FlaxEngine.GUI } } - Render2D.DrawText(_font.GetFont(), Material, _text, rect, color, hAlignment, wAlignment, Wrapping, BaseLinesGapScale, scale); + if (EnableFontFallback) + { + Render2D.DrawText(_font.GetFont(), Material, _text, rect, color, hAlignment, wAlignment, Wrapping, BaseLinesGapScale, scale); + } + else + { + var layout = new TextLayoutOptions + { + Bounds = rect, + HorizontalAlignment = hAlignment, + VerticalAlignment = wAlignment, + TextWrapping = Wrapping, + Scale = scale, + BaseLinesGapScale = BaseLinesGapScale, + }; + Render2D.DrawTextInternal(_font.GetFont(), _text, color, ref layout, Material); + } if (ClipText) Render2D.PopClip(); @@ -254,7 +276,8 @@ namespace FlaxEngine.GUI layout.Bounds.Size.X = Width - Margin.Width; else if (_autoWidth && !_autoHeight) layout.Bounds.Size.Y = Height - Margin.Height; - _textSize = font.MeasureText(_text, ref layout); + _textSize = EnableFontFallback ? + font.MeasureText(_text, ref layout) : font.MeasureTextInternal(_text, ref layout); _textSize.Y *= BaseLinesGapScale; // Check if size is controlled via text diff --git a/Source/Engine/UI/GUI/Common/TextBox.cs b/Source/Engine/UI/GUI/Common/TextBox.cs index 3c0d55008..53266a1b2 100644 --- a/Source/Engine/UI/GUI/Common/TextBox.cs +++ b/Source/Engine/UI/GUI/Common/TextBox.cs @@ -1,6 +1,8 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. +using System.ComponentModel; + namespace FlaxEngine.GUI { /// @@ -65,6 +67,12 @@ namespace FlaxEngine.GUI [EditorDisplay("Text Style"), EditorOrder(2022), Tooltip("The color of the selection (Transparent if not used).")] public Color SelectionColor { get; set; } + /// + /// Gets or sets whether to fallback when the primary font cannot render a char. + /// + [EditorOrder(120), DefaultValue(true), Tooltip("Whether to fallback when the font cannot render a char.")] + public bool EnableFontFallback { get; set; } = true; + /// /// Initializes a new instance of the class. /// @@ -104,7 +112,8 @@ namespace FlaxEngine.GUI return Float2.Zero; } - return font.MeasureText(_text, ref _layout); + return EnableFontFallback ? font.MeasureText(_text, ref _layout) : + font.MeasureTextInternal(_text, ref _layout); } /// @@ -117,8 +126,9 @@ namespace FlaxEngine.GUI return Float2.Zero; } - height = font.GetMaxHeight() / DpiScale; - return font.GetCharPosition(_text, index, ref _layout); + height = (EnableFontFallback ? font.GetMaxHeight() : font.Height) / DpiScale; + return EnableFontFallback ? font.GetCharPosition(_text, index, ref _layout) : + font.GetCharPositionInternal(_text, index, ref _layout); } /// @@ -130,7 +140,8 @@ namespace FlaxEngine.GUI return 0; } - return font.HitTestText(_text, location, ref _layout); + return EnableFontFallback ? font.HitTestText(_text, location, ref _layout) : + font.HitTestTextInternal(_text, location, ref _layout); } /// @@ -169,8 +180,12 @@ namespace FlaxEngine.GUI // Check if sth is selected to draw selection if (HasSelection) { - var leftEdge = font.GetCharPosition(_text, SelectionLeft, ref _layout); - var rightEdge = font.GetCharPosition(_text, SelectionRight, ref _layout); + var leftEdge = EnableFontFallback ? + font.GetCharPosition(_text, SelectionLeft, ref _layout) : + font.GetCharPositionInternal(_text, SelectionLeft, ref _layout); + var rightEdge = EnableFontFallback ? + font.GetCharPosition(_text, SelectionRight, ref _layout) : + font.GetCharPositionInternal(_text, SelectionRight, ref _layout); float fontHeight = font.GetMaxHeight() / DpiScale; // Draw selection background @@ -211,11 +226,19 @@ namespace FlaxEngine.GUI var color = TextColor; if (!enabled) color *= 0.6f; - Render2D.DrawText(font, _text, color, ref _layout, TextMaterial); + if (EnableFontFallback) + Render2D.DrawText(font, _text, color, ref _layout, TextMaterial); + else + // Draw without fallback + Render2D.DrawTextInternal(font, _text, color, ref _layout, TextMaterial); } else if (!string.IsNullOrEmpty(_watermarkText) && !IsFocused) { - Render2D.DrawText(font, _watermarkText, WatermarkTextColor, ref _layout, TextMaterial); + if (EnableFontFallback) + Render2D.DrawText(font, _watermarkText, WatermarkTextColor, ref _layout, TextMaterial); + else + // Draw without fallback + Render2D.DrawTextInternal(font, _watermarkText, WatermarkTextColor, ref _layout, TextMaterial); } // Caret From 4e2f0cd22cf910ed8abe3151f77d71ca23c2408b Mon Sep 17 00:00:00 2001 From: whocares77 <97740209+whocares77@users.noreply.github.com> Date: Tue, 19 Dec 2023 11:02:18 +0300 Subject: [PATCH 053/139] Added "Create parent for selected actors" context menu button --- Source/Editor/Modules/SceneEditingModule.cs | 32 +++++++++++++++++++ Source/Editor/Modules/UIModule.cs | 4 +++ .../Windows/SceneTreeWindow.ContextMenu.cs | 7 ++++ Source/Editor/Windows/SceneTreeWindow.cs | 2 ++ 4 files changed, 45 insertions(+) diff --git a/Source/Editor/Modules/SceneEditingModule.cs b/Source/Editor/Modules/SceneEditingModule.cs index 970ca8e99..3b982d7ad 100644 --- a/Source/Editor/Modules/SceneEditingModule.cs +++ b/Source/Editor/Modules/SceneEditingModule.cs @@ -534,6 +534,38 @@ namespace FlaxEditor.Modules Delete(); } + /// + /// Create parent for selected actors. + /// + public void CreateParentForSelectedActors() + { + Actor actor = new EmptyActor(); + Editor.SceneEditing.Spawn(actor, null, false); + List selection = Editor.SceneEditing.Selection; + for (int i = 0; i < selection.Count; i++) + { + if (selection[i] is ActorNode node) + { + if (node.ParentNode != node.ParentScene) // if parent node is not Scene + { + if (selection.Contains(node.ParentNode)) + { + return; // if parent and child nodes selected together, don't touch child nodes + } + else + { // put created node as child of the Parent Node of node + int parentOrder = node.Actor.OrderInParent; + actor.Parent = node.Actor.Parent; + actor.OrderInParent = parentOrder; + } + } + node.Actor.Parent = actor; + } + } + Editor.SceneEditing.Select(actor); + Editor.Scene.GetActorNode(actor).TreeNode.StartRenaming(Editor.Windows.SceneWin, Editor.Windows.SceneWin.SceneTreePanel); + } + /// /// Duplicates the selected objects. Supports undo/redo. /// diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs index 3386d411c..7920d5de4 100644 --- a/Source/Editor/Modules/UIModule.cs +++ b/Source/Editor/Modules/UIModule.cs @@ -50,6 +50,7 @@ namespace FlaxEditor.Modules private ContextMenuButton _menuEditCut; private ContextMenuButton _menuEditCopy; private ContextMenuButton _menuEditPaste; + private ContextMenuButton _menuCreateParentForSelectedActors; private ContextMenuButton _menuEditDelete; private ContextMenuButton _menuEditDuplicate; private ContextMenuButton _menuEditSelectAll; @@ -549,6 +550,8 @@ namespace FlaxEditor.Modules _menuEditCopy = cm.AddButton("Copy", inputOptions.Copy, Editor.SceneEditing.Copy); _menuEditPaste = cm.AddButton("Paste", inputOptions.Paste, Editor.SceneEditing.Paste); cm.AddSeparator(); + _menuCreateParentForSelectedActors = cm.AddButton("Create parent for selected actors", Editor.SceneEditing.CreateParentForSelectedActors); + cm.AddSeparator(); _menuEditDelete = cm.AddButton("Delete", inputOptions.Delete, Editor.SceneEditing.Delete); _menuEditDuplicate = cm.AddButton("Duplicate", inputOptions.Duplicate, Editor.SceneEditing.Duplicate); cm.AddSeparator(); @@ -858,6 +861,7 @@ namespace FlaxEditor.Modules _menuEditCut.Enabled = hasSthSelected; _menuEditCopy.Enabled = hasSthSelected; _menuEditPaste.Enabled = canEditScene; + _menuCreateParentForSelectedActors.Enabled = canEditScene && hasSthSelected; _menuEditDelete.Enabled = hasSthSelected; _menuEditDuplicate.Enabled = hasSthSelected; _menuEditSelectAll.Enabled = Level.IsAnySceneLoaded; diff --git a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs index 0c8e0f283..fa4abfeca 100644 --- a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs +++ b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs @@ -132,6 +132,13 @@ namespace FlaxEditor.Windows b = contextMenu.AddButton("Cut", inputOptions.Cut, Editor.SceneEditing.Cut); b.Enabled = canEditScene; + // Create a new hierarchy from selected actors + + contextMenu.AddSeparator(); + + b = contextMenu.AddButton("Create parent for selected actors", Editor.SceneEditing.CreateParentForSelectedActors); + b.Enabled = canEditScene && hasSthSelected; + // Prefab options contextMenu.AddSeparator(); diff --git a/Source/Editor/Windows/SceneTreeWindow.cs b/Source/Editor/Windows/SceneTreeWindow.cs index 63ba7b960..e4af43dee 100644 --- a/Source/Editor/Windows/SceneTreeWindow.cs +++ b/Source/Editor/Windows/SceneTreeWindow.cs @@ -23,6 +23,8 @@ namespace FlaxEditor.Windows /// public partial class SceneTreeWindow : SceneEditorWindow { + public Panel SceneTreePanel => _sceneTreePanel; + private TextBox _searchBox; private Tree _tree; private Panel _sceneTreePanel; From 52da42e62e4a88096984971a84157937961f5cef Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Thu, 11 Jan 2024 14:19:51 -0600 Subject: [PATCH 054/139] Add reload project menu button --- Source/Editor/Modules/UIModule.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs index 3386d411c..195df7728 100644 --- a/Source/Editor/Modules/UIModule.cs +++ b/Source/Editor/Modules/UIModule.cs @@ -535,6 +535,7 @@ namespace FlaxEditor.Modules _menuFileRecompileScripts = cm.AddButton("Recompile scripts", inputOptions.RecompileScripts, ScriptsBuilder.Compile); cm.AddSeparator(); cm.AddButton("Open project...", OpenProject); + cm.AddButton("Reload project", ReloadProject); cm.AddSeparator(); cm.AddButton("Exit", "Alt+F4", () => Editor.Windows.MainWindow.Close(ClosingReason.User)); @@ -822,6 +823,13 @@ namespace FlaxEditor.Modules } } + private void ReloadProject() + { + // Open project, then close it + Editor.OpenProject(Editor.GameProject.ProjectPath); + Editor.Windows.MainWindow.Close(ClosingReason.User); + } + private void OnMenuFileShowHide(Control control) { if (control.Visible == false) From 64e3db3a98a2d5c9577f8783574df2c114f18daa Mon Sep 17 00:00:00 2001 From: NoriteSC <53096989+NoriteSC@users.noreply.github.com> Date: Sat, 13 Jan 2024 17:32:07 +0100 Subject: [PATCH 055/139] Update Array.h added IsValidIndex --- Source/Engine/Core/Collections/Array.h | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Core/Collections/Array.h b/Source/Engine/Core/Collections/Array.h index a8390f051..9c50c5ccf 100644 --- a/Source/Engine/Core/Collections/Array.h +++ b/Source/Engine/Core/Collections/Array.h @@ -737,7 +737,18 @@ public: ::Swap(other, *this); } } - + + /// + /// Determines if is valid index. + /// + /// The index. + /// + /// true if is valid a index; otherwise, false. + /// + bool IsValidIndex(int index) const + { + return index < _count && index >= 0; + } /// /// Reverses the order of the added items in the collection. /// From a9259b20a47bd43c7b35dc7abb9faf65e5ee1d69 Mon Sep 17 00:00:00 2001 From: nothingTVatYT <34131388+nothingTVatYT@users.noreply.github.com> Date: Wed, 7 Feb 2024 02:01:39 +0100 Subject: [PATCH 056/139] fix off-by-one in collection range check The check failed randomly after script reloading an dissued the following error: ``` 00:19:37.789 ]: [Error] Exception has been thrown during Window.OnDraw. Index was out of range. Must be non-negative and less than the size of the collection. (Parameter 'index') [ 00:19:37.805 ]: [Warning] Exception has been thrown during Window.OnUpdate. Index was out of range. Must be non-negative and less than the size of the collection. (Parameter 'index') Stack strace: at FlaxEditor.GUI.Tabs.Tabs.get_SelectedTab() in /home/me/Flax/FlaxEngine/Source/Editor/GUI/Tabs/Tabs.cs:line 242 ``` --- Source/Editor/GUI/Tabs/Tabs.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/GUI/Tabs/Tabs.cs b/Source/Editor/GUI/Tabs/Tabs.cs index 3c70363e7..c9b1e1eff 100644 --- a/Source/Editor/GUI/Tabs/Tabs.cs +++ b/Source/Editor/GUI/Tabs/Tabs.cs @@ -239,7 +239,7 @@ namespace FlaxEditor.GUI.Tabs /// public Tab SelectedTab { - get => _selectedIndex < 0 || Children.Count <= _selectedIndex ? null : Children[_selectedIndex + 1] as Tab; + get => _selectedIndex < 0 || Children.Count <= (_selectedIndex+1) ? null : Children[_selectedIndex + 1] as Tab; set => SelectedTabIndex = value != null ? Children.IndexOf(value) - 1 : -1; } From c67d352065c6b0b1e431816ed3b353c18a069ea5 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 7 Feb 2024 12:51:52 +0100 Subject: [PATCH 057/139] Refactor win32 exe icon updating #928 --- .../Platform/Windows/WindowsPlatformTools.cpp | 454 +++++++++++++++- Source/Editor/Utilities/EditorUtilities.cpp | 492 ------------------ Source/Editor/Utilities/EditorUtilities.h | 8 - 3 files changed, 453 insertions(+), 501 deletions(-) diff --git a/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp b/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp index feca5c0a0..447331ac9 100644 --- a/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp +++ b/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp @@ -5,11 +5,463 @@ #include "WindowsPlatformTools.h" #include "Engine/Platform/FileSystem.h" #include "Engine/Platform/Windows/WindowsPlatformSettings.h" +#include "Engine/Core/Math/Color32.h" #include "Engine/Core/Config/GameSettings.h" #include "Editor/Utilities/EditorUtilities.h" #include "Engine/Graphics/Textures/TextureData.h" +#include "Engine/Tools/TextureTool/TextureTool.h" #include "Engine/Content/Content.h" #include "Engine/Content/JsonAsset.h" +#include + +#define MSDOS_SIGNATURE 0x5A4D +#define PE_SIGNATURE 0x00004550 +#define PE_32BIT_SIGNATURE 0x10B +#define PE_64BIT_SIGNATURE 0x20B +#define PE_SECTION_UNINITIALIZED_DATA 0x00000080 +#define PE_IMAGE_DIRECTORY_ENTRY_RESOURCE 2 +#define PE_IMAGE_RT_ICON 3 + +/// +/// MS-DOS header found at the beginning in a PE format file. +/// +struct MSDOSHeader +{ + uint16 signature; + uint16 lastSize; + uint16 numBlocks; + uint16 numReloc; + uint16 hdrSize; + uint16 minAlloc; + uint16 maxAlloc; + uint16 ss; + uint16 sp; + uint16 checksum; + uint16 ip; + uint16 cs; + uint16 relocPos; + uint16 numOverlay; + uint16 reserved1[4]; + uint16 oemId; + uint16 oemInfo; + uint16 reserved2[10]; + uint32 lfanew; +}; + +/// +/// COFF header found in a PE format file. +/// +struct COFFHeader +{ + uint16 machine; + uint16 numSections; + uint32 timeDateStamp; + uint32 ptrSymbolTable; + uint32 numSymbols; + uint16 sizeOptHeader; + uint16 characteristics; +}; + +/// +/// Contains address and size of data areas in a PE image. +/// +struct PEDataDirectory +{ + uint32 virtualAddress; + uint32 size; +}; + +/// +/// Optional header in a 32-bit PE format file. +/// +struct PEOptionalHeader32 +{ + uint16 signature; + uint8 majorLinkerVersion; + uint8 minorLinkerVersion; + uint32 sizeCode; + uint32 sizeInitializedData; + uint32 sizeUninitializedData; + uint32 addressEntryPoint; + uint32 baseCode; + uint32 baseData; + uint32 baseImage; + uint32 alignmentSection; + uint32 alignmentFile; + uint16 majorOSVersion; + uint16 minorOSVersion; + uint16 majorImageVersion; + uint16 minorImageVersion; + uint16 majorSubsystemVersion; + uint16 minorSubsystemVersion; + uint32 reserved; + uint32 sizeImage; + uint32 sizeHeaders; + uint32 checksum; + uint16 subsystem; + uint16 characteristics; + uint32 sizeStackReserve; + uint32 sizeStackCommit; + uint32 sizeHeapReserve; + uint32 sizeHeapCommit; + uint32 loaderFlags; + uint32 NumRvaAndSizes; + PEDataDirectory dataDirectory[16]; +}; + +/// +/// Optional header in a 64-bit PE format file. +/// +struct PEOptionalHeader64 +{ + uint16 signature; + uint8 majorLinkerVersion; + uint8 minorLinkerVersion; + uint32 sizeCode; + uint32 sizeInitializedData; + uint32 sizeUninitializedData; + uint32 addressEntryPoint; + uint32 baseCode; + uint64 baseImage; + uint32 alignmentSection; + uint32 alignmentFile; + uint16 majorOSVersion; + uint16 minorOSVersion; + uint16 majorImageVersion; + uint16 minorImageVersion; + uint16 majorSubsystemVersion; + uint16 minorSubsystemVersion; + uint32 reserved; + uint32 sizeImage; + uint32 sizeHeaders; + uint32 checksum; + uint16 subsystem; + uint16 characteristics; + uint64 sizeStackReserve; + uint64 sizeStackCommit; + uint64 sizeHeapReserve; + uint64 sizeHeapCommit; + uint32 loaderFlags; + uint32 NumRvaAndSizes; + PEDataDirectory dataDirectory[16]; +}; + +/// +/// A section header in a PE format file. +/// +struct PESectionHeader +{ + char name[8]; + uint32 virtualSize; + uint32 relativeVirtualAddress; + uint32 physicalSize; + uint32 physicalAddress; + uint8 deprecated[12]; + uint32 flags; +}; + +/// +/// A resource table header within a .rsrc section in a PE format file. +/// +struct PEImageResourceDirectory +{ + uint32 flags; + uint32 timeDateStamp; + uint16 majorVersion; + uint16 minorVersion; + uint16 numNamedEntries; + uint16 numIdEntries; +}; + +/// +/// A single entry in a resource table within a .rsrc section in a PE format file. +/// +struct PEImageResourceEntry +{ + uint32 type; + uint32 offsetDirectory : 31; + uint32 isDirectory : 1; +}; + +/// +/// An entry in a resource table referencing resource data. Found within a .rsrc section in a PE format file. +/// +struct PEImageResourceEntryData +{ + uint32 offsetData; + uint32 size; + uint32 codePage; + uint32 resourceHandle; +}; + +/// +/// Header used in icon file format. +/// +struct IconHeader +{ + uint32 size; + int32 width; + int32 height; + uint16 planes; + uint16 bitCount; + uint32 compression; + uint32 sizeImage; + int32 xPelsPerMeter; + int32 yPelsPerMeter; + uint32 clrUsed; + uint32 clrImportant; +}; + +void UpdateIconData(uint8* iconData, const TextureData* icon) +{ + IconHeader* iconHeader = (IconHeader*)iconData; + if (iconHeader->size != sizeof(IconHeader) || iconHeader->compression != 0 || iconHeader->planes != 1 || iconHeader->bitCount != 32) + { + // Unsupported format + return; + } + uint8* iconPixels = iconData + sizeof(IconHeader); + const uint32 width = iconHeader->width; + const uint32 height = iconHeader->height / 2; + + // Try to pick a proper mip (require the same size) + int32 srcPixelsMip = 0; + const int32 mipLevels = icon->GetMipLevels(); + for (int32 mipIndex = 0; mipIndex < mipLevels; mipIndex++) + { + const uint32 iconWidth = Math::Max(1, icon->Width >> mipIndex); + const uint32 iconHeight = Math::Max(1, icon->Height >> mipIndex); + if (width == iconWidth && height == iconHeight) + { + srcPixelsMip = mipIndex; + break; + } + } + const TextureMipData* srcPixels = icon->GetData(0, srcPixelsMip); + const Color32* srcPixelsData = (Color32*)srcPixels->Data.Get(); + const Int2 srcPixelsSize(Math::Max(1, icon->Width >> srcPixelsMip), Math::Max(1, icon->Height >> srcPixelsMip)); + const auto sampler = TextureTool::GetSampler(icon->Format); + ASSERT_LOW_LAYER(sampler); + + // Write colors + uint32* colorData = (uint32*)iconPixels; + uint32 idx = 0; + for (int32 y = (int32)height - 1; y >= 0; y--) + { + float v = (float)y / height; + for (uint32 x = 0; x < width; x++) + { + float u = (float)x / width; + const Color c = TextureTool::SampleLinear(sampler, Float2(u, v), srcPixelsData, srcPixelsSize, srcPixels->RowPitch); + colorData[idx++] = Color32(c).GetAsBGRA(); + } + } + + // Write AND mask + uint32 colorDataSize = width * height * sizeof(uint32); + uint8* maskData = iconPixels + colorDataSize; + uint32 numPackedPixels = width / 8; // One per bit in byte + for (int32 y = (int32)height - 1; y >= 0; y--) + { + uint8 mask = 0; + float v = (float)y / height; + for (uint32 packedX = 0; packedX < numPackedPixels; packedX++) + { + for (uint32 pixelIdx = 0; pixelIdx < 8; pixelIdx++) + { + uint32 x = packedX * 8 + pixelIdx; + float u = (float)x / width; + const Color c = TextureTool::SampleLinear(sampler, Float2(u, v), srcPixelsData, srcPixelsSize, srcPixels->RowPitch); + if (c.A < 0.25f) + mask |= 1 << (7 - pixelIdx); + } + *maskData = mask; + maskData++; + } + } +} + +void SetIconData(PEImageResourceDirectory* base, PEImageResourceDirectory* current, uint8* imageData, uint32 sectionAddress, const TextureData* iconRGBA8) +{ + uint32 numEntries = current->numIdEntries; // Not supporting name entries + PEImageResourceEntry* entries = (PEImageResourceEntry*)(current + 1); + for (uint32 i = 0; i < numEntries; i++) + { + // Only at root does the type identify resource type + if (base == current && entries[i].type != PE_IMAGE_RT_ICON) + continue; + + if (entries[i].isDirectory) + { + PEImageResourceDirectory* child = (PEImageResourceDirectory*)(((uint8*)base) + entries[i].offsetDirectory); + SetIconData(base, child, imageData, sectionAddress, iconRGBA8); + } + else + { + PEImageResourceEntryData* data = (PEImageResourceEntryData*)(((uint8*)base) + entries[i].offsetDirectory); + uint8* iconData = imageData + (data->offsetData - sectionAddress); + UpdateIconData(iconData, iconRGBA8); + } + } +} + +bool UpdateExeIcon(const String& path, const TextureData& icon) +{ + if (!FileSystem::FileExists(path)) + { + LOG(Warning, "Missing file"); + return true; + } + if (icon.Width < 1 || icon.Height < 1 || icon.GetMipLevels() <= 0) + { + LOG(Warning, "Inalid icon data"); + return true; + } + + // Ensure that image format can be sampled + const TextureData* iconRGBA8 = &icon; + TextureData tmpData1; + //if (icon.Format != PixelFormat::R8G8B8A8_UNorm) + if (TextureTool::GetSampler(icon.Format) == nullptr) + { + if (TextureTool::Convert(tmpData1, *iconRGBA8, PixelFormat::R8G8B8A8_UNorm)) + { + LOG(Warning, "Failed convert icon data."); + return true; + } + iconRGBA8 = &tmpData1; + } + + // Use fixed-size input icon image + TextureData tmpData2; + if (iconRGBA8->Width != 256 || iconRGBA8->Height != 256) + { + if (TextureTool::Resize(tmpData2, *iconRGBA8, 256, 256)) + { + LOG(Warning, "Failed resize icon data."); + return true; + } + iconRGBA8 = &tmpData2; + } + + // A PE file is structured as such: + // - MSDOS Header + // - PE Signature + // - COFF Header + // - PE Optional Header + // - One or multiple sections + // - .code + // - .data + // - ... + // - .rsrc + // - icon/cursor/etc data + + std::fstream stream; +#if PLATFORM_WINDOWS + stream.open(path.Get(), std::ios::in | std::ios::out | std::ios::binary); +#else + StringAsANSI<> pathAnsi(path.Get()); + stream.open(pathAnsi.Get(), std::ios::in | std::ios::out | std::ios::binary); +#endif + if (!stream.is_open()) + { + LOG(Warning, "Cannot open file"); + return true; + } + + // First check magic number to ensure file is even an executable + uint16 magicNum; + stream.read((char*)&magicNum, sizeof(magicNum)); + if (magicNum != MSDOS_SIGNATURE) + { + LOG(Warning, "Provided file is not a valid executable."); + return true; + } + + // Read the MSDOS header and skip over it + stream.seekg(0); + MSDOSHeader msdosHeader; + stream.read((char*)&msdosHeader, sizeof(MSDOSHeader)); + + // Read PE signature + stream.seekg(msdosHeader.lfanew); + uint32 peSignature; + stream.read((char*)&peSignature, sizeof(peSignature)); + if (peSignature != PE_SIGNATURE) + { + LOG(Warning, "Provided file is not in PE format."); + return true; + } + + // Read COFF header + COFFHeader coffHeader; + stream.read((char*)&coffHeader, sizeof(COFFHeader)); + if (coffHeader.sizeOptHeader == 0) + { + LOG(Warning, "Provided file is not a valid executable."); + return true; + } + uint32 sectionHeadersCount = coffHeader.numSections; + + // Read optional header + auto optionalHeaderPos = stream.tellg(); + uint16 optionalHeaderSignature; + stream.read((char*)&optionalHeaderSignature, sizeof(optionalHeaderSignature)); + PEDataDirectory* dataDirectory = nullptr; + stream.seekg(optionalHeaderPos); + if (optionalHeaderSignature == PE_32BIT_SIGNATURE) + { + PEOptionalHeader32 optionalHeader; + stream.read((char*)&optionalHeader, sizeof(optionalHeader)); + dataDirectory = optionalHeader.dataDirectory + PE_IMAGE_DIRECTORY_ENTRY_RESOURCE; + } + else if (optionalHeaderSignature == PE_64BIT_SIGNATURE) + { + PEOptionalHeader64 optionalHeader; + stream.read((char*)&optionalHeader, sizeof(optionalHeader)); + dataDirectory = optionalHeader.dataDirectory + PE_IMAGE_DIRECTORY_ENTRY_RESOURCE; + } + else + { + LOG(Warning, "Unrecognized PE format."); + return true; + } + + // Read section headers + auto sectionHeaderPos = optionalHeaderPos + (std::ifstream::pos_type)coffHeader.sizeOptHeader; + stream.seekg(sectionHeaderPos); + Array sectionHeaders; + sectionHeaders.Resize(sectionHeadersCount); + stream.read((char*)sectionHeaders.Get(), sizeof(PESectionHeader) * sectionHeadersCount); + + // Look for .rsrc section header + for (uint32 i = 0; i < sectionHeadersCount; i++) + { + PESectionHeader& sectionHeader = sectionHeaders[i]; + if (sectionHeader.flags & PE_SECTION_UNINITIALIZED_DATA) + continue; + if (strcmp(sectionHeader.name, ".rsrc") == 0) + { + uint32 imageSize = sectionHeader.physicalSize; + Array imageData; + imageData.Resize(imageSize); + + stream.seekg(sectionHeader.physicalAddress); + stream.read((char*)imageData.Get(), imageSize); + + uint32 resourceDirOffset = dataDirectory->virtualAddress - sectionHeader.relativeVirtualAddress; + PEImageResourceDirectory* resourceDirectory = (PEImageResourceDirectory*)&imageData.Get()[resourceDirOffset]; + + SetIconData(resourceDirectory, resourceDirectory, imageData.Get(), sectionHeader.relativeVirtualAddress, iconRGBA8); + stream.seekp(sectionHeader.physicalAddress); + stream.write((char*)imageData.Get(), imageSize); + } + } + + stream.close(); + + return false; +} IMPLEMENT_ENGINE_SETTINGS_GETTER(WindowsPlatformSettings, WindowsPlatform); @@ -50,7 +502,7 @@ bool WindowsPlatformTools::OnDeployBinaries(CookingData& data) TextureData iconData; if (!EditorUtilities::GetApplicationImage(platformSettings->OverrideIcon, iconData)) { - if (EditorUtilities::UpdateExeIcon(files[0], iconData)) + if (UpdateExeIcon(files[0], iconData)) { data.Error(TEXT("Failed to change output executable file icon.")); return true; diff --git a/Source/Editor/Utilities/EditorUtilities.cpp b/Source/Editor/Utilities/EditorUtilities.cpp index a2418ceb1..a4000a324 100644 --- a/Source/Editor/Utilities/EditorUtilities.cpp +++ b/Source/Editor/Utilities/EditorUtilities.cpp @@ -9,7 +9,6 @@ #include "Engine/Graphics/Textures/TextureData.h" #include "Engine/Graphics/PixelFormatExtensions.h" #include "Engine/Tools/TextureTool/TextureTool.h" -#include "Engine/Core/Math/Color32.h" #include "Engine/Core/Config/GameSettings.h" #include "Engine/Content/Content.h" #include "Engine/Content/AssetReference.h" @@ -20,497 +19,6 @@ #endif #include -#define MSDOS_SIGNATURE 0x5A4D -#define PE_SIGNATURE 0x00004550 -#define PE_32BIT_SIGNATURE 0x10B -#define PE_64BIT_SIGNATURE 0x20B -#define PE_NUM_DIRECTORY_ENTRIES 16 -#define PE_SECTION_UNINITIALIZED_DATA 0x00000080 -#define PE_IMAGE_DIRECTORY_ENTRY_RESOURCE 2 -#define PE_IMAGE_RT_ICON 3 - -/// -/// MS-DOS header found at the beginning in a PE format file. -/// -struct MSDOSHeader -{ - uint16 signature; - uint16 lastSize; - uint16 numBlocks; - uint16 numReloc; - uint16 hdrSize; - uint16 minAlloc; - uint16 maxAlloc; - uint16 ss; - uint16 sp; - uint16 checksum; - uint16 ip; - uint16 cs; - uint16 relocPos; - uint16 numOverlay; - uint16 reserved1[4]; - uint16 oemId; - uint16 oemInfo; - uint16 reserved2[10]; - uint32 lfanew; -}; - -/// -/// COFF header found in a PE format file. -/// -struct COFFHeader -{ - uint16 machine; - uint16 numSections; - uint32 timeDateStamp; - uint32 ptrSymbolTable; - uint32 numSymbols; - uint16 sizeOptHeader; - uint16 characteristics; -}; - -/// -/// Contains address and size of data areas in a PE image. -/// -struct PEDataDirectory -{ - uint32 virtualAddress; - uint32 size; -}; - -/// -/// Optional header in a 32-bit PE format file. -/// -struct PEOptionalHeader32 -{ - uint16 signature; - uint8 majorLinkerVersion; - uint8 minorLinkerVersion; - uint32 sizeCode; - uint32 sizeInitializedData; - uint32 sizeUninitializedData; - uint32 addressEntryPoint; - uint32 baseCode; - uint32 baseData; - uint32 baseImage; - uint32 alignmentSection; - uint32 alignmentFile; - uint16 majorOSVersion; - uint16 minorOSVersion; - uint16 majorImageVersion; - uint16 minorImageVersion; - uint16 majorSubsystemVersion; - uint16 minorSubsystemVersion; - uint32 reserved; - uint32 sizeImage; - uint32 sizeHeaders; - uint32 checksum; - uint16 subsystem; - uint16 characteristics; - uint32 sizeStackReserve; - uint32 sizeStackCommit; - uint32 sizeHeapReserve; - uint32 sizeHeapCommit; - uint32 loaderFlags; - uint32 NumRvaAndSizes; - PEDataDirectory dataDirectory[16]; -}; - -/// -/// Optional header in a 64-bit PE format file. -/// -struct PEOptionalHeader64 -{ - uint16 signature; - uint8 majorLinkerVersion; - uint8 minorLinkerVersion; - uint32 sizeCode; - uint32 sizeInitializedData; - uint32 sizeUninitializedData; - uint32 addressEntryPoint; - uint32 baseCode; - uint64 baseImage; - uint32 alignmentSection; - uint32 alignmentFile; - uint16 majorOSVersion; - uint16 minorOSVersion; - uint16 majorImageVersion; - uint16 minorImageVersion; - uint16 majorSubsystemVersion; - uint16 minorSubsystemVersion; - uint32 reserved; - uint32 sizeImage; - uint32 sizeHeaders; - uint32 checksum; - uint16 subsystem; - uint16 characteristics; - uint64 sizeStackReserve; - uint64 sizeStackCommit; - uint64 sizeHeapReserve; - uint64 sizeHeapCommit; - uint32 loaderFlags; - uint32 NumRvaAndSizes; - PEDataDirectory dataDirectory[16]; -}; - -/// -/// A section header in a PE format file. -/// -struct PESectionHeader -{ - char name[8]; - uint32 virtualSize; - uint32 relativeVirtualAddress; - uint32 physicalSize; - uint32 physicalAddress; - uint8 deprecated[12]; - uint32 flags; -}; - -/// -/// A resource table header within a .rsrc section in a PE format file. -/// -struct PEImageResourceDirectory -{ - uint32 flags; - uint32 timeDateStamp; - uint16 majorVersion; - uint16 minorVersion; - uint16 numNamedEntries; - uint16 numIdEntries; -}; - -/// -/// A single entry in a resource table within a .rsrc section in a PE format file. -/// -struct PEImageResourceEntry -{ - uint32 type; - uint32 offsetDirectory : 31; - uint32 isDirectory : 1; -}; - -/// -/// An entry in a resource table referencing resource data. Found within a .rsrc section in a PE format file. -/// -struct PEImageResourceEntryData -{ - uint32 offsetData; - uint32 size; - uint32 codePage; - uint32 resourceHandle; -}; - -/// -/// Header used in icon file format. -/// -struct IconHeader -{ - uint32 size; - int32 width; - int32 height; - uint16 planes; - uint16 bitCount; - uint32 compression; - uint32 sizeImage; - int32 xPelsPerMeter; - int32 yPelsPerMeter; - uint32 clrUsed; - uint32 clrImportant; -}; - -void UpdateIconData(uint8* iconData, const TextureData* icon) -{ - IconHeader* iconHeader = (IconHeader*)iconData; - - if (iconHeader->size != sizeof(IconHeader) || iconHeader->compression != 0 || iconHeader->planes != 1 || iconHeader->bitCount != 32) - { - // Unsupported format - return; - } - - uint8* iconPixels = iconData + sizeof(IconHeader); - uint32 width = iconHeader->width; - uint32 height = iconHeader->height / 2; - - // Check if can use mip from texture data or sample different mip - uint32 iconTexSize; - if (width != height) - { - // Only square icons are supported - return; - } - if (Math::IsPowerOfTwo(width)) - { - // Use mip - iconTexSize = width; - } - else - { - // Use resized mip - iconTexSize = Math::RoundUpToPowerOf2(width); - } - - // Try to pick a proper mip (require the same size) - const TextureMipData* srcPixels = nullptr; - int32 mipLevels = icon->GetMipLevels(); - for (int32 mipIndex = 0; mipIndex < mipLevels; mipIndex++) - { - uint32 iconWidth = Math::Max(1, icon->Width >> mipIndex); - uint32 iconHeight = Math::Max(1, icon->Height >> mipIndex); - - if (iconTexSize == iconWidth && iconTexSize == iconHeight) - { - srcPixels = icon->GetData(0, mipIndex); - break; - } - } - if (srcPixels == nullptr) - { - // No icon of this size provided - return; - } - - // Write colors - uint32* colorData = (uint32*)iconPixels; - - uint32 idx = 0; - for (int32 y = (int32)height - 1; y >= 0; y--) - { - float v = (float)y / height; - for (uint32 x = 0; x < width; x++) - { - float u = (float)x / width; - - int32 i = (int32)(v * iconTexSize) * srcPixels->RowPitch + (int32)(u * iconTexSize) * sizeof(Color32); - colorData[idx++] = ((Color32*)&srcPixels->Data.Get()[i])->GetAsBGRA(); - } - } - - // Write AND mask - uint32 colorDataSize = width * height * sizeof(uint32); - uint8* maskData = iconPixels + colorDataSize; - - // One per bit in byte - uint32 numPackedPixels = width / 8; - - for (int32 y = (int32)height - 1; y >= 0; y--) - { - uint8 mask = 0; - float v = (float)y / height; - for (uint32 packedX = 0; packedX < numPackedPixels; packedX++) - { - for (uint32 pixelIdx = 0; pixelIdx < 8; pixelIdx++) - { - uint32 x = packedX * 8 + pixelIdx; - float u = (float)x / width; - int32 i = (int32)(v * iconTexSize) * srcPixels->RowPitch + (int32)(u * iconTexSize) * sizeof(Color32); - Color32 color = *((Color32*)&srcPixels->Data.Get()[i]); - if (color.A < 64) - mask |= 1 << (7 - pixelIdx); - } - - *maskData = mask; - maskData++; - } - } -} - -void SetIconData(PEImageResourceDirectory* base, PEImageResourceDirectory* current, uint8* imageData, uint32 sectionAddress, const TextureData* iconRGBA8) -{ - uint32 numEntries = current->numIdEntries; // Not supporting name entries - PEImageResourceEntry* entries = (PEImageResourceEntry*)(current + 1); - - for (uint32 i = 0; i < numEntries; i++) - { - // Only at root does the type identify resource type - if (base == current && entries[i].type != PE_IMAGE_RT_ICON) - continue; - - if (entries[i].isDirectory) - { - PEImageResourceDirectory* child = (PEImageResourceDirectory*)(((uint8*)base) + entries[i].offsetDirectory); - SetIconData(base, child, imageData, sectionAddress, iconRGBA8); - } - else - { - PEImageResourceEntryData* data = (PEImageResourceEntryData*)(((uint8*)base) + entries[i].offsetDirectory); - - uint8* iconData = imageData + (data->offsetData - sectionAddress); - UpdateIconData(iconData, iconRGBA8); - } - } -} - -bool EditorUtilities::UpdateExeIcon(const String& path, const TextureData& icon) -{ - // Validate input - if (!FileSystem::FileExists(path)) - { - LOG(Warning, "Missing file"); - return true; - } - if (icon.Width < 1 || icon.Height < 1 || icon.GetMipLevels() <= 0) - { - LOG(Warning, "Inalid icon data"); - return true; - } - - // Convert to RGBA8 format if need to - const TextureData* iconRGBA8 = &icon; - TextureData tmpData1; - if (icon.Format != PixelFormat::R8G8B8A8_UNorm) - { - if (TextureTool::Convert(tmpData1, *iconRGBA8, PixelFormat::R8G8B8A8_UNorm)) - { - LOG(Warning, "Failed convert icon data."); - return true; - } - iconRGBA8 = &tmpData1; - } - - // Resize if need to - TextureData tmpData2; - if (iconRGBA8->Width > 256 || iconRGBA8->Height > 256) - { - if (TextureTool::Resize(tmpData2, *iconRGBA8, 256, 256)) - { - LOG(Warning, "Failed resize icon data."); - return true; - } - iconRGBA8 = &tmpData2; - } - - // A PE file is structured as such: - // - MSDOS Header - // - PE Signature - // - COFF Header - // - PE Optional Header - // - One or multiple sections - // - .code - // - .data - // - ... - // - .rsrc - // - icon/cursor/etc data - - std::fstream stream; -#if PLATFORM_WINDOWS - stream.open(path.Get(), std::ios::in | std::ios::out | std::ios::binary); -#else - StringAsANSI<> pathAnsi(path.Get()); - stream.open(pathAnsi.Get(), std::ios::in | std::ios::out | std::ios::binary); -#endif - if (!stream.is_open()) - { - LOG(Warning, "Cannot open file"); - return true; - } - - // First check magic number to ensure file is even an executable - uint16 magicNum; - stream.read((char*)&magicNum, sizeof(magicNum)); - if (magicNum != MSDOS_SIGNATURE) - { - LOG(Warning, "Provided file is not a valid executable."); - return true; - } - - // Read the MSDOS header and skip over it - stream.seekg(0); - - MSDOSHeader msdosHeader; - stream.read((char*)&msdosHeader, sizeof(MSDOSHeader)); - - // Read PE signature - stream.seekg(msdosHeader.lfanew); - - uint32 peSignature; - stream.read((char*)&peSignature, sizeof(peSignature)); - - if (peSignature != PE_SIGNATURE) - { - LOG(Warning, "Provided file is not in PE format."); - return true; - } - - // Read COFF header - COFFHeader coffHeader; - stream.read((char*)&coffHeader, sizeof(COFFHeader)); - - // .exe files always have an optional header - if (coffHeader.sizeOptHeader == 0) - { - LOG(Warning, "Provided file is not a valid executable."); - return true; - } - - uint32 numSectionHeaders = coffHeader.numSections; - - // Read optional header - auto optionalHeaderPos = stream.tellg(); - - uint16 optionalHeaderSignature; - stream.read((char*)&optionalHeaderSignature, sizeof(optionalHeaderSignature)); - - PEDataDirectory* dataDirectory = nullptr; - stream.seekg(optionalHeaderPos); - if (optionalHeaderSignature == PE_32BIT_SIGNATURE) - { - PEOptionalHeader32 optionalHeader; - stream.read((char*)&optionalHeader, sizeof(optionalHeader)); - - dataDirectory = optionalHeader.dataDirectory + PE_IMAGE_DIRECTORY_ENTRY_RESOURCE; - } - else if (optionalHeaderSignature == PE_64BIT_SIGNATURE) - { - PEOptionalHeader64 optionalHeader; - stream.read((char*)&optionalHeader, sizeof(optionalHeader)); - - dataDirectory = optionalHeader.dataDirectory + PE_IMAGE_DIRECTORY_ENTRY_RESOURCE; - } - else - { - LOG(Warning, "Unrecognized PE format."); - return true; - } - - // Read section headers - auto sectionHeaderPos = optionalHeaderPos + (std::ifstream::pos_type)coffHeader.sizeOptHeader; - stream.seekg(sectionHeaderPos); - - Array sectionHeaders; - sectionHeaders.Resize(numSectionHeaders); - stream.read((char*)sectionHeaders.Get(), sizeof(PESectionHeader) * numSectionHeaders); - - // Look for .rsrc section header - for (uint32 i = 0; i < numSectionHeaders; i++) - { - if (sectionHeaders[i].flags & PE_SECTION_UNINITIALIZED_DATA) - continue; - - if (strcmp(sectionHeaders[i].name, ".rsrc") == 0) - { - uint32 imageSize = sectionHeaders[i].physicalSize; - Array imageData; - imageData.Resize(imageSize); - - stream.seekg(sectionHeaders[i].physicalAddress); - stream.read((char*)imageData.Get(), imageSize); - - uint32 resourceDirOffset = dataDirectory->virtualAddress - sectionHeaders[i].relativeVirtualAddress; - PEImageResourceDirectory* resourceDirectory = (PEImageResourceDirectory*)&imageData.Get()[resourceDirOffset]; - - SetIconData(resourceDirectory, resourceDirectory, imageData.Get(), sectionHeaders[i].relativeVirtualAddress, iconRGBA8); - stream.seekp(sectionHeaders[i].physicalAddress); - stream.write((char*)imageData.Get(), imageSize); - } - } - - stream.close(); - - return false; -} - bool EditorUtilities::FormatAppPackageName(String& packageName) { const auto gameSettings = GameSettings::Get(); diff --git a/Source/Editor/Utilities/EditorUtilities.h b/Source/Editor/Utilities/EditorUtilities.h index 881bfe3b8..10e09cee1 100644 --- a/Source/Editor/Utilities/EditorUtilities.h +++ b/Source/Editor/Utilities/EditorUtilities.h @@ -22,14 +22,6 @@ public: SplashScreen, }; - /// - /// Updates the Win32 executable file icon. - /// - /// The exe path. - /// The icon image data. - /// True if fails, otherwise false. - static bool UpdateExeIcon(const String& path, const TextureData& icon); - static bool FormatAppPackageName(String& packageName); static bool GetApplicationImage(const Guid& imageId, TextureData& imageData, ApplicationImageType type = ApplicationImageType::Icon); static bool GetTexture(const Guid& textureId, TextureData& textureData); From 8441726da774f3f6793c65fcc6a346b8d9ef13ba Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 7 Feb 2024 18:43:48 +0100 Subject: [PATCH 058/139] Improve support for fixed-arrays in api fields --- Source/Editor/Windows/Profiler/CPU.cs | 4 - Source/Engine/Physics/Collisions.h | 6 +- .../Bindings/BindingsGenerator.CSharp.cs | 160 ++++++++++-------- 3 files changed, 88 insertions(+), 82 deletions(-) diff --git a/Source/Editor/Windows/Profiler/CPU.cs b/Source/Editor/Windows/Profiler/CPU.cs index ba569f04f..7638485e4 100644 --- a/Source/Editor/Windows/Profiler/CPU.cs +++ b/Source/Editor/Windows/Profiler/CPU.cs @@ -20,9 +20,7 @@ namespace FlaxEngine get { fixed (short* name = Name0) - { return new string((char*)name); - } } } @@ -31,9 +29,7 @@ namespace FlaxEngine fixed (short* name = Name0) { fixed (char* p = prefix) - { return Utils.MemoryCompare(new IntPtr(name), new IntPtr(p), (ulong)(prefix.Length * 2)) == 0; - } } } } diff --git a/Source/Engine/Physics/Collisions.h b/Source/Engine/Physics/Collisions.h index 7336e8d5c..230337bc9 100644 --- a/Source/Engine/Physics/Collisions.h +++ b/Source/Engine/Physics/Collisions.h @@ -9,7 +9,7 @@ class PhysicsColliderActor; /// /// Contains a contact point data for the collision location. /// -API_STRUCT() struct FLAXENGINE_API ContactPoint +API_STRUCT(NoDefault) struct FLAXENGINE_API ContactPoint { DECLARE_SCRIPTING_TYPE_MINIMAL(ContactPoint); @@ -41,7 +41,7 @@ struct TIsPODType /// /// Contains a collision information passed to the OnCollisionEnter/OnCollisionExit events. /// -API_STRUCT() struct FLAXENGINE_API Collision +API_STRUCT(NoDefault) struct FLAXENGINE_API Collision { DECLARE_SCRIPTING_TYPE_MINIMAL(Collision); @@ -81,7 +81,7 @@ API_STRUCT() struct FLAXENGINE_API Collision /// /// The contacts locations. /// - API_FIELD(Private, NoArray) ContactPoint Contacts[COLLISION_NAX_CONTACT_POINTS]; + API_FIELD(Internal, NoArray) ContactPoint Contacts[COLLISION_NAX_CONTACT_POINTS]; public: /// diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index 51ce5b647..7c5eb270f 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -90,6 +90,15 @@ namespace Flax.Build.Bindings "Int4", }; + private static bool GenerateCSharpUseFixedBuffer(string managedType) + { + return managedType == "byte" || managedType == "char" || + managedType == "short" || managedType == "ushort" || + managedType == "int" || managedType == "uint" || + managedType == "long" || managedType == "ulong" || + managedType == "float" || managedType == "double"; + } + private static string GenerateCSharpDefaultValueNativeToManaged(BuildData buildData, string value, ApiTypeInfo caller, TypeInfo valueType = null, bool attribute = false, string managedType = null) { if (string.IsNullOrEmpty(value)) @@ -1453,10 +1462,9 @@ namespace Flax.Build.Bindings contents.Append(indent + "{"); indent += " "; - toNativeContent.Append($"return new {structureInfo.Name}Internal() {{ "); - toManagedContent.Append($"return new {structureInfo.Name}() {{ "); + toNativeContent.Append($"var unmanaged = new {structureInfo.Name}Internal();").AppendLine(); + toManagedContent.Append($"var managed = new {structureInfo.Name}();").AppendLine(); - bool useSeparator = false; contents.AppendLine(); foreach (var fieldInfo in structureInfo.Fields) { @@ -1474,11 +1482,7 @@ namespace Flax.Build.Bindings else originalType = type = GenerateCSharpNativeToManaged(buildData, fieldInfo.Type, structureInfo); - contents.Append(indent).Append(GenerateCSharpAccessLevel(fieldInfo.Access)); - if (fieldInfo.IsConstexpr) - contents.Append("const "); - else if (fieldInfo.IsStatic) - contents.Append("static "); + contents.Append(indent).Append("public "); var apiType = FindApiTypeInfo(buildData, fieldInfo.Type, structureInfo); bool internalType = apiType is StructureInfo fieldStructureInfo && UseCustomMarshalling(buildData, fieldStructureInfo, structureInfo); @@ -1486,16 +1490,43 @@ namespace Flax.Build.Bindings if (fieldInfo.Type.IsArray && (fieldInfo.NoArray || structureInfo.IsPod)) { - contents.Append(type).Append(' ').Append(fieldInfo.Name + "0;").AppendLine(); - for (int i = 1; i < fieldInfo.Type.ArraySize; i++) +#if USE_NETCORE + if (GenerateCSharpUseFixedBuffer(originalType)) { - contents.AppendLine(); - GenerateCSharpAttributes(buildData, contents, indent, structureInfo, fieldInfo, fieldInfo.IsStatic); - contents.Append(indent).Append(GenerateCSharpAccessLevel(fieldInfo.Access)); - if (fieldInfo.IsStatic) - contents.Append("static "); - contents.Append(type).Append(' ').Append(fieldInfo.Name + i).Append(';').AppendLine(); + // Use fixed statement with primitive types of buffers + contents.Append($"fixed {originalType} {fieldInfo.Name}0[{fieldInfo.Type.ArraySize}];").AppendLine(); + + // Copy fixed-size array + toManagedContent.AppendLine($"FlaxEngine.Utils.MemoryCopy(new IntPtr(managed.{fieldInfo.Name}0), new IntPtr(unmanaged.{fieldInfo.Name}0), sizeof({originalType}) * {fieldInfo.Type.ArraySize}ul);"); + toNativeContent.AppendLine($"FlaxEngine.Utils.MemoryCopy(new IntPtr(unmanaged.{fieldInfo.Name}0), new IntPtr(managed.{fieldInfo.Name}0), sizeof({originalType}) * {fieldInfo.Type.ArraySize}ul);"); } + else +#endif + { + // Padding in structs for fixed-size array + contents.Append(type).Append(' ').Append(fieldInfo.Name).Append("0;").AppendLine(); + for (int i = 1; i < fieldInfo.Type.ArraySize; i++) + { + GenerateCSharpAttributes(buildData, contents, indent, structureInfo, fieldInfo, fieldInfo.IsStatic); + contents.Append(indent).Append("public "); + contents.Append(type).Append(' ').Append(fieldInfo.Name + i).Append(';').AppendLine(); + } + + // Copy fixed-size array item one-by-one + if (fieldInfo.Access == AccessLevel.Public || fieldInfo.Access == AccessLevel.Internal) + { + for (int i = 0; i < fieldInfo.Type.ArraySize; i++) + { + toManagedContent.AppendLine($"managed.{fieldInfo.Name}{i} = unmanaged.{fieldInfo.Name}{i};"); + toNativeContent.AppendLine($"unmanaged.{fieldInfo.Name}{i} = managed.{fieldInfo.Name}{i};"); + } + } + else + { + throw new NotImplementedException("TODO: generate utility method to copy private/protected array data items"); + } + } + continue; } else { @@ -1525,34 +1556,17 @@ namespace Flax.Build.Bindings //else if (type == "Guid") // type = "GuidNative"; - contents.Append(type).Append(' ').Append(fieldInfo.Name); - contents.Append(';').AppendLine(); + contents.Append(type).Append(' ').Append(fieldInfo.Name).Append(';').AppendLine(); } // Generate struct constructor/getter and deconstructor/setter function - if (fieldInfo.NoArray && fieldInfo.Type.IsArray) - continue; - - if (useSeparator) - { - toManagedContent.Append(", "); - toNativeContent.Append(", "); - freeContents2.Append(""); - freeContents.Append(""); - } - useSeparator = true; - - toManagedContent.Append(fieldInfo.Name); - toManagedContent.Append(" = "); - - toNativeContent.Append(fieldInfo.Name); - toNativeContent.Append(" = "); - + toManagedContent.Append("managed.").Append(fieldInfo.Name).Append(" = "); + toNativeContent.Append("unmanaged.").Append(fieldInfo.Name).Append(" = "); if (fieldInfo.Type.IsObjectRef) { var managedType = GenerateCSharpNativeToManaged(buildData, fieldInfo.Type.GenericArgs[0], structureInfo); - toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As<{managedType}>(ManagedHandle.FromIntPtr(managed.{fieldInfo.Name}).Target) : null"); - toNativeContent.Append($"managed.{fieldInfo.Name} != null ? ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak) : IntPtr.Zero"); + toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As<{managedType}>(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target) : null;"); + toNativeContent.AppendLine($"managed.{fieldInfo.Name} != null ? ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak) : IntPtr.Zero;"); freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); // Permanent ScriptingObject handle is passed from native side, do not release it @@ -1560,8 +1574,8 @@ namespace Flax.Build.Bindings } else if (fieldInfo.Type.Type == "ScriptingObject") { - toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As(ManagedHandle.FromIntPtr(managed.{fieldInfo.Name}).Target) : null"); - toNativeContent.Append($"managed.{fieldInfo.Name} != null ? ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak) : IntPtr.Zero"); + toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target) : null;"); + toNativeContent.AppendLine($"managed.{fieldInfo.Name} != null ? ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak) : IntPtr.Zero;"); freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); // Permanent ScriptingObject handle is passed from native side, do not release it @@ -1569,8 +1583,8 @@ namespace Flax.Build.Bindings } else if (fieldInfo.Type.IsPtr && originalType != "IntPtr" && !originalType.EndsWith("*")) { - toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As<{originalType}>(ManagedHandle.FromIntPtr(managed.{fieldInfo.Name}).Target) : null"); - toNativeContent.Append($"managed.{fieldInfo.Name} != null ? ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak) : IntPtr.Zero"); + toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As<{originalType}>(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target) : null;"); + toNativeContent.AppendLine($"managed.{fieldInfo.Name} != null ? ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak) : IntPtr.Zero;"); freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); // Permanent ScriptingObject handle is passed from native side, do not release it @@ -1578,8 +1592,8 @@ namespace Flax.Build.Bindings } else if (fieldInfo.Type.Type == "Dictionary") { - toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As<{originalType}>(ManagedHandle.FromIntPtr(managed.{fieldInfo.Name}).Target) : null"); - toNativeContent.Append($"ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak)"); + toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As<{originalType}>(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target) : null;"); + toNativeContent.AppendLine($"ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak);"); freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); } @@ -1591,16 +1605,16 @@ namespace Flax.Build.Bindings // Marshal blittable array elements back to original non-blittable elements string originalElementTypeMarshaller = originalElementType + "Marshaller"; string internalElementType = $"{originalElementTypeMarshaller}.{originalElementType}Internal"; - toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? NativeInterop.ConvertArray((Unsafe.As(ManagedHandle.FromIntPtr(managed.{fieldInfo.Name}).Target)).ToSpan<{internalElementType}>(), {originalElementTypeMarshaller}.ToManaged) : null"); - toNativeContent.Append($"managed.{fieldInfo.Name}?.Length > 0 ? ManagedHandle.ToIntPtr(ManagedArray.WrapNewArray(NativeInterop.ConvertArray(managed.{fieldInfo.Name}, {originalElementTypeMarshaller}.ToNative)), GCHandleType.Weak) : IntPtr.Zero"); + toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? NativeInterop.ConvertArray((Unsafe.As(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target)).ToSpan<{internalElementType}>(), {originalElementTypeMarshaller}.ToManaged) : null;"); + toNativeContent.AppendLine($"managed.{fieldInfo.Name}?.Length > 0 ? ManagedHandle.ToIntPtr(ManagedArray.WrapNewArray(NativeInterop.ConvertArray(managed.{fieldInfo.Name}, {originalElementTypeMarshaller}.ToNative)), GCHandleType.Weak) : IntPtr.Zero;"); freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle handle = ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); Span<{internalElementType}> values = (Unsafe.As(handle.Target)).ToSpan<{internalElementType}>(); foreach (var value in values) {{ {originalElementTypeMarshaller}.Free(value); }} (Unsafe.As(handle.Target)).Free(); handle.Free(); }}"); freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle handle = ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); Span<{internalElementType}> values = (Unsafe.As(handle.Target)).ToSpan<{internalElementType}>(); foreach (var value in values) {{ {originalElementTypeMarshaller}.Free(value); }} (Unsafe.As(handle.Target)).Free(); handle.Free(); }}"); } else if (fieldInfo.Type.GenericArgs[0].IsObjectRef) { // Array elements passed as GCHandles - toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? NativeInterop.GCHandleArrayToManagedArray<{originalElementType}>(Unsafe.As(ManagedHandle.FromIntPtr(managed.{fieldInfo.Name}).Target)) : null"); - toNativeContent.Append($"managed.{fieldInfo.Name}?.Length > 0 ? ManagedHandle.ToIntPtr(NativeInterop.ManagedArrayToGCHandleWrappedArray(managed.{fieldInfo.Name}), GCHandleType.Weak) : IntPtr.Zero"); + toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? NativeInterop.GCHandleArrayToManagedArray<{originalElementType}>(Unsafe.As(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target)) : null;"); + toNativeContent.AppendLine($"managed.{fieldInfo.Name}?.Length > 0 ? ManagedHandle.ToIntPtr(NativeInterop.ManagedArrayToGCHandleWrappedArray(managed.{fieldInfo.Name}), GCHandleType.Weak) : IntPtr.Zero;"); freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle handle = ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); Span ptrs = (Unsafe.As(handle.Target)).ToSpan(); foreach (var ptr in ptrs) {{ if (ptr != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(ptr).Free(); }} }} (Unsafe.As(handle.Target)).Free(); handle.Free(); }}"); // Permanent ScriptingObject handle is passed from native side, do not release it @@ -1609,53 +1623,53 @@ namespace Flax.Build.Bindings else { // Blittable array elements - toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? (Unsafe.As(ManagedHandle.FromIntPtr(managed.{fieldInfo.Name}).Target)).ToArray<{originalElementType}>() : null"); - toNativeContent.Append($"managed.{fieldInfo.Name}?.Length > 0 ? ManagedHandle.ToIntPtr(ManagedArray.WrapNewArray(managed.{fieldInfo.Name}), GCHandleType.Weak) : IntPtr.Zero"); + toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? (Unsafe.As(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target)).ToArray<{originalElementType}>() : null;"); + toNativeContent.AppendLine($"managed.{fieldInfo.Name}?.Length > 0 ? ManagedHandle.ToIntPtr(ManagedArray.WrapNewArray(managed.{fieldInfo.Name}), GCHandleType.Weak) : IntPtr.Zero;"); freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle handle = ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); (Unsafe.As(handle.Target)).Free(); handle.Free(); }}"); freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle handle = ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); (Unsafe.As(handle.Target)).Free(); handle.Free(); }}"); } } else if (fieldInfo.Type.Type == "Version") { - toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As<{originalType}>(ManagedHandle.FromIntPtr(managed.{fieldInfo.Name}).Target) : null"); - toNativeContent.Append($"ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak)"); + toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As<{originalType}>(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target) : null;"); + toNativeContent.AppendLine($"ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak);"); freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); } else if (originalType == "string") { - toManagedContent.Append($"ManagedString.ToManaged(managed.{fieldInfo.Name})"); - toNativeContent.Append($"ManagedString.ToNative(managed.{fieldInfo.Name})"); + toManagedContent.AppendLine($"ManagedString.ToManaged(unmanaged.{fieldInfo.Name});"); + toNativeContent.AppendLine($"ManagedString.ToNative(managed.{fieldInfo.Name});"); freeContents.AppendLine($"ManagedString.Free(unmanaged.{fieldInfo.Name});"); freeContents2.AppendLine($"ManagedString.Free(unmanaged.{fieldInfo.Name});"); } else if (originalType == "bool") { - toManagedContent.Append($"managed.{fieldInfo.Name} != 0"); - toNativeContent.Append($"managed.{fieldInfo.Name} ? (byte)1 : (byte)0"); + toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != 0;"); + toNativeContent.AppendLine($"managed.{fieldInfo.Name} ? (byte)1 : (byte)0;"); } else if (fieldInfo.Type.Type == "Variant") { // Variant passed as boxed object handle - toManagedContent.Append($"ManagedHandleMarshaller.NativeToManaged.ConvertToManaged(managed.{fieldInfo.Name})"); - toNativeContent.Append($"ManagedHandleMarshaller.NativeToManaged.ConvertToUnmanaged(managed.{fieldInfo.Name})"); + toManagedContent.AppendLine($"ManagedHandleMarshaller.NativeToManaged.ConvertToManaged(unmanaged.{fieldInfo.Name});"); + toNativeContent.AppendLine($"ManagedHandleMarshaller.NativeToManaged.ConvertToUnmanaged(managed.{fieldInfo.Name});"); } else if (internalType) { - toManagedContent.Append($"{internalTypeMarshaller}.ToManaged(managed.{fieldInfo.Name})"); - toNativeContent.Append($"{internalTypeMarshaller}.ToNative(managed.{fieldInfo.Name})"); + toManagedContent.AppendLine($"{internalTypeMarshaller}.ToManaged(unmanaged.{fieldInfo.Name});"); + toNativeContent.AppendLine($"{internalTypeMarshaller}.ToNative(managed.{fieldInfo.Name});"); freeContents.AppendLine($"{internalTypeMarshaller}.Free(unmanaged.{fieldInfo.Name});"); freeContents2.AppendLine($"{internalTypeMarshaller}.Free(unmanaged.{fieldInfo.Name});"); } /*else if (originalType == "Guid") { - toManagedContent.Append("(Guid)managed.").Append(fieldInfo.Name); + toManagedContent.Append("(Guid)unmanaged.").Append(fieldInfo.Name); toNativeContent.Append("(GuidNative)managed.").Append(fieldInfo.Name); }*/ else { - toManagedContent.Append("managed.").Append(fieldInfo.Name); - toNativeContent.Append("managed.").Append(fieldInfo.Name); + toManagedContent.Append("unmanaged.").Append(fieldInfo.Name).AppendLine(";"); + toNativeContent.Append("managed.").Append(fieldInfo.Name).AppendLine(";"); } } @@ -1663,8 +1677,8 @@ namespace Flax.Build.Bindings indent = indent.Substring(0, indent.Length - 4); contents.AppendLine(indent + "}").AppendLine(); - toManagedContent.AppendLine(" };"); - toNativeContent.AppendLine(" };"); + toNativeContent.Append("return unmanaged;"); + toManagedContent.Append("return managed;"); } var indent2 = indent + " "; @@ -1712,7 +1726,7 @@ namespace Flax.Build.Bindings contents.Append(indent).AppendLine("{").Append(indent2).AppendLine(freeContents.Replace("\n", "\n" + indent2).ToString().TrimEnd()).Append(indent).AppendLine("}"); // Managed/native converters - contents.Append(indent).AppendLine($"internal static {structureInfo.Name} ToManaged({structureInfo.Name}Internal managed)"); + contents.Append(indent).AppendLine($"internal static {structureInfo.Name} ToManaged({structureInfo.Name}Internal unmanaged)"); contents.Append(indent).AppendLine("{").Append(indent2).AppendLine(toManagedContent.Replace("\n", "\n" + indent2).ToString().TrimEnd()).Append(indent).AppendLine("}"); contents.Append(indent).AppendLine($"internal static {structureInfo.Name}Internal ToNative({structureInfo.Name} managed)"); contents.Append(indent).AppendLine("{").Append(indent2).AppendLine(toNativeContent.Replace("\n", "\n" + indent2).ToString().TrimEnd()).Append(indent).AppendLine("}"); @@ -1767,15 +1781,12 @@ namespace Flax.Build.Bindings managedType = GenerateCSharpNativeToManaged(buildData, fieldInfo.Type, structureInfo); fieldInfo.Type.IsArray = true; #if USE_NETCORE - // Use fixed statement with primitive types of buffers - if (managedType == "char") + if (GenerateCSharpUseFixedBuffer(managedType)) { - // char's are not blittable, store as short instead - contents.Append($"fixed short {fieldInfo.Name}0[{fieldInfo.Type.ArraySize}]; // {managedType}*").AppendLine(); - } - else if (managedType == "byte") - { - contents.Append($"fixed byte {fieldInfo.Name}0[{fieldInfo.Type.ArraySize}]; // {managedType}*").AppendLine(); + // Use fixed statement with primitive types of buffers + if (managedType == "char") + managedType = "short"; // char's are not blittable, store as short instead + contents.Append($"fixed {managedType} {fieldInfo.Name}0[{fieldInfo.Type.ArraySize}];").AppendLine(); } else #endif @@ -1785,7 +1796,6 @@ namespace Flax.Build.Bindings for (int i = 1; i < fieldInfo.Type.ArraySize; i++) { contents.AppendLine(); - GenerateCSharpComment(contents, indent, fieldInfo.Comment); GenerateCSharpAttributes(buildData, contents, indent, structureInfo, fieldInfo, fieldInfo.IsStatic); contents.Append(indent).Append(GenerateCSharpAccessLevel(fieldInfo.Access)); if (fieldInfo.IsStatic) From 2042525e92938bd886dcd516eee2aa125ca8cb14 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 7 Feb 2024 18:53:09 +0100 Subject: [PATCH 059/139] Add pooling to some `StringBuilder` objects in build tool --- .../Flax.Build/Bindings/BindingsGenerator.CSharp.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index 7c5eb270f..845e4e62b 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -1444,10 +1444,10 @@ namespace Flax.Build.Bindings indent += " "; - StringBuilder toManagedContent = new StringBuilder(); - StringBuilder toNativeContent = new StringBuilder(); - StringBuilder freeContents = new StringBuilder(); - StringBuilder freeContents2 = new StringBuilder(); + var toManagedContent = GetStringBuilder(); + var toNativeContent = GetStringBuilder(); + var freeContents = GetStringBuilder(); + var freeContents2 = GetStringBuilder(); { // Native struct begin @@ -1734,6 +1734,11 @@ namespace Flax.Build.Bindings contents.AppendLine("#pragma warning restore 1591"); indent = indent.Substring(0, indent.Length - 4); contents.Append(indent).AppendLine("}").AppendLine(); + + PutStringBuilder(toManagedContent); + PutStringBuilder(toNativeContent); + PutStringBuilder(freeContents); + PutStringBuilder(freeContents2); } #endif // Struct docs From d1f40d1c47c78a75a0fa82772a5a88ea0b08fde8 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 7 Feb 2024 18:53:55 +0100 Subject: [PATCH 060/139] Fix missing visibleIf attr --- Source/Engine/Tools/ModelTool/ModelTool.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Tools/ModelTool/ModelTool.h b/Source/Engine/Tools/ModelTool/ModelTool.h index df89a1519..90292d72c 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.h +++ b/Source/Engine/Tools/ModelTool/ModelTool.h @@ -251,7 +251,7 @@ public: API_FIELD(Attributes="EditorOrder(1140), EditorDisplay(\"Level Of Detail\"), VisibleIf(nameof(ShowGeometry))") bool SloppyOptimization = false; // Only used if Sloppy is false. Target error is an approximate measure of the deviation from the original mesh using distance normalized to [0..1] range (e.g. 1e-2f means that simplifier will try to maintain the error to be below 1% of the mesh extents). - API_FIELD(Attributes="EditorOrder(1150), EditorDisplay(\"Level Of Detail\"), VisibleIf(nameof(SloppyOptimization), true), Limit(0.01f, 1, 0.001f)") + API_FIELD(Attributes="EditorOrder(1150), EditorDisplay(\"Level Of Detail\"), VisibleIf(nameof(SloppyOptimization), true), VisibleIf(nameof(ShowGeometry)), Limit(0.01f, 1, 0.001f)") float LODTargetError = 0.05f; public: // Materials From e75902e9003b83d7253dafc52b761aa5042d562c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 7 Feb 2024 18:56:07 +0100 Subject: [PATCH 061/139] Variosu tweaks --- Source/Editor/Modules/WindowsModule.cs | 2 -- Source/Editor/Surface/Archetypes/Animation.StateMachine.cs | 2 -- Source/Editor/Windows/EditorWindow.cs | 2 ++ Source/Engine/Animations/Graph/AnimGraph.h | 2 +- Source/Engine/Animations/Graph/AnimGroup.Animation.cpp | 2 +- 5 files changed, 4 insertions(+), 6 deletions(-) diff --git a/Source/Editor/Modules/WindowsModule.cs b/Source/Editor/Modules/WindowsModule.cs index a935e73f2..cf2715b48 100644 --- a/Source/Editor/Modules/WindowsModule.cs +++ b/Source/Editor/Modules/WindowsModule.cs @@ -725,9 +725,7 @@ namespace FlaxEditor.Modules for (int i = 0; i < Windows.Count; i++) { if (string.Equals(Windows[i].SerializationTypename, typename, StringComparison.OrdinalIgnoreCase)) - { return Windows[i]; - } } // Check if it's an asset ID diff --git a/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs b/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs index 9ec1bab19..abe66d3cc 100644 --- a/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs +++ b/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs @@ -191,9 +191,7 @@ namespace FlaxEditor.Surface.Archetypes var value = title; int count = 1; while (!OnRenameValidate(null, value)) - { value = title + " " + count++; - } Values[0] = value; Title = value; diff --git a/Source/Editor/Windows/EditorWindow.cs b/Source/Editor/Windows/EditorWindow.cs index ff0673c11..df70a0b33 100644 --- a/Source/Editor/Windows/EditorWindow.cs +++ b/Source/Editor/Windows/EditorWindow.cs @@ -226,6 +226,8 @@ namespace FlaxEditor.Windows /// public override void OnDestroy() { + if (IsDisposing) + return; OnExit(); // Unregister diff --git a/Source/Engine/Animations/Graph/AnimGraph.h b/Source/Engine/Animations/Graph/AnimGraph.h index c160b671c..a6dc68596 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.h +++ b/Source/Engine/Animations/Graph/AnimGraph.h @@ -205,7 +205,7 @@ struct FLAXENGINE_API AnimGraphSlot /// /// The animation graph state container for a single node playback trace (eg. animation sample info or state transition). Can be used by Anim Graph debugging or custom scripting. /// -API_STRUCT() struct FLAXENGINE_API AnimGraphTraceEvent +API_STRUCT(NoDefault) struct FLAXENGINE_API AnimGraphTraceEvent { DECLARE_SCRIPTING_TYPE_MINIMAL(AnimGraphTraceEvent); diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp index d3b61c951..a9d29cda3 100644 --- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp +++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp @@ -506,7 +506,7 @@ Variant AnimGraphExecutor::SampleState(AnimGraphNode* state) void AnimGraphExecutor::InitStateTransition(AnimGraphContext& context, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, AnimGraphStateTransition* transition) { - // Reset transiton + // Reset transition stateMachineBucket.ActiveTransition = transition; stateMachineBucket.TransitionPosition = 0.0f; From a38d1ad7ccb8e58a11cd436788691a355b07b4e6 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 7 Feb 2024 19:03:38 +0100 Subject: [PATCH 062/139] Fix anim graph trace events debugging to include nodes path for nested graphs --- Source/Editor/Surface/AnimGraphSurface.cs | 26 ++++++++++++++++--- .../Editor/Surface/VisjectSurface.Context.cs | 2 ++ .../Editor/Surface/VisjectSurfaceContext.cs | 5 ++++ Source/Engine/Animations/Graph/AnimGraph.cpp | 1 + Source/Engine/Animations/Graph/AnimGraph.h | 5 +++- .../Animations/Graph/AnimGroup.Animation.cpp | 23 +++++++++++----- 6 files changed, 51 insertions(+), 11 deletions(-) diff --git a/Source/Editor/Surface/AnimGraphSurface.cs b/Source/Editor/Surface/AnimGraphSurface.cs index ccd78412a..3b704e26e 100644 --- a/Source/Editor/Surface/AnimGraphSurface.cs +++ b/Source/Editor/Surface/AnimGraphSurface.cs @@ -115,16 +115,36 @@ namespace FlaxEditor.Surface internal AnimGraphTraceEvent[] LastTraceEvents; - internal bool TryGetTraceEvent(SurfaceNode node, out AnimGraphTraceEvent traceEvent) + internal unsafe bool TryGetTraceEvent(SurfaceNode node, out AnimGraphTraceEvent traceEvent) { if (LastTraceEvents != null) { foreach (var e in LastTraceEvents) { + // Node IDs must match if (e.NodeId == node.ID) { - traceEvent = e; - return true; + uint* nodePath = e.NodePath0; + + // Get size of the path + int nodePathSize = 0; + while (nodePathSize < 8 && nodePath[nodePathSize] != 0) + nodePathSize++; + + // Follow input node contexts path to verify if it matches with the path in the event + var c = node.Context; + for (int i = nodePathSize - 1; i >= 0 && c != null; i--) + { + if (c.OwnerNodeID != nodePath[i]) + c = null; + else + c = c.Parent; + } + if (c != null) + { + traceEvent = e; + return true; + } } } } diff --git a/Source/Editor/Surface/VisjectSurface.Context.cs b/Source/Editor/Surface/VisjectSurface.Context.cs index 1d8b97729..691ad4e50 100644 --- a/Source/Editor/Surface/VisjectSurface.Context.cs +++ b/Source/Editor/Surface/VisjectSurface.Context.cs @@ -62,6 +62,8 @@ namespace FlaxEditor.Surface surfaceContext = CreateContext(_context, context); _context?.Children.Add(surfaceContext); _contextCache.Add(contextHandle, surfaceContext); + if (context is SurfaceNode asNode) + surfaceContext.OwnerNodeID = asNode.ID; context.OnContextCreated(surfaceContext); diff --git a/Source/Editor/Surface/VisjectSurfaceContext.cs b/Source/Editor/Surface/VisjectSurfaceContext.cs index 0886996b6..9902f224a 100644 --- a/Source/Editor/Surface/VisjectSurfaceContext.cs +++ b/Source/Editor/Surface/VisjectSurfaceContext.cs @@ -156,6 +156,11 @@ namespace FlaxEditor.Surface /// public event Action ControlDeleted; + /// + /// Identifier of the node that 'owns' this context (eg. State Machine which created this graph of state nodes). + /// + public uint OwnerNodeID; + /// /// Initializes a new instance of the class. /// diff --git a/Source/Engine/Animations/Graph/AnimGraph.cpp b/Source/Engine/Animations/Graph/AnimGraph.cpp index f7b883bd7..9ec87c78f 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.cpp +++ b/Source/Engine/Animations/Graph/AnimGraph.cpp @@ -215,6 +215,7 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt) // Prepare context data for the evaluation context.GraphStack.Clear(); context.GraphStack.Push((Graph*)&_graph); + context.NodePath.Clear(); context.Data = &data; context.DeltaTime = dt; context.CurrentFrameIndex = ++data.CurrentFrame; diff --git a/Source/Engine/Animations/Graph/AnimGraph.h b/Source/Engine/Animations/Graph/AnimGraph.h index a6dc68596..d8acae234 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.h +++ b/Source/Engine/Animations/Graph/AnimGraph.h @@ -215,6 +215,8 @@ API_STRUCT(NoDefault) struct FLAXENGINE_API AnimGraphTraceEvent API_FIELD() float Value = 0; // Identifier of the node in the graph. API_FIELD() uint32 NodeId = 0; + // Ids of graph nodes (call of hierarchy). + API_FIELD(Internal, NoArray) uint32 NodePath[8] = {}; }; /// @@ -796,6 +798,7 @@ struct AnimGraphContext AnimGraphTransitionData TransitionData; Array> CallStack; Array> GraphStack; + Array > NodePath; Dictionary Functions; ChunkedArray PoseCache; int32 PoseCacheSize; @@ -891,7 +894,7 @@ private: Variant SampleAnimationsWithBlend(AnimGraphNode* node, bool loop, float length, float startTimePos, float prevTimePos, float& newTimePos, Animation* animA, Animation* animB, float speedA, float speedB, float alpha); Variant SampleAnimationsWithBlend(AnimGraphNode* node, bool loop, float length, float startTimePos, float prevTimePos, float& newTimePos, Animation* animA, Animation* animB, Animation* animC, float speedA, float speedB, float speedC, float alphaA, float alphaB, float alphaC); Variant Blend(AnimGraphNode* node, const Value& poseA, const Value& poseB, float alpha, AlphaBlendMode alphaMode); - Variant SampleState(AnimGraphNode* state); + Variant SampleState(AnimGraphContext& context, const AnimGraphNode* state); void InitStateTransition(AnimGraphContext& context, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, AnimGraphStateTransition* transition = nullptr); AnimGraphStateTransition* UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, AnimGraphNode* state, AnimGraphNode* ignoreState = nullptr); void UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, const AnimGraphNode::StateBaseData& stateData); diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp index a9d29cda3..3d729e80e 100644 --- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp +++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp @@ -227,6 +227,9 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* trace.Asset = anim; trace.Value = animPos; trace.NodeId = node->ID; + auto* nodePath = context.NodePath.Get(); + for (int32 i = 0; i < context.NodePath.Count(); i++) + trace.NodePath[i] = nodePath[i]; } // Evaluate nested animations @@ -494,14 +497,17 @@ Variant AnimGraphExecutor::Blend(AnimGraphNode* node, const Value& poseA, const return nodes; } -Variant AnimGraphExecutor::SampleState(AnimGraphNode* state) +Variant AnimGraphExecutor::SampleState(AnimGraphContext& context, const AnimGraphNode* state) { auto& data = state->Data.State; if (data.Graph == nullptr || data.Graph->GetRootNode() == nullptr) return Value::Null; ANIM_GRAPH_PROFILE_EVENT("Evaluate State"); + context.NodePath.Add(state->ID); auto rootNode = data.Graph->GetRootNode(); - return eatBox((Node*)rootNode, &rootNode->Boxes[0]); + auto result = eatBox((Node*)rootNode, &rootNode->Boxes[0]); + context.NodePath.Pop(); + return result; } void AnimGraphExecutor::InitStateTransition(AnimGraphContext& context, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, AnimGraphStateTransition* transition) @@ -537,7 +543,7 @@ AnimGraphStateTransition* AnimGraphExecutor::UpdateStateTransitions(AnimGraphCon } // Evaluate source state transition data (position, length, etc.) - const Value sourceStatePtr = SampleState(state); + const Value sourceStatePtr = SampleState(context, state); auto& transitionData = context.TransitionData; // Note: this could support nested transitions but who uses state machine inside transition rule? if (ANIM_GRAPH_IS_VALID_PTR(sourceStatePtr)) { @@ -1660,6 +1666,8 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu bucket.CurrentState = bucket.ActiveTransition->Destination; \ InitStateTransition(context, bucket) + context.NodePath.Push(node->ID); + // Update the active transition if (bucket.ActiveTransition) { @@ -1767,11 +1775,11 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu if (bucket.BaseTransitionState) { // Sample the other state (eg. when blending from interrupted state to the another state from the old destination) - value = SampleState(bucket.BaseTransitionState); + value = SampleState(context, bucket.BaseTransitionState); if (bucket.BaseTransition) { // Evaluate the base pose from the time when transition was interrupted - const auto destinationState = SampleState(bucket.BaseTransition->Destination); + const auto destinationState = SampleState(context, bucket.BaseTransition->Destination); const float alpha = bucket.BaseTransitionPosition / bucket.BaseTransition->BlendDuration; value = Blend(node, value, destinationState, alpha, bucket.BaseTransition->BlendMode); } @@ -1779,14 +1787,14 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu else { // Sample the current state - value = SampleState(bucket.CurrentState); + value = SampleState(context, bucket.CurrentState); } // Handle active transition blending if (bucket.ActiveTransition) { // Sample the active transition destination state - const auto destinationState = SampleState(bucket.ActiveTransition->Destination); + const auto destinationState = SampleState(context, bucket.ActiveTransition->Destination); // Perform blending const float alpha = bucket.TransitionPosition / bucket.ActiveTransition->BlendDuration; @@ -1794,6 +1802,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu } bucket.LastUpdateFrame = context.CurrentFrameIndex; + context.NodePath.Pop(); #undef END_TRANSITION break; } From cfb8350c65f642dbe8a6fb3c710e8c24b3f0cc96 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 7 Feb 2024 19:22:07 +0100 Subject: [PATCH 063/139] Fix anim graph debugging to handle nested graph connections highlights properly --- Source/Editor/Surface/AnimGraphSurface.cs | 7 +----- .../Editor/Surface/VisjectSurface.Context.cs | 24 +++++++++++++++++++ .../Windows/Assets/AnimationGraphWindow.cs | 19 ++++++++------- Source/Engine/Animations/Animations.cpp | 4 ++-- Source/Engine/Animations/Animations.h | 21 ++++++++++++++-- Source/Engine/Animations/Graph/AnimGraph.cpp | 10 +++++++- .../Animations/Graph/AnimGroup.Animation.cpp | 2 +- 7 files changed, 67 insertions(+), 20 deletions(-) diff --git a/Source/Editor/Surface/AnimGraphSurface.cs b/Source/Editor/Surface/AnimGraphSurface.cs index 3b704e26e..56764bafd 100644 --- a/Source/Editor/Surface/AnimGraphSurface.cs +++ b/Source/Editor/Surface/AnimGraphSurface.cs @@ -134,12 +134,7 @@ namespace FlaxEditor.Surface // Follow input node contexts path to verify if it matches with the path in the event var c = node.Context; for (int i = nodePathSize - 1; i >= 0 && c != null; i--) - { - if (c.OwnerNodeID != nodePath[i]) - c = null; - else - c = c.Parent; - } + c = c.OwnerNodeID == nodePath[i] ? c.Parent : null; if (c != null) { traceEvent = e; diff --git a/Source/Editor/Surface/VisjectSurface.Context.cs b/Source/Editor/Surface/VisjectSurface.Context.cs index 691ad4e50..0cb8e3ead 100644 --- a/Source/Editor/Surface/VisjectSurface.Context.cs +++ b/Source/Editor/Surface/VisjectSurface.Context.cs @@ -33,6 +33,30 @@ namespace FlaxEditor.Surface /// public event Action ContextChanged; + /// + /// Finds the surface context with the given owning nodes IDs path. + /// + /// The node ids path. + /// Found context or null if cannot. + public VisjectSurfaceContext FindContext(Span nodePath) + { + // Get size of the path + int nodePathSize = 0; + while (nodePathSize < nodePath.Length && nodePath[nodePathSize] != 0) + nodePathSize++; + + // Follow each context path to verify if it matches with the path in the input path + foreach (var e in _contextCache) + { + var c = e.Value; + for (int i = nodePathSize - 1; i >= 0 && c != null; i--) + c = c.OwnerNodeID == nodePath[i] ? c.Parent : null; + if (c != null) + return e.Value; + } + return null; + } + /// /// Creates the Visject surface context for the given surface data source context. /// diff --git a/Source/Editor/Windows/Assets/AnimationGraphWindow.cs b/Source/Editor/Windows/Assets/AnimationGraphWindow.cs index e0d8c9ded..93eea95e1 100644 --- a/Source/Editor/Windows/Assets/AnimationGraphWindow.cs +++ b/Source/Editor/Windows/Assets/AnimationGraphWindow.cs @@ -154,10 +154,11 @@ namespace FlaxEditor.Windows.Assets } [StructLayout(LayoutKind.Sequential)] - private struct AnimGraphDebugFlowInfo + private unsafe struct AnimGraphDebugFlowInfo { public uint NodeId; public int BoxId; + public fixed uint NodePath[8]; } private FlaxObjectRefPickerControl _debugPicker; @@ -252,25 +253,26 @@ namespace FlaxEditor.Windows.Assets return obj is AnimatedModel player && player.AnimationGraph == OriginalAsset; } - private void OnDebugFlow(Asset asset, Object obj, uint nodeId, uint boxId) + private unsafe void OnDebugFlow(Animations.DebugFlowInfo flowInfo) { // Filter the flow if (_debugPicker.Value != null) { - if (asset != OriginalAsset || _debugPicker.Value != obj) + if (flowInfo.Asset != OriginalAsset || _debugPicker.Value != flowInfo.Instance) return; } else { - if (asset != Asset || _preview.PreviewActor != obj) + if (flowInfo.Asset != Asset || _preview.PreviewActor != flowInfo.Instance) return; } // Register flow to show it in UI on a surface - var flowInfo = new AnimGraphDebugFlowInfo { NodeId = nodeId, BoxId = (int)boxId }; + var flow = new AnimGraphDebugFlowInfo { NodeId = flowInfo.NodeId, BoxId = (int)flowInfo.BoxId }; + Utils.MemoryCopy(new IntPtr(flow.NodePath), new IntPtr(flowInfo.NodePath0), sizeof(uint) * 8ul); lock (_debugFlows) { - _debugFlows.Add(flowInfo); + _debugFlows.Add(flow); } } @@ -394,7 +396,7 @@ namespace FlaxEditor.Windows.Assets } /// - public override void OnUpdate() + public override unsafe void OnUpdate() { // Extract animations playback state from the events tracing var debugActor = _debugPicker.Value as AnimatedModel; @@ -413,7 +415,8 @@ namespace FlaxEditor.Windows.Assets { foreach (var debugFlow in _debugFlows) { - var node = Surface.Context.FindNode(debugFlow.NodeId); + var context = Surface.FindContext(new Span(debugFlow.NodePath, 8)); + var node = context?.FindNode(debugFlow.NodeId); var box = node?.GetBox(debugFlow.BoxId); box?.HighlightConnections(); } diff --git a/Source/Engine/Animations/Animations.cpp b/Source/Engine/Animations/Animations.cpp index e571af162..59acc5860 100644 --- a/Source/Engine/Animations/Animations.cpp +++ b/Source/Engine/Animations/Animations.cpp @@ -52,7 +52,7 @@ namespace AnimationsService AnimationManagerInstance; TaskGraphSystem* Animations::System = nullptr; #if USE_EDITOR -Delegate Animations::DebugFlow; +Delegate Animations::DebugFlow; #endif AnimEvent::AnimEvent(const SpawnParams& params) @@ -127,7 +127,7 @@ void AnimationsSystem::Execute(TaskGraph* graph) #if USE_EDITOR // If debug flow is registered, then warm it up (eg. static cached method inside DebugFlow_ManagedWrapper) so it doesn't crash on highly multi-threaded code if (Animations::DebugFlow.IsBinded()) - Animations::DebugFlow(nullptr, nullptr, 0, 0); + Animations::DebugFlow(Animations::DebugFlowInfo()); #endif // Schedule work to update all animated models in async diff --git a/Source/Engine/Animations/Animations.h b/Source/Engine/Animations/Animations.h index d82b9d05f..038d20d9d 100644 --- a/Source/Engine/Animations/Animations.h +++ b/Source/Engine/Animations/Animations.h @@ -22,8 +22,25 @@ API_CLASS(Static) class FLAXENGINE_API Animations API_FIELD(ReadOnly) static TaskGraphSystem* System; #if USE_EDITOR - // Custom event that is called every time the Anim Graph signal flows over the graph (including the data connections). Can be used to read and visualize the animation blending logic. Args are: anim graph asset, animated object, node id, box id - API_EVENT() static Delegate DebugFlow; + // Data wrapper for the debug flow information. + API_STRUCT(NoDefault) struct DebugFlowInfo + { + DECLARE_SCRIPTING_TYPE_MINIMAL(DebugFlowInfo); + + // Anim Graph asset + API_FIELD() Asset* Asset = nullptr; + // Animated actor + API_FIELD() ScriptingObject* Instance = nullptr; + // Graph node id. + API_FIELD() uint32 NodeId = 0; + // Graph box id. + API_FIELD() uint32 BoxId = 0; + // Ids of graph nodes (call of hierarchy). + API_FIELD(Internal, NoArray) uint32 NodePath[8] = {}; + }; + + // Custom event that is called every time the Anim Graph signal flows over the graph (including the data connections). Can be used to read and visualize the animation blending logic. + API_EVENT() static Delegate DebugFlow; #endif /// diff --git a/Source/Engine/Animations/Graph/AnimGraph.cpp b/Source/Engine/Animations/Graph/AnimGraph.cpp index 9ec87c78f..40c33a3e8 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.cpp +++ b/Source/Engine/Animations/Graph/AnimGraph.cpp @@ -425,7 +425,15 @@ VisjectExecutor::Value AnimGraphExecutor::eatBox(Node* caller, Box* box) context.CallStack.Add(caller); #if USE_EDITOR - Animations::DebugFlow(_graph._owner, context.Data->Object, box->GetParent()->ID, box->ID); + Animations::DebugFlowInfo flowInfo; + flowInfo.Asset = _graph._owner; + flowInfo.Instance = context.Data->Object; + flowInfo.NodeId = box->GetParent()->ID; + flowInfo.BoxId = box->ID; + const auto* nodePath = context.NodePath.Get(); + for (int32 i = 0; i < context.NodePath.Count(); i++) + flowInfo.NodePath[i] = nodePath[i]; + Animations::DebugFlow(flowInfo); #endif // Call per group custom processing event diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp index 3d729e80e..0fab934ca 100644 --- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp +++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp @@ -227,7 +227,7 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* trace.Asset = anim; trace.Value = animPos; trace.NodeId = node->ID; - auto* nodePath = context.NodePath.Get(); + const auto* nodePath = context.NodePath.Get(); for (int32 i = 0; i < context.NodePath.Count(); i++) trace.NodePath[i] = nodePath[i]; } From eed780a0b0ab3ca79eee931483d3eb9c078e315b Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 7 Feb 2024 19:31:34 +0100 Subject: [PATCH 064/139] Add highlighting active state in anim graph --- .../Archetypes/Animation.StateMachine.cs | 15 +++++++++++ Source/Engine/Animations/Graph/AnimGraph.h | 2 ++ .../Animations/Graph/AnimGroup.Animation.cpp | 25 +++++++++++++++---- 3 files changed, 37 insertions(+), 5 deletions(-) diff --git a/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs b/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs index abe66d3cc..bb4bc724d 100644 --- a/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs +++ b/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs @@ -653,6 +653,7 @@ namespace FlaxEditor.Surface.Archetypes protected Rectangle _renameButtonRect; private bool _cursorChanged = false; private bool _textRectHovered = false; + private bool _debugActive; /// /// The transitions list from this state to the others. @@ -1090,6 +1091,16 @@ namespace FlaxEditor.Surface.Archetypes // TODO: maybe update only on actual transitions change? UpdateTransitions(); + + // Debug current state + if (((AnimGraphSurface)Surface).TryGetTraceEvent(this, out var traceEvent)) + { + _debugActive = true; + } + else + { + _debugActive = false; + } } /// @@ -1130,6 +1141,10 @@ namespace FlaxEditor.Surface.Archetypes // Close button Render2D.DrawSprite(style.Cross, _closeButtonRect, _closeButtonRect.Contains(_mousePosition) ? style.Foreground : style.ForegroundGrey); + + // Debug outline + if (_debugActive) + Render2D.DrawRectangle(_textRect.MakeExpanded(1.0f), style.ProgressNormal); } /// diff --git a/Source/Engine/Animations/Graph/AnimGraph.h b/Source/Engine/Animations/Graph/AnimGraph.h index d8acae234..a774e6b85 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.h +++ b/Source/Engine/Animations/Graph/AnimGraph.h @@ -803,6 +803,8 @@ struct AnimGraphContext ChunkedArray PoseCache; int32 PoseCacheSize; Dictionary ValueCache; + + AnimGraphTraceEvent& AddTraceEvent(const AnimGraphNode* node); }; /// diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp index 0fab934ca..57829e8fd 100644 --- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp +++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp @@ -52,6 +52,17 @@ void RetargetSkeletonNode(const SkeletonData& sourceSkeleton, const SkeletonData node = value; } +AnimGraphTraceEvent& AnimGraphContext::AddTraceEvent(const AnimGraphNode* node) +{ + auto& trace = Data->TraceEvents.AddOne(); + trace.Value = 0.0f; + trace.NodeId = node->ID; + const auto* nodePath = NodePath.Get(); + for (int32 i = 0; i < NodePath.Count(); i++) + trace.NodePath[i] = nodePath[i]; + return trace; +} + int32 AnimGraphExecutor::GetRootNodeIndex(Animation* anim) { // TODO: cache the root node index (use dictionary with Animation* -> int32 for fast lookups) @@ -223,13 +234,9 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* auto& context = Context.Get(); if (context.Data->EnableTracing) { - auto& trace = context.Data->TraceEvents.AddOne(); + auto& trace = context.AddTraceEvent(node); trace.Asset = anim; trace.Value = animPos; - trace.NodeId = node->ID; - const auto* nodePath = context.NodePath.Get(); - for (int32 i = 0; i < context.NodePath.Count(); i++) - trace.NodePath[i] = nodePath[i]; } // Evaluate nested animations @@ -502,11 +509,19 @@ Variant AnimGraphExecutor::SampleState(AnimGraphContext& context, const AnimGrap auto& data = state->Data.State; if (data.Graph == nullptr || data.Graph->GetRootNode() == nullptr) return Value::Null; + + // Add to trace + if (context.Data->EnableTracing) + { + auto& trace = context.AddTraceEvent(state); + } + ANIM_GRAPH_PROFILE_EVENT("Evaluate State"); context.NodePath.Add(state->ID); auto rootNode = data.Graph->GetRootNode(); auto result = eatBox((Node*)rootNode, &rootNode->Boxes[0]); context.NodePath.Pop(); + return result; } From 082768d08c09a4aa6431083ff8b75ec59b21b5b2 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 7 Feb 2024 23:39:02 +0100 Subject: [PATCH 065/139] Refactor `ThreadLocal` when running on hardware with more cores than `PLATFORM_THREADS_LIMIT` --- .../Animations/Graph/AnimGraph.Custom.cpp | 2 +- Source/Engine/Animations/Graph/AnimGraph.cpp | 17 +- Source/Engine/Animations/Graph/AnimGraph.h | 2 +- .../Animations/Graph/AnimGroup.Animation.cpp | 12 +- Source/Engine/Content/Storage/FlaxStorage.cpp | 13 +- Source/Engine/Content/Storage/FlaxStorage.h | 2 +- Source/Engine/Core/Collections/Sorting.cpp | 7 +- Source/Engine/Engine/EngineService.cpp | 16 +- Source/Engine/Level/SceneObjectsFactory.cpp | 2 +- ...rticleEmitterGraph.CPU.ParticleModules.cpp | 4 +- .../ParticleEmitterGraph.CPU.Particles.cpp | 8 +- .../Graph/CPU/ParticleEmitterGraph.CPU.cpp | 16 +- .../Graph/CPU/ParticleEmitterGraph.CPU.h | 2 +- Source/Engine/Platform/Base/PlatformBase.cpp | 4 + Source/Engine/Scripting/Scripting.cpp | 2 +- Source/Engine/Scripting/Scripting.h | 4 +- Source/Engine/Serialization/FileReadStream.h | 5 - Source/Engine/Threading/JobSystem.cpp | 2 +- Source/Engine/Threading/ThreadLocal.h | 164 ++++++++---------- Source/Engine/Threading/ThreadPool.cpp | 2 +- 20 files changed, 147 insertions(+), 139 deletions(-) diff --git a/Source/Engine/Animations/Graph/AnimGraph.Custom.cpp b/Source/Engine/Animations/Graph/AnimGraph.Custom.cpp index cdd5b03eb..860ac99d4 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.Custom.cpp +++ b/Source/Engine/Animations/Graph/AnimGraph.Custom.cpp @@ -89,7 +89,7 @@ void AnimGraphExecutor::initRuntime() void AnimGraphExecutor::ProcessGroupCustom(Box* boxBase, Node* nodeBase, Value& value) { #if USE_CSHARP - auto& context = Context.Get(); + auto& context = *Context.Get(); if (context.ValueCache.TryGet(boxBase, value)) return; auto box = (AnimGraphBox*)boxBase; diff --git a/Source/Engine/Animations/Graph/AnimGraph.cpp b/Source/Engine/Animations/Graph/AnimGraph.cpp index 40c33a3e8..09c03b71a 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.cpp +++ b/Source/Engine/Animations/Graph/AnimGraph.cpp @@ -9,7 +9,7 @@ extern void RetargetSkeletonNode(const SkeletonData& sourceSkeleton, const SkeletonData& targetSkeleton, const SkinnedModel::SkeletonMapping& sourceMapping, Transform& node, int32 i); -ThreadLocal AnimGraphExecutor::Context; +ThreadLocal AnimGraphExecutor::Context; Transform AnimGraphImpulse::GetNodeModelTransformation(SkeletonData& skeleton, int32 nodeIndex) const { @@ -104,7 +104,7 @@ AnimGraphInstanceData::OutgoingEvent AnimGraphInstanceData::ActiveEvent::End(Ani AnimGraphImpulse* AnimGraphNode::GetNodes(AnimGraphExecutor* executor) { - auto& context = AnimGraphExecutor::Context.Get(); + auto& context = *AnimGraphExecutor::Context.Get(); const int32 count = executor->_skeletonNodesCount; if (context.PoseCacheSize == context.PoseCache.Count()) context.PoseCache.AddOne(); @@ -204,7 +204,10 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt) // Initialize auto& skeleton = _graph.BaseModel->Skeleton; - auto& context = Context.Get(); + auto& contextPtr = Context.Get(); + if (!contextPtr) + contextPtr = New(); + auto& context = *contextPtr; { ANIM_GRAPH_PROFILE_EVENT("Init"); @@ -378,12 +381,12 @@ void AnimGraphExecutor::GetInputValue(Box* box, Value& result) AnimGraphImpulse* AnimGraphExecutor::GetEmptyNodes() { - return &Context.Get().EmptyNodes; + return &Context.Get()->EmptyNodes; } void AnimGraphExecutor::InitNodes(AnimGraphImpulse* nodes) const { - const auto& emptyNodes = Context.Get().EmptyNodes; + const auto& emptyNodes = Context.Get()->EmptyNodes; Platform::MemoryCopy(nodes->Nodes.Get(), emptyNodes.Nodes.Get(), sizeof(Transform) * _skeletonNodesCount); nodes->RootMotion = emptyNodes.RootMotion; nodes->Position = emptyNodes.Position; @@ -405,7 +408,7 @@ void AnimGraphExecutor::ResetBuckets(AnimGraphContext& context, AnimGraphBase* g VisjectExecutor::Value AnimGraphExecutor::eatBox(Node* caller, Box* box) { - auto& context = Context.Get(); + auto& context = *Context.Get(); // Check if graph is looped or is too deep if (context.CallStack.Count() >= ANIM_GRAPH_MAX_CALL_STACK) @@ -450,6 +453,6 @@ VisjectExecutor::Value AnimGraphExecutor::eatBox(Node* caller, Box* box) VisjectExecutor::Graph* AnimGraphExecutor::GetCurrentGraph() const { - auto& context = Context.Get(); + auto& context = *Context.Get(); return context.GraphStack.Peek(); } diff --git a/Source/Engine/Animations/Graph/AnimGraph.h b/Source/Engine/Animations/Graph/AnimGraph.h index a774e6b85..f3c148e0a 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.h +++ b/Source/Engine/Animations/Graph/AnimGraph.h @@ -819,7 +819,7 @@ private: int32 _skeletonNodesCount = 0; // Per-thread context to allow async execution - static ThreadLocal Context; + static ThreadLocal Context; public: /// diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp index 57829e8fd..536ed7d83 100644 --- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp +++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp @@ -87,7 +87,7 @@ void AnimGraphExecutor::ProcessAnimEvents(AnimGraphNode* node, bool loop, float if (anim->Events.Count() == 0) return; ANIM_GRAPH_PROFILE_EVENT("Events"); - auto& context = Context.Get(); + auto& context = *Context.Get(); float eventTimeMin = animPrevPos; float eventTimeMax = animPos; if (loop && context.DeltaTime * speed < 0) @@ -231,7 +231,7 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* const float animPrevPos = GetAnimSamplePos(length, anim, prevPos, speed); // Add to trace - auto& context = Context.Get(); + auto& context = *Context.Get(); if (context.Data->EnableTracing) { auto& trace = context.AddTraceEvent(node); @@ -655,7 +655,7 @@ void ComputeMultiBlendLength(float& length, AnimGraphNode* node) void AnimGraphExecutor::ProcessGroupParameters(Box* box, Node* node, Value& value) { - auto& context = Context.Get(); + auto& context = *Context.Get(); switch (node->TypeID) { // Get @@ -766,7 +766,7 @@ void AnimGraphExecutor::ProcessGroupParameters(Box* box, Node* node, Value& valu void AnimGraphExecutor::ProcessGroupTools(Box* box, Node* nodeBase, Value& value) { - auto& context = Context.Get(); + auto& context = *Context.Get(); auto node = (AnimGraphNode*)nodeBase; switch (node->TypeID) { @@ -790,7 +790,7 @@ void AnimGraphExecutor::ProcessGroupTools(Box* box, Node* nodeBase, Value& value void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Value& value) { - auto& context = Context.Get(); + auto& context = *Context.Get(); if (context.ValueCache.TryGet(boxBase, value)) return; auto box = (AnimGraphBox*)boxBase; @@ -2272,7 +2272,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu void AnimGraphExecutor::ProcessGroupFunction(Box* boxBase, Node* node, Value& value) { - auto& context = Context.Get(); + auto& context = *Context.Get(); if (context.ValueCache.TryGet(boxBase, value)) return; switch (node->TypeID) diff --git a/Source/Engine/Content/Storage/FlaxStorage.cpp b/Source/Engine/Content/Storage/FlaxStorage.cpp index df99418bb..ca39ebaf9 100644 --- a/Source/Engine/Content/Storage/FlaxStorage.cpp +++ b/Source/Engine/Content/Storage/FlaxStorage.cpp @@ -211,7 +211,10 @@ FlaxStorage::~FlaxStorage() #if USE_EDITOR // Ensure to close any outstanding file handles to prevent file locking in case it failed to load - _file.DeleteAll(); + Array streams; + _file.GetValues(streams); + for (FileReadStream* stream : streams) + Delete(stream); #endif } @@ -1264,7 +1267,6 @@ bool FlaxStorage::LoadAssetHeader(const Entry& e, AssetInitData& data) } #if ASSETS_LOADING_EXTRA_VERIFICATION - // Validate loaded header (asset ID and type ID must be the same) if (e.ID != data.Header.ID) { @@ -1274,7 +1276,6 @@ bool FlaxStorage::LoadAssetHeader(const Entry& e, AssetInitData& data) { LOG(Error, "Loading asset header data mismatch! Expected Type Name: {0}, loaded header: {1}.\nSource: {2}", e.TypeName, data.Header.ToString(), ToString()); } - #endif return false; @@ -1337,7 +1338,11 @@ bool FlaxStorage::CloseFileHandles() return true; // Failed, someone is still accessing the file // Close file handles (from all threads) - _file.DeleteAll(); + Array streams; + _file.GetValues(streams); + for (FileReadStream* stream : streams) + Delete(stream); + _file.Clear(); return false; } diff --git a/Source/Engine/Content/Storage/FlaxStorage.h b/Source/Engine/Content/Storage/FlaxStorage.h index 77c912c5a..842511430 100644 --- a/Source/Engine/Content/Storage/FlaxStorage.h +++ b/Source/Engine/Content/Storage/FlaxStorage.h @@ -94,7 +94,7 @@ protected: CriticalSection _loadLocker; // Storage - ThreadLocalObject _file; + ThreadLocal _file; Array _chunks; // Metadata diff --git a/Source/Engine/Core/Collections/Sorting.cpp b/Source/Engine/Core/Collections/Sorting.cpp index 49ce0f3d4..85c0fc9f1 100644 --- a/Source/Engine/Core/Collections/Sorting.cpp +++ b/Source/Engine/Core/Collections/Sorting.cpp @@ -5,11 +5,14 @@ #include "Engine/Threading/ThreadLocal.h" // Use a cached storage for the sorting (one per thread to reduce locking) -ThreadLocal SortingStacks; +ThreadLocal SortingStacks; Sorting::SortingStack& Sorting::SortingStack::Get() { - return SortingStacks.Get(); + SortingStack*& stack = SortingStacks.Get(); + if (!stack) + stack = New(); + return *stack; } Sorting::SortingStack::SortingStack() diff --git a/Source/Engine/Engine/EngineService.cpp b/Source/Engine/Engine/EngineService.cpp index 7eea66853..c400ef943 100644 --- a/Source/Engine/Engine/EngineService.cpp +++ b/Source/Engine/Engine/EngineService.cpp @@ -72,9 +72,6 @@ void EngineService::OnInit() // Init services from front to back auto& services = GetServices(); -#if TRACY_ENABLE - Char nameBuffer[100]; -#endif for (int32 i = 0; i < services.Count(); i++) { const auto service = services[i]; @@ -82,6 +79,7 @@ void EngineService::OnInit() #if TRACY_ENABLE ZoneScoped; int32 nameBufferLength = 0; + Char nameBuffer[100]; for (int32 j = 0; j < name.Length(); j++) if (name[j] != ' ') nameBuffer[nameBufferLength++] = name[j]; @@ -114,6 +112,18 @@ void EngineService::OnDispose() const auto service = services[i]; if (service->IsInitialized) { +#if TRACY_ENABLE + ZoneScoped; + const StringView name(service->Name); + int32 nameBufferLength = 0; + Char nameBuffer[100]; + for (int32 j = 0; j < name.Length(); j++) + if (name[j] != ' ') + nameBuffer[nameBufferLength++] = name[j]; + Platform::MemoryCopy(nameBuffer + nameBufferLength, TEXT("::Dispose"), 10 * sizeof(Char)); + nameBufferLength += 10; + ZoneName(nameBuffer, nameBufferLength); +#endif service->IsInitialized = false; service->Dispose(); } diff --git a/Source/Engine/Level/SceneObjectsFactory.cpp b/Source/Engine/Level/SceneObjectsFactory.cpp index d9c86d250..006e54aa6 100644 --- a/Source/Engine/Level/SceneObjectsFactory.cpp +++ b/Source/Engine/Level/SceneObjectsFactory.cpp @@ -63,7 +63,7 @@ SceneObjectsFactory::Context::~Context() { if (Async) { - Array> modifiers; + Array> modifiers; Modifiers.GetValues(modifiers); for (ISerializeModifier* e : modifiers) { diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp index 6ea4b8b4e..f198fdcd7 100644 --- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp +++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp @@ -52,7 +52,7 @@ namespace int32 ParticleEmitterGraphCPUExecutor::ProcessSpawnModule(int32 index) { const auto node = _graph.SpawnModules[index]; - auto& context = Context.Get(); + auto& context = *Context.Get(); auto& data = context.Data->SpawnModulesData[index]; // Accumulate the previous frame fraction @@ -120,7 +120,7 @@ int32 ParticleEmitterGraphCPUExecutor::ProcessSpawnModule(int32 index) void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* node, int32 particlesStart, int32 particlesEnd) { - auto& context = Context.Get(); + auto& context = *Context.Get(); auto stride = context.Data->Buffer->Stride; auto start = context.Data->Buffer->GetParticleCPU(particlesStart); diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp index cb2d7004e..4c30746c3 100644 --- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp +++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp @@ -12,7 +12,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupParameters(Box* box, Node* node, Value& value) { - auto& context = Context.Get(); + auto& context = *Context.Get(); switch (node->TypeID) { // Get @@ -168,7 +168,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupTextures(Box* box, Node* node, void ParticleEmitterGraphCPUExecutor::ProcessGroupTools(Box* box, Node* node, Value& value) { - auto& context = Context.Get(); + auto& context = *Context.Get(); switch (node->TypeID) { // Linearize Depth @@ -202,7 +202,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupTools(Box* box, Node* node, Va void ParticleEmitterGraphCPUExecutor::ProcessGroupParticles(Box* box, Node* nodeBase, Value& value) { - auto& context = Context.Get(); + auto& context = *Context.Get(); auto node = (ParticleEmitterGraphCPUNode*)nodeBase; switch (node->TypeID) { @@ -468,7 +468,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupParticles(Box* box, Node* node void ParticleEmitterGraphCPUExecutor::ProcessGroupFunction(Box* box, Node* node, Value& value) { - auto& context = Context.Get(); + auto& context = *Context.Get(); switch (node->TypeID) { // Function Input diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp index a8f7898e1..12ecd054b 100644 --- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp +++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp @@ -8,7 +8,7 @@ #include "Engine/Engine/Time.h" #include "Engine/Profiler/ProfilerCPU.h" -ThreadLocal ParticleEmitterGraphCPUExecutor::Context; +ThreadLocal ParticleEmitterGraphCPUExecutor::Context; namespace { @@ -122,7 +122,10 @@ ParticleEmitterGraphCPUExecutor::ParticleEmitterGraphCPUExecutor(ParticleEmitter void ParticleEmitterGraphCPUExecutor::Init(ParticleEmitter* emitter, ParticleEffect* effect, ParticleEmitterInstance& data, float dt) { - auto& context = Context.Get(); + auto& contextPtr = Context.Get(); + if (!contextPtr) + contextPtr = New(); + auto& context = *contextPtr; context.GraphStack.Clear(); context.GraphStack.Push(&_graph); context.Data = &data; @@ -252,8 +255,8 @@ bool ParticleEmitterGraphCPUExecutor::ComputeBounds(ParticleEmitter* emitter, Pa case 401: { // Prepare graph data - auto& context = Context.Get(); Init(emitter, effect, data); + auto& context = *Context.Get(); // Find the maximum radius of the particle light float maxRadius = 0.0f; @@ -377,7 +380,7 @@ void ParticleEmitterGraphCPUExecutor::Draw(ParticleEmitter* emitter, ParticleEff // Prepare graph data Init(emitter, effect, data); - auto& context = Context.Get(); + auto& context = *Context.Get(); // Draw lights for (int32 moduleIndex = 0; moduleIndex < emitter->Graph.LightModules.Count(); moduleIndex++) @@ -571,7 +574,6 @@ int32 ParticleEmitterGraphCPUExecutor::UpdateSpawn(ParticleEmitter* emitter, Par PROFILE_CPU_NAMED("Spawn"); // Prepare data - auto& context = Context.Get(); Init(emitter, effect, data, dt); // Spawn particles @@ -587,7 +589,7 @@ int32 ParticleEmitterGraphCPUExecutor::UpdateSpawn(ParticleEmitter* emitter, Par VisjectExecutor::Value ParticleEmitterGraphCPUExecutor::eatBox(Node* caller, Box* box) { // Check if graph is looped or is too deep - auto& context = Context.Get(); + auto& context = *Context.Get(); if (context.CallStackSize >= PARTICLE_EMITTER_MAX_CALL_STACK) { OnError(caller, box, TEXT("Graph is looped or too deep!")); @@ -618,6 +620,6 @@ VisjectExecutor::Value ParticleEmitterGraphCPUExecutor::eatBox(Node* caller, Box VisjectExecutor::Graph* ParticleEmitterGraphCPUExecutor::GetCurrentGraph() const { - auto& context = Context.Get(); + auto& context = *Context.Get(); return (Graph*)context.GraphStack.Peek(); } diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.h b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.h index 4f55da6e2..34a65d721 100644 --- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.h +++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.h @@ -133,7 +133,7 @@ private: ParticleEmitterGraphCPU& _graph; // Per-thread context to allow async execution - static ThreadLocal Context; + static ThreadLocal Context; public: /// diff --git a/Source/Engine/Platform/Base/PlatformBase.cpp b/Source/Engine/Platform/Base/PlatformBase.cpp index 9a6b1b9dc..5c1b2ca28 100644 --- a/Source/Engine/Platform/Base/PlatformBase.cpp +++ b/Source/Engine/Platform/Base/PlatformBase.cpp @@ -41,6 +41,10 @@ static_assert(sizeof(bool) == 1, "Invalid bool type size."); static_assert(sizeof(float) == 4, "Invalid float type size."); static_assert(sizeof(double) == 8, "Invalid double type size."); +// Check configuration +static_assert((PLATFORM_THREADS_LIMIT & (PLATFORM_THREADS_LIMIT - 1)) == 0, "Threads limit must be power of two."); +static_assert(PLATFORM_THREADS_LIMIT % 4 == 0, "Threads limit must be multiple of 4."); + float PlatformBase::CustomDpiScale = 1.0f; Array> PlatformBase::Users; Delegate PlatformBase::UserAdded; diff --git a/Source/Engine/Scripting/Scripting.cpp b/Source/Engine/Scripting/Scripting.cpp index 6cabbc0dd..e3a0991e3 100644 --- a/Source/Engine/Scripting/Scripting.cpp +++ b/Source/Engine/Scripting/Scripting.cpp @@ -115,7 +115,7 @@ Action Scripting::ScriptsLoaded; Action Scripting::ScriptsUnload; Action Scripting::ScriptsReloading; Action Scripting::ScriptsReloaded; -ThreadLocal Scripting::ObjectsLookupIdMapping; +ThreadLocal Scripting::ObjectsLookupIdMapping; ScriptingService ScriptingServiceInstance; bool initFlaxEngine(); diff --git a/Source/Engine/Scripting/Scripting.h b/Source/Engine/Scripting/Scripting.h index a22349928..6ed2beca4 100644 --- a/Source/Engine/Scripting/Scripting.h +++ b/Source/Engine/Scripting/Scripting.h @@ -6,7 +6,7 @@ #include "Engine/Scripting/ScriptingType.h" #include "Types.h" -template +template class ThreadLocal; /// @@ -114,7 +114,7 @@ public: /// /// The objects lookup identifier mapping used to override the object ids on FindObject call (used by the object references deserialization). /// - static ThreadLocal ObjectsLookupIdMapping; + static ThreadLocal ObjectsLookupIdMapping; /// /// Finds the object by the given identifier. Searches registered scene objects and optionally assets. Logs warning if fails. diff --git a/Source/Engine/Serialization/FileReadStream.h b/Source/Engine/Serialization/FileReadStream.h index 40a14185b..57287abcf 100644 --- a/Source/Engine/Serialization/FileReadStream.h +++ b/Source/Engine/Serialization/FileReadStream.h @@ -12,7 +12,6 @@ class FLAXENGINE_API FileReadStream : public ReadStream { private: - File* _file; uint32 _virtualPosInBuffer; // Current position in the buffer (index) uint32 _bufferSize; // Amount of loaded bytes from the file to the buffer @@ -33,11 +32,9 @@ public: ~FileReadStream(); public: - /// /// Gets the file handle. /// - /// File FORCE_INLINE const File* GetFile() const { return _file; @@ -49,7 +46,6 @@ public: void Unlink(); public: - /// /// Open file to write data to it /// @@ -58,7 +54,6 @@ public: static FileReadStream* Open(const StringView& path); public: - // [ReadStream] void Flush() final override; void Close() final override; diff --git a/Source/Engine/Threading/JobSystem.cpp b/Source/Engine/Threading/JobSystem.cpp index c89e6aca5..dea8298ce 100644 --- a/Source/Engine/Threading/JobSystem.cpp +++ b/Source/Engine/Threading/JobSystem.cpp @@ -93,7 +93,7 @@ struct TIsPODType namespace { JobSystemService JobSystemInstance; - Thread* Threads[PLATFORM_THREADS_LIMIT] = {}; + Thread* Threads[PLATFORM_THREADS_LIMIT / 2] = {}; int32 ThreadsCount = 0; bool JobStartingOnDispatch = true; volatile int64 ExitFlag = 0; diff --git a/Source/Engine/Threading/ThreadLocal.h b/Source/Engine/Threading/ThreadLocal.h index 4de8ced57..aba1f1170 100644 --- a/Source/Engine/Threading/ThreadLocal.h +++ b/Source/Engine/Threading/ThreadLocal.h @@ -6,14 +6,14 @@ #include "Engine/Platform/Platform.h" /// -/// Per-thread local variable storage. -/// Implemented using atomic with per-thread storage indexed via thread id hashing. -/// ForConsider using 'THREADLOCAL' define before the variable instead. +/// Per-thread local variable storage for basic types (POD). Implemented using atomic with per-thread storage indexed via thread id hashing. Consider using 'THREADLOCAL' define before the variable instead. /// -template +template class ThreadLocal { protected: + constexpr static int32 DynamicMaxThreads = 1024; + static_assert(TIsPODType::Value, "Only POD types are supported"); struct Bucket { @@ -21,34 +21,29 @@ protected: T Value; }; - Bucket _buckets[MaxThreads]; + Bucket _staticBuckets[MaxThreads]; + Bucket* _dynamicBuckets = nullptr; public: - ThreadLocal() { - // Clear buckets - if (ClearMemory) - { - Platform::MemoryClear(_buckets, sizeof(_buckets)); - } - else - { - for (int32 i = 0; i < MaxThreads; i++) - _buckets[i].ThreadID = 0; - } + Platform::MemoryClear(_staticBuckets, sizeof(_staticBuckets)); + } + + ~ThreadLocal() + { + Platform::Free(_dynamicBuckets); } public: - - T& Get() + FORCE_INLINE T& Get() { - return _buckets[GetIndex()].Value; + return GetBucket().Value; } - void Set(const T& value) + FORCE_INLINE void Set(const T& value) { - _buckets[GetIndex()].Value = value; + GetBucket().Value = value; } int32 Count() const @@ -56,9 +51,17 @@ public: int32 result = 0; for (int32 i = 0; i < MaxThreads; i++) { - if (Platform::AtomicRead((int64 volatile*)&_buckets[i].ThreadID) != 0) + if (Platform::AtomicRead((int64 volatile*)&_staticBuckets[i].ThreadID) != 0) result++; } + if (auto dynamicBuckets = (Bucket*)Platform::AtomicRead((intptr volatile*)&_dynamicBuckets)) + { + for (int32 i = 0; i < MaxThreads; i++) + { + if (Platform::AtomicRead((int64 volatile*)&dynamicBuckets[i].ThreadID) != 0) + result++; + } + } return result; } @@ -67,89 +70,72 @@ public: { for (int32 i = 0; i < MaxThreads; i++) { - if (Platform::AtomicRead((int64 volatile*)&_buckets[i].ThreadID) != 0) - result.Add(_buckets[i].Value); + if (Platform::AtomicRead((int64 volatile*)&_staticBuckets[i].ThreadID) != 0) + result.Add(_staticBuckets[i].Value); } + if (auto dynamicBuckets = (Bucket*)Platform::AtomicRead((intptr volatile*)&_dynamicBuckets)) + { + for (int32 i = 0; i < MaxThreads; i++) + { + if (Platform::AtomicRead((int64 volatile*)&dynamicBuckets[i].ThreadID) != 0) + result.Add(dynamicBuckets[i].Value); + } + } + } + + void Clear() + { + Platform::MemoryClear(_staticBuckets, sizeof(_staticBuckets)); + Platform::Free(_dynamicBuckets); + _dynamicBuckets = nullptr; } protected: - - FORCE_INLINE static int32 Hash(const int64 value) + Bucket& GetBucket() { - return value & (MaxThreads - 1); - } + const int64 key = (int64)Platform::GetCurrentThreadID(); - FORCE_INLINE int32 GetIndex() - { - int64 key = (int64)Platform::GetCurrentThreadID(); - auto index = Hash(key); - while (true) + // Search statically allocated buckets + int32 index = (int32)(key & (MaxThreads - 1)); + int32 spaceLeft = MaxThreads; + while (spaceLeft) { - const int64 value = Platform::AtomicRead(&_buckets[index].ThreadID); + const int64 value = Platform::AtomicRead(&_staticBuckets[index].ThreadID); if (value == key) - break; - if (value == 0 && Platform::InterlockedCompareExchange(&_buckets[index].ThreadID, key, 0) == 0) - break; - index = Hash(index + 1); + return _staticBuckets[index]; + if (value == 0 && Platform::InterlockedCompareExchange(&_staticBuckets[index].ThreadID, key, 0) == 0) + return _staticBuckets[index]; + index = (index + 1) & (MaxThreads - 1); + spaceLeft--; } - return index; - } -}; -/// -/// Per thread local object -/// -template -class ThreadLocalObject : public ThreadLocal -{ -public: - - typedef ThreadLocal Base; - -public: - - void Delete() - { - auto value = Base::Get(); - Base::SetAll(nullptr); - ::Delete(value); - } - - void DeleteAll() - { - for (int32 i = 0; i < MaxThreads; i++) + // Allocate dynamic buckets if missing + DYNAMIC: + auto dynamicBuckets = (Bucket*)Platform::AtomicRead((intptr volatile*)&_dynamicBuckets); + if (!dynamicBuckets) { - auto& bucket = Base::_buckets[i]; - if (bucket.Value != nullptr) + dynamicBuckets = (Bucket*)Platform::Allocate(DynamicMaxThreads * sizeof(Bucket), 16); + Platform::MemoryClear(dynamicBuckets, DynamicMaxThreads * sizeof(Bucket)); + if (Platform::InterlockedCompareExchange((intptr volatile*)&_dynamicBuckets, (intptr)dynamicBuckets, 0) != 0) { - ::Delete(bucket.Value); - bucket.ThreadID = 0; - bucket.Value = nullptr; + Platform::Free(dynamicBuckets); + goto DYNAMIC; } } - } - template - void GetNotNullValues(Array& result) const - { - result.EnsureCapacity(MaxThreads); - for (int32 i = 0; i < MaxThreads; i++) + // Search dynamically allocated buckets + index = (int32)(key & (DynamicMaxThreads - 1)); + spaceLeft = DynamicMaxThreads; + while (spaceLeft) { - if (Base::_buckets[i].Value != nullptr) - { - result.Add(Base::_buckets[i].Value); - } + const int64 value = Platform::AtomicRead(&dynamicBuckets[index].ThreadID); + if (value == key) + return dynamicBuckets[index]; + if (value == 0 && Platform::InterlockedCompareExchange(&dynamicBuckets[index].ThreadID, key, 0) == 0) + return dynamicBuckets[index]; + index = (index + 1) & (DynamicMaxThreads - 1); + spaceLeft--; } - } - - int32 CountNotNullValues() const - { - int32 result = 0; - for (int32 i = 0; i < MaxThreads; i++) - { - if (Base::_buckets[i].Value != nullptr) - result++; - } - return result; + return *(Bucket*)nullptr; } }; diff --git a/Source/Engine/Threading/ThreadPool.cpp b/Source/Engine/Threading/ThreadPool.cpp index b7db81ffa..2b6ed5e26 100644 --- a/Source/Engine/Threading/ThreadPool.cpp +++ b/Source/Engine/Threading/ThreadPool.cpp @@ -58,7 +58,7 @@ ThreadPoolService ThreadPoolServiceInstance; bool ThreadPoolService::Init() { // Spawn threads - const int32 numThreads = Math::Clamp(Platform::GetCPUInfo().ProcessorCoreCount - 1, 2, PLATFORM_THREADS_LIMIT); + const int32 numThreads = Math::Clamp(Platform::GetCPUInfo().ProcessorCoreCount - 1, 2, PLATFORM_THREADS_LIMIT / 2); LOG(Info, "Spawning {0} Thread Pool workers", numThreads); for (int32 i = ThreadPoolImpl::Threads.Count(); i < numThreads; i++) { From f9ca69d8a9c15dc5a9562ce971f0a4108df38945 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 8 Feb 2024 10:47:55 +0100 Subject: [PATCH 066/139] Disable dynamic buckets in `ThreadLocal` on non-Desktop platforms that use fixed set of thread count --- Source/Engine/Threading/ThreadLocal.h | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Threading/ThreadLocal.h b/Source/Engine/Threading/ThreadLocal.h index aba1f1170..b766d0430 100644 --- a/Source/Engine/Threading/ThreadLocal.h +++ b/Source/Engine/Threading/ThreadLocal.h @@ -5,6 +5,8 @@ #include "Engine/Core/Types/BaseTypes.h" #include "Engine/Platform/Platform.h" +#define THREAD_LOCAL_USE_DYNAMIC_BUCKETS (PLATFORM_DESKTOP) + /// /// Per-thread local variable storage for basic types (POD). Implemented using atomic with per-thread storage indexed via thread id hashing. Consider using 'THREADLOCAL' define before the variable instead. /// @@ -12,7 +14,6 @@ template class ThreadLocal { protected: - constexpr static int32 DynamicMaxThreads = 1024; static_assert(TIsPODType::Value, "Only POD types are supported"); struct Bucket @@ -22,7 +23,10 @@ protected: }; Bucket _staticBuckets[MaxThreads]; +#if THREAD_LOCAL_USE_DYNAMIC_BUCKETS Bucket* _dynamicBuckets = nullptr; + constexpr static int32 DynamicMaxThreads = 1024; +#endif public: ThreadLocal() @@ -30,10 +34,12 @@ public: Platform::MemoryClear(_staticBuckets, sizeof(_staticBuckets)); } +#if THREAD_LOCAL_USE_DYNAMIC_BUCKETS ~ThreadLocal() { Platform::Free(_dynamicBuckets); } +#endif public: FORCE_INLINE T& Get() @@ -54,6 +60,7 @@ public: if (Platform::AtomicRead((int64 volatile*)&_staticBuckets[i].ThreadID) != 0) result++; } +#if THREAD_LOCAL_USE_DYNAMIC_BUCKETS if (auto dynamicBuckets = (Bucket*)Platform::AtomicRead((intptr volatile*)&_dynamicBuckets)) { for (int32 i = 0; i < MaxThreads; i++) @@ -62,6 +69,7 @@ public: result++; } } +#endif return result; } @@ -73,6 +81,7 @@ public: if (Platform::AtomicRead((int64 volatile*)&_staticBuckets[i].ThreadID) != 0) result.Add(_staticBuckets[i].Value); } +#if THREAD_LOCAL_USE_DYNAMIC_BUCKETS if (auto dynamicBuckets = (Bucket*)Platform::AtomicRead((intptr volatile*)&_dynamicBuckets)) { for (int32 i = 0; i < MaxThreads; i++) @@ -81,13 +90,16 @@ public: result.Add(dynamicBuckets[i].Value); } } +#endif } void Clear() { Platform::MemoryClear(_staticBuckets, sizeof(_staticBuckets)); +#if THREAD_LOCAL_USE_DYNAMIC_BUCKETS Platform::Free(_dynamicBuckets); _dynamicBuckets = nullptr; +#endif } protected: @@ -109,6 +121,7 @@ protected: spaceLeft--; } +#if THREAD_LOCAL_USE_DYNAMIC_BUCKETS // Allocate dynamic buckets if missing DYNAMIC: auto dynamicBuckets = (Bucket*)Platform::AtomicRead((intptr volatile*)&_dynamicBuckets); @@ -136,6 +149,8 @@ protected: index = (index + 1) & (DynamicMaxThreads - 1); spaceLeft--; } +#endif + return *(Bucket*)nullptr; } }; From d08843900e11e41e89707fb46c6bf0d806903a8d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 9 Feb 2024 15:37:29 +0100 Subject: [PATCH 067/139] Add `AnimationRootMotionFlags` to configure root motion component extraction Add `RootMotionMode` to support extracting root motion from animated skeleton pose center of mass #1429 #2152 --- .../Editor/Content/Import/ModelImportEntry.cs | 1 + Source/Engine/Animations/AnimationData.cpp | 84 ++++++++++ Source/Engine/Animations/AnimationData.h | 96 ++++------- Source/Engine/Animations/Curve.h | 2 +- .../Animations/Graph/AnimGraph.Base.cpp | 2 +- Source/Engine/Animations/Graph/AnimGraph.cpp | 2 +- Source/Engine/Animations/Graph/AnimGraph.h | 6 +- .../Animations/Graph/AnimGroup.Animation.cpp | 55 +++++-- Source/Engine/Content/Assets/Animation.cpp | 21 ++- .../ContentImporters/CreateAnimationGraph.cpp | 2 +- Source/Engine/Graphics/Models/ModelData.cpp | 10 +- Source/Engine/Graphics/Models/SkeletonData.h | 46 ++---- Source/Engine/Graphics/Models/SkinnedMesh.cpp | 63 ++++++++ Source/Engine/Tools/ModelTool/ModelTool.cpp | 150 +++++++++++++++++- Source/Engine/Tools/ModelTool/ModelTool.h | 20 ++- 15 files changed, 416 insertions(+), 144 deletions(-) create mode 100644 Source/Engine/Animations/AnimationData.cpp diff --git a/Source/Editor/Content/Import/ModelImportEntry.cs b/Source/Editor/Content/Import/ModelImportEntry.cs index 484efb1d9..fac782cbd 100644 --- a/Source/Editor/Content/Import/ModelImportEntry.cs +++ b/Source/Editor/Content/Import/ModelImportEntry.cs @@ -16,6 +16,7 @@ namespace FlaxEngine.Tools private bool ShowModel => Type == ModelType.Model || Type == ModelType.Prefab; private bool ShowSkinnedModel => Type == ModelType.SkinnedModel || Type == ModelType.Prefab; private bool ShowAnimation => Type == ModelType.Animation || Type == ModelType.Prefab; + private bool ShowRootMotion => ShowAnimation && RootMotion != RootMotionMode.None; private bool ShowSmoothingNormalsAngle => ShowGeometry && CalculateNormals; private bool ShowSmoothingTangentsAngle => ShowGeometry && CalculateTangents; private bool ShowFramesRange => ShowAnimation && Duration == AnimationDuration.Custom; diff --git a/Source/Engine/Animations/AnimationData.cpp b/Source/Engine/Animations/AnimationData.cpp new file mode 100644 index 000000000..8c3611d7a --- /dev/null +++ b/Source/Engine/Animations/AnimationData.cpp @@ -0,0 +1,84 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +#include "AnimationData.h" + +void NodeAnimationData::Evaluate(float time, Transform* result, bool loop) const +{ + if (Position.GetKeyframes().HasItems()) +#if USE_LARGE_WORLDS + { + Float3 position; + Position.Evaluate(position, time, loop); + result->Translation = position; + } +#else + Position.Evaluate(result->Translation, time, loop); +#endif + if (Rotation.GetKeyframes().HasItems()) + Rotation.Evaluate(result->Orientation, time, loop); + if (Scale.GetKeyframes().HasItems()) + Scale.Evaluate(result->Scale, time, loop); +} + +void NodeAnimationData::EvaluateAll(float time, Transform* result, bool loop) const +{ + Float3 position; + Position.Evaluate(position, time, loop); + result->Translation = position; + Rotation.Evaluate(result->Orientation, time, loop); + Scale.Evaluate(result->Scale, time, loop); +} + +int32 NodeAnimationData::GetKeyframesCount() const +{ + return Position.GetKeyframes().Count() + Rotation.GetKeyframes().Count() + Scale.GetKeyframes().Count(); +} + +uint64 NodeAnimationData::GetMemoryUsage() const +{ + return NodeName.Length() * sizeof(Char) + Position.GetMemoryUsage() + Rotation.GetMemoryUsage() + Scale.GetMemoryUsage(); +} + +uint64 AnimationData::GetMemoryUsage() const +{ + uint64 result = (Name.Length() + RootNodeName.Length()) * sizeof(Char) + Channels.Capacity() * sizeof(NodeAnimationData); + for (const auto& e : Channels) + result += e.GetMemoryUsage(); + return result; +} + +int32 AnimationData::GetKeyframesCount() const +{ + int32 result = 0; + for (int32 i = 0; i < Channels.Count(); i++) + result += Channels[i].GetKeyframesCount(); + return result; +} + +NodeAnimationData* AnimationData::GetChannel(const StringView& name) +{ + for (auto& e : Channels) + if (e.NodeName == name) + return &e; + return nullptr; +} + +void AnimationData::Swap(AnimationData& other) +{ + ::Swap(Duration, other.Duration); + ::Swap(FramesPerSecond, other.FramesPerSecond); + ::Swap(RootMotionFlags, other.RootMotionFlags); + ::Swap(Name, other.Name); + ::Swap(RootNodeName, other.RootNodeName); + Channels.Swap(other.Channels); +} + +void AnimationData::Dispose() +{ + Name.Clear(); + Duration = 0.0; + FramesPerSecond = 0.0; + RootNodeName.Clear(); + RootMotionFlags = AnimationRootMotionFlags::None; + Channels.Resize(0); +} diff --git a/Source/Engine/Animations/AnimationData.h b/Source/Engine/Animations/AnimationData.h index c69de8afa..00717b05f 100644 --- a/Source/Engine/Animations/AnimationData.h +++ b/Source/Engine/Animations/AnimationData.h @@ -3,8 +3,8 @@ #pragma once #include "Engine/Core/Types/String.h" -#include "Engine/Animations/Curve.h" #include "Engine/Core/Math/Transform.h" +#include "Engine/Animations/Curve.h" /// /// Single node animation data container. @@ -50,19 +50,7 @@ public: /// The time to evaluate the curves at. /// The interpolated value from the curve at provided time. /// If true the curve will loop when it goes past the end or beginning. Otherwise the curve value will be clamped. - void Evaluate(float time, Transform* result, bool loop = true) const - { - if (Position.GetKeyframes().HasItems()) - { - Float3 position; - Position.Evaluate(position, time, loop); - result->Translation = position; - } - if (Rotation.GetKeyframes().HasItems()) - Rotation.Evaluate(result->Orientation, time, loop); - if (Scale.GetKeyframes().HasItems()) - Scale.Evaluate(result->Scale, time, loop); - } + void Evaluate(float time, Transform* result, bool loop = true) const; /// /// Evaluates the animation transformation at the specified time. @@ -70,29 +58,37 @@ public: /// The time to evaluate the curves at. /// The interpolated value from the curve at provided time. /// If true the curve will loop when it goes past the end or beginning. Otherwise the curve value will be clamped. - void EvaluateAll(float time, Transform* result, bool loop = true) const - { - Float3 position; - Position.Evaluate(position, time, loop); - result->Translation = position; - Rotation.Evaluate(result->Orientation, time, loop); - Scale.Evaluate(result->Scale, time, loop); - } + void EvaluateAll(float time, Transform* result, bool loop = true) const; /// /// Gets the total amount of keyframes in the animation curves. /// - int32 GetKeyframesCount() const - { - return Position.GetKeyframes().Count() + Rotation.GetKeyframes().Count() + Scale.GetKeyframes().Count(); - } + int32 GetKeyframesCount() const; - uint64 GetMemoryUsage() const - { - return NodeName.Length() * sizeof(Char) + Position.GetMemoryUsage() + Rotation.GetMemoryUsage() + Scale.GetMemoryUsage(); - } + uint64 GetMemoryUsage() const; }; +/// +/// Root Motion modes that can be applied by the animation. Used as flags for selective behavior. +/// +API_ENUM(Attributes="Flags") enum class AnimationRootMotionFlags : byte +{ + // No root motion. + None = 0, + // Root node position along XZ plane. Applies horizontal movement. Good for stationary animations (eg. idle). + RootPositionXZ = 1 << 0, + // Root node position along Y axis (up). Applies vertical movement. Good for all 'grounded' animations unless jumping is handled from code. + RootPositionY = 1 << 1, + // Root node rotation. Applies orientation changes. Good for animations that have baked-in root rotation (eg. turn animations). + RootRotation = 1 << 2, + // Root node position. + RootPosition = RootPositionXZ | RootPositionY, + // Root node position and rotation. + RootTransform = RootPosition | RootRotation, +}; + +DECLARE_ENUM_OPERATORS(AnimationRootMotionFlags); + /// /// Skeleton nodes animation data container. Includes metadata about animation sampling, duration and node animations curves. /// @@ -111,7 +107,7 @@ struct AnimationData /// /// Enables root motion extraction support from this animation. /// - bool EnableRootMotion = false; + AnimationRootMotionFlags RootMotionFlags = AnimationRootMotionFlags::None; /// /// The animation name. @@ -140,49 +136,23 @@ public: return static_cast(Duration / FramesPerSecond); } - uint64 GetMemoryUsage() const - { - uint64 result = (Name.Length() + RootNodeName.Length()) * sizeof(Char) + Channels.Capacity() * sizeof(NodeAnimationData); - for (const auto& e : Channels) - result += e.GetMemoryUsage(); - return result; - } + uint64 GetMemoryUsage() const; /// /// Gets the total amount of keyframes in the all animation channels. /// - int32 GetKeyframesCount() const - { - int32 result = 0; - for (int32 i = 0; i < Channels.Count(); i++) - result += Channels[i].GetKeyframesCount(); - return result; - } + int32 GetKeyframesCount() const; + + NodeAnimationData* GetChannel(const StringView& name); /// /// Swaps the contents of object with the other object without copy operation. Performs fast internal data exchange. /// /// The other object. - void Swap(AnimationData& other) - { - ::Swap(Duration, other.Duration); - ::Swap(FramesPerSecond, other.FramesPerSecond); - ::Swap(EnableRootMotion, other.EnableRootMotion); - ::Swap(Name, other.Name); - ::Swap(RootNodeName, other.RootNodeName); - Channels.Swap(other.Channels); - } + void Swap(AnimationData& other); /// /// Releases data. /// - void Dispose() - { - Name.Clear(); - Duration = 0.0; - FramesPerSecond = 0.0; - RootNodeName.Clear(); - EnableRootMotion = false; - Channels.Resize(0); - } + void Dispose(); }; diff --git a/Source/Engine/Animations/Curve.h b/Source/Engine/Animations/Curve.h index 74ad4168d..e0e7bd6b5 100644 --- a/Source/Engine/Animations/Curve.h +++ b/Source/Engine/Animations/Curve.h @@ -730,7 +730,7 @@ public: void TransformTime(float timeScale, float timeOffset) { for (int32 i = 0; i < _keyframes.Count(); i++) - _keyframes[i].Time = _keyframes[i].Time * timeScale + timeOffset;; + _keyframes[i].Time = _keyframes[i].Time * timeScale + timeOffset; } uint64 GetMemoryUsage() const diff --git a/Source/Engine/Animations/Graph/AnimGraph.Base.cpp b/Source/Engine/Animations/Graph/AnimGraph.Base.cpp index c05b82b70..af5508705 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.Base.cpp +++ b/Source/Engine/Animations/Graph/AnimGraph.Base.cpp @@ -157,7 +157,7 @@ bool AnimGraphBase::onNodeLoaded(Node* n) if (_rootNode->Values.Count() < 1) { _rootNode->Values.Resize(1); - _rootNode->Values[0] = (int32)RootMotionMode::NoExtraction; + _rootNode->Values[0] = (int32)RootMotionExtraction::NoExtraction; } break; // Animation diff --git a/Source/Engine/Animations/Graph/AnimGraph.cpp b/Source/Engine/Animations/Graph/AnimGraph.cpp index 09c03b71a..93bfc2609 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.cpp +++ b/Source/Engine/Animations/Graph/AnimGraph.cpp @@ -213,7 +213,7 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt) // Init data from base model _skeletonNodesCount = skeleton.Nodes.Count(); - _rootMotionMode = (RootMotionMode)(int32)_graph._rootNode->Values[0]; + _rootMotionMode = (RootMotionExtraction)(int32)_graph._rootNode->Values[0]; // Prepare context data for the evaluation context.GraphStack.Clear(); diff --git a/Source/Engine/Animations/Graph/AnimGraph.h b/Source/Engine/Animations/Graph/AnimGraph.h index f3c148e0a..b570cf9fe 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.h +++ b/Source/Engine/Animations/Graph/AnimGraph.h @@ -92,9 +92,9 @@ enum class BoneTransformMode }; /// -/// The animated model root motion mode. +/// The animated model root motion extraction modes. /// -enum class RootMotionMode +enum class RootMotionExtraction { /// /// Don't extract nor apply the root motion. @@ -815,7 +815,7 @@ class AnimGraphExecutor : public VisjectExecutor friend AnimGraphNode; private: AnimGraph& _graph; - RootMotionMode _rootMotionMode = RootMotionMode::NoExtraction; + RootMotionExtraction _rootMotionMode = RootMotionExtraction::NoExtraction; int32 _skeletonNodesCount = 0; // Per-thread context to allow async execution diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp index 536ed7d83..30b1bb022 100644 --- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp +++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp @@ -21,13 +21,13 @@ namespace base += additive; } - FORCE_INLINE void NormalizeRotations(AnimGraphImpulse* nodes, RootMotionMode rootMotionMode) + FORCE_INLINE void NormalizeRotations(AnimGraphImpulse* nodes, RootMotionExtraction rootMotionMode) { for (int32 i = 0; i < nodes->Nodes.Count(); i++) { nodes->Nodes[i].Orientation.Normalize(); } - if (rootMotionMode != RootMotionMode::NoExtraction) + if (rootMotionMode != RootMotionExtraction::NoExtraction) { nodes->RootMotion.Orientation.Normalize(); } @@ -323,16 +323,21 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* } // Handle root motion - if (_rootMotionMode != RootMotionMode::NoExtraction && anim->Data.EnableRootMotion) + if (_rootMotionMode != RootMotionExtraction::NoExtraction && anim->Data.RootMotionFlags != AnimationRootMotionFlags::None) { // Calculate the root motion node transformation + const bool motionPositionXZ = EnumHasAnyFlags(anim->Data.RootMotionFlags, AnimationRootMotionFlags::RootPositionXZ); + const bool motionPositionY = EnumHasAnyFlags(anim->Data.RootMotionFlags, AnimationRootMotionFlags::RootPositionY); + const bool motionRotation = EnumHasAnyFlags(anim->Data.RootMotionFlags, AnimationRootMotionFlags::RootRotation); + const Vector3 motionPositionMask(motionPositionXZ ? 1.0f : 0.0f, motionPositionY ? 1.0f : 0.0f, motionPositionXZ ? 1.0f : 0.0f); + const bool motionPosition = motionPositionXZ | motionPositionY; const int32 rootNodeIndex = GetRootNodeIndex(anim); const Transform& refPose = emptyNodes->Nodes[rootNodeIndex]; Transform& rootNode = nodes->Nodes[rootNodeIndex]; Transform& dstNode = nodes->RootMotion; Transform srcNode = Transform::Identity; const int32 nodeToChannel = mapping.NodesMapping[rootNodeIndex]; - if (_rootMotionMode == RootMotionMode::Enable && nodeToChannel != -1) + if (_rootMotionMode == RootMotionExtraction::Enable && nodeToChannel != -1) { // Get the root bone transformation Transform rootBefore = refPose; @@ -356,16 +361,20 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* // Complex motion calculation to preserve the looped movement // (end - before + now - begin) // It sums the motion since the last update to anim end and since the start to now - srcNode.Translation = rootEnd.Translation - rootBefore.Translation + rootNode.Translation - rootBegin.Translation; - srcNode.Orientation = rootEnd.Orientation * rootBefore.Orientation.Conjugated() * (rootNode.Orientation * rootBegin.Orientation.Conjugated()); + if (motionPosition) + srcNode.Translation = (rootEnd.Translation - rootBefore.Translation + rootNode.Translation - rootBegin.Translation) * motionPositionMask; + if (motionRotation) + srcNode.Orientation = rootEnd.Orientation * rootBefore.Orientation.Conjugated() * (rootNode.Orientation * rootBegin.Orientation.Conjugated()); //srcNode.Orientation = Quaternion::Identity; } else { // Simple motion delta // (now - before) - srcNode.Translation = rootNode.Translation - rootBefore.Translation; - srcNode.Orientation = rootBefore.Orientation.Conjugated() * rootNode.Orientation; + if (motionPosition) + srcNode.Translation = (rootNode.Translation - rootBefore.Translation) * motionPositionMask; + if (motionRotation) + srcNode.Orientation = rootBefore.Orientation.Conjugated() * rootNode.Orientation; } // Convert root motion from local-space to the actor-space (eg. if root node is not actually a root and its parents have rotation/scale) @@ -379,28 +388,40 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* } } - // Remove root node motion after extraction - rootNode = refPose; + // Remove root node motion after extraction (only extracted components) + if (motionPosition) + rootNode.Translation = refPose.Translation * motionPositionMask + rootNode.Translation * (Vector3::One - motionPositionMask); + if (motionRotation) + rootNode.Orientation = refPose.Orientation; // Blend root motion if (mode == ProcessAnimationMode::BlendAdditive) { - dstNode.Translation += srcNode.Translation * weight; - BlendAdditiveWeightedRotation(dstNode.Orientation, srcNode.Orientation, weight); + if (motionPosition) + dstNode.Translation += srcNode.Translation * weight * motionPositionMask; + if (motionRotation) + BlendAdditiveWeightedRotation(dstNode.Orientation, srcNode.Orientation, weight); } else if (mode == ProcessAnimationMode::Add) { - dstNode.Translation += srcNode.Translation * weight; - dstNode.Orientation += srcNode.Orientation * weight; + if (motionPosition) + dstNode.Translation += srcNode.Translation * weight * motionPositionMask; + if (motionRotation) + dstNode.Orientation += srcNode.Orientation * weight; } else if (weighted) { - dstNode.Translation = srcNode.Translation * weight; - dstNode.Orientation = srcNode.Orientation * weight; + if (motionPosition) + dstNode.Translation = srcNode.Translation * weight * motionPositionMask; + if (motionRotation) + dstNode.Orientation = srcNode.Orientation * weight; } else { - dstNode = srcNode; + if (motionPosition) + dstNode.Translation = srcNode.Translation * motionPositionMask; + if (motionRotation) + dstNode.Orientation = srcNode.Orientation; } } diff --git a/Source/Engine/Content/Assets/Animation.cpp b/Source/Engine/Content/Assets/Animation.cpp index 8aa635244..ec418c5fb 100644 --- a/Source/Engine/Content/Assets/Animation.cpp +++ b/Source/Engine/Content/Assets/Animation.cpp @@ -413,10 +413,10 @@ bool Animation::Save(const StringView& path) MemoryWriteStream stream(4096); // Info - stream.WriteInt32(102); + stream.WriteInt32(103); stream.WriteDouble(Data.Duration); stream.WriteDouble(Data.FramesPerSecond); - stream.WriteBool(Data.EnableRootMotion); + stream.WriteByte((byte)Data.RootMotionFlags); stream.WriteString(Data.RootNodeName, 13); // Animation channels @@ -532,17 +532,22 @@ Asset::LoadResult Animation::load() int32 headerVersion = *(int32*)stream.GetPositionHandle(); switch (headerVersion) { - case 100: - case 101: - case 102: - { + case 103: stream.ReadInt32(&headerVersion); stream.ReadDouble(&Data.Duration); stream.ReadDouble(&Data.FramesPerSecond); - Data.EnableRootMotion = stream.ReadBool(); + stream.ReadByte((byte*)&Data.RootMotionFlags); + stream.ReadString(&Data.RootNodeName, 13); + break; + case 100: + case 101: + case 102: + stream.ReadInt32(&headerVersion); + stream.ReadDouble(&Data.Duration); + stream.ReadDouble(&Data.FramesPerSecond); + Data.RootMotionFlags = stream.ReadBool() ? AnimationRootMotionFlags::RootPositionXZ : AnimationRootMotionFlags::None; stream.ReadString(&Data.RootNodeName, 13); break; - } default: stream.ReadDouble(&Data.Duration); stream.ReadDouble(&Data.FramesPerSecond); diff --git a/Source/Engine/ContentImporters/CreateAnimationGraph.cpp b/Source/Engine/ContentImporters/CreateAnimationGraph.cpp index 695e5c2a7..e9c88b68c 100644 --- a/Source/Engine/ContentImporters/CreateAnimationGraph.cpp +++ b/Source/Engine/ContentImporters/CreateAnimationGraph.cpp @@ -20,7 +20,7 @@ CreateAssetResult CreateAnimationGraph::Create(CreateAssetContext& context) rootNode.Type = GRAPH_NODE_MAKE_TYPE(9, 1); rootNode.ID = 1; rootNode.Values.Resize(1); - rootNode.Values[0] = (int32)RootMotionMode::NoExtraction; + rootNode.Values[0] = (int32)RootMotionExtraction::NoExtraction; rootNode.Boxes.Resize(1); rootNode.Boxes[0] = AnimGraphBox(&rootNode, 0, VariantType::Void); diff --git a/Source/Engine/Graphics/Models/ModelData.cpp b/Source/Engine/Graphics/Models/ModelData.cpp index 6e6ae04b5..cc95ed6ba 100644 --- a/Source/Engine/Graphics/Models/ModelData.cpp +++ b/Source/Engine/Graphics/Models/ModelData.cpp @@ -911,10 +911,10 @@ bool ModelData::Pack2AnimationHeader(WriteStream* stream, int32 animIndex) const } // Info - stream->WriteInt32(100); // Header version (for fast version upgrades without serialization format change) + stream->WriteInt32(103); // Header version (for fast version upgrades without serialization format change) stream->WriteDouble(anim.Duration); stream->WriteDouble(anim.FramesPerSecond); - stream->WriteBool(anim.EnableRootMotion); + stream->WriteByte((byte)anim.RootMotionFlags); stream->WriteString(anim.RootNodeName, 13); // Animation channels @@ -928,6 +928,12 @@ bool ModelData::Pack2AnimationHeader(WriteStream* stream, int32 animIndex) const Serialization::Serialize(*stream, channel.Scale); } + // Animation events + stream->WriteInt32(0); + + // Nested animations + stream->WriteInt32(0); + return false; } diff --git a/Source/Engine/Graphics/Models/SkeletonData.h b/Source/Engine/Graphics/Models/SkeletonData.h index 816f69c85..83908d18a 100644 --- a/Source/Engine/Graphics/Models/SkeletonData.h +++ b/Source/Engine/Graphics/Models/SkeletonData.h @@ -91,7 +91,7 @@ public: FORCE_INLINE SkeletonNode& RootNode() { ASSERT(Nodes.HasItems()); - return Nodes[0]; + return Nodes.Get()[0]; } /// @@ -100,52 +100,24 @@ public: FORCE_INLINE const SkeletonNode& RootNode() const { ASSERT(Nodes.HasItems()); - return Nodes[0]; + return Nodes.Get()[0]; } /// /// Swaps the contents of object with the other object without copy operation. Performs fast internal data exchange. /// - void Swap(SkeletonData& other) - { - Nodes.Swap(other.Nodes); - Bones.Swap(other.Bones); - } + void Swap(SkeletonData& other); - int32 FindNode(const StringView& name) const - { - for (int32 i = 0; i < Nodes.Count(); i++) - { - if (Nodes[i].Name == name) - return i; - } - return -1; - } + Transform GetNodeTransform(int32 nodeIndex) const; + void SetNodeTransform(int32 nodeIndex, const Transform& value); - int32 FindBone(int32 nodeIndex) const - { - for (int32 i = 0; i < Bones.Count(); i++) - { - if (Bones[i].NodeIndex == nodeIndex) - return i; - } - return -1; - } + int32 FindNode(const StringView& name) const; + int32 FindBone(int32 nodeIndex) const; - uint64 GetMemoryUsage() const - { - uint64 result = Nodes.Capacity() * sizeof(SkeletonNode) + Bones.Capacity() * sizeof(SkeletonBone); - for (const auto& e : Nodes) - result += (e.Name.Length() + 1) * sizeof(Char); - return result; - } + uint64 GetMemoryUsage() const; /// /// Releases data. /// - void Dispose() - { - Nodes.Resize(0); - Bones.Resize(0); - } + void Dispose(); }; diff --git a/Source/Engine/Graphics/Models/SkinnedMesh.cpp b/Source/Engine/Graphics/Models/SkinnedMesh.cpp index 48ff53549..fc0dccf8a 100644 --- a/Source/Engine/Graphics/Models/SkinnedMesh.cpp +++ b/Source/Engine/Graphics/Models/SkinnedMesh.cpp @@ -18,6 +18,69 @@ #include "Engine/Threading/Task.h" #include "Engine/Threading/Threading.h" +void SkeletonData::Swap(SkeletonData& other) +{ + Nodes.Swap(other.Nodes); + Bones.Swap(other.Bones); +} + +Transform SkeletonData::GetNodeTransform(int32 nodeIndex) const +{ + const int32 parentIndex = Nodes[nodeIndex].ParentIndex; + if (parentIndex == -1) + { + return Nodes[nodeIndex].LocalTransform; + } + const Transform parentTransform = GetNodeTransform(parentIndex); + return parentTransform.LocalToWorld(Nodes[nodeIndex].LocalTransform); +} + +void SkeletonData::SetNodeTransform(int32 nodeIndex, const Transform& value) +{ + const int32 parentIndex = Nodes[nodeIndex].ParentIndex; + if (parentIndex == -1) + { + Nodes[nodeIndex].LocalTransform = value; + return; + } + const Transform parentTransform = GetNodeTransform(parentIndex); + parentTransform.WorldToLocal(value, Nodes[nodeIndex].LocalTransform); +} + +int32 SkeletonData::FindNode(const StringView& name) const +{ + for (int32 i = 0; i < Nodes.Count(); i++) + { + if (Nodes[i].Name == name) + return i; + } + return -1; +} + +int32 SkeletonData::FindBone(int32 nodeIndex) const +{ + for (int32 i = 0; i < Bones.Count(); i++) + { + if (Bones[i].NodeIndex == nodeIndex) + return i; + } + return -1; +} + +uint64 SkeletonData::GetMemoryUsage() const +{ + uint64 result = Nodes.Capacity() * sizeof(SkeletonNode) + Bones.Capacity() * sizeof(SkeletonBone); + for (const auto& e : Nodes) + result += (e.Name.Length() + 1) * sizeof(Char); + return result; +} + +void SkeletonData::Dispose() +{ + Nodes.Resize(0); + Bones.Resize(0); +} + void SkinnedMesh::Init(SkinnedModel* model, int32 lodIndex, int32 index, int32 materialSlotIndex, const BoundingBox& box, const BoundingSphere& sphere) { _model = model; diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index d0aa537f6..447301f46 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -19,15 +19,15 @@ #include "Engine/Content/Content.h" #include "Engine/Serialization/MemoryWriteStream.h" #if USE_EDITOR +#include "Engine/Core/Utilities.h" +#include "Engine/Core/Types/StringView.h" #include "Engine/Core/Types/DateTime.h" #include "Engine/Core/Types/TimeSpan.h" #include "Engine/Core/Types/Pair.h" #include "Engine/Core/Types/Variant.h" +#include "Engine/Platform/FileSystem.h" #include "Engine/Graphics/Models/SkeletonUpdater.h" #include "Engine/Graphics/Models/SkeletonMapping.h" -#include "Engine/Core/Utilities.h" -#include "Engine/Core/Types/StringView.h" -#include "Engine/Platform/FileSystem.h" #include "Engine/Tools/TextureTool/TextureTool.h" #include "Engine/ContentImporters/AssetsImportingManager.h" #include "Engine/ContentImporters/CreateMaterial.h" @@ -35,6 +35,7 @@ #include "Engine/ContentImporters/CreateCollisionData.h" #include "Engine/Serialization/Serialization.h" #include "Editor/Utilities/EditorUtilities.h" +#include "Engine/Animations/Graph/AnimGraph.h" #include #endif @@ -361,7 +362,8 @@ void ModelTool::Options::Serialize(SerializeStream& stream, const void* otherObj SERIALIZE(SkipEmptyCurves); SERIALIZE(OptimizeKeyframes); SERIALIZE(ImportScaleTracks); - SERIALIZE(EnableRootMotion); + SERIALIZE(RootMotion); + SERIALIZE(RootMotionFlags); SERIALIZE(RootNodeName); SERIALIZE(GenerateLODs); SERIALIZE(BaseLOD); @@ -410,7 +412,8 @@ void ModelTool::Options::Deserialize(DeserializeStream& stream, ISerializeModifi DESERIALIZE(SkipEmptyCurves); DESERIALIZE(OptimizeKeyframes); DESERIALIZE(ImportScaleTracks); - DESERIALIZE(EnableRootMotion); + DESERIALIZE(RootMotion); + DESERIALIZE(RootMotionFlags); DESERIALIZE(RootNodeName); DESERIALIZE(GenerateLODs); DESERIALIZE(BaseLOD); @@ -435,6 +438,15 @@ void ModelTool::Options::Deserialize(DeserializeStream& stream, ISerializeModifi DESERIALIZE(AnimationIndex); if (AnimationIndex != -1) ObjectIndex = AnimationIndex; + + // [Deprecated on 08.02.2024, expires on 08.02.2026] + bool EnableRootMotion = false; + DESERIALIZE(EnableRootMotion); + if (EnableRootMotion) + { + RootMotion = RootMotionMode::ExtractNode; + RootMotionFlags = AnimationRootMotionFlags::RootPositionXZ; + } } void RemoveNamespace(String& name) @@ -809,6 +821,8 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option break; case ModelType::Animation: options.ImportTypes = ImportDataTypes::Animations; + if (options.RootMotion == RootMotionMode::ExtractCenterOfMass) + options.ImportTypes |= ImportDataTypes::Skeleton; break; case ModelType::Prefab: options.ImportTypes = ImportDataTypes::Geometry | ImportDataTypes::Nodes | ImportDataTypes::Animations; @@ -1373,6 +1387,129 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option } } + // Process root motion setup + animation.RootMotionFlags = options.RootMotion != RootMotionMode::None ? options.RootMotionFlags : AnimationRootMotionFlags::None; + animation.RootNodeName = options.RootNodeName.TrimTrailing(); + if (animation.RootMotionFlags != AnimationRootMotionFlags::None && animation.Channels.HasItems()) + { + if (options.RootMotion == RootMotionMode::ExtractNode) + { + if (animation.RootNodeName.HasChars() && animation.GetChannel(animation.RootNodeName) == nullptr) + { + LOG(Warning, "Missing Root Motion node '{}'", animation.RootNodeName); + } + } + else if (options.RootMotion == RootMotionMode::ExtractCenterOfMass && data.Skeleton.Nodes.HasItems()) // TODO: finish implementing this + { + // Setup root node animation track + const auto& rootName = data.Skeleton.Nodes.First().Name; + auto rootChannelPtr = animation.GetChannel(rootName); + if (!rootChannelPtr) + { + animation.Channels.Insert(0, NodeAnimationData()); + rootChannelPtr = &animation.Channels[0]; + rootChannelPtr->NodeName = rootName; + } + animation.RootNodeName = rootName; + auto& rootChannel = *rootChannelPtr; + rootChannel.Position.Clear(); + + // Calculate skeleton center of mass position over the animation frames + const int32 frames = (int32)animation.Duration; + const int32 nodes = data.Skeleton.Nodes.Count(); + Array centerOfMass; + centerOfMass.Resize(frames); + for (int32 frame = 0; frame < frames; frame++) + { + auto& key = centerOfMass[frame]; + + // Evaluate skeleton pose at the animation frame + AnimGraphImpulse pose; + pose.Nodes.Resize(nodes); + for (int32 nodeIndex = 0; nodeIndex < nodes; nodeIndex++) + { + Transform srcNode = data.Skeleton.Nodes[nodeIndex].LocalTransform; + auto& node = data.Skeleton.Nodes[nodeIndex]; + if (auto* channel = animation.GetChannel(node.Name)) + channel->Evaluate(frame, &srcNode, false); + pose.Nodes[nodeIndex] = srcNode; + } + + // Calculate average location of the pose (center of mass) + key = Float3::Zero; + for (int32 nodeIndex = 0; nodeIndex < nodes; nodeIndex++) + key += pose.GetNodeModelTransformation(data.Skeleton, nodeIndex).Translation; + key /= nodes; + } + + // Calculate skeleton center of mass movement over the animation frames + rootChannel.Position.Resize(frames); + const Float3 centerOfMassRefPose = centerOfMass[0]; + for (int32 frame = 0; frame < frames; frame++) + { + auto& key = rootChannel.Position[frame]; + key.Time = frame; + key.Value = centerOfMass[frame] - centerOfMassRefPose; + } + + // Remove root motion from the children (eg. if Root moves, then Hips should skip that movement delta) + Float3 maxMotion = Float3::Zero; + for (int32 i = 0; i < animation.Channels.Count(); i++) + { + auto& anim = animation.Channels[i]; + const int32 animNodeIndex = data.Skeleton.FindNode(anim.NodeName); + + // Skip channels that have one of their parents already animated + { + int32 nodeIndex = animNodeIndex; + nodeIndex = data.Skeleton.Nodes[nodeIndex].ParentIndex; + while (nodeIndex > 0) + { + const String& nodeName = data.Skeleton.Nodes[nodeIndex].Name; + if (animation.GetChannel(nodeName) != nullptr) + break; + nodeIndex = data.Skeleton.Nodes[nodeIndex].ParentIndex; + } + if (nodeIndex > 0 || &anim == rootChannelPtr) + continue; + } + + // Remove motion + auto& animPos = anim.Position.GetKeyframes(); + for (int32 frame = 0; frame < animPos.Count(); frame++) + { + auto& key = animPos[frame]; + + // Evaluate root motion at the keyframe location + Float3 rootMotion; + rootChannel.Position.Evaluate(rootMotion, key.Time, false); + Float3::Max(maxMotion, rootMotion, maxMotion); + + // Evaluate skeleton pose at the animation frame + AnimGraphImpulse pose; + pose.Nodes.Resize(nodes); + pose.Nodes[0] = data.Skeleton.Nodes[0].LocalTransform; // Use ref pose of root + for (int32 nodeIndex = 1; nodeIndex < nodes; nodeIndex++) // Skip new root + { + Transform srcNode = data.Skeleton.Nodes[nodeIndex].LocalTransform; + auto& node = data.Skeleton.Nodes[nodeIndex]; + if (auto* channel = animation.GetChannel(node.Name)) + channel->Evaluate(frame, &srcNode, false); + pose.Nodes[nodeIndex] = srcNode; + } + + // Convert root motion to the local space of this node so the node stays at the same location after adding new root channel + Transform parentNodeTransform = pose.GetNodeModelTransformation(data.Skeleton, data.Skeleton.Nodes[animNodeIndex].ParentIndex); + rootMotion = parentNodeTransform.WorldToLocal(rootMotion); + + // Remove motion + key.Value -= rootMotion; + } + } + LOG(Info, "Calculated root motion: {}", maxMotion); + } + } + // Optimize the keyframes if (options.OptimizeKeyframes) { @@ -1395,9 +1532,6 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option const int32 after = animation.GetKeyframesCount(); LOG(Info, "Optimized {0} animation keyframe(s). Before: {1}, after: {2}, Ratio: {3}%", before - after, before, after, Utilities::RoundTo2DecimalPlaces((float)after / before)); } - - animation.EnableRootMotion = options.EnableRootMotion; - animation.RootNodeName = options.RootNodeName; } } diff --git a/Source/Engine/Tools/ModelTool/ModelTool.h b/Source/Engine/Tools/ModelTool/ModelTool.h index 90292d72c..6468c1a43 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.h +++ b/Source/Engine/Tools/ModelTool/ModelTool.h @@ -129,6 +129,19 @@ public: Custom = 1, }; + /// + /// Declares the imported animation Root Motion modes. + /// + API_ENUM(Attributes="HideInEditor") enum class RootMotionMode + { + // Root Motion feature is disabled. + None = 0, + // Motion is extracted from the root node (or node specified by name). + ExtractNode = 1, + // Motion is extracted from the center of mass movement (estimated based on the skeleton pose animation). + ExtractCenterOfMass = 2, + }; + /// /// Model import options. /// @@ -228,9 +241,12 @@ public: bool ImportScaleTracks = false; // Enables root motion extraction support from this animation. API_FIELD(Attributes="EditorOrder(1060), EditorDisplay(\"Animation\"), VisibleIf(nameof(ShowAnimation))") - bool EnableRootMotion = false; + RootMotionMode RootMotion = RootMotionMode::None; + // Adjusts root motion applying flags. Can customize how root node animation can affect target actor movement (eg. apply both position and rotation changes). + API_FIELD(Attributes="EditorOrder(1060), EditorDisplay(\"Animation\"), VisibleIf(nameof(ShowRootMotion))") + AnimationRootMotionFlags RootMotionFlags = AnimationRootMotionFlags::RootPositionXZ; // The custom node name to be used as a root motion source. If not specified the actual root node will be used. - API_FIELD(Attributes="EditorOrder(1070), EditorDisplay(\"Animation\"), VisibleIf(nameof(ShowAnimation))") + API_FIELD(Attributes="EditorOrder(1070), EditorDisplay(\"Animation\"), VisibleIf(nameof(ShowRootMotion))") String RootNodeName = TEXT(""); public: // Level Of Detail From da1b2f0c07caf20034bb84253f3cbd66a965a691 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 9 Feb 2024 15:37:42 +0100 Subject: [PATCH 068/139] Fix crash when file handle was null --- Source/Engine/Content/Storage/FlaxStorage.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Content/Storage/FlaxStorage.cpp b/Source/Engine/Content/Storage/FlaxStorage.cpp index ca39ebaf9..17cf2d193 100644 --- a/Source/Engine/Content/Storage/FlaxStorage.cpp +++ b/Source/Engine/Content/Storage/FlaxStorage.cpp @@ -214,7 +214,10 @@ FlaxStorage::~FlaxStorage() Array streams; _file.GetValues(streams); for (FileReadStream* stream : streams) - Delete(stream); + { + if (stream) + Delete(stream); + } #endif } @@ -1341,7 +1344,10 @@ bool FlaxStorage::CloseFileHandles() Array streams; _file.GetValues(streams); for (FileReadStream* stream : streams) - Delete(stream); + { + if (stream) + Delete(stream); + } _file.Clear(); return false; } From 87c66b01682c80df6a285cb363449615cefd6aae Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 9 Feb 2024 15:38:11 +0100 Subject: [PATCH 069/139] Fix root motion preview in animation window --- Source/Editor/Windows/Assets/AnimationWindow.cs | 2 +- Source/Engine/Content/Assets/AnimationGraph.cpp | 4 ++-- Source/Engine/Content/Assets/AnimationGraph.h | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Source/Editor/Windows/Assets/AnimationWindow.cs b/Source/Editor/Windows/Assets/AnimationWindow.cs index 8f88d93f6..c512d287f 100644 --- a/Source/Editor/Windows/Assets/AnimationWindow.cs +++ b/Source/Editor/Windows/Assets/AnimationWindow.cs @@ -48,7 +48,7 @@ namespace FlaxEditor.Windows.Assets // Use virtual animation graph to playback the animation _animGraph = FlaxEngine.Content.CreateVirtualAsset(); - _animGraph.InitAsAnimation(model, _window.Asset); + _animGraph.InitAsAnimation(model, _window.Asset, true, true); PreviewActor.AnimationGraph = _animGraph; } diff --git a/Source/Engine/Content/Assets/AnimationGraph.cpp b/Source/Engine/Content/Assets/AnimationGraph.cpp index fe87abfe4..9b431aefa 100644 --- a/Source/Engine/Content/Assets/AnimationGraph.cpp +++ b/Source/Engine/Content/Assets/AnimationGraph.cpp @@ -67,7 +67,7 @@ void AnimationGraph::OnDependencyModified(BinaryAsset* asset) #endif -bool AnimationGraph::InitAsAnimation(SkinnedModel* baseModel, Animation* anim, bool loop) +bool AnimationGraph::InitAsAnimation(SkinnedModel* baseModel, Animation* anim, bool loop, bool rootMotion) { if (!IsVirtual()) { @@ -89,7 +89,7 @@ bool AnimationGraph::InitAsAnimation(SkinnedModel* baseModel, Animation* anim, b rootNode.Type = GRAPH_NODE_MAKE_TYPE(9, 1); rootNode.ID = 1; rootNode.Values.Resize(1); - rootNode.Values[0] = (int32)RootMotionMode::NoExtraction; + rootNode.Values[0] = (int32)(rootMotion ? RootMotionExtraction::Enable : RootMotionExtraction::Ignore); rootNode.Boxes.Resize(1); rootNode.Boxes[0] = AnimGraphBox(&rootNode, 0, VariantType::Void); auto& animNode = graph.Nodes[1]; diff --git a/Source/Engine/Content/Assets/AnimationGraph.h b/Source/Engine/Content/Assets/AnimationGraph.h index f378b9bbe..716329347 100644 --- a/Source/Engine/Content/Assets/AnimationGraph.h +++ b/Source/Engine/Content/Assets/AnimationGraph.h @@ -37,8 +37,9 @@ public: /// The base model asset. /// The animation to play. /// True if play animation in a loop. + /// True if apply root motion. Otherwise it will be ignored. /// True if failed, otherwise false. - API_FUNCTION() bool InitAsAnimation(SkinnedModel* baseModel, Animation* anim, bool loop = true); + API_FUNCTION() bool InitAsAnimation(SkinnedModel* baseModel, Animation* anim, bool loop = true, bool rootMotion = false); /// /// Tries to load surface graph from the asset. From 080202cf959f8828095100156868c31322965479 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 9 Feb 2024 15:52:53 +0100 Subject: [PATCH 070/139] Fix crash when storage file gets deleted due to missing ref from async thread which failed to load it --- .../Content/Storage/ContentStorageManager.cpp | 29 ++++++++++--------- .../Content/Storage/FlaxStorageReference.h | 6 ++-- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/Source/Engine/Content/Storage/ContentStorageManager.cpp b/Source/Engine/Content/Storage/ContentStorageManager.cpp index 61e73a3f2..47bdcd3b2 100644 --- a/Source/Engine/Content/Storage/ContentStorageManager.cpp +++ b/Source/Engine/Content/Storage/ContentStorageManager.cpp @@ -58,8 +58,8 @@ FlaxStorageReference ContentStorageManager::GetStorage(const StringView& path, b Locker.Lock(); // Try fast lookup - FlaxStorage* result; - if (!StorageMap.TryGet(path, result)) + FlaxStorage* storage; + if (!StorageMap.TryGet(path, storage)) { // Detect storage type and create object const bool isPackage = path.EndsWith(StringView(PACKAGE_FILES_EXTENSION)); @@ -67,39 +67,42 @@ FlaxStorageReference ContentStorageManager::GetStorage(const StringView& path, b { auto package = New(path); Packages.Add(package); - result = package; + storage = package; } else { auto file = New(path); Files.Add(file); - result = file; + storage = file; } // Register storage container - StorageMap.Add(path, result); + StorageMap.Add(path, storage); } + // Build reference (before releasing the lock so ContentStorageSystem::Job won't delete it when running from async thread) + FlaxStorageReference result(storage); + Locker.Unlock(); if (loadIt) { // Initialize storage container - result->LockChunks(); - const bool loadFailed = result->Load(); - result->UnlockChunks(); + storage->LockChunks(); + const bool loadFailed = storage->Load(); + storage->UnlockChunks(); if (loadFailed) { LOG(Error, "Failed to load {0}.", path); Locker.Lock(); StorageMap.Remove(path); - if (result->IsPackage()) - Packages.Remove((FlaxPackage*)result); + if (storage->IsPackage()) + Packages.Remove((FlaxPackage*)storage); else - Files.Remove((FlaxFile*)result); + Files.Remove((FlaxFile*)storage); Locker.Unlock(); - Delete(result); - return nullptr; + result = nullptr; + Delete(storage); } } diff --git a/Source/Engine/Content/Storage/FlaxStorageReference.h b/Source/Engine/Content/Storage/FlaxStorageReference.h index 03f4d0c8d..e4a5df079 100644 --- a/Source/Engine/Content/Storage/FlaxStorageReference.h +++ b/Source/Engine/Content/Storage/FlaxStorageReference.h @@ -58,17 +58,17 @@ public: return _storage != nullptr; } - FORCE_INLINE bool operator ==(const FlaxStorageReference& other) const + FORCE_INLINE bool operator==(const FlaxStorageReference& other) const { return _storage == other._storage; } - FORCE_INLINE bool operator !=(const FlaxStorageReference& other) const + FORCE_INLINE bool operator!=(const FlaxStorageReference& other) const { return _storage != other._storage; } - FORCE_INLINE FlaxStorage* operator ->() const + FORCE_INLINE FlaxStorage* operator->() const { return _storage; } From c6460078829c45ab942b28c8f6e7ed2a7725f473 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 9 Feb 2024 23:29:43 +0100 Subject: [PATCH 071/139] Fix color grading issue with LUT texture used --- Content/Shaders/ColorGrading.flax | 4 ++-- Source/Shaders/ColorGrading.shader | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Content/Shaders/ColorGrading.flax b/Content/Shaders/ColorGrading.flax index dae561361..ee620d1a2 100644 --- a/Content/Shaders/ColorGrading.flax +++ b/Content/Shaders/ColorGrading.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ce60152b7076175eb50e07ad9895eacbfb4a9ed1365c1507266b21fcd3339958 -size 10925 +oid sha256:58bf83f2b334cd28a2db8a2c60a17c56f813edc9050a2d006456f8479cd05d13 +size 10629 diff --git a/Source/Shaders/ColorGrading.shader b/Source/Shaders/ColorGrading.shader index ae639fc4d..9d9175614 100644 --- a/Source/Shaders/ColorGrading.shader +++ b/Source/Shaders/ColorGrading.shader @@ -243,7 +243,7 @@ float4 CombineLUTs(float2 uv, uint layerIndex) // Apply LDR LUT color grading { - float3 uvw = color * (15.0 / 16.0) + (0.5f / 16.0); + float3 uvw = saturate(color) * (15.0 / 16.0) + (0.5f / 16.0); float3 lutColor = SampleUnwrappedTexture3D(LutTexture, SamplerLinearClamp, uvw, 16).rgb; color = lerp(color, lutColor, LutWeight); } From 9c5c9cb76fc2edf012a68c00f98c7db0fd96a6a5 Mon Sep 17 00:00:00 2001 From: nothingTVatYT <34131388+nothingTVatYT@users.noreply.github.com> Date: Sat, 10 Feb 2024 12:48:14 +0100 Subject: [PATCH 072/139] refresh thumbnails on selection --- Source/Editor/Windows/ContentWindow.ContextMenu.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Source/Editor/Windows/ContentWindow.ContextMenu.cs b/Source/Editor/Windows/ContentWindow.ContextMenu.cs index dda812f84..a21ccbed7 100644 --- a/Source/Editor/Windows/ContentWindow.ContextMenu.cs +++ b/Source/Editor/Windows/ContentWindow.ContextMenu.cs @@ -79,7 +79,14 @@ namespace FlaxEditor.Windows if (item.HasDefaultThumbnail == false) { - cm.AddButton("Refresh thumbnail", item.RefreshThumbnail); + if (_view.SelectedCount > 1) + cm.AddButton("Refresh thumbnails", () => + { + foreach (var e in _view.Selection) + e.RefreshThumbnail(); + }); + else + cm.AddButton("Refresh thumbnail", item.RefreshThumbnail); } if (!isFolder) From 4a19657e7524d5ca6524013b0f1c06ef72f9fdde Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 10 Feb 2024 17:26:10 +0100 Subject: [PATCH 073/139] Fix crash when replicating C# object with `NetworkReplicated` attribute on derived generic class #1988 --- Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs index d646a81cd..7e4f26bbf 100644 --- a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs +++ b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs @@ -802,7 +802,7 @@ namespace Flax.Build.Plugins // Serialize base type if (type.BaseType != null && type.BaseType.FullName != "System.ValueType" && type.BaseType.FullName != "FlaxEngine.Object" && type.BaseType.CanBeResolved()) { - GenerateSerializeCallback(module, il, type.BaseType.Resolve(), serialize); + GenerateSerializeCallback(module, il, type.BaseType, serialize); } var ildContext = new DotnetIlContext(il); @@ -874,12 +874,13 @@ namespace Flax.Build.Plugins return m; } - private static void GenerateSerializeCallback(ModuleDefinition module, ILProcessor il, TypeDefinition type, bool serialize) + private static void GenerateSerializeCallback(ModuleDefinition module, ILProcessor il, TypeReference type, bool serialize) { if (type.IsScriptingObject()) { // NetworkReplicator.InvokeSerializer(typeof(), instance, stream, ) - il.Emit(OpCodes.Ldtoken, module.ImportReference(type)); + module.ImportReference(type); + il.Emit(OpCodes.Ldtoken, type); module.GetType("System.Type", out var typeType); var getTypeFromHandle = typeType.Resolve().GetMethod("GetTypeFromHandle"); il.Emit(OpCodes.Call, module.ImportReference(getTypeFromHandle)); From 196a4ffe4927956559e6d0b9c13d511167b5ff5b Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 10 Feb 2024 18:17:29 +0100 Subject: [PATCH 074/139] Add warning on not implemented generic type network serializer (need better codegen) #1988 --- Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs index 7e4f26bbf..085c56680 100644 --- a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs +++ b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs @@ -805,6 +805,9 @@ namespace Flax.Build.Plugins GenerateSerializeCallback(module, il, type.BaseType, serialize); } + if (type.HasGenericParameters) // TODO: implement network replication for generic classes + MonoCecil.CompilationError($"Not supported generic type '{type.FullName}' for network replication."); + var ildContext = new DotnetIlContext(il); // Serialize all type fields marked with NetworkReplicated attribute From 46441f6a030ac7d71df625b6d515e702b56e2d62 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 10 Feb 2024 18:43:15 +0100 Subject: [PATCH 075/139] Fix crash when using multi-threaded objects spawn and caching scripting VTables --- Source/Engine/Scripting/BinaryModule.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Scripting/BinaryModule.cpp b/Source/Engine/Scripting/BinaryModule.cpp index 44ef32503..0cded22b4 100644 --- a/Source/Engine/Scripting/BinaryModule.cpp +++ b/Source/Engine/Scripting/BinaryModule.cpp @@ -533,7 +533,12 @@ void ScriptingType::HackObjectVTable(void* object, ScriptingTypeHandle baseTypeH if (!Script.VTable) { // Ensure to have valid Script VTable hacked - SetupScriptObjectVTable(object, baseTypeHandle, wrapperIndex); + BinaryModule::Locker.Lock(); + if (!Script.VTable) + { + SetupScriptObjectVTable(object, baseTypeHandle, wrapperIndex); + } + BinaryModule::Locker.Unlock(); } // Override object vtable with hacked one that has calls to overriden scripting functions From 04f1c9a59b59a79b1ecd4005c8bfea25afe40a5f Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 10 Feb 2024 21:06:48 +0100 Subject: [PATCH 076/139] Fix missing networked object ID resolving #1607 --- Source/Engine/Networking/NetworkReplicator.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp index f9efba4a5..407bd8c58 100644 --- a/Source/Engine/Networking/NetworkReplicator.cpp +++ b/Source/Engine/Networking/NetworkReplicator.cpp @@ -714,6 +714,7 @@ void InvokeObjectReplication(NetworkReplicatedObject& item, uint32 ownerFrame, b stream->SenderId = senderClientId; // Deserialize object + Scripting::ObjectsLookupIdMapping.Set(&IdsRemappingTable); const bool failed = NetworkReplicator::InvokeSerializer(obj->GetTypeHandle(), obj, stream, false); if (failed) { From 36beac51546991d778fb5ed55eef5d81ee9f52d7 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 11 Feb 2024 12:44:40 +0100 Subject: [PATCH 077/139] Fix deadlock when stack overflows in the Anim Graph update --- Source/Engine/Animations/Graph/AnimGraph.cpp | 4 ++++ Source/Engine/Animations/Graph/AnimGraph.h | 1 + 2 files changed, 5 insertions(+) diff --git a/Source/Engine/Animations/Graph/AnimGraph.cpp b/Source/Engine/Animations/Graph/AnimGraph.cpp index 93bfc2609..2fc9332ba 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.cpp +++ b/Source/Engine/Animations/Graph/AnimGraph.cpp @@ -221,6 +221,7 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt) context.NodePath.Clear(); context.Data = &data; context.DeltaTime = dt; + context.StackOverFlow = false; context.CurrentFrameIndex = ++data.CurrentFrame; context.CallStack.Clear(); context.Functions.Clear(); @@ -411,9 +412,12 @@ VisjectExecutor::Value AnimGraphExecutor::eatBox(Node* caller, Box* box) auto& context = *Context.Get(); // Check if graph is looped or is too deep + if (context.StackOverFlow) + return Value::Zero; if (context.CallStack.Count() >= ANIM_GRAPH_MAX_CALL_STACK) { OnError(caller, box, TEXT("Graph is looped or too deep!")); + context.StackOverFlow = true; return Value::Zero; } #if !BUILD_RELEASE diff --git a/Source/Engine/Animations/Graph/AnimGraph.h b/Source/Engine/Animations/Graph/AnimGraph.h index b570cf9fe..c069ddb06 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.h +++ b/Source/Engine/Animations/Graph/AnimGraph.h @@ -796,6 +796,7 @@ struct AnimGraphContext AnimGraphInstanceData* Data; AnimGraphImpulse EmptyNodes; AnimGraphTransitionData TransitionData; + bool StackOverFlow; Array> CallStack; Array> GraphStack; Array > NodePath; From f044569b6738883a70ff02f48fb58cdf121b582f Mon Sep 17 00:00:00 2001 From: nothingTVatYT <34131388+nothingTVatYT@users.noreply.github.com> Date: Mon, 12 Feb 2024 01:26:01 +0100 Subject: [PATCH 078/139] initialize the bool --- Source/Engine/Platform/Linux/LinuxWindow.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Engine/Platform/Linux/LinuxWindow.cpp b/Source/Engine/Platform/Linux/LinuxWindow.cpp index 9004ed84c..b771bb45f 100644 --- a/Source/Engine/Platform/Linux/LinuxWindow.cpp +++ b/Source/Engine/Platform/Linux/LinuxWindow.cpp @@ -55,6 +55,8 @@ LinuxWindow::LinuxWindow(const CreateWindowSettings& settings) return; auto screen = XDefaultScreen(display); + // default to false + _isHorizontalFlippingMouse = false; // Cache data int32 width = Math::TruncToInt(settings.Size.X); int32 height = Math::TruncToInt(settings.Size.Y); From 07e93e261eb0626a3c4c79cc556f7af923ec334d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 13 Feb 2024 09:52:26 +0100 Subject: [PATCH 079/139] Fix physics simulation result collection to happen before draw or next update --- Source/Engine/Engine/Engine.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Engine/Engine.cpp b/Source/Engine/Engine/Engine.cpp index 259649062..fe7541adb 100644 --- a/Source/Engine/Engine/Engine.cpp +++ b/Source/Engine/Engine/Engine.cpp @@ -214,9 +214,6 @@ int32 Engine::Main(const Char* cmdLine) Time::OnEndDraw(); FrameMark; } - - // Collect physics simulation results (does nothing if Simulate hasn't been called in the previous loop step) - Physics::CollectResults(); } // Call on exit event @@ -288,6 +285,9 @@ void Engine::OnLateFixedUpdate() // Update services EngineService::OnLateFixedUpdate(); + + // Collect physics simulation results (does nothing if Simulate hasn't been called in the previous loop step) + Physics::CollectResults(); } void Engine::OnUpdate() From 6548ca1148a4164cac9c6799b35430ddd041bf3f Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 13 Feb 2024 10:43:20 +0100 Subject: [PATCH 080/139] Fix `JsonAsset::GetInstance` to properly check base class #2224 --- Source/Engine/Content/JsonAsset.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Content/JsonAsset.h b/Source/Engine/Content/JsonAsset.h index a8e89cec0..21b68c691 100644 --- a/Source/Engine/Content/JsonAsset.h +++ b/Source/Engine/Content/JsonAsset.h @@ -139,7 +139,8 @@ public: T* GetInstance() const { const_cast(this)->CreateInstance(); - return Instance && InstanceType.IsAssignableFrom(T::TypeInitializer) ? (T*)Instance : nullptr; + const ScriptingTypeHandle& type = T::TypeInitializer; + return Instance && type.IsAssignableFrom(InstanceType) ? (T*)Instance : nullptr; } public: From 6f4e599cc2ec8b4c9f1851f294bf48368eb13687 Mon Sep 17 00:00:00 2001 From: nothingTVatYT <34131388+nothingTVatYT@users.noreply.github.com> Date: Tue, 13 Feb 2024 22:09:51 +0100 Subject: [PATCH 081/139] add borderless mode --- Source/Engine/Platform/Linux/LinuxWindow.cpp | 36 ++++++++++++++++++++ Source/Engine/Platform/Linux/LinuxWindow.h | 6 ++-- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Platform/Linux/LinuxWindow.cpp b/Source/Engine/Platform/Linux/LinuxWindow.cpp index 9004ed84c..fa0770cd1 100644 --- a/Source/Engine/Platform/Linux/LinuxWindow.cpp +++ b/Source/Engine/Platform/Linux/LinuxWindow.cpp @@ -42,6 +42,15 @@ extern Array KeyCodeMap; extern X11::Cursor Cursors[(int32)CursorType::MAX]; extern Window* MouseTrackingWindow; +typedef struct XDisplayHints +{ + unsigned long flags; + unsigned long functions; + unsigned long decorations; + long inputMode; + unsigned long status; +} XDisplayHints; + static constexpr uint32 MouseDoubleClickTime = 500; static constexpr uint32 MaxDoubleClickDistanceSquared = 10; static X11::Time MouseLastButtonPressTime = 0; @@ -332,6 +341,33 @@ void LinuxWindow::Maximize() Maximize(true); } +void LinuxWindow::SetBorderless(bool isBorderless, bool maximized) +{ + LINUX_WINDOW_PROLOG; + if (!display) + return; + XDisplayHints hints; + hints.flags = 2; + hints.decorations = isBorderless ? 0 : 1; + const X11::Atom property = X11::XInternAtom(display, "_MOTIF_WM_HINTS", true); + if (property != 0) { + X11::XChangeProperty(display, window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5); + } +} + +void LinuxWindow::SetIsFullscreen(bool isFullscreen) +{ + if (isFullscreen) + { + SetBorderless(true); + Maximize(false); + } else + { + SetBorderless(false); + Restore(); + } +} + void LinuxWindow::Restore() { if (IsMaximized()) diff --git a/Source/Engine/Platform/Linux/LinuxWindow.h b/Source/Engine/Platform/Linux/LinuxWindow.h index 52c039f6f..454fc26c1 100644 --- a/Source/Engine/Platform/Linux/LinuxWindow.h +++ b/Source/Engine/Platform/Linux/LinuxWindow.h @@ -71,14 +71,16 @@ public: void Hide() override; void Minimize() override; void Maximize() override; - void Restore() override; + void SetBorderless(bool isBorderless, bool maximized = false) override; + void Restore() override; bool IsClosed() const override; bool IsForegroundWindow() const override; void BringToFront(bool force = false) override; void SetClientBounds(const Rectangle& clientArea) override; void SetPosition(const Float2& position) override; void SetClientPosition(const Float2& position) override; - Float2 GetPosition() const override; + void SetIsFullscreen(bool isFullscreen) override; + Float2 GetPosition() const override; Float2 GetSize() const override; Float2 GetClientSize() const override; Float2 ScreenToClient(const Float2& screenPos) const override; From 31437e6ddef3ed8963631d6e2b5576eb8add07c3 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 14 Feb 2024 12:17:02 +0100 Subject: [PATCH 082/139] Fix copy/paste for UI brushes --- Source/Editor/CustomEditors/CustomEditor.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Source/Editor/CustomEditors/CustomEditor.cs b/Source/Editor/CustomEditors/CustomEditor.cs index a519b1da1..6ae0f0562 100644 --- a/Source/Editor/CustomEditors/CustomEditor.cs +++ b/Source/Editor/CustomEditors/CustomEditor.cs @@ -543,10 +543,11 @@ namespace FlaxEditor.CustomEditors try { string text; + var value = Values[0]; if (ParentEditor is Dedicated.ScriptsEditor) { // Script - text = JsonSerializer.Serialize(Values[0]); + text = JsonSerializer.Serialize(value); // Remove properties that should be ignored when copy/pasting data if (text == null) @@ -576,12 +577,12 @@ namespace FlaxEditor.CustomEditors else if (ScriptType.FlaxObject.IsAssignableFrom(Values.Type)) { // Object reference - text = JsonSerializer.GetStringID(Values[0] as FlaxEngine.Object); + text = JsonSerializer.GetStringID(value as FlaxEngine.Object); } else { // Default - text = JsonSerializer.Serialize(Values[0]); + text = JsonSerializer.Serialize(value, TypeUtils.GetType(Values.Type)); } Clipboard.Text = text; } From 3958a4740f6b388e16e7f2fac474b0528d893897 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 14 Feb 2024 12:36:04 +0100 Subject: [PATCH 083/139] Add option to enable Depth Test on cloth painting debug preview (enabled by default) --- Source/Editor/Tools/ClothPainting.cs | 15 +++++++++++++++ Source/Engine/Physics/Actors/Cloth.cpp | 6 +++--- Source/Engine/Physics/Actors/Cloth.h | 5 +++++ 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/Source/Editor/Tools/ClothPainting.cs b/Source/Editor/Tools/ClothPainting.cs index 28536ea83..6a98a3b5f 100644 --- a/Source/Editor/Tools/ClothPainting.cs +++ b/Source/Editor/Tools/ClothPainting.cs @@ -43,6 +43,19 @@ namespace FlaxEngine.Tools /// Enables continuous painting, otherwise single paint on click. /// public bool ContinuousPaint; + + /// + /// Enables drawing cloth paint debugging with Depth Test enabled (skips occluded vertices). + /// + public bool DebugDrawDepthTest + { + get => Gizmo.Cloth?.DebugDrawDepthTest ?? true; + set + { + if (Gizmo.Cloth != null) + Gizmo.Cloth.DebugDrawDepthTest = value; + } + } #pragma warning restore CS0649 public override void Init(IGizmoOwner owner) @@ -62,6 +75,7 @@ namespace FlaxEngine.Tools public override void Dispose() { Owner.Gizmos.Remove(Gizmo); + Gizmo = null; base.Dispose(); } @@ -83,6 +97,7 @@ namespace FlaxEngine.Tools private EditClothPaintAction _undoAction; public bool IsPainting => _isPainting; + public Cloth Cloth => _cloth; public ClothPaintingGizmo(IGizmoOwner owner, ClothPaintingGizmoMode mode) : base(owner) diff --git a/Source/Engine/Physics/Actors/Cloth.cpp b/Source/Engine/Physics/Actors/Cloth.cpp index b2db80b0b..a35acb066 100644 --- a/Source/Engine/Physics/Actors/Cloth.cpp +++ b/Source/Engine/Physics/Actors/Cloth.cpp @@ -415,9 +415,9 @@ void Cloth::OnDebugDrawSelected() c1 = Color::Lerp(Color::Red, Color::White, _paint[i1]); c2 = Color::Lerp(Color::Red, Color::White, _paint[i2]); } - DebugDraw::DrawLine(v0, v1, c0, c1, 0, false); - DebugDraw::DrawLine(v1, v2, c1, c2, 0, false); - DebugDraw::DrawLine(v2, v0, c2, c0, 0, false); + DebugDraw::DrawLine(v0, v1, c0, c1, 0, DebugDrawDepthTest); + DebugDraw::DrawLine(v1, v2, c1, c2, 0, DebugDrawDepthTest); + DebugDraw::DrawLine(v2, v0, c2, c0, 0, DebugDrawDepthTest); } PhysicsBackend::UnlockClothParticles(_cloth); } diff --git a/Source/Engine/Physics/Actors/Cloth.h b/Source/Engine/Physics/Actors/Cloth.h index 6137ec3b6..1b97330e0 100644 --- a/Source/Engine/Physics/Actors/Cloth.h +++ b/Source/Engine/Physics/Actors/Cloth.h @@ -332,6 +332,11 @@ public: bool OnPreUpdate(); void OnPostUpdate(); +private: +#if USE_EDITOR + API_FIELD(Internal) bool DebugDrawDepthTest = true; +#endif + public: // [Actor] void Draw(RenderContext& renderContext) override; From ebcc864b06b5ffe3fdbc734698e7cf5d1653c67a Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 14 Feb 2024 12:47:34 +0100 Subject: [PATCH 084/139] Fix missing file error in `Content::GetAssetInfo` --- Source/Engine/Content/Content.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Engine/Content/Content.cpp b/Source/Engine/Content/Content.cpp index 897817850..2be4df29d 100644 --- a/Source/Engine/Content/Content.cpp +++ b/Source/Engine/Content/Content.cpp @@ -277,6 +277,8 @@ bool Content::GetAssetInfo(const StringView& path, AssetInfo& info) // Find asset in registry if (Cache.FindAsset(path, info)) return true; + if (!FileSystem::FileExists(path)) + return false; PROFILE_CPU(); const auto extension = FileSystem::GetExtension(path).ToLower(); From 42363e411e3a74a80747b5fa6b585f2f7ad0a1f5 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 14 Feb 2024 18:21:30 +0100 Subject: [PATCH 085/139] Various tweaks --- Source/Engine/Physics/Collisions.h | 8 +--- .../Physics/PhysX/PhysicsBackendPhysX.cpp | 44 +++++++++---------- .../PhysX/SimulationEventCallbackPhysX.cpp | 22 +++------- .../PhysX/SimulationEventCallbackPhysX.h | 5 --- Source/Engine/Physics/PhysicalMaterial.h | 3 +- Source/Engine/Physics/Types.h | 10 ++--- Source/Engine/Tools/ModelTool/ModelTool.cpp | 8 ++-- 7 files changed, 38 insertions(+), 62 deletions(-) diff --git a/Source/Engine/Physics/Collisions.h b/Source/Engine/Physics/Collisions.h index 230337bc9..c1a2679a4 100644 --- a/Source/Engine/Physics/Collisions.h +++ b/Source/Engine/Physics/Collisions.h @@ -58,9 +58,7 @@ API_STRUCT(NoDefault) struct FLAXENGINE_API Collision /// /// The total impulse applied to this contact pair to resolve the collision. /// - /// - /// The total impulse is obtained by summing up impulses applied at all contact points in this collision pair. - /// + /// The total impulse is obtained by summing up impulses applied at all contact points in this collision pair. API_FIELD() Vector3 Impulse; /// @@ -87,9 +85,7 @@ public: /// /// Gets the relative linear velocity of the two colliding objects. /// - /// - /// Can be used to detect stronger collisions. - /// + /// Can be used to detect stronger collisions. Vector3 GetRelativeVelocity() const { return ThisVelocity - OtherVelocity; diff --git a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp index 99a5abc56..f3e7baed1 100644 --- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp +++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp @@ -228,9 +228,7 @@ class QueryFilterPhysX : public PxQueryFilterCallback // Check mask const PxFilterData shapeFilter = shape->getQueryFilterData(); if ((filterData.word0 & shapeFilter.word0) == 0) - { return PxQueryHitType::eNONE; - } // Check if skip triggers const bool hitTriggers = filterData.word2 != 0; @@ -483,8 +481,10 @@ protected: } }; +#define PxHitFlagEmpty (PxHitFlags)0 +#define SCENE_QUERY_FLAGS (PxHitFlag::ePOSITION | PxHitFlag::eNORMAL | PxHitFlag::eFACE_INDEX | PxHitFlag::eUV) + #define SCENE_QUERY_SETUP(blockSingle) auto scenePhysX = (ScenePhysX*)scene; if (scene == nullptr) return false; \ - const PxHitFlags hitFlags = PxHitFlag::ePOSITION | PxHitFlag::eNORMAL | PxHitFlag::eUV; \ PxQueryFilterData filterData; \ filterData.flags |= PxQueryFlag::ePREFILTER; \ filterData.data.word0 = layerMask; \ @@ -1735,8 +1735,6 @@ void PhysicsBackend::EndSimulateScene(void* scene) { PROFILE_CPU_NAMED("Physics.SendEvents"); - - scenePhysX->EventsCallback.CollectResults(); scenePhysX->EventsCallback.SendTriggerEvents(); scenePhysX->EventsCallback.SendCollisionEvents(); scenePhysX->EventsCallback.SendJointEvents(); @@ -1880,14 +1878,14 @@ bool PhysicsBackend::RayCast(void* scene, const Vector3& origin, const Vector3& { SCENE_QUERY_SETUP(true); PxRaycastBuffer buffer; - return scenePhysX->Scene->raycast(C2P(origin - scenePhysX->Origin), C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter); + return scenePhysX->Scene->raycast(C2P(origin - scenePhysX->Origin), C2P(direction), maxDistance, buffer, PxHitFlagEmpty, filterData, &QueryFilter); } bool PhysicsBackend::RayCast(void* scene, const Vector3& origin, const Vector3& direction, RayCastHit& hitInfo, const float maxDistance, uint32 layerMask, bool hitTriggers) { SCENE_QUERY_SETUP(true); PxRaycastBuffer buffer; - if (!scenePhysX->Scene->raycast(C2P(origin - scenePhysX->Origin), C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter)) + if (!scenePhysX->Scene->raycast(C2P(origin - scenePhysX->Origin), C2P(direction), maxDistance, buffer, SCENE_QUERY_FLAGS, filterData, &QueryFilter)) return false; SCENE_QUERY_COLLECT_SINGLE(); return true; @@ -1897,7 +1895,7 @@ bool PhysicsBackend::RayCastAll(void* scene, const Vector3& origin, const Vector { SCENE_QUERY_SETUP(false); DynamicHitBuffer buffer; - if (!scenePhysX->Scene->raycast(C2P(origin - scenePhysX->Origin), C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter)) + if (!scenePhysX->Scene->raycast(C2P(origin - scenePhysX->Origin), C2P(direction), maxDistance, buffer, SCENE_QUERY_FLAGS, filterData, &QueryFilter)) return false; SCENE_QUERY_COLLECT_ALL(); return true; @@ -1908,7 +1906,7 @@ bool PhysicsBackend::BoxCast(void* scene, const Vector3& center, const Vector3& SCENE_QUERY_SETUP_SWEEP_1(); const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation)); const PxBoxGeometry geometry(C2P(halfExtents)); - return scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter); + return scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, PxHitFlagEmpty, filterData, &QueryFilter); } bool PhysicsBackend::BoxCast(void* scene, const Vector3& center, const Vector3& halfExtents, const Vector3& direction, RayCastHit& hitInfo, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers) @@ -1916,7 +1914,7 @@ bool PhysicsBackend::BoxCast(void* scene, const Vector3& center, const Vector3& SCENE_QUERY_SETUP_SWEEP_1(); const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation)); const PxBoxGeometry geometry(C2P(halfExtents)); - if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter)) + if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, SCENE_QUERY_FLAGS, filterData, &QueryFilter)) return false; SCENE_QUERY_COLLECT_SINGLE(); return true; @@ -1927,7 +1925,7 @@ bool PhysicsBackend::BoxCastAll(void* scene, const Vector3& center, const Vector SCENE_QUERY_SETUP_SWEEP(); const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation)); const PxBoxGeometry geometry(C2P(halfExtents)); - if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter)) + if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, SCENE_QUERY_FLAGS, filterData, &QueryFilter)) return false; SCENE_QUERY_COLLECT_ALL(); return true; @@ -1938,7 +1936,7 @@ bool PhysicsBackend::SphereCast(void* scene, const Vector3& center, const float SCENE_QUERY_SETUP_SWEEP_1(); const PxTransform pose(C2P(center - scenePhysX->Origin)); const PxSphereGeometry geometry(radius); - return scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter); + return scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, PxHitFlagEmpty, filterData, &QueryFilter); } bool PhysicsBackend::SphereCast(void* scene, const Vector3& center, const float radius, const Vector3& direction, RayCastHit& hitInfo, const float maxDistance, uint32 layerMask, bool hitTriggers) @@ -1946,7 +1944,7 @@ bool PhysicsBackend::SphereCast(void* scene, const Vector3& center, const float SCENE_QUERY_SETUP_SWEEP_1(); const PxTransform pose(C2P(center - scenePhysX->Origin)); const PxSphereGeometry geometry(radius); - if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter)) + if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, SCENE_QUERY_FLAGS, filterData, &QueryFilter)) return false; SCENE_QUERY_COLLECT_SINGLE(); return true; @@ -1957,7 +1955,7 @@ bool PhysicsBackend::SphereCastAll(void* scene, const Vector3& center, const flo SCENE_QUERY_SETUP_SWEEP(); const PxTransform pose(C2P(center - scenePhysX->Origin)); const PxSphereGeometry geometry(radius); - if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter)) + if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, SCENE_QUERY_FLAGS, filterData, &QueryFilter)) return false; SCENE_QUERY_COLLECT_ALL(); return true; @@ -1968,7 +1966,7 @@ bool PhysicsBackend::CapsuleCast(void* scene, const Vector3& center, const float SCENE_QUERY_SETUP_SWEEP_1(); const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation)); const PxCapsuleGeometry geometry(radius, height * 0.5f); - return scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter); + return scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, PxHitFlagEmpty, filterData, &QueryFilter); } bool PhysicsBackend::CapsuleCast(void* scene, const Vector3& center, const float radius, const float height, const Vector3& direction, RayCastHit& hitInfo, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers) @@ -1976,7 +1974,7 @@ bool PhysicsBackend::CapsuleCast(void* scene, const Vector3& center, const float SCENE_QUERY_SETUP_SWEEP_1(); const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation)); const PxCapsuleGeometry geometry(radius, height * 0.5f); - if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter)) + if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, SCENE_QUERY_FLAGS, filterData, &QueryFilter)) return false; SCENE_QUERY_COLLECT_SINGLE(); return true; @@ -1987,7 +1985,7 @@ bool PhysicsBackend::CapsuleCastAll(void* scene, const Vector3& center, const fl SCENE_QUERY_SETUP_SWEEP(); const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation)); const PxCapsuleGeometry geometry(radius, height * 0.5f); - if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter)) + if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, SCENE_QUERY_FLAGS, filterData, &QueryFilter)) return false; SCENE_QUERY_COLLECT_ALL(); return true; @@ -1999,7 +1997,7 @@ bool PhysicsBackend::ConvexCast(void* scene, const Vector3& center, const Collis SCENE_QUERY_SETUP_SWEEP_1(); const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation)); const PxConvexMeshGeometry geometry((PxConvexMesh*)convexMesh->GetConvex(), PxMeshScale(C2P(scale))); - return scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter); + return scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, PxHitFlagEmpty, filterData, &QueryFilter); } bool PhysicsBackend::ConvexCast(void* scene, const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, const Vector3& direction, RayCastHit& hitInfo, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers) @@ -2008,7 +2006,7 @@ bool PhysicsBackend::ConvexCast(void* scene, const Vector3& center, const Collis SCENE_QUERY_SETUP_SWEEP_1(); const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation)); const PxConvexMeshGeometry geometry((PxConvexMesh*)convexMesh->GetConvex(), PxMeshScale(C2P(scale))); - if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter)) + if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, SCENE_QUERY_FLAGS, filterData, &QueryFilter)) return false; SCENE_QUERY_COLLECT_SINGLE(); return true; @@ -2020,7 +2018,7 @@ bool PhysicsBackend::ConvexCastAll(void* scene, const Vector3& center, const Col SCENE_QUERY_SETUP_SWEEP(); const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation)); const PxConvexMeshGeometry geometry((PxConvexMesh*)convexMesh->GetConvex(), PxMeshScale(C2P(scale))); - if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter)) + if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, SCENE_QUERY_FLAGS, filterData, &QueryFilter)) return false; SCENE_QUERY_COLLECT_ALL(); return true; @@ -2608,9 +2606,8 @@ bool PhysicsBackend::RayCastShape(void* shape, const Vector3& position, const Qu auto shapePhysX = (PxShape*)shape; const Vector3 sceneOrigin = SceneOrigins[shapePhysX->getActor() ? shapePhysX->getActor()->getScene() : nullptr]; const PxTransform trans(C2P(position - sceneOrigin), C2P(orientation)); - const PxHitFlags hitFlags = (PxHitFlags)0; PxRaycastHit hit; - if (PxGeometryQuery::raycast(C2P(origin - sceneOrigin), C2P(direction), shapePhysX->getGeometry(), trans, maxDistance, hitFlags, 1, &hit) != 0) + if (PxGeometryQuery::raycast(C2P(origin - sceneOrigin), C2P(direction), shapePhysX->getGeometry(), trans, maxDistance, PxHitFlagEmpty, 1, &hit) != 0) { resultHitDistance = hit.distance; return true; @@ -2623,9 +2620,8 @@ bool PhysicsBackend::RayCastShape(void* shape, const Vector3& position, const Qu auto shapePhysX = (PxShape*)shape; const Vector3 sceneOrigin = SceneOrigins[shapePhysX->getActor() ? shapePhysX->getActor()->getScene() : nullptr]; const PxTransform trans(C2P(position - sceneOrigin), C2P(orientation)); - const PxHitFlags hitFlags = PxHitFlag::ePOSITION | PxHitFlag::eNORMAL | PxHitFlag::eFACE_INDEX | PxHitFlag::eUV; PxRaycastHit hit; - if (PxGeometryQuery::raycast(C2P(origin - sceneOrigin), C2P(direction), shapePhysX->getGeometry(), trans, maxDistance, hitFlags, 1, &hit) == 0) + if (PxGeometryQuery::raycast(C2P(origin - sceneOrigin), C2P(direction), shapePhysX->getGeometry(), trans, maxDistance, SCENE_QUERY_FLAGS, 1, &hit) == 0) return false; P2C(hit, hitInfo); hitInfo.Point += sceneOrigin; diff --git a/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp b/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp index 5781e4641..fc33b0dfe 100644 --- a/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp +++ b/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp @@ -38,10 +38,6 @@ void SimulationEventCallback::Clear() BrokenJoints.Clear(); } -void SimulationEventCallback::CollectResults() -{ -} - void SimulationEventCallback::SendCollisionEvents() { for (auto& c : RemovedCollisions) @@ -132,7 +128,6 @@ void SimulationEventCallback::onContact(const PxContactPairHeader& pairHeader, c //const PxU32 flippedContacts = (pair.flags & PxContactPairFlag::eINTERNAL_CONTACTS_ARE_FLIPPED); const bool hasImpulses = pair.flags.isSet(PxContactPairFlag::eINTERNAL_HAS_IMPULSES); const bool hasPostVelocities = !pair.flags.isSet(PxContactPairFlag::eACTOR_PAIR_LOST_TOUCH); - PxU32 nbContacts = 0; PxVec3 totalImpulse(0.0f); c.ThisActor = static_cast(pair.shapes[0]->userData); @@ -144,29 +139,25 @@ void SimulationEventCallback::onContact(const PxContactPairHeader& pairHeader, c } // Extract contact points + c.ContactsCount = 0; while (i.hasNextPatch()) { i.nextPatch(); - while (i.hasNextContact() && nbContacts < COLLISION_NAX_CONTACT_POINTS) + while (i.hasNextContact() && c.ContactsCount < COLLISION_NAX_CONTACT_POINTS) { i.nextContact(); - const PxVec3 point = i.getContactPoint(); const PxVec3 normal = i.getContactNormal(); if (hasImpulses) - totalImpulse += normal * impulses[nbContacts]; + totalImpulse += normal * impulses[c.ContactsCount]; - //PxU32 internalFaceIndex0 = flippedContacts ? iter.getFaceIndex1() : iter.getFaceIndex0(); - //PxU32 internalFaceIndex1 = flippedContacts ? iter.getFaceIndex0() : iter.getFaceIndex1(); - - ContactPoint& contact = c.Contacts[nbContacts]; + ContactPoint& contact = c.Contacts[c.ContactsCount++]; contact.Point = P2C(point); contact.Normal = P2C(normal); contact.Separation = i.getSeparation(); - - nbContacts++; } } + c.Impulse = P2C(totalImpulse); // Extract velocities c.ThisVelocity = c.OtherVelocity = Vector3::Zero; @@ -183,9 +174,6 @@ void SimulationEventCallback::onContact(const PxContactPairHeader& pairHeader, c } } - c.ContactsCount = nbContacts; - c.Impulse = P2C(totalImpulse); - if (pair.flags & PxContactPairFlag::eACTOR_PAIR_HAS_FIRST_TOUCH) { NewCollisions.Add(c); diff --git a/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.h b/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.h index f9081df66..f10f926eb 100644 --- a/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.h +++ b/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.h @@ -49,11 +49,6 @@ public: /// void Clear(); - /// - /// Generates the new/old/removed collisions and a valid trigger pairs. - /// - void CollectResults(); - /// /// Sends the collision events to the managed objects. /// diff --git a/Source/Engine/Physics/PhysicalMaterial.h b/Source/Engine/Physics/PhysicalMaterial.h index b9c2e4554..ff27437e8 100644 --- a/Source/Engine/Physics/PhysicalMaterial.h +++ b/Source/Engine/Physics/PhysicalMaterial.h @@ -9,7 +9,8 @@ /// /// Physical materials are used to define the response of a physical object when interacting dynamically with the world. /// -API_CLASS(Attributes = "ContentContextMenu(\"New/Physics/Physical Material\")") class FLAXENGINE_API PhysicalMaterial final : public ISerializable +API_CLASS(Attributes = "ContentContextMenu(\"New/Physics/Physical Material\")") +class FLAXENGINE_API PhysicalMaterial final : public ISerializable { API_AUTO_SERIALIZATION(); DECLARE_SCRIPTING_TYPE_MINIMAL(PhysicalMaterial); diff --git a/Source/Engine/Physics/Types.h b/Source/Engine/Physics/Types.h index ed12f8611..1631f694b 100644 --- a/Source/Engine/Physics/Types.h +++ b/Source/Engine/Physics/Types.h @@ -151,17 +151,17 @@ API_STRUCT() struct RayCastHit /// API_FIELD() float Distance; + /// + /// The point in the world space where ray hit the collider. + /// + API_FIELD() Vector3 Point; + /// /// The index of the face that was hit. Valid only for convex mesh (polygon index), triangle mesh (triangle index) and height field (triangle index). /// /// API_FIELD() uint32 FaceIndex; - /// - /// The point in the world space where ray hit the collider. - /// - API_FIELD() Vector3 Point; - /// /// The barycentric coordinates of hit triangle. Valid only for triangle mesh and height field. /// diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index 447301f46..9578f6406 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -1431,7 +1431,7 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option Transform srcNode = data.Skeleton.Nodes[nodeIndex].LocalTransform; auto& node = data.Skeleton.Nodes[nodeIndex]; if (auto* channel = animation.GetChannel(node.Name)) - channel->Evaluate(frame, &srcNode, false); + channel->Evaluate((float)frame, &srcNode, false); pose.Nodes[nodeIndex] = srcNode; } @@ -1439,7 +1439,7 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option key = Float3::Zero; for (int32 nodeIndex = 0; nodeIndex < nodes; nodeIndex++) key += pose.GetNodeModelTransformation(data.Skeleton, nodeIndex).Translation; - key /= nodes; + key /= (float)nodes; } // Calculate skeleton center of mass movement over the animation frames @@ -1448,7 +1448,7 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option for (int32 frame = 0; frame < frames; frame++) { auto& key = rootChannel.Position[frame]; - key.Time = frame; + key.Time = (float)frame; key.Value = centerOfMass[frame] - centerOfMassRefPose; } @@ -1494,7 +1494,7 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option Transform srcNode = data.Skeleton.Nodes[nodeIndex].LocalTransform; auto& node = data.Skeleton.Nodes[nodeIndex]; if (auto* channel = animation.GetChannel(node.Name)) - channel->Evaluate(frame, &srcNode, false); + channel->Evaluate((float)frame, &srcNode, false); pose.Nodes[nodeIndex] = srcNode; } From db7dfdb0b14c1d28013a6eefdfe1903c8c981255 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 14 Feb 2024 18:55:48 +0100 Subject: [PATCH 086/139] Add support for structure and script types in `JsonAsset` --- Source/Engine/Content/JsonAsset.cpp | 66 +++++++++++++++++++++-------- 1 file changed, 48 insertions(+), 18 deletions(-) diff --git a/Source/Engine/Content/JsonAsset.cpp b/Source/Engine/Content/JsonAsset.cpp index 9977a28e1..60ff29be1 100644 --- a/Source/Engine/Content/JsonAsset.cpp +++ b/Source/Engine/Content/JsonAsset.cpp @@ -393,34 +393,57 @@ bool JsonAsset::CreateInstance() if (typeHandle) { auto& type = typeHandle.GetType(); + + // Ensure that object can deserialized + const ScriptingType::InterfaceImplementation* interface = type.GetInterface(ISerializable::TypeInitializer); + if (!interface) + { + LOG(Warning, "Cannot deserialize {0} from Json Asset because it doesn't implement ISerializable interface.", type.ToString()); + return false; + } + auto modifier = Cache::ISerializeModifier.Get(); + modifier->EngineBuild = DataEngineBuild; + + // Create object switch (type.Type) { case ScriptingTypes::Class: + case ScriptingTypes::Structure: { - // Ensure that object can deserialized - const ScriptingType::InterfaceImplementation* interface = type.GetInterface(ISerializable::TypeInitializer); - if (!interface) - { - LOG(Warning, "Cannot deserialize {0} from Json Asset because it doesn't implement ISerializable interface.", type.ToString()); - break; - } - - // Allocate object const auto instance = Allocator::Allocate(type.Size); if (!instance) return true; Instance = instance; - InstanceType = typeHandle; - _dtor = type.Class.Dtor; - type.Class.Ctor(instance); + if (type.Type == ScriptingTypes::Class) + { + _dtor = type.Class.Dtor; + type.Class.Ctor(instance); + } + else + { + _dtor = type.Struct.Dtor; + type.Struct.Ctor(instance); + } // Deserialize object - auto modifier = Cache::ISerializeModifier.Get(); - modifier->EngineBuild = DataEngineBuild; ((ISerializable*)((byte*)instance + interface->VTableOffset))->Deserialize(*Data, modifier.Value); break; } + case ScriptingTypes::Script: + { + const ScriptingObjectSpawnParams params(Guid::New(), typeHandle); + const auto instance = type.Script.Spawn(params); + if (!instance) + return true; + Instance = instance; + _dtor = nullptr; + + // Deserialize object + ToInterface(instance)->Deserialize(*Data, modifier.Value); + break; } + } + InstanceType = typeHandle; } return false; @@ -441,13 +464,20 @@ void JsonAsset::DeleteInstance() } // C++ instance - if (!Instance || !_dtor) + if (!Instance) return; - _dtor(Instance); + if (_dtor) + { + _dtor(Instance); + _dtor = nullptr; + Allocator::Free(Instance); + } + else + { + Delete((ScriptingObject*)Instance); + } InstanceType = ScriptingTypeHandle(); - Allocator::Free(Instance); Instance = nullptr; - _dtor = nullptr; } #if USE_EDITOR From f1c0aea10f144860bf52e65712b78512212eb1e1 Mon Sep 17 00:00:00 2001 From: nothingTVatYT <34131388+nothingTVatYT@users.noreply.github.com> Date: Wed, 14 Feb 2024 23:14:25 +0100 Subject: [PATCH 087/139] add rubberband selection --- Source/Editor/Content/GUI/ContentView.cs | 55 ++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/Source/Editor/Content/GUI/ContentView.cs b/Source/Editor/Content/GUI/ContentView.cs index 259be104b..ab31530f1 100644 --- a/Source/Editor/Content/GUI/ContentView.cs +++ b/Source/Editor/Content/GUI/ContentView.cs @@ -56,6 +56,9 @@ namespace FlaxEditor.Content.GUI private float _viewScale = 1.0f; private ContentViewType _viewType = ContentViewType.Tiles; + private bool _isRubberBandSpanning = false; + private Float2 _mousePresslocation; + private Rectangle _rubberBandRectangle; #region External Events @@ -607,9 +610,51 @@ namespace FlaxEditor.Content.GUI { if (base.OnMouseDown(location, button)) return true; + if (button == MouseButton.Left) + { + _mousePresslocation = location; + _rubberBandRectangle = new Rectangle(_mousePresslocation, 0, 0); + _isRubberBandSpanning = true; + StartMouseCapture(); + } return AutoFocus && Focus(this); } + /// + public override void OnMouseMove(Float2 location) + { + if (_isRubberBandSpanning) + { + _rubberBandRectangle.Width = location.X - _mousePresslocation.X; + _rubberBandRectangle.Height = location.Y - _mousePresslocation.Y; + } + base.OnMouseMove(location); + } + + /// + public override bool OnMouseUp(Float2 location, MouseButton button) + { + if (_isRubberBandSpanning) + { + _isRubberBandSpanning = false; + EndMouseCapture(); + if (_rubberBandRectangle.Width < 0 || _rubberBandRectangle.Height < 0) + { + // make sure we have a well-formed rectangle i.e. size is positive and X/Y is upper left corner + var size = _rubberBandRectangle.Size; + _rubberBandRectangle.X = Mathf.Min(_rubberBandRectangle.X, _rubberBandRectangle.X + _rubberBandRectangle.Width); + _rubberBandRectangle.Y = Mathf.Min(_rubberBandRectangle.Y, _rubberBandRectangle.Y + _rubberBandRectangle.Height); + size.X = Mathf.Abs(size.X); + size.Y = Mathf.Abs(size.Y); + _rubberBandRectangle.Size = size; + } + var itemsInRectangle = _items.Where(t => _rubberBandRectangle.Intersects(t.Bounds)).ToList(); + Select(itemsInRectangle, Input.GetKey(KeyboardKeys.Shift)); + return true; + } + return base.OnMouseUp(location, button); + } + /// public override bool OnMouseWheel(Float2 location, float delta) { @@ -714,6 +759,16 @@ namespace FlaxEditor.Content.GUI return false; } + /// + public override void DrawSelf() + { + base.DrawSelf(); + if (_isRubberBandSpanning) + { + Render2D.DrawRectangle(_rubberBandRectangle, Color.White); + } + } + /// protected override void PerformLayoutBeforeChildren() { From defe7454ced3b8a46b16886e12818a340f4a71ce Mon Sep 17 00:00:00 2001 From: nothingTVatYT <34131388+nothingTVatYT@users.noreply.github.com> Date: Wed, 14 Feb 2024 23:25:27 +0100 Subject: [PATCH 088/139] Revert "add borderless mode" This reverts commit 6f4e599cc2ec8b4c9f1851f294bf48368eb13687. --- Source/Engine/Platform/Linux/LinuxWindow.cpp | 36 -------------------- Source/Engine/Platform/Linux/LinuxWindow.h | 6 ++-- 2 files changed, 2 insertions(+), 40 deletions(-) diff --git a/Source/Engine/Platform/Linux/LinuxWindow.cpp b/Source/Engine/Platform/Linux/LinuxWindow.cpp index fa0770cd1..9004ed84c 100644 --- a/Source/Engine/Platform/Linux/LinuxWindow.cpp +++ b/Source/Engine/Platform/Linux/LinuxWindow.cpp @@ -42,15 +42,6 @@ extern Array KeyCodeMap; extern X11::Cursor Cursors[(int32)CursorType::MAX]; extern Window* MouseTrackingWindow; -typedef struct XDisplayHints -{ - unsigned long flags; - unsigned long functions; - unsigned long decorations; - long inputMode; - unsigned long status; -} XDisplayHints; - static constexpr uint32 MouseDoubleClickTime = 500; static constexpr uint32 MaxDoubleClickDistanceSquared = 10; static X11::Time MouseLastButtonPressTime = 0; @@ -341,33 +332,6 @@ void LinuxWindow::Maximize() Maximize(true); } -void LinuxWindow::SetBorderless(bool isBorderless, bool maximized) -{ - LINUX_WINDOW_PROLOG; - if (!display) - return; - XDisplayHints hints; - hints.flags = 2; - hints.decorations = isBorderless ? 0 : 1; - const X11::Atom property = X11::XInternAtom(display, "_MOTIF_WM_HINTS", true); - if (property != 0) { - X11::XChangeProperty(display, window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5); - } -} - -void LinuxWindow::SetIsFullscreen(bool isFullscreen) -{ - if (isFullscreen) - { - SetBorderless(true); - Maximize(false); - } else - { - SetBorderless(false); - Restore(); - } -} - void LinuxWindow::Restore() { if (IsMaximized()) diff --git a/Source/Engine/Platform/Linux/LinuxWindow.h b/Source/Engine/Platform/Linux/LinuxWindow.h index 454fc26c1..52c039f6f 100644 --- a/Source/Engine/Platform/Linux/LinuxWindow.h +++ b/Source/Engine/Platform/Linux/LinuxWindow.h @@ -71,16 +71,14 @@ public: void Hide() override; void Minimize() override; void Maximize() override; - void SetBorderless(bool isBorderless, bool maximized = false) override; - void Restore() override; + void Restore() override; bool IsClosed() const override; bool IsForegroundWindow() const override; void BringToFront(bool force = false) override; void SetClientBounds(const Rectangle& clientArea) override; void SetPosition(const Float2& position) override; void SetClientPosition(const Float2& position) override; - void SetIsFullscreen(bool isFullscreen) override; - Float2 GetPosition() const override; + Float2 GetPosition() const override; Float2 GetSize() const override; Float2 GetClientSize() const override; Float2 ScreenToClient(const Float2& screenPos) const override; From 355835439a05339e54f4a863206eced8e763c30e Mon Sep 17 00:00:00 2001 From: nothingTVatYT <34131388+nothingTVatYT@users.noreply.github.com> Date: Wed, 14 Feb 2024 23:48:30 +0100 Subject: [PATCH 089/139] draw rubber band on top of items --- Source/Editor/Content/GUI/ContentView.cs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/Source/Editor/Content/GUI/ContentView.cs b/Source/Editor/Content/GUI/ContentView.cs index ab31530f1..26d89aac0 100644 --- a/Source/Editor/Content/GUI/ContentView.cs +++ b/Source/Editor/Content/GUI/ContentView.cs @@ -603,6 +603,11 @@ namespace FlaxEditor.Content.GUI { Render2D.DrawText(style.FontSmall, IsSearching ? "No results" : "Empty", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); } + + if (_isRubberBandSpanning) + { + Render2D.DrawRectangle(_rubberBandRectangle, Color.White); + } } /// @@ -759,16 +764,6 @@ namespace FlaxEditor.Content.GUI return false; } - /// - public override void DrawSelf() - { - base.DrawSelf(); - if (_isRubberBandSpanning) - { - Render2D.DrawRectangle(_rubberBandRectangle, Color.White); - } - } - /// protected override void PerformLayoutBeforeChildren() { From 75b7eaf345aeb4a71b60bfc8e59742b7cdb7db9e Mon Sep 17 00:00:00 2001 From: nothingTVatYT <34131388+nothingTVatYT@users.noreply.github.com> Date: Thu, 15 Feb 2024 00:01:29 +0100 Subject: [PATCH 090/139] copy color and style from visject --- Source/Editor/Content/GUI/ContentView.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/Editor/Content/GUI/ContentView.cs b/Source/Editor/Content/GUI/ContentView.cs index 26d89aac0..83c5377b7 100644 --- a/Source/Editor/Content/GUI/ContentView.cs +++ b/Source/Editor/Content/GUI/ContentView.cs @@ -606,7 +606,8 @@ namespace FlaxEditor.Content.GUI if (_isRubberBandSpanning) { - Render2D.DrawRectangle(_rubberBandRectangle, Color.White); + Render2D.FillRectangle(_rubberBandRectangle, Color.Orange * 0.4f); + Render2D.DrawRectangle(_rubberBandRectangle, Color.Orange); } } From f730657518374f3cbce5b56a8966f8b41fe7b5b3 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 15 Feb 2024 10:47:45 +0100 Subject: [PATCH 091/139] Add support for using pointer in `MarshalAs` in scripting types --- Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs | 2 +- .../Flax.Build/Bindings/BindingsGenerator.Api.cs | 2 +- .../Flax.Build/Bindings/BindingsGenerator.CSharp.cs | 4 ++-- .../Flax.Build/Bindings/BindingsGenerator.Cache.cs | 2 +- .../Flax.Build/Bindings/BindingsGenerator.Cpp.cs | 4 ++-- .../Flax.Build/Bindings/BindingsGenerator.Parsing.cs | 9 +++++++-- Source/Tools/Flax.Build/Bindings/TypeInfo.cs | 11 +++++++++++ 7 files changed, 25 insertions(+), 9 deletions(-) diff --git a/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs b/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs index b0167c024..72653900c 100644 --- a/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs @@ -22,7 +22,7 @@ namespace Flax.Build.Bindings public string[] Comment; public bool IsInBuild; public bool IsDeprecated; - public string MarshalAs; + public TypeInfo MarshalAs; internal bool IsInited; internal TypedefInfo Instigator; diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs index 4a0070710..1849513f9 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs @@ -197,7 +197,7 @@ namespace Flax.Build.Bindings if (apiType != null) { if (apiType.MarshalAs != null) - return UsePassByReference(buildData, new TypeInfo(apiType.MarshalAs), caller); + return UsePassByReference(buildData, apiType.MarshalAs, caller); // Skip for scripting objects if (apiType.IsScriptingObject) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index 845e4e62b..dc9f74369 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -439,7 +439,7 @@ namespace Flax.Build.Bindings } if (apiType.MarshalAs != null) - return GenerateCSharpManagedToNativeType(buildData, new TypeInfo(apiType.MarshalAs), caller); + return GenerateCSharpManagedToNativeType(buildData, apiType.MarshalAs, caller); if (apiType.IsScriptingObject || apiType.IsInterface) return "IntPtr"; } @@ -531,7 +531,7 @@ namespace Flax.Build.Bindings { var apiType = FindApiTypeInfo(buildData, functionInfo.ReturnType, caller); if (apiType != null && apiType.MarshalAs != null) - returnValueType = GenerateCSharpNativeToManaged(buildData, new TypeInfo(apiType.MarshalAs), caller); + returnValueType = GenerateCSharpNativeToManaged(buildData, apiType.MarshalAs, caller); else returnValueType = GenerateCSharpNativeToManaged(buildData, functionInfo.ReturnType, caller); } diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs index 079a5839f..d78af861f 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs @@ -19,7 +19,7 @@ namespace Flax.Build.Bindings partial class BindingsGenerator { private static readonly Dictionary TypeCache = new Dictionary(); - private const int CacheVersion = 21; + private const int CacheVersion = 22; internal static void Write(BinaryWriter writer, string e) { diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index 4e632014a..c9c80fe87 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -597,7 +597,7 @@ namespace Flax.Build.Bindings CppReferencesFiles.Add(apiType.File); if (apiType.MarshalAs != null) - return GenerateCppWrapperNativeToManaged(buildData, new TypeInfo(apiType.MarshalAs), caller, out type, functionInfo); + return GenerateCppWrapperNativeToManaged(buildData, apiType.MarshalAs, caller, out type, functionInfo); // Scripting Object if (apiType.IsScriptingObject) @@ -801,7 +801,7 @@ namespace Flax.Build.Bindings if (apiType != null) { if (apiType.MarshalAs != null) - return GenerateCppWrapperManagedToNative(buildData, new TypeInfo(apiType.MarshalAs), caller, out type, out apiType, functionInfo, out needLocalVariable); + return GenerateCppWrapperManagedToNative(buildData, apiType.MarshalAs, caller, out type, out apiType, functionInfo, out needLocalVariable); // Scripting Object (for non-pod types converting only, other API converts managed to unmanaged object in C# wrapper code) if (CppNonPodTypesConvertingGeneration && apiType.IsScriptingObject && typeInfo.IsPtr) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs index 0b71af086..3db43edf0 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs @@ -185,6 +185,11 @@ namespace Flax.Build.Bindings tag.Value = tag.Value.Substring(1, tag.Value.Length - 2); if (tag.Value.Contains("\\\"")) tag.Value = tag.Value.Replace("\\\"", "\""); + token = context.Tokenizer.NextToken(); + if (token.Type == TokenType.Multiply) + tag.Value += token.Value; + else + context.Tokenizer.PreviousToken(); parameters.Add(tag); break; case TokenType.Whitespace: @@ -647,7 +652,7 @@ namespace Flax.Build.Bindings desc.Namespace = tag.Value; break; case "marshalas": - desc.MarshalAs = tag.Value; + desc.MarshalAs = TypeInfo.FromString(tag.Value); break; case "tag": ParseTag(ref desc.Tags, tag); @@ -1236,7 +1241,7 @@ namespace Flax.Build.Bindings desc.Namespace = tag.Value; break; case "marshalas": - desc.MarshalAs = tag.Value; + desc.MarshalAs = TypeInfo.FromString(tag.Value); break; case "tag": ParseTag(ref desc.Tags, tag); diff --git a/Source/Tools/Flax.Build/Bindings/TypeInfo.cs b/Source/Tools/Flax.Build/Bindings/TypeInfo.cs index cdf293085..61946bae2 100644 --- a/Source/Tools/Flax.Build/Bindings/TypeInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/TypeInfo.cs @@ -180,6 +180,17 @@ namespace Flax.Build.Bindings return sb.ToString(); } + public static TypeInfo FromString(string text) + { + var result = new TypeInfo(text); + if (result.Type.EndsWith('*')) + { + result.IsPtr = true; + result.Type = result.Type.Substring(0, result.Type.Length - 1); + } + return result; + } + public string ToString(bool canRef = true) { var sb = new StringBuilder(64); From 43f344ee138d0500bd222ed77c674d02c9053972 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 15 Feb 2024 10:54:33 +0100 Subject: [PATCH 092/139] Simplify bindings code for object refs --- .../Bindings/BindingsGenerator.Cpp.cs | 22 +++++-------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index c9c80fe87..8e1836b2f 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -167,11 +167,7 @@ namespace Flax.Build.Bindings return $"Variant(StringView({value}))"; if (typeInfo.Type == "StringAnsi") return $"Variant(StringAnsiView({value}))"; - if (typeInfo.Type == "AssetReference" || - typeInfo.Type == "WeakAssetReference" || - typeInfo.Type == "SoftAssetReference" || - typeInfo.Type == "ScriptingObjectReference" || - typeInfo.Type == "SoftObjectReference") + if (typeInfo.IsObjectRef) return $"Variant({value}.Get())"; if (typeInfo.IsArray) { @@ -227,10 +223,10 @@ namespace Flax.Build.Bindings return $"(StringAnsiView){value}"; if (typeInfo.IsPtr && typeInfo.IsConst && typeInfo.Type == "Char") return $"((StringView){value}).GetText()"; // (StringView)Variant, if not empty, is guaranteed to point to a null-terminated buffer. - if (typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference" || typeInfo.Type == "SoftAssetReference") - return $"ScriptingObject::Cast<{typeInfo.GenericArgs[0].Type}>((Asset*){value})"; if (typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "SoftObjectReference") return $"ScriptingObject::Cast<{typeInfo.GenericArgs[0].Type}>((ScriptingObject*){value})"; + if (typeInfo.IsObjectRef) + return $"ScriptingObject::Cast<{typeInfo.GenericArgs[0].Type}>((Asset*){value})"; if (typeInfo.IsArray) throw new Exception($"Not supported type to convert from the Variant to fixed-size array '{typeInfo}[{typeInfo.ArraySize}]'."); if (typeInfo.Type == "Array" && typeInfo.GenericArgs != null) @@ -514,11 +510,7 @@ namespace Flax.Build.Bindings return "MUtils::ToManaged({0})"; default: // Object reference property - if ((typeInfo.Type == "ScriptingObjectReference" || - typeInfo.Type == "AssetReference" || - typeInfo.Type == "WeakAssetReference" || - typeInfo.Type == "SoftAssetReference" || - typeInfo.Type == "SoftObjectReference") && typeInfo.GenericArgs != null) + if (typeInfo.IsObjectRef) { type = "MObject*"; return "{0}.GetManagedInstance()"; @@ -704,11 +696,7 @@ namespace Flax.Build.Bindings return "MUtils::ToNative({0})"; default: // Object reference property - if ((typeInfo.Type == "ScriptingObjectReference" || - typeInfo.Type == "AssetReference" || - typeInfo.Type == "WeakAssetReference" || - typeInfo.Type == "SoftAssetReference" || - typeInfo.Type == "SoftObjectReference") && typeInfo.GenericArgs != null) + if (typeInfo.IsObjectRef) { // For non-pod types converting only, other API converts managed to unmanaged object in C# wrapper code) if (CppNonPodTypesConvertingGeneration) From fe7cc62728f52ba323be0355b505c32ec679ca03 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 15 Feb 2024 11:22:01 +0100 Subject: [PATCH 093/139] Add `JsonAssetReference` type for scripting --- .../CustomEditors/Editors/AssetRefEditor.cs | 24 ++++-- Source/Engine/Content/JsonAssetReference.cs | 83 +++++++++++++++++++ Source/Engine/Content/JsonAssetReference.h | 25 ++++++ 3 files changed, 126 insertions(+), 6 deletions(-) create mode 100644 Source/Engine/Content/JsonAssetReference.cs create mode 100644 Source/Engine/Content/JsonAssetReference.h diff --git a/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs b/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs index 1f3359fd5..db8ec1152 100644 --- a/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs +++ b/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs @@ -51,10 +51,13 @@ namespace FlaxEditor.CustomEditors.Editors return; Picker = layout.Custom().CustomControl; - _valueType = Values.Type.Type != typeof(object) || Values[0] == null ? Values.Type : TypeUtils.GetObjectType(Values[0]); + var value = Values[0]; + _valueType = Values.Type.Type != typeof(object) || value == null ? Values.Type : TypeUtils.GetObjectType(value); var assetType = _valueType; if (assetType == typeof(string)) assetType = new ScriptType(typeof(Asset)); + else if (_valueType.Type != null && _valueType.Type.Name == typeof(JsonAssetReference<>).Name) + assetType = new ScriptType(_valueType.Type.GenericTypeArguments[0]); float height = 48; var attributes = Values.GetAttributes(); @@ -102,6 +105,12 @@ namespace FlaxEditor.CustomEditors.Editors SetValue(new SceneReference(Picker.Validator.SelectedID)); else if (_valueType.Type == typeof(string)) SetValue(Picker.Validator.SelectedPath); + else if (_valueType.Type.Name == typeof(JsonAssetReference<>).Name) + { + var value = Values[0]; + value.GetType().GetField("Asset").SetValue(value, Picker.Validator.SelectedAsset as JsonAsset); + SetValue(value); + } else SetValue(Picker.Validator.SelectedAsset); } @@ -114,16 +123,19 @@ namespace FlaxEditor.CustomEditors.Editors if (!HasDifferentValues) { _isRefreshing = true; - if (Values[0] is AssetItem assetItem) + var value = Values[0]; + if (value is AssetItem assetItem) Picker.Validator.SelectedItem = assetItem; - else if (Values[0] is Guid guid) + else if (value is Guid guid) Picker.Validator.SelectedID = guid; - else if (Values[0] is SceneReference sceneAsset) + else if (value is SceneReference sceneAsset) Picker.Validator.SelectedItem = Editor.Instance.ContentDatabase.FindAsset(sceneAsset.ID); - else if (Values[0] is string path) + else if (value is string path) Picker.Validator.SelectedPath = path; + else if (value != null && value.GetType().Name == typeof(JsonAssetReference<>).Name) + Picker.Validator.SelectedAsset = value.GetType().GetField("Asset").GetValue(value) as JsonAsset; else - Picker.Validator.SelectedAsset = Values[0] as Asset; + Picker.Validator.SelectedAsset = value as Asset; _isRefreshing = false; } } diff --git a/Source/Engine/Content/JsonAssetReference.cs b/Source/Engine/Content/JsonAssetReference.cs new file mode 100644 index 000000000..adfaa5179 --- /dev/null +++ b/Source/Engine/Content/JsonAssetReference.cs @@ -0,0 +1,83 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +using System; + +namespace FlaxEngine +{ + /// + /// Json asset reference utility. References resource with a typed data type. + /// + /// Type of the asset instance type. +#if FLAX_EDITOR + [CustomEditor(typeof(FlaxEditor.CustomEditors.Editors.AssetRefEditor))] +#endif + public struct JsonAssetReference + { + /// + /// Gets or sets the referenced asset. + /// + public JsonAsset Asset; + + /// + /// Gets the instance of the Json Asset. Null if unset. + /// + /// instance of the Json Asset or null if unset. + public T Get() + { + return (T)Asset?.Instance; + } + + /// + /// Initializes a new instance of the structure. + /// + /// The Json Asset. + public JsonAssetReference(JsonAsset asset) + { + Asset = asset; + } + + /// + /// Implicit cast operator. + /// + public static implicit operator JsonAsset(JsonAssetReference value) + { + return value.Asset; + } + + /// + /// Implicit cast operator. + /// + public static implicit operator IntPtr(JsonAssetReference value) + { + return Object.GetUnmanagedPtr(value.Asset); + } + + /// + /// Implicit cast operator. + /// + public static implicit operator JsonAssetReference(JsonAsset value) + { + return new JsonAssetReference(value); + } + + /// + /// Implicit cast operator. + /// + public static implicit operator JsonAssetReference(IntPtr valuePtr) + { + return new JsonAssetReference(Object.FromUnmanagedPtr(valuePtr) as JsonAsset); + } + + /// + public override string ToString() + { + return Asset?.ToString(); + } + + /// + public override int GetHashCode() + { + return Asset?.GetHashCode() ?? 0; + } + } +} diff --git a/Source/Engine/Content/JsonAssetReference.h b/Source/Engine/Content/JsonAssetReference.h new file mode 100644 index 000000000..82fbe47f1 --- /dev/null +++ b/Source/Engine/Content/JsonAssetReference.h @@ -0,0 +1,25 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Content/JsonAsset.h" +#include "Engine/Content/AssetReference.h" + +/// +/// Json asset reference utility. References resource with a typed data type. +/// +/// Type of the asset instance type. +template +API_STRUCT(NoDefault, Template, MarshalAs=JsonAsset*) struct JsonAssetReference : AssetReference +{ + JsonAssetReference& operator=(JsonAsset* asset) noexcept + { + OnSet(asset); + return *this; + } + + operator JsonAsset*() const + { + return Get(); + } +}; From 753829677515d78348f8739db8a3a7f528064075 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 15 Feb 2024 11:45:50 +0100 Subject: [PATCH 094/139] Refactor `PhysicalMaterial` usage to utilize `JsonAssetReference` struct --- .../Engine/Physics/Colliders/CharacterController.cpp | 2 +- Source/Engine/Physics/Colliders/Collider.cpp | 4 ++-- Source/Engine/Physics/Colliders/Collider.h | 6 +++--- Source/Engine/Physics/PhysicalMaterial.h | 10 +--------- Source/Engine/Physics/Physics.cpp | 5 ----- Source/Engine/Terrain/Terrain.cpp | 2 +- Source/Engine/Terrain/Terrain.h | 6 +++--- Source/Engine/Terrain/TerrainPatch.cpp | 2 +- 8 files changed, 12 insertions(+), 25 deletions(-) diff --git a/Source/Engine/Physics/Colliders/CharacterController.cpp b/Source/Engine/Physics/Colliders/CharacterController.cpp index 9b36a92ff..38ab0394d 100644 --- a/Source/Engine/Physics/Colliders/CharacterController.cpp +++ b/Source/Engine/Physics/Colliders/CharacterController.cpp @@ -206,7 +206,7 @@ void CharacterController::CreateController() _cachedScale = GetScale(); const float scaling = _cachedScale.GetAbsolute().MaxValue(); const Vector3 position = _transform.LocalToWorld(_center); - _controller = PhysicsBackend::CreateController(GetPhysicsScene()->GetPhysicsScene(), this, this, _contactOffset, position, _slopeLimit, (int32)_nonWalkableMode, Material.Get(), Math::Abs(_radius) * scaling, Math::Abs(_height) * scaling, _stepOffset, _shape); + _controller = PhysicsBackend::CreateController(GetPhysicsScene()->GetPhysicsScene(), this, this, _contactOffset, position, _slopeLimit, (int32)_nonWalkableMode, Material, Math::Abs(_radius) * scaling, Math::Abs(_height) * scaling, _stepOffset, _shape); // Setup PhysicsBackend::SetControllerUpDirection(_controller, _upDirection); diff --git a/Source/Engine/Physics/Colliders/Collider.cpp b/Source/Engine/Physics/Colliders/Collider.cpp index 4326a033d..e354057d2 100644 --- a/Source/Engine/Physics/Colliders/Collider.cpp +++ b/Source/Engine/Physics/Colliders/Collider.cpp @@ -201,7 +201,7 @@ void Collider::CreateShape() // Create shape const bool isTrigger = _isTrigger && CanBeTrigger(); - _shape = PhysicsBackend::CreateShape(this, shape, Material.Get(), IsActiveInHierarchy(), isTrigger); + _shape = PhysicsBackend::CreateShape(this, shape, Material, IsActiveInHierarchy(), isTrigger); PhysicsBackend::SetShapeContactOffset(_shape, _contactOffset); UpdateLayerBits(); } @@ -288,7 +288,7 @@ void Collider::OnMaterialChanged() { // Update the shape material if (_shape) - PhysicsBackend::SetShapeMaterial(_shape, Material.Get()); + PhysicsBackend::SetShapeMaterial(_shape, Material); } void Collider::BeginPlay(SceneBeginData* data) diff --git a/Source/Engine/Physics/Colliders/Collider.h b/Source/Engine/Physics/Colliders/Collider.h index bd17aa27a..cbbb7e522 100644 --- a/Source/Engine/Physics/Colliders/Collider.h +++ b/Source/Engine/Physics/Colliders/Collider.h @@ -4,7 +4,7 @@ #include "Engine/Physics/Types.h" #include "Engine/Content/JsonAsset.h" -#include "Engine/Content/AssetReference.h" +#include "Engine/Content/JsonAssetReference.h" #include "Engine/Physics/Actors/PhysicsColliderActor.h" struct RayCastHit; @@ -80,8 +80,8 @@ public: /// /// The physical material used to define the collider physical properties. /// - API_FIELD(Attributes="EditorOrder(2), DefaultValue(null), AssetReference(typeof(PhysicalMaterial), true), EditorDisplay(\"Collider\")") - AssetReference Material; + API_FIELD(Attributes="EditorOrder(2), DefaultValue(null), EditorDisplay(\"Collider\")") + JsonAssetReference Material; public: /// diff --git a/Source/Engine/Physics/PhysicalMaterial.h b/Source/Engine/Physics/PhysicalMaterial.h index ff27437e8..f1aa0c1fd 100644 --- a/Source/Engine/Physics/PhysicalMaterial.h +++ b/Source/Engine/Physics/PhysicalMaterial.h @@ -15,17 +15,9 @@ class FLAXENGINE_API PhysicalMaterial final : public ISerializable API_AUTO_SERIALIZATION(); DECLARE_SCRIPTING_TYPE_MINIMAL(PhysicalMaterial); private: - void* _material; + void* _material = nullptr; public: - /// - /// Initializes a new instance of the class. - /// - PhysicalMaterial(); - - /// - /// Finalizes an instance of the class. - /// ~PhysicalMaterial(); public: diff --git a/Source/Engine/Physics/Physics.cpp b/Source/Engine/Physics/Physics.cpp index 5d9b218ec..942f1b79b 100644 --- a/Source/Engine/Physics/Physics.cpp +++ b/Source/Engine/Physics/Physics.cpp @@ -78,11 +78,6 @@ void PhysicsSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* } } -PhysicalMaterial::PhysicalMaterial() - : _material(nullptr) -{ -} - PhysicalMaterial::~PhysicalMaterial() { if (_material) diff --git a/Source/Engine/Terrain/Terrain.cpp b/Source/Engine/Terrain/Terrain.cpp index f347e3732..772abf4d3 100644 --- a/Source/Engine/Terrain/Terrain.cpp +++ b/Source/Engine/Terrain/Terrain.cpp @@ -239,7 +239,7 @@ void Terrain::OnPhysicalMaterialChanged() const auto patch = _patches[pathIndex]; if (patch->HasCollision()) { - PhysicsBackend::SetShapeMaterial(patch->_physicsShape, PhysicalMaterial.Get()); + PhysicsBackend::SetShapeMaterial(patch->_physicsShape, PhysicalMaterial); } } } diff --git a/Source/Engine/Terrain/Terrain.h b/Source/Engine/Terrain/Terrain.h index 636f30206..1f495c5ab 100644 --- a/Source/Engine/Terrain/Terrain.h +++ b/Source/Engine/Terrain/Terrain.h @@ -2,7 +2,7 @@ #pragma once -#include "Engine/Content/JsonAsset.h" +#include "Engine/Content/JsonAssetReference.h" #include "Engine/Content/Assets/MaterialBase.h" #include "Engine/Physics/Actors/PhysicsColliderActor.h" @@ -79,8 +79,8 @@ public: /// /// The physical material used to define the terrain collider physical properties. /// - API_FIELD(Attributes="EditorOrder(520), DefaultValue(null), Limit(-1, 100, 0.1f), EditorDisplay(\"Collision\"), AssetReference(typeof(PhysicalMaterial), true)") - AssetReference PhysicalMaterial; + API_FIELD(Attributes="EditorOrder(520), DefaultValue(null), Limit(-1, 100, 0.1f), EditorDisplay(\"Collision\")") + JsonAssetReference<::PhysicalMaterial> PhysicalMaterial; /// /// The draw passes to use for rendering this object. diff --git a/Source/Engine/Terrain/TerrainPatch.cpp b/Source/Engine/Terrain/TerrainPatch.cpp index 8e05062c8..7fa449601 100644 --- a/Source/Engine/Terrain/TerrainPatch.cpp +++ b/Source/Engine/Terrain/TerrainPatch.cpp @@ -2125,7 +2125,7 @@ void TerrainPatch::CreateCollision() shape.SetHeightField(_physicsHeightField, heightScale, rowScale, columnScale); // Create shape - _physicsShape = PhysicsBackend::CreateShape(_terrain, shape, _terrain->PhysicalMaterial.Get(), _terrain->IsActiveInHierarchy(), false); + _physicsShape = PhysicsBackend::CreateShape(_terrain, shape, _terrain->PhysicalMaterial, _terrain->IsActiveInHierarchy(), false); PhysicsBackend::SetShapeLocalPose(_physicsShape, Vector3(0, _yOffset * terrainTransform.Scale.Y, 0), Quaternion::Identity); // Create static actor From b5e23f0096a6438659f362bce115a00101bb80d5 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 15 Feb 2024 11:46:17 +0100 Subject: [PATCH 095/139] Improve JsonAssetReference --- Source/Engine/AI/BehaviorKnowledgeSelector.cs | 4 +- Source/Engine/Content/JsonAssetReference.cs | 38 ++++++++++++++++--- Source/Engine/Content/JsonAssetReference.h | 9 +++++ 3 files changed, 43 insertions(+), 8 deletions(-) diff --git a/Source/Engine/AI/BehaviorKnowledgeSelector.cs b/Source/Engine/AI/BehaviorKnowledgeSelector.cs index 5c642e92a..cdcaf6c40 100644 --- a/Source/Engine/AI/BehaviorKnowledgeSelector.cs +++ b/Source/Engine/AI/BehaviorKnowledgeSelector.cs @@ -146,7 +146,7 @@ namespace FlaxEngine public string Path; /// - /// Initializes a new instance of the structure. + /// Initializes a new instance of the structure. /// /// The selector path. public BehaviorKnowledgeSelector(string path) @@ -155,7 +155,7 @@ namespace FlaxEngine } /// - /// Initializes a new instance of the structure. + /// Initializes a new instance of the structure. /// /// The other selector. public BehaviorKnowledgeSelector(BehaviorKnowledgeSelectorAny other) diff --git a/Source/Engine/Content/JsonAssetReference.cs b/Source/Engine/Content/JsonAssetReference.cs index adfaa5179..7c8a8902f 100644 --- a/Source/Engine/Content/JsonAssetReference.cs +++ b/Source/Engine/Content/JsonAssetReference.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; +using System.Runtime.CompilerServices; namespace FlaxEngine { @@ -19,13 +20,9 @@ namespace FlaxEngine public JsonAsset Asset; /// - /// Gets the instance of the Json Asset. Null if unset. + /// Gets the instance of the serialized object from the json asset data. Cached internally. /// - /// instance of the Json Asset or null if unset. - public T Get() - { - return (T)Asset?.Instance; - } + public T Instance => (T)Asset?.Instance; /// /// Initializes a new instance of the structure. @@ -68,6 +65,35 @@ namespace FlaxEngine return new JsonAssetReference(Object.FromUnmanagedPtr(valuePtr) as JsonAsset); } + /// + /// Checks if the object exists (reference is not null and the unmanaged object pointer is valid). + /// + /// The object to check. + /// True if object is valid, otherwise false. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator bool(JsonAssetReference obj) + { + return obj.Asset; + } + + /// + /// Checks whether the two objects are equal. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(JsonAssetReference left, JsonAssetReference right) + { + return left.Asset == right.Asset; + } + + /// + /// Checks whether the two objects are not equal. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(JsonAssetReference left, JsonAssetReference right) + { + return left.Asset != right.Asset; + } + /// public override string ToString() { diff --git a/Source/Engine/Content/JsonAssetReference.h b/Source/Engine/Content/JsonAssetReference.h index 82fbe47f1..d84c44926 100644 --- a/Source/Engine/Content/JsonAssetReference.h +++ b/Source/Engine/Content/JsonAssetReference.h @@ -12,6 +12,15 @@ template API_STRUCT(NoDefault, Template, MarshalAs=JsonAsset*) struct JsonAssetReference : AssetReference { + /// + /// Gets the deserialized native object instance of the given type. Returns null if asset is not loaded or loaded object has different type. + /// + /// The asset instance object or null. + FORCE_INLINE T* GetInstance() const + { + return _asset ? Get()->GetInstance() : nullptr; + } + JsonAssetReference& operator=(JsonAsset* asset) noexcept { OnSet(asset); From 5fc7c6e190500b109b8d540420f6b7e1ba26c57f Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 15 Feb 2024 18:26:12 +0100 Subject: [PATCH 096/139] Add `MinCount` and `MaxCount` to `Collection` attribute --- .../CustomEditors/Editors/CollectionEditor.cs | 29 +++++++++++-------- .../Attributes/CollectionAttribute.cs | 10 +++++++ 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs index 00322fc81..38dce0a25 100644 --- a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs +++ b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs @@ -176,8 +176,8 @@ namespace FlaxEditor.CustomEditors.Editors private IntValueBox _sizeBox; private Color _background; - private int _elementsCount; - private bool _readOnly; + private int _elementsCount, _minCount, _maxCount; + private bool _canResize; private bool _canReorderItems; private CollectionAttribute.DisplayType _displayType; @@ -209,8 +209,10 @@ namespace FlaxEditor.CustomEditors.Editors return; var size = Count; - _readOnly = false; + _canResize = true; _canReorderItems = true; + _minCount = 0; + _maxCount = 0; _background = FlaxEngine.GUI.Style.Current.CollectionBackgroundColor; _displayType = CollectionAttribute.DisplayType.Header; NotNullItems = false; @@ -222,7 +224,9 @@ namespace FlaxEditor.CustomEditors.Editors var collection = (CollectionAttribute)attributes?.FirstOrDefault(x => x is CollectionAttribute); if (collection != null) { - _readOnly = collection.ReadOnly; + _canResize = !collection.ReadOnly; + _minCount = collection.MinCount; + _maxCount = collection.MaxCount; _canReorderItems = collection.CanReorderItems; NotNullItems = collection.NotNullItems; if (collection.BackgroundColor.HasValue) @@ -231,6 +235,9 @@ namespace FlaxEditor.CustomEditors.Editors spacing = collection.Spacing; _displayType = collection.Display; } + if (_maxCount == 0) + _maxCount = ushort.MaxValue; + _canResize &= _minCount < _maxCount; var dragArea = layout.CustomContainer(); dragArea.CustomControl.Editor = this; @@ -268,8 +275,8 @@ namespace FlaxEditor.CustomEditors.Editors var y = -dropPanel.HeaderHeight + dropPanel.HeaderTextMargin.Top; _sizeBox = new IntValueBox(size) { - MinValue = 0, - MaxValue = ushort.MaxValue, + MinValue = _minCount, + MaxValue = _maxCount, AnchorPreset = AnchorPresets.TopRight, Bounds = new Rectangle(-40 - dropPanel.ItemsMargin.Right, y, 40, height), Parent = dropPanel, @@ -283,7 +290,7 @@ namespace FlaxEditor.CustomEditors.Editors Parent = dropPanel }; - if (_readOnly || (NotNullItems && size == 0)) + if (!_canResize || (NotNullItems && size == 0)) { _sizeBox.IsReadOnly = true; _sizeBox.Enabled = false; @@ -339,7 +346,7 @@ namespace FlaxEditor.CustomEditors.Editors _elementsCount = size; // Add/Remove buttons - if (!_readOnly) + if (_canResize) { var panel = dragArea.HorizontalPanel(); panel.Panel.Size = new Float2(0, 20); @@ -347,25 +354,23 @@ namespace FlaxEditor.CustomEditors.Editors var removeButton = panel.Button("-", "Remove last item"); removeButton.Button.Size = new Float2(16, 16); - removeButton.Button.Enabled = size > 0; + removeButton.Button.Enabled = size > _minCount; removeButton.Button.AnchorPreset = AnchorPresets.TopRight; removeButton.Button.Clicked += () => { if (IsSetBlocked) return; - Resize(Count - 1); }; var addButton = panel.Button("+", "Add new item"); addButton.Button.Size = new Float2(16, 16); - addButton.Button.Enabled = !NotNullItems || size > 0; + addButton.Button.Enabled = (!NotNullItems || size > 0) && size < _maxCount; addButton.Button.AnchorPreset = AnchorPresets.TopRight; addButton.Button.Clicked += () => { if (IsSetBlocked) return; - Resize(Count + 1); }; } diff --git a/Source/Engine/Scripting/Attributes/CollectionAttribute.cs b/Source/Engine/Scripting/Attributes/CollectionAttribute.cs index cd2e962e6..0603a9390 100644 --- a/Source/Engine/Scripting/Attributes/CollectionAttribute.cs +++ b/Source/Engine/Scripting/Attributes/CollectionAttribute.cs @@ -61,6 +61,16 @@ namespace FlaxEngine /// public float Spacing; + /// + /// The minimum size of the collection. + /// + public int MinCount; + + /// + /// The maximum size of the collection. Zero if unlimited. + /// + public int MaxCount; + /// /// The collection background color. /// From c7a449fe1c63bcb1a444345e0da7ded499202f70 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 15 Feb 2024 18:28:51 +0100 Subject: [PATCH 097/139] Fix marshaling custom type array to C# with `MarshalAs` used --- .../Bindings/BindingsGenerator.CSharp.cs | 72 ++++++++++++++----- .../Bindings/BindingsGenerator.Cpp.cs | 43 ++++++++--- 2 files changed, 88 insertions(+), 27 deletions(-) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index dc9f74369..e1d4df083 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -270,7 +270,7 @@ namespace Flax.Build.Bindings return value; } - private static string GenerateCSharpNativeToManaged(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller) + private static string GenerateCSharpNativeToManaged(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller, bool marshalling = false) { string result; if (typeInfo?.Type == null) @@ -280,7 +280,7 @@ namespace Flax.Build.Bindings if (typeInfo.IsArray) { typeInfo.IsArray = false; - result = GenerateCSharpNativeToManaged(buildData, typeInfo, caller); + result = GenerateCSharpNativeToManaged(buildData, typeInfo, caller, marshalling); typeInfo.IsArray = true; return result + "[]"; } @@ -307,7 +307,7 @@ namespace Flax.Build.Bindings // Object reference property if (typeInfo.IsObjectRef) - return GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller); + return GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller, marshalling); if (typeInfo.Type == "SoftTypeReference" || typeInfo.Type == "SoftObjectReference") return typeInfo.Type; @@ -317,15 +317,25 @@ namespace Flax.Build.Bindings #else if ((typeInfo.Type == "Array" || typeInfo.Type == "Span" || typeInfo.Type == "DataContainer") && typeInfo.GenericArgs != null) #endif - return GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller) + "[]"; + { + var arrayTypeInfo = typeInfo.GenericArgs[0]; + if (marshalling) + { + // Convert array that uses different type for marshalling + var arrayApiType = FindApiTypeInfo(buildData, arrayTypeInfo, caller); + if (arrayApiType != null && arrayApiType.MarshalAs != null) + arrayTypeInfo = arrayApiType.MarshalAs; + } + return GenerateCSharpNativeToManaged(buildData, arrayTypeInfo, caller) + "[]"; + } // Dictionary if (typeInfo.Type == "Dictionary" && typeInfo.GenericArgs != null) - return string.Format("System.Collections.Generic.Dictionary<{0}, {1}>", GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller), GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[1], caller)); + return string.Format("System.Collections.Generic.Dictionary<{0}, {1}>", GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller, marshalling), GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[1], caller, marshalling)); // HashSet if (typeInfo.Type == "HashSet" && typeInfo.GenericArgs != null) - return string.Format("System.Collections.Generic.HashSet<{0}>", GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller)); + return string.Format("System.Collections.Generic.HashSet<{0}>", GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller, marshalling)); // BitArray if (typeInfo.Type == "BitArray" && typeInfo.GenericArgs != null) @@ -348,16 +358,16 @@ namespace Flax.Build.Bindings // TODO: generate delegates globally in the module namespace to share more code (smaller binary size) var key = string.Empty; for (int i = 0; i < typeInfo.GenericArgs.Count; i++) - key += GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[i], caller); + key += GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[i], caller, marshalling); if (!CSharpAdditionalCodeCache.TryGetValue(key, out var delegateName)) { delegateName = "Delegate" + CSharpAdditionalCodeCache.Count; - var signature = $"public delegate {GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller)} {delegateName}("; + var signature = $"public delegate {GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller, marshalling)} {delegateName}("; for (int i = 1; i < typeInfo.GenericArgs.Count; i++) { if (i != 1) signature += ", "; - signature += GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[i], caller); + signature += GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[i], caller, marshalling); signature += $" arg{(i - 1)}"; } signature += ");"; @@ -390,11 +400,14 @@ namespace Flax.Build.Bindings { typeName += '<'; foreach (var arg in typeInfo.GenericArgs) - typeName += GenerateCSharpNativeToManaged(buildData, arg, caller); + typeName += GenerateCSharpNativeToManaged(buildData, arg, caller, marshalling); typeName += '>'; } if (apiType != null) { + if (marshalling && apiType.MarshalAs != null) + return GenerateCSharpNativeToManaged(buildData, apiType.MarshalAs, caller); + // Add reference to the namespace CSharpUsedNamespaces.Add(apiType.Namespace); var apiTypeParent = apiType.Parent; @@ -419,11 +432,11 @@ namespace Flax.Build.Bindings return typeName; } - private static string GenerateCSharpManagedToNativeType(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller) + private static string GenerateCSharpManagedToNativeType(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller, bool marshalling = false) { // Fixed-size array if (typeInfo.IsArray) - return GenerateCSharpNativeToManaged(buildData, typeInfo, caller); + return GenerateCSharpNativeToManaged(buildData, typeInfo, caller, marshalling); // Find API type info var apiType = FindApiTypeInfo(buildData, typeInfo, caller); @@ -439,7 +452,7 @@ namespace Flax.Build.Bindings } if (apiType.MarshalAs != null) - return GenerateCSharpManagedToNativeType(buildData, apiType.MarshalAs, caller); + return GenerateCSharpManagedToNativeType(buildData, apiType.MarshalAs, caller, marshalling); if (apiType.IsScriptingObject || apiType.IsInterface) return "IntPtr"; } @@ -452,7 +465,7 @@ namespace Flax.Build.Bindings if (typeInfo.Type == "Function" && typeInfo.GenericArgs != null) return "IntPtr"; - return GenerateCSharpNativeToManaged(buildData, typeInfo, caller); + return GenerateCSharpNativeToManaged(buildData, typeInfo, caller, marshalling); } private static string GenerateCSharpManagedToNativeConverter(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller) @@ -485,6 +498,18 @@ namespace Flax.Build.Bindings case "Function": // delegate return "NativeInterop.GetFunctionPointerForDelegate({0})"; + case "Array": + case "Span": + case "DataContainer": + if (typeInfo.GenericArgs != null) + { + // Convert array that uses different type for marshalling + var arrayTypeInfo = typeInfo.GenericArgs[0]; + var arrayApiType = FindApiTypeInfo(buildData, arrayTypeInfo, caller); + if (arrayApiType != null && arrayApiType.MarshalAs != null) + return $"{{0}}.ConvertArray(x => ({GenerateCSharpNativeToManaged(buildData, arrayApiType.MarshalAs, caller)})x)"; + } + return string.Empty; default: var apiType = FindApiTypeInfo(buildData, typeInfo, caller); if (apiType != null) @@ -531,9 +556,9 @@ namespace Flax.Build.Bindings { var apiType = FindApiTypeInfo(buildData, functionInfo.ReturnType, caller); if (apiType != null && apiType.MarshalAs != null) - returnValueType = GenerateCSharpNativeToManaged(buildData, apiType.MarshalAs, caller); + returnValueType = GenerateCSharpNativeToManaged(buildData, apiType.MarshalAs, caller, true); else - returnValueType = GenerateCSharpNativeToManaged(buildData, functionInfo.ReturnType, caller); + returnValueType = GenerateCSharpNativeToManaged(buildData, functionInfo.ReturnType, caller, true); } #if USE_NETCORE @@ -594,7 +619,7 @@ namespace Flax.Build.Bindings contents.Append(", "); separator = true; - var nativeType = GenerateCSharpManagedToNativeType(buildData, parameterInfo.Type, caller); + var nativeType = GenerateCSharpManagedToNativeType(buildData, parameterInfo.Type, caller, true); #if USE_NETCORE string parameterMarshalType = ""; if (nativeType == "System.Type") @@ -643,7 +668,7 @@ namespace Flax.Build.Bindings contents.Append(", "); separator = true; - var nativeType = GenerateCSharpManagedToNativeType(buildData, parameterInfo.Type, caller); + var nativeType = GenerateCSharpManagedToNativeType(buildData, parameterInfo.Type, caller, true); #if USE_NETCORE string parameterMarshalType = ""; if (parameterInfo.IsOut && parameterInfo.DefaultValue == "var __resultAsRef") @@ -756,7 +781,16 @@ namespace Flax.Build.Bindings } } - contents.Append(");"); + contents.Append(')'); + if ((functionInfo.ReturnType.Type == "Array" || functionInfo.ReturnType.Type == "Span" || functionInfo.ReturnType.Type == "DataContainer") && functionInfo.ReturnType.GenericArgs != null) + { + // Convert array that uses different type for marshalling + var arrayTypeInfo = functionInfo.ReturnType.GenericArgs[0]; + var arrayApiType = FindApiTypeInfo(buildData, arrayTypeInfo, caller); + if (arrayApiType != null && arrayApiType.MarshalAs != null) + contents.Append($".ConvertArray(x => ({GenerateCSharpNativeToManaged(buildData, arrayTypeInfo, caller)})x)"); + } + contents.Append(';'); // Return result if (functionInfo.Glue.UseReferenceForResult) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index 8e1836b2f..35d1d32fa 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -305,7 +305,7 @@ namespace Flax.Build.Bindings private static string GenerateCppGetMClass(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller, FunctionInfo functionInfo) { // Optimal path for in-build types - var managedType = GenerateCSharpNativeToManaged(buildData, typeInfo, caller); + var managedType = GenerateCSharpNativeToManaged(buildData, typeInfo, caller, true); switch (managedType) { // In-built types (cached by the engine on startup) @@ -388,7 +388,7 @@ namespace Flax.Build.Bindings CppIncludeFiles.Add("Engine/Scripting/ManagedCLR/MClass.h"); // Optimal path for in-build types - var managedType = GenerateCSharpNativeToManaged(buildData, typeInfo, caller); + var managedType = GenerateCSharpNativeToManaged(buildData, typeInfo, caller, true); switch (managedType) { case "bool": @@ -519,16 +519,28 @@ namespace Flax.Build.Bindings // Array or DataContainer if ((typeInfo.Type == "Array" || typeInfo.Type == "Span" || typeInfo.Type == "DataContainer") && typeInfo.GenericArgs != null) { + var arrayTypeInfo = typeInfo.GenericArgs[0]; #if USE_NETCORE // Boolean arrays does not support custom marshalling for some unknown reason - if (typeInfo.GenericArgs[0].Type == "bool") + if (arrayTypeInfo.Type == "bool") { type = "bool*"; return "MUtils::ToBoolArray({0})"; } + var arrayApiType = FindApiTypeInfo(buildData, arrayTypeInfo, caller); #endif type = "MArray*"; - return "MUtils::ToArray({0}, " + GenerateCppGetMClass(buildData, typeInfo.GenericArgs[0], caller, functionInfo) + ")"; + if (arrayApiType != null && arrayApiType.MarshalAs != null) + { + // Convert array that uses different type for marshalling + if (arrayApiType != null && arrayApiType.MarshalAs != null) + arrayTypeInfo = arrayApiType.MarshalAs; // Convert array that uses different type for marshalling + var genericArgs = arrayApiType.MarshalAs.GetFullNameNative(buildData, caller); + if (typeInfo.GenericArgs.Count != 1) + genericArgs += ", " + typeInfo.GenericArgs[1]; + return "MUtils::ToArray(Array<" + genericArgs + ">({0}), " + GenerateCppGetMClass(buildData, arrayTypeInfo, caller, functionInfo) + ")"; + } + return "MUtils::ToArray({0}, " + GenerateCppGetMClass(buildData, arrayTypeInfo, caller, functionInfo) + ")"; } // Span @@ -719,11 +731,26 @@ namespace Flax.Build.Bindings // Array if (typeInfo.Type == "Array" && typeInfo.GenericArgs != null) { - var T = typeInfo.GenericArgs[0].GetFullNameNative(buildData, caller); - type = "MArray*"; + var arrayTypeInfo = typeInfo.GenericArgs[0]; + var arrayApiType = FindApiTypeInfo(buildData, arrayTypeInfo, caller); + if (arrayApiType != null && arrayApiType.MarshalAs != null) + arrayTypeInfo = arrayApiType.MarshalAs; + var genericArgs = arrayTypeInfo.GetFullNameNative(buildData, caller); if (typeInfo.GenericArgs.Count != 1) - return "MUtils::ToArray<" + T + ", " + typeInfo.GenericArgs[1] + ">({0})"; - return "MUtils::ToArray<" + T + ">({0})"; + genericArgs += ", " + typeInfo.GenericArgs[1]; + + type = "MArray*"; + var result = "MUtils::ToArray<" + genericArgs + ">({0})"; + + if (arrayApiType != null && arrayApiType.MarshalAs != null) + { + // Convert array that uses different type for marshalling + genericArgs = typeInfo.GenericArgs[0].GetFullNameNative(buildData, caller); + if (typeInfo.GenericArgs.Count != 1) + genericArgs += ", " + typeInfo.GenericArgs[1]; + result = $"Array<{genericArgs}>({result})"; + } + return result; } // Span or DataContainer From 0b2fcff4eab4ae559311b675e370798788efa185 Mon Sep 17 00:00:00 2001 From: rkrahn Date: Thu, 15 Feb 2024 14:34:50 -0800 Subject: [PATCH 098/139] Fix NextUnitVector2 Fixed NextUnitVector2 method not producing a vector within a unit circle. --- Source/Engine/Utilities/Extensions.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Utilities/Extensions.cs b/Source/Engine/Utilities/Extensions.cs index a1834624e..57eafc4a3 100644 --- a/Source/Engine/Utilities/Extensions.cs +++ b/Source/Engine/Utilities/Extensions.cs @@ -297,8 +297,9 @@ namespace FlaxEngine.Utilities /// A random . public static Vector2 NextUnitVector2(this Random random, float radius = 1.0f) { - var randomRadius = (float)random.NextDouble() * radius; - return new Vector2((float)Math.Cos(random.NextDouble()) * randomRadius, (float)Math.Sin(random.NextDouble()) * randomRadius); + float magnitude = (float)random.NextDouble() * radius; + double randomDegree = random.NextDouble() * 360; + return new Vector2((float)Math.Cos(randomDegree) * magnitude, (float)Math.Sin(randomDegree) * magnitude); } /// From 9a5db2ff8e745151f15281f40ab78fa55fa8ee6d Mon Sep 17 00:00:00 2001 From: rkrahn Date: Thu, 15 Feb 2024 14:51:55 -0800 Subject: [PATCH 099/139] Add NextUnitCircleVector2 extension method Adds the NextUnitCircleVector2 extension method which produces a Vector2 on a unit circle with a magnitude equal to the radius. --- Source/Engine/Utilities/Extensions.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Source/Engine/Utilities/Extensions.cs b/Source/Engine/Utilities/Extensions.cs index a1834624e..dde5eb5c7 100644 --- a/Source/Engine/Utilities/Extensions.cs +++ b/Source/Engine/Utilities/Extensions.cs @@ -300,6 +300,18 @@ namespace FlaxEngine.Utilities var randomRadius = (float)random.NextDouble() * radius; return new Vector2((float)Math.Cos(random.NextDouble()) * randomRadius, (float)Math.Sin(random.NextDouble()) * randomRadius); } + + /// + /// Generates a random point on a circle of a given radius. + /// + /// An instance of . + /// Radius of circle. Default 1.0f./>. + /// A random . + public static Vector2 NextUnitCircleVector2(this Random random, float radius = 1.0f) + { + double randomDegree = random.NextDouble() * 360; + return new Vector2((float)Math.Cos(randomDegree) * radius, (float)Math.Sin(randomDegree) * radius); + } /// /// Generates a uniformly distributed random unit length vector point on a unit sphere. From f0f8da3c09a871160ffe7a3d34f4ace3d50ec52e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 16 Feb 2024 12:29:59 +0100 Subject: [PATCH 100/139] Various fixes --- Source/Engine/AI/BehaviorKnowledgeSelector.h | 7 +++++ Source/Engine/Content/JsonAssetReference.cs | 28 ++++++++++++++++++-- Source/Engine/Content/JsonAssetReference.h | 7 +++++ Source/Engine/Engine/NativeInterop.cs | 4 +-- 4 files changed, 42 insertions(+), 4 deletions(-) diff --git a/Source/Engine/AI/BehaviorKnowledgeSelector.h b/Source/Engine/AI/BehaviorKnowledgeSelector.h index 976711282..092d42b1c 100644 --- a/Source/Engine/AI/BehaviorKnowledgeSelector.h +++ b/Source/Engine/AI/BehaviorKnowledgeSelector.h @@ -91,6 +91,13 @@ API_STRUCT(InBuild, Template, MarshalAs=StringAnsi) struct FLAXENGINE_API Behavi return false; } + BehaviorKnowledgeSelector() = default; + + BehaviorKnowledgeSelector(const StringAnsi& other) + { + Path = other; + } + BehaviorKnowledgeSelector& operator=(const StringAnsiView& other) noexcept { Path = other; diff --git a/Source/Engine/Content/JsonAssetReference.cs b/Source/Engine/Content/JsonAssetReference.cs index 7c8a8902f..766645161 100644 --- a/Source/Engine/Content/JsonAssetReference.cs +++ b/Source/Engine/Content/JsonAssetReference.cs @@ -12,7 +12,7 @@ namespace FlaxEngine #if FLAX_EDITOR [CustomEditor(typeof(FlaxEditor.CustomEditors.Editors.AssetRefEditor))] #endif - public struct JsonAssetReference + public struct JsonAssetReference : IComparable, IComparable>, IEquatable> { /// /// Gets or sets the referenced asset. @@ -94,16 +94,40 @@ namespace FlaxEngine return left.Asset != right.Asset; } + /// + public bool Equals(JsonAssetReference other) + { + return Asset == other.Asset; + } + + /// + public int CompareTo(JsonAssetReference other) + { + return Object.GetUnmanagedPtr(Asset).CompareTo(Object.GetUnmanagedPtr(other.Asset)); + } + + /// + public override bool Equals(object obj) + { + return obj is JsonAssetReference other && Asset == other.Asset; + } + /// public override string ToString() { return Asset?.ToString(); } + /// + public int CompareTo(object obj) + { + return obj is JsonAssetReference other ? CompareTo(other) : 1; + } + /// public override int GetHashCode() { - return Asset?.GetHashCode() ?? 0; + return (Asset != null ? Asset.GetHashCode() : 0); } } } diff --git a/Source/Engine/Content/JsonAssetReference.h b/Source/Engine/Content/JsonAssetReference.h index d84c44926..965a0aaa0 100644 --- a/Source/Engine/Content/JsonAssetReference.h +++ b/Source/Engine/Content/JsonAssetReference.h @@ -12,6 +12,13 @@ template API_STRUCT(NoDefault, Template, MarshalAs=JsonAsset*) struct JsonAssetReference : AssetReference { + JsonAssetReference() = default; + + JsonAssetReference(JsonAsset* asset) + { + OnSet(asset); + } + /// /// Gets the deserialized native object instance of the given type. Returns null if asset is not loaded or loaded object has different type. /// diff --git a/Source/Engine/Engine/NativeInterop.cs b/Source/Engine/Engine/NativeInterop.cs index 026f54fec..7dfd52d22 100644 --- a/Source/Engine/Engine/NativeInterop.cs +++ b/Source/Engine/Engine/NativeInterop.cs @@ -249,7 +249,7 @@ namespace FlaxEngine.Interop /// The input array. /// Converter callback. /// The output array. - public static TDst[] ConvertArray(Span src, Func convertFunc) + public static TDst[] ConvertArray(this Span src, Func convertFunc) { TDst[] dst = new TDst[src.Length]; for (int i = 0; i < src.Length; i++) @@ -265,7 +265,7 @@ namespace FlaxEngine.Interop /// The input array. /// Converter callback. /// The output array. - public static TDst[] ConvertArray(TSrc[] src, Func convertFunc) + public static TDst[] ConvertArray(this TSrc[] src, Func convertFunc) { TDst[] dst = new TDst[src.Length]; for (int i = 0; i < src.Length; i++) From f04f1cc90e74ae3368831b85cced31e5746a3bac Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 16 Feb 2024 12:46:35 +0100 Subject: [PATCH 101/139] Add `ScriptingEnum::ToStringFlags` for printing flag enums into readable text --- Source/Engine/Graphics/GPUBuffer.cpp | 32 ++++--------------- .../Engine/Graphics/Textures/GPUTexture.cpp | 31 ++++-------------- Source/Engine/Scripting/Enums.h | 26 +++++++++++++++ 3 files changed, 38 insertions(+), 51 deletions(-) diff --git a/Source/Engine/Graphics/GPUBuffer.cpp b/Source/Engine/Graphics/GPUBuffer.cpp index 6e400bbdd..6d1ceea41 100644 --- a/Source/Engine/Graphics/GPUBuffer.cpp +++ b/Source/Engine/Graphics/GPUBuffer.cpp @@ -12,6 +12,7 @@ #include "Engine/Debug/Exceptions/InvalidOperationException.h" #include "Engine/Debug/Exceptions/ArgumentNullException.h" #include "Engine/Debug/Exceptions/ArgumentOutOfRangeException.h" +#include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Scripting/Enums.h" #include "Engine/Threading/ThreadPoolTask.h" #include "Engine/Threading/Threading.h" @@ -81,33 +82,10 @@ bool GPUBufferDescription::Equals(const GPUBufferDescription& other) const String GPUBufferDescription::ToString() const { - // TODO: add tool to Format to string - - String flags; - if (Flags == GPUBufferFlags::None) - { - flags = TEXT("None"); - } - else - { - // TODO: create tool to auto convert flag enums to string - -#define CONVERT_FLAGS_FLAGS_2_STR(value) if (EnumHasAnyFlags(Flags, GPUBufferFlags::value)) { if (flags.HasChars()) flags += TEXT('|'); flags += TEXT(#value); } - CONVERT_FLAGS_FLAGS_2_STR(ShaderResource); - CONVERT_FLAGS_FLAGS_2_STR(VertexBuffer); - CONVERT_FLAGS_FLAGS_2_STR(IndexBuffer); - CONVERT_FLAGS_FLAGS_2_STR(UnorderedAccess); - CONVERT_FLAGS_FLAGS_2_STR(Append); - CONVERT_FLAGS_FLAGS_2_STR(Counter); - CONVERT_FLAGS_FLAGS_2_STR(Argument); - CONVERT_FLAGS_FLAGS_2_STR(Structured); -#undef CONVERT_FLAGS_FLAGS_2_STR - } - return String::Format(TEXT("Size: {0}, Stride: {1}, Flags: {2}, Format: {3}, Usage: {4}"), Size, Stride, - flags, + ScriptingEnum::ToStringFlags(Flags), ScriptingEnum::ToString(Format), (int32)Usage); } @@ -212,7 +190,7 @@ GPUBuffer* GPUBuffer::ToStagingUpload() const bool GPUBuffer::Resize(uint32 newSize) { - // Validate input + PROFILE_CPU(); if (!IsAllocated()) { Log::InvalidOperationException(TEXT("Buffer.Resize")); @@ -236,12 +214,12 @@ bool GPUBuffer::DownloadData(BytesContainer& result) LOG(Warning, "Cannot download GPU buffer data from an empty buffer."); return true; } - if (_desc.Usage == GPUResourceUsage::StagingReadback || _desc.Usage == GPUResourceUsage::Dynamic) { // Use faster path for staging resources return GetData(result); } + PROFILE_CPU(); // Ensure not running on main thread if (IsInMainThread()) @@ -358,6 +336,7 @@ Task* GPUBuffer::DownloadDataAsync(BytesContainer& result) bool GPUBuffer::GetData(BytesContainer& output) { + PROFILE_CPU(); void* mapped = Map(GPUResourceMapMode::Read); if (!mapped) return true; @@ -368,6 +347,7 @@ bool GPUBuffer::GetData(BytesContainer& output) void GPUBuffer::SetData(const void* data, uint32 size) { + PROFILE_CPU(); if (size == 0 || data == nullptr) { Log::ArgumentNullException(TEXT("Buffer.SetData")); diff --git a/Source/Engine/Graphics/Textures/GPUTexture.cpp b/Source/Engine/Graphics/Textures/GPUTexture.cpp index 403f545ba..93642f00a 100644 --- a/Source/Engine/Graphics/Textures/GPUTexture.cpp +++ b/Source/Engine/Graphics/Textures/GPUTexture.cpp @@ -15,6 +15,7 @@ #include "Engine/Graphics/GPULimits.h" #include "Engine/Threading/ThreadPoolTask.h" #include "Engine/Graphics/GPUDevice.h" +#include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Scripting/Enums.h" namespace @@ -158,29 +159,6 @@ bool GPUTextureDescription::Equals(const GPUTextureDescription& other) const String GPUTextureDescription::ToString() const { - // TODO: add tool to Format to string - - String flags; - if (Flags == GPUTextureFlags::None) - { - flags = TEXT("None"); - } - else - { - // TODO: create tool to auto convert flag enums to string - -#define CONVERT_FLAGS_FLAGS_2_STR(value) if (EnumHasAnyFlags(Flags, GPUTextureFlags::value)) { if (flags.HasChars()) flags += TEXT('|'); flags += TEXT(#value); } - CONVERT_FLAGS_FLAGS_2_STR(ShaderResource); - CONVERT_FLAGS_FLAGS_2_STR(RenderTarget); - CONVERT_FLAGS_FLAGS_2_STR(UnorderedAccess); - CONVERT_FLAGS_FLAGS_2_STR(DepthStencil); - CONVERT_FLAGS_FLAGS_2_STR(PerMipViews); - CONVERT_FLAGS_FLAGS_2_STR(PerSliceViews); - CONVERT_FLAGS_FLAGS_2_STR(ReadOnlyDepthView); - CONVERT_FLAGS_FLAGS_2_STR(BackBuffer); -#undef CONVERT_FLAGS_FLAGS_2_STR - } - return String::Format(TEXT("Size: {0}x{1}x{2}[{3}], Type: {4}, Mips: {5}, Format: {6}, MSAA: {7}, Flags: {8}, Usage: {9}"), Width, Height, @@ -190,7 +168,7 @@ String GPUTextureDescription::ToString() const MipLevels, ScriptingEnum::ToString(Format), ::ToString(MultiSampleLevel), - flags, + ScriptingEnum::ToStringFlags(Flags), (int32)Usage); } @@ -544,7 +522,7 @@ GPUTexture* GPUTexture::ToStagingUpload() const bool GPUTexture::Resize(int32 width, int32 height, int32 depth, PixelFormat format) { - // Validate texture is created + PROFILE_CPU(); if (!IsAllocated()) { LOG(Warning, "Cannot resize not created textures."); @@ -608,6 +586,7 @@ GPUTask* GPUTexture::UploadMipMapAsync(const BytesContainer& data, int32 mipInde GPUTask* GPUTexture::UploadMipMapAsync(const BytesContainer& data, int32 mipIndex, int32 rowPitch, int32 slicePitch, bool copyData) { + PROFILE_CPU(); ASSERT(IsAllocated()); ASSERT(mipIndex < MipLevels() && data.IsValid()); ASSERT(data.Length() >= slicePitch); @@ -699,6 +678,7 @@ bool GPUTexture::DownloadData(TextureData& result) { MISSING_CODE("support volume texture data downloading."); } + PROFILE_CPU(); // Use faster path for staging resources if (IsStaging()) @@ -780,6 +760,7 @@ Task* GPUTexture::DownloadDataAsync(TextureData& result) { MISSING_CODE("support volume texture data downloading."); } + PROFILE_CPU(); // Use faster path for staging resources if (IsStaging()) diff --git a/Source/Engine/Scripting/Enums.h b/Source/Engine/Scripting/Enums.h index dd605d303..7c8d36f24 100644 --- a/Source/Engine/Scripting/Enums.h +++ b/Source/Engine/Scripting/Enums.h @@ -68,4 +68,30 @@ public: { return FromString(StringAnsi(name)); } + + // Gets the name of the enum value as separated flags + template + static String ToStringFlags(EnumType value, Char separator = '|') + { + String result; + if (const auto items = GetItems()) + { + for (int32 i = 0; items[i].Name; i++) + { + const uint64 itemValue = items[i].Value; + if ((uint64)value == 0 && itemValue == 0) + { + result = items[i].Name; + break; + } + if (itemValue != 0 && EnumHasAllFlags(value, (EnumType)itemValue)) + { + if (result.HasChars()) + result += separator; + result += items[i].Name; + } + } + } + return result; + } }; From 42b4443e14030baca6658eb525b1e064041dec4a Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 16 Feb 2024 17:11:40 +0100 Subject: [PATCH 102/139] Add support for multiple physical materials in terrain - one for each painted layer #1112 #2159 --- .../Tools/Terrain/CreateTerrainDialog.cs | 4 - .../Physics/PhysX/PhysicsBackendPhysX.cpp | 46 +- Source/Engine/Physics/PhysicsBackend.h | 16 +- Source/Engine/Physics/PhysicsBackendEmpty.cpp | 11 +- Source/Engine/Terrain/Terrain.cpp | 46 +- Source/Engine/Terrain/Terrain.h | 36 +- Source/Engine/Terrain/TerrainPatch.cpp | 411 +++++++++--------- Source/Engine/Terrain/TerrainPatch.h | 34 +- 8 files changed, 306 insertions(+), 298 deletions(-) diff --git a/Source/Editor/Tools/Terrain/CreateTerrainDialog.cs b/Source/Editor/Tools/Terrain/CreateTerrainDialog.cs index 252891d44..fd28c302b 100644 --- a/Source/Editor/Tools/Terrain/CreateTerrainDialog.cs +++ b/Source/Editor/Tools/Terrain/CreateTerrainDialog.cs @@ -45,9 +45,6 @@ namespace FlaxEditor.Tools.Terrain [EditorOrder(130), EditorDisplay("Layout"), DefaultValue(null), Tooltip("The default material used for terrain rendering (chunks can override this). It must have Domain set to terrain.")] public MaterialBase Material; - [EditorOrder(200), EditorDisplay("Collision"), DefaultValue(null), AssetReference(typeof(PhysicalMaterial), true), Tooltip("Terrain default physical material used to define the collider physical properties.")] - public JsonAsset PhysicalMaterial; - [EditorOrder(210), EditorDisplay("Collision", "Collision LOD"), DefaultValue(-1), Limit(-1, 100, 0.1f), Tooltip("Terrain geometry LOD index used for collision.")] public int CollisionLOD = -1; @@ -152,7 +149,6 @@ namespace FlaxEditor.Tools.Terrain terrain.Setup(_options.LODCount, (int)_options.ChunkSize); terrain.Transform = new Transform(_options.Position, _options.Orientation, _options.Scale); terrain.Material = _options.Material; - terrain.PhysicalMaterial = _options.PhysicalMaterial; terrain.CollisionLOD = _options.CollisionLOD; if (_options.Heightmap) terrain.Position -= new Vector3(0, _options.HeightmapScale * 0.5f, 0); diff --git a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp index f3e7baed1..cb2f3e736 100644 --- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp +++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp @@ -644,6 +644,19 @@ void GetShapeGeometry(const CollisionShape& shape, PxGeometryHolder& geometry) } } +void GetShapeMaterials(Array>& materialsPhysX, Span materials) +{ + materialsPhysX.Resize(materials.Length()); + for (int32 i = 0; i < materials.Length(); i++) + { + PxMaterial* materialPhysX = DefaultMaterial; + const JsonAsset* material = materials.Get()[i]; + if (material && !material->WaitForLoaded() && material->Instance) + materialPhysX = (PxMaterial*)((PhysicalMaterial*)material->Instance)->GetPhysicsMaterial(); + materialsPhysX.Get()[i] = materialPhysX; + } +} + PxFilterFlags FilterShader( PxFilterObjectAttributes attributes0, PxFilterData filterData0, PxFilterObjectAttributes attributes1, PxFilterData filterData1, @@ -2449,17 +2462,14 @@ void PhysicsBackend::AddRigidDynamicActorTorque(void* actor, const Vector3& torq actorPhysX->addTorque(C2P(torque), static_cast(mode)); } -void* PhysicsBackend::CreateShape(PhysicsColliderActor* collider, const CollisionShape& geometry, JsonAsset* material, bool enabled, bool trigger) +void* PhysicsBackend::CreateShape(PhysicsColliderActor* collider, const CollisionShape& geometry, Span materials, bool enabled, bool trigger) { const PxShapeFlags shapeFlags = GetShapeFlags(trigger, enabled); - PxMaterial* materialPhysX = DefaultMaterial; - if (material && !material->WaitForLoaded() && material->Instance) - { - materialPhysX = (PxMaterial*)((PhysicalMaterial*)material->Instance)->GetPhysicsMaterial(); - } + Array> materialsPhysX; + GetShapeMaterials(materialsPhysX, materials); PxGeometryHolder geometryPhysX; GetShapeGeometry(geometry, geometryPhysX); - PxShape* shapePhysX = PhysX->createShape(geometryPhysX.any(), *materialPhysX, true, shapeFlags); + PxShape* shapePhysX = PhysX->createShape(geometryPhysX.any(), materialsPhysX.Get(), materialsPhysX.Count(), true, shapeFlags); shapePhysX->userData = collider; #if PHYSX_DEBUG_NAMING shapePhysX->setName("Shape"); @@ -2549,15 +2559,12 @@ void PhysicsBackend::SetShapeContactOffset(void* shape, float value) shapePhysX->setContactOffset(Math::Max(shapePhysX->getRestOffset() + ZeroTolerance, value)); } -void PhysicsBackend::SetShapeMaterial(void* shape, JsonAsset* material) +void PhysicsBackend::SetShapeMaterials(void* shape, Span materials) { auto shapePhysX = (PxShape*)shape; - PxMaterial* materialPhysX = DefaultMaterial; - if (material && !material->WaitForLoaded() && material->Instance) - { - materialPhysX = (PxMaterial*)((PhysicalMaterial*)material->Instance)->GetPhysicsMaterial(); - } - shapePhysX->setMaterials(&materialPhysX, 1); + Array> materialsPhysX; + GetShapeMaterials(materialsPhysX, materials); + shapePhysX->setMaterials(materialsPhysX.Get(), materialsPhysX.Count()); } void PhysicsBackend::SetShapeGeometry(void* shape, const CollisionShape& geometry) @@ -4077,10 +4084,17 @@ void PhysicsBackend::GetHeightFieldSize(void* heightField, int32& rows, int32& c columns = (int32)heightFieldPhysX->getNbColumns(); } -float PhysicsBackend::GetHeightFieldHeight(void* heightField, float x, float z) +float PhysicsBackend::GetHeightFieldHeight(void* heightField, int32 x, int32 z) { auto heightFieldPhysX = (PxHeightField*)heightField; - return heightFieldPhysX->getHeight(x, z); + return heightFieldPhysX->getHeight((float)x, (float)z); +} + +PhysicsBackend::HeightFieldSample PhysicsBackend::GetHeightFieldSample(void* heightField, int32 x, int32 z) +{ + auto heightFieldPhysX = (PxHeightField*)heightField; + auto sample = heightFieldPhysX->getSample(x, z); + return { sample.height, sample.materialIndex0, sample.materialIndex1 }; } bool PhysicsBackend::ModifyHeightField(void* heightField, int32 startCol, int32 startRow, int32 cols, int32 rows, const HeightFieldSample* data) diff --git a/Source/Engine/Physics/PhysicsBackend.h b/Source/Engine/Physics/PhysicsBackend.h index f20683e31..d7393df2c 100644 --- a/Source/Engine/Physics/PhysicsBackend.h +++ b/Source/Engine/Physics/PhysicsBackend.h @@ -4,6 +4,7 @@ #include "Physics.h" #include "PhysicsSettings.h" +#include "Engine/Core/Types/Span.h" struct HingeJointDrive; struct SpringParameters; @@ -182,7 +183,7 @@ public: static void AddRigidDynamicActorTorque(void* actor, const Vector3& torque, ForceMode mode); // Shapes - static void* CreateShape(PhysicsColliderActor* collider, const CollisionShape& geometry, JsonAsset* material, bool enabled, bool trigger); + static void* CreateShape(PhysicsColliderActor* collider, const CollisionShape& geometry, Span materials, bool enabled, bool trigger); static void SetShapeState(void* shape, bool enabled, bool trigger); static void SetShapeFilterMask(void* shape, uint32 mask0, uint32 mask1); static void* GetShapeActor(void* shape); @@ -191,7 +192,7 @@ public: static void GetShapeLocalPose(void* shape, Vector3& position, Quaternion& orientation); static void SetShapeLocalPose(void* shape, const Vector3& position, const Quaternion& orientation); static void SetShapeContactOffset(void* shape, float value); - static void SetShapeMaterial(void* shape, JsonAsset* material); + static void SetShapeMaterials(void* shape, Span materials); static void SetShapeGeometry(void* shape, const CollisionShape& geometry); static void AttachShape(void* shape, void* actor); static void DetachShape(void* shape, void* actor); @@ -303,7 +304,8 @@ public: static void GetTriangleMeshTriangles(void* triangleMesh, Array& vertexBuffer, Array& indexBuffer); static const uint32* GetTriangleMeshRemap(void* triangleMesh, uint32& count); static void GetHeightFieldSize(void* heightField, int32& rows, int32& columns); - static float GetHeightFieldHeight(void* heightField, float x, float z); + static float GetHeightFieldHeight(void* heightField, int32 x, int32 z); + static HeightFieldSample GetHeightFieldSample(void* heightField, int32 x, int32 z); static bool ModifyHeightField(void* heightField, int32 startCol, int32 startRow, int32 cols, int32 rows, const HeightFieldSample* data); static void FlushRequests(); static void FlushRequests(void* scene); @@ -330,6 +332,14 @@ public: flags = (RigidDynamicFlags)(((uint32)flags & ~(uint32)flag) | (value ? (uint32)flag : 0)); SetRigidDynamicActorFlags(actor, flags); } + FORCE_INLINE static void* CreateShape(PhysicsColliderActor* collider, const CollisionShape& geometry, JsonAsset* material, bool enabled, bool trigger) + { + return CreateShape(collider, geometry, Span(&material, 1), enabled, trigger); + } + FORCE_INLINE static void SetShapeMaterial(void* shape, JsonAsset* material) + { + SetShapeMaterials(shape, Span(&material, 1)); + } }; DECLARE_ENUM_OPERATORS(PhysicsBackend::ActorFlags); diff --git a/Source/Engine/Physics/PhysicsBackendEmpty.cpp b/Source/Engine/Physics/PhysicsBackendEmpty.cpp index 1a418af0c..e26898494 100644 --- a/Source/Engine/Physics/PhysicsBackendEmpty.cpp +++ b/Source/Engine/Physics/PhysicsBackendEmpty.cpp @@ -408,7 +408,7 @@ void PhysicsBackend::AddRigidDynamicActorTorque(void* actor, const Vector3& torq { } -void* PhysicsBackend::CreateShape(PhysicsColliderActor* collider, const CollisionShape& geometry, JsonAsset* material, bool enabled, bool trigger) +void* PhysicsBackend::CreateShape(PhysicsColliderActor* collider, const CollisionShape& geometry, Span materials, bool enabled, bool trigger) { return DUMY_HANDLE; } @@ -447,7 +447,7 @@ void PhysicsBackend::SetShapeContactOffset(void* shape, float value) { } -void PhysicsBackend::SetShapeMaterial(void* shape, JsonAsset* material) +void PhysicsBackend::SetShapeMaterials(void* shape, Span materials) { } @@ -826,11 +826,16 @@ void PhysicsBackend::GetHeightFieldSize(void* heightField, int32& rows, int32& c columns = 0; } -float PhysicsBackend::GetHeightFieldHeight(void* heightField, float x, float z) +float PhysicsBackend::GetHeightFieldHeight(void* heightField, int32 x, int32 z) { return 0.0f; } +PhysicsBackend::HeightFieldSample PhysicsBackend::GetHeightFieldSample(void* heightField, int32 x, int32 z) +{ + return HeightFieldSample(); +} + bool PhysicsBackend::ModifyHeightField(void* heightField, int32 startCol, int32 startRow, int32 cols, int32 rows, const HeightFieldSample* data) { return true; diff --git a/Source/Engine/Terrain/Terrain.cpp b/Source/Engine/Terrain/Terrain.cpp index 772abf4d3..3f71760c3 100644 --- a/Source/Engine/Terrain/Terrain.cpp +++ b/Source/Engine/Terrain/Terrain.cpp @@ -29,7 +29,7 @@ Terrain::Terrain(const SpawnParams& params) , _cachedScale(1.0f) { _drawCategory = SceneRendering::SceneDrawAsync; - PhysicalMaterial.Changed.Bind(this); + _physicalMaterials.Resize(8); } Terrain::~Terrain() @@ -228,22 +228,6 @@ void Terrain::DrawChunk(const RenderContext& renderContext, const Int2& patchCoo } } -void Terrain::OnPhysicalMaterialChanged() -{ - if (_patches.IsEmpty()) - return; - - // Update the shapes material - for (int32 pathIndex = 0; pathIndex < _patches.Count(); pathIndex++) - { - const auto patch = _patches[pathIndex]; - if (patch->HasCollision()) - { - PhysicsBackend::SetShapeMaterial(patch->_physicsShape, PhysicalMaterial); - } - } -} - #if TERRAIN_USE_PHYSICS_DEBUG void Terrain::DrawPhysicsDebug(RenderView& view) @@ -295,6 +279,21 @@ void Terrain::SetCollisionLOD(int32 value) #endif } +void Terrain::SetPhysicalMaterials(const Array, FixedAllocation<8>>& value) +{ + _physicalMaterials = value; + _physicalMaterials.Resize(8); + JsonAsset* materials[8]; + for (int32 i = 0;i<8;i++) + materials[i] = _physicalMaterials[i]; + for (int32 pathIndex = 0; pathIndex < _patches.Count(); pathIndex++) + { + const auto patch = _patches.Get()[pathIndex]; + if (patch->HasCollision()) + PhysicsBackend::SetShapeMaterials(patch->_physicsShape, ToSpan(materials, 8)); + } +} + TerrainPatch* Terrain::GetPatch(const Int2& patchCoord) const { return GetPatch(patchCoord.X, patchCoord.Y); @@ -667,8 +666,8 @@ void Terrain::Serialize(SerializeStream& stream, const void* otherObj) SERIALIZE_MEMBER(ScaleInLightmap, _scaleInLightmap); SERIALIZE_MEMBER(BoundsExtent, _boundsExtent); SERIALIZE_MEMBER(CollisionLOD, _collisionLod); + SERIALIZE_MEMBER(PhysicalMaterials, _physicalMaterials); SERIALIZE(Material); - SERIALIZE(PhysicalMaterial); SERIALIZE(DrawModes); SERIALIZE_MEMBER(LODCount, _lodCount); @@ -714,8 +713,8 @@ void Terrain::Deserialize(DeserializeStream& stream, ISerializeModifier* modifie DESERIALIZE_MEMBER(LODDistribution, _lodDistribution); DESERIALIZE_MEMBER(ScaleInLightmap, _scaleInLightmap); DESERIALIZE_MEMBER(BoundsExtent, _boundsExtent); + DESERIALIZE_MEMBER(PhysicalMaterials, _physicalMaterials); DESERIALIZE(Material); - DESERIALIZE(PhysicalMaterial); DESERIALIZE(DrawModes); member = stream.FindMember("LODCount"); @@ -780,6 +779,15 @@ void Terrain::Deserialize(DeserializeStream& stream, ISerializeModifier* modifie // [Deprecated on 27.04.2022, expires on 27.04.2024] if (modifier->EngineBuild <= 6331) DrawModes |= DrawPass::GlobalSurfaceAtlas; + + // [Deprecated on 15.02.2024, expires on 15.02.2026] + JsonAssetReference PhysicalMaterial; + DESERIALIZE(PhysicalMaterial); + if (PhysicalMaterial) + { + for (auto& e : _physicalMaterials) + e = PhysicalMaterial; + } } RigidBody* Terrain::GetAttachedRigidBody() const diff --git a/Source/Engine/Terrain/Terrain.h b/Source/Engine/Terrain/Terrain.h index 1f495c5ab..aad0e4e1b 100644 --- a/Source/Engine/Terrain/Terrain.h +++ b/Source/Engine/Terrain/Terrain.h @@ -41,13 +41,12 @@ struct RenderView; /// API_CLASS(Sealed) class FLAXENGINE_API Terrain : public PhysicsColliderActor { -DECLARE_SCENE_OBJECT(Terrain); + DECLARE_SCENE_OBJECT(Terrain); friend Terrain; friend TerrainPatch; friend TerrainChunk; private: - char _lodBias; char _forcedLod; char _collisionLod; @@ -60,28 +59,21 @@ private: Float3 _cachedScale; Array> _patches; Array _drawChunks; + Array, FixedAllocation<8>> _physicalMaterials; public: - /// /// Finalizes an instance of the class. /// ~Terrain(); public: - /// /// The default material used for terrain rendering (chunks can override this). /// API_FIELD(Attributes="EditorOrder(100), DefaultValue(null), EditorDisplay(\"Terrain\")") AssetReference Material; - /// - /// The physical material used to define the terrain collider physical properties. - /// - API_FIELD(Attributes="EditorOrder(520), DefaultValue(null), Limit(-1, 100, 0.1f), EditorDisplay(\"Collision\")") - JsonAssetReference<::PhysicalMaterial> PhysicalMaterial; - /// /// The draw passes to use for rendering this object. /// @@ -89,7 +81,6 @@ public: DrawPass DrawModes = DrawPass::Default; public: - /// /// Gets the terrain Level Of Detail bias value. Allows to increase or decrease rendered terrain quality. /// @@ -180,6 +171,21 @@ public: /// API_PROPERTY() void SetCollisionLOD(int32 value); + /// + /// Gets the list with physical materials used to define the terrain collider physical properties - each for terrain layer (layer index matches index in this array). + /// + API_PROPERTY(Attributes="EditorOrder(520), EditorDisplay(\"Collision\"), Collection(MinCount = 8, MaxCount = 8)") + FORCE_INLINE const Array, FixedAllocation<8>>& GetPhysicalMaterials() const + { + return _physicalMaterials; + } + + /// + /// Sets the list with physical materials used to define the terrain collider physical properties - each for terrain layer (layer index matches index in this array). + /// + API_PROPERTY() + void SetPhysicalMaterials(const Array, FixedAllocation<8>>& value); + /// /// Gets the terrain Level Of Detail count. /// @@ -311,7 +317,6 @@ public: #endif public: - #if TERRAIN_EDITING /// @@ -362,7 +367,6 @@ public: void RemoveLightmap(); public: - /// /// Performs a raycast against this terrain collision shape. /// @@ -432,14 +436,11 @@ public: API_FUNCTION() void DrawChunk(API_PARAM(Ref) const RenderContext& renderContext, API_PARAM(Ref) const Int2& patchCoord, API_PARAM(Ref) const Int2& chunkCoord, MaterialBase* material, int32 lodIndex = 0) const; private: - - void OnPhysicalMaterialChanged(); #if TERRAIN_USE_PHYSICS_DEBUG - void DrawPhysicsDebug(RenderView& view); + void DrawPhysicsDebug(RenderView& view); #endif public: - // [PhysicsColliderActor] void Draw(RenderContext& renderContext) override; #if USE_EDITOR @@ -452,7 +453,6 @@ public: RigidBody* GetAttachedRigidBody() const override; protected: - // [PhysicsColliderActor] void OnEnable() override; void OnDisable() override; diff --git a/Source/Engine/Terrain/TerrainPatch.cpp b/Source/Engine/Terrain/TerrainPatch.cpp index 7fa449601..d30f65ec7 100644 --- a/Source/Engine/Terrain/TerrainPatch.cpp +++ b/Source/Engine/Terrain/TerrainPatch.cpp @@ -27,6 +27,9 @@ #include "Engine/ContentImporters/AssetsImportingManager.h" #endif #endif +#if TERRAIN_UPDATING +#include "Engine/Core/Collections/ArrayExtensions.h" +#endif #if USE_EDITOR #include "Engine/Debug/DebugDraw.h" #endif @@ -105,6 +108,7 @@ void TerrainPatch::RemoveLightmap() void TerrainPatch::UpdateBounds() { + PROFILE_CPU(); Chunks[0].UpdateBounds(); _bounds = Chunks[0]._bounds; for (int32 i = 1; i < CHUNKS_COUNT; i++) @@ -116,6 +120,8 @@ void TerrainPatch::UpdateBounds() void TerrainPatch::UpdateTransform() { + PROFILE_CPU(); + // Update physics if (_physicsActor) { @@ -138,8 +144,14 @@ void TerrainPatch::UpdateTransform() #if TERRAIN_EDITING || TERRAIN_UPDATING +bool IsValidMaterial(const JsonAssetReference& e) +{ + return e; +} + struct TerrainDataUpdateInfo { + TerrainPatch* Patch; int32 ChunkSize; int32 VertexCountEdge; int32 HeightmapSize; @@ -147,6 +159,36 @@ struct TerrainDataUpdateInfo int32 TextureSize; float PatchOffset; float PatchHeight; + Color32* SplatMaps[TERRAIN_MAX_SPLATMAPS_COUNT] = {}; + + TerrainDataUpdateInfo(TerrainPatch* patch, float patchOffset = 0.0f, float patchHeight = 1.0f) + : Patch(patch) + , PatchOffset(patchOffset) + , PatchHeight(patchHeight) + { + ChunkSize = patch->GetTerrain()->GetChunkSize(); + VertexCountEdge = ChunkSize + 1; + HeightmapSize = ChunkSize * TerrainPatch::CHUNKS_COUNT_EDGE + 1; + HeightmapLength = HeightmapSize * HeightmapSize; + TextureSize = VertexCountEdge * TerrainPatch::CHUNKS_COUNT_EDGE; + } + + bool UsePhysicalMaterials() const + { + return ArrayExtensions::Any>(Patch->GetTerrain()->GetPhysicalMaterials(), IsValidMaterial); + } + + // When using physical materials, then get splatmaps data required for per-triangle material indices + void GetSplatMaps() + { + if (SplatMaps[0]) + return; + if (UsePhysicalMaterials()) + { + for (int32 i = 0; i < TERRAIN_MAX_SPLATMAPS_COUNT; i++) + SplatMaps[i] = Patch->GetSplatMapData(i); + } + } }; // Shared data container for the terrain data updating shared by the normals and collision generation logic @@ -321,10 +363,9 @@ void UpdateNormalsAndHoles(const TerrainDataUpdateInfo& info, const float* heigh PROFILE_CPU_NAMED("Terrain.CalculateNormals"); // Expand the area for the normals to prevent issues on the edges (for the averaged normals) - const int32 heightMapSize = info.HeightmapSize; const Int2 modifiedEnd = modifiedOffset + modifiedSize; const Int2 normalsStart = Int2::Max(Int2::Zero, modifiedOffset - 1); - const Int2 normalsEnd = Int2::Min(heightMapSize, modifiedEnd + 1); + const Int2 normalsEnd = Int2::Min(info.HeightmapSize, modifiedEnd + 1); const Int2 normalsSize = normalsEnd - normalsStart; // Prepare memory @@ -342,7 +383,7 @@ void UpdateNormalsAndHoles(const TerrainDataUpdateInfo& info, const float* heigh // Get four vertices from the quad #define GET_VERTEX(a, b) \ int32 i##a##b = (z + (b) - normalsStart.Y) * normalsSize.X + (x + (a) - normalsStart.X); \ - int32 h##a##b = (z + (b)) * heightMapSize + (x + (a)); \ + int32 h##a##b = (z + (b)) * info.HeightmapSize + (x + (a)); \ Float3 v##a##b; v##a##b.X = (x + (a)) * TERRAIN_UNITS_PER_VERTEX; \ v##a##b.Y = heightmap[h##a##b]; \ v##a##b.Z = (z + (b)) * TERRAIN_UNITS_PER_VERTEX @@ -428,7 +469,7 @@ void UpdateNormalsAndHoles(const TerrainDataUpdateInfo& info, const float* heigh const int32 dz = chunkHeightmapZ + z - modifiedOffset.Y; if (dz < 0 || dz >= modifiedSize.Y) continue; - const int32 hz = (chunkHeightmapZ + z) * heightMapSize; + const int32 hz = (chunkHeightmapZ + z) * info.HeightmapSize; const int32 sz = (chunkHeightmapZ + z - normalsStart.Y) * normalsSize.X; const int32 tz = (chunkTextureZ + z) * info.TextureSize; @@ -546,13 +587,52 @@ void FixMips(const TerrainDataUpdateInfo& info, TextureBase::InitData* initData, } } -bool CookCollision(const TerrainDataUpdateInfo& info, TextureBase::InitData* initData, int32 collisionLod, Array* collisionData) +FORCE_INLINE byte GetPhysicalMaterial(const Color32& raw, const TerrainDataUpdateInfo& info, int32 chunkZ, int32 chunkX, int32 z, int32 x) +{ + byte result = 0; + if (ReadIsHole(raw)) + { + // Hole + result = (uint8)PhysicsBackend::HeightFieldMaterial::Hole; + } + else if (info.SplatMaps[0]) + { + // Use the layer with the highest influence (splatmap data is Mip0 so convert x/z coords back to LOD0) + uint8 layer = 0; + uint8 layerWeight = 0; + const int32 splatmapTextureIndex = (chunkZ * info.ChunkSize + z) * info.HeightmapSize + chunkX * info.ChunkSize + x; + for (int32 splatIndex = 0; splatIndex < TERRAIN_MAX_SPLATMAPS_COUNT; splatIndex++) + { + for (int32 channelIndex = 0; channelIndex < 4; channelIndex++) + { + // Assume splatmap data pitch matches the row size and shift by channel index to simply sample at R chanel + const Color32* splatmap = (const Color32*)((const byte*)info.SplatMaps[splatIndex] + channelIndex); + const uint8 splat = splatmap[splatmapTextureIndex].R; + if (splat > layerWeight) + { + layer = splatIndex * 4 + channelIndex; + layerWeight = splat; + if (layerWeight == MAX_uint8) + break; + } + } + if (layerWeight == MAX_uint8) + break; + } + result = layer; + } + return result; +} + +bool CookCollision(TerrainDataUpdateInfo& info, TextureBase::InitData* initData, int32 collisionLod, Array* collisionData) { #if COMPILE_WITH_PHYSICS_COOKING + info.GetSplatMaps(); PROFILE_CPU_NAMED("Terrain.CookCollision"); // Prepare data const int32 collisionLOD = Math::Clamp(collisionLod, 0, initData->Mips.Count() - 1); + const int32 collisionLODInv = (int32)Math::Pow(2.0f, (float)collisionLOD); const int32 heightFieldChunkSize = ((info.ChunkSize + 1) >> collisionLOD) - 1; const int32 heightFieldSize = heightFieldChunkSize * TerrainPatch::CHUNKS_COUNT_EDGE + 1; const int32 heightFieldLength = heightFieldSize * heightFieldSize; @@ -562,36 +642,30 @@ bool CookCollision(const TerrainDataUpdateInfo& info, TextureBase::InitData* ini Platform::MemoryClear(heightFieldData, sizeof(PhysicsBackend::HeightFieldSample) * heightFieldLength); // Setup terrain collision information - auto& mip = initData->Mips[collisionLOD]; + const auto& mip = initData->Mips[collisionLOD]; const int32 vertexCountEdgeMip = info.VertexCountEdge >> collisionLOD; const int32 textureSizeMip = info.TextureSize >> collisionLOD; for (int32 chunkX = 0; chunkX < TerrainPatch::CHUNKS_COUNT_EDGE; chunkX++) { const int32 chunkTextureX = chunkX * vertexCountEdgeMip; const int32 chunkStartX = chunkX * heightFieldChunkSize; - for (int32 chunkZ = 0; chunkZ < TerrainPatch::CHUNKS_COUNT_EDGE; chunkZ++) { const int32 chunkTextureZ = chunkZ * vertexCountEdgeMip; const int32 chunkStartZ = chunkZ * heightFieldChunkSize; - for (int32 z = 0; z < vertexCountEdgeMip; z++) { + const int32 heightmapZ = chunkStartZ + z; for (int32 x = 0; x < vertexCountEdgeMip; x++) { - const int32 textureIndex = (chunkTextureZ + z) * textureSizeMip + chunkTextureX + x; - - const Color32 raw = mip.Data.Get()[textureIndex]; - const float normalizedHeight = ReadNormalizedHeight(raw); - const bool isHole = ReadIsHole(raw); - const int32 heightmapX = chunkStartX + x; - const int32 heightmapZ = chunkStartZ + z; + + const int32 textureIndex = (chunkTextureZ + z) * textureSizeMip + chunkTextureX + x; + const Color32 raw = mip.Data.Get()[textureIndex]; + sample.Height = int16(TERRAIN_PATCH_COLLISION_QUANTIZATION * ReadNormalizedHeight(raw)); + sample.MaterialIndex0 = sample.MaterialIndex1 = GetPhysicalMaterial(raw, info, chunkZ, chunkX, z * collisionLODInv, x * collisionLODInv); + const int32 dstIndex = (heightmapX * heightFieldSize) + heightmapZ; - - sample.Height = int16(TERRAIN_PATCH_COLLISION_QUANTIZATION * normalizedHeight); - sample.MaterialIndex0 = sample.MaterialIndex1 = isHole ? (uint8)PhysicsBackend::HeightFieldMaterial::Hole : 0; - heightFieldData[dstIndex] = sample; } } @@ -620,14 +694,16 @@ bool CookCollision(const TerrainDataUpdateInfo& info, TextureBase::InitData* ini #endif } -bool ModifyCollision(const TerrainDataUpdateInfo& info, TextureBase::InitData* initData, int32 collisionLod, const Int2& modifiedOffset, const Int2& modifiedSize, void* heightField) +bool ModifyCollision(TerrainDataUpdateInfo& info, TextureBase::InitData* initData, int32 collisionLod, const Int2& modifiedOffset, const Int2& modifiedSize, void* heightField) { + info.GetSplatMaps(); PROFILE_CPU_NAMED("Terrain.ModifyCollision"); // Prepare data const Vector2 modifiedOffsetRatio((float)modifiedOffset.X / info.HeightmapSize, (float)modifiedOffset.Y / info.HeightmapSize); const Vector2 modifiedSizeRatio((float)modifiedSize.X / info.HeightmapSize, (float)modifiedSize.Y / info.HeightmapSize); const int32 collisionLOD = Math::Clamp(collisionLod, 0, initData->Mips.Count() - 1); + const int32 collisionLODInv = (int32)Math::Pow(2.0f, (float)collisionLOD); const int32 heightFieldChunkSize = ((info.ChunkSize + 1) >> collisionLOD) - 1; const int32 heightFieldSize = heightFieldChunkSize * TerrainPatch::CHUNKS_COUNT_EDGE + 1; const Int2 samplesOffset(Vector2::Floor(modifiedOffsetRatio * (float)heightFieldSize)); @@ -646,56 +722,45 @@ bool ModifyCollision(const TerrainDataUpdateInfo& info, TextureBase::InitData* i Platform::MemoryClear(heightFieldData, sizeof(PhysicsBackend::HeightFieldSample) * heightFieldDataLength); // Setup terrain collision information - auto& mip = initData->Mips[collisionLOD]; + const auto& mip = initData->Mips[collisionLOD]; const int32 vertexCountEdgeMip = info.VertexCountEdge >> collisionLOD; const int32 textureSizeMip = info.TextureSize >> collisionLOD; for (int32 chunkX = 0; chunkX < TerrainPatch::CHUNKS_COUNT_EDGE; chunkX++) { const int32 chunkTextureX = chunkX * vertexCountEdgeMip; const int32 chunkStartX = chunkX * heightFieldChunkSize; - - // Skip unmodified chunks if (chunkStartX >= samplesEnd.X || chunkStartX + vertexCountEdgeMip < samplesOffset.X) - continue; + continue; // Skip unmodified chunks for (int32 chunkZ = 0; chunkZ < TerrainPatch::CHUNKS_COUNT_EDGE; chunkZ++) { const int32 chunkTextureZ = chunkZ * vertexCountEdgeMip; const int32 chunkStartZ = chunkZ * heightFieldChunkSize; - - // Skip unmodified chunks if (chunkStartZ >= samplesEnd.Y || chunkStartZ + vertexCountEdgeMip < samplesOffset.Y) - continue; + continue; // Skip unmodified chunks // TODO: adjust loop range to reduce iterations count for edge cases (skip checking unmodified samples) for (int32 z = 0; z < vertexCountEdgeMip; z++) { - // Skip unmodified columns const int32 heightmapZ = chunkStartZ + z; const int32 heightmapLocalZ = heightmapZ - samplesOffset.Y; if (heightmapLocalZ < 0 || heightmapLocalZ >= samplesSize.Y) - continue; + continue; // Skip unmodified columns // TODO: adjust loop range to reduce iterations count for edge cases (skip checking unmodified samples) for (int32 x = 0; x < vertexCountEdgeMip; x++) { - // Skip unmodified rows const int32 heightmapX = chunkStartX + x; const int32 heightmapLocalX = heightmapX - samplesOffset.X; if (heightmapLocalX < 0 || heightmapLocalX >= samplesSize.X) - continue; + continue; // Skip unmodified rows const int32 textureIndex = (chunkTextureZ + z) * textureSizeMip + chunkTextureX + x; - const Color32 raw = mip.Data.Get()[textureIndex]; - const float normalizedHeight = ReadNormalizedHeight(raw); - const bool isHole = ReadIsHole(raw); + sample.Height = int16(TERRAIN_PATCH_COLLISION_QUANTIZATION * ReadNormalizedHeight(raw)); + sample.MaterialIndex0 = sample.MaterialIndex1 = GetPhysicalMaterial(raw, info, chunkZ, chunkX, z * collisionLODInv, x * collisionLODInv); const int32 dstIndex = (heightmapLocalX * samplesSize.Y) + heightmapLocalZ; - - sample.Height = int16(TERRAIN_PATCH_COLLISION_QUANTIZATION * normalizedHeight); - sample.MaterialIndex0 = sample.MaterialIndex1 = isHole ? (uint8)PhysicsBackend::HeightFieldMaterial::Hole : 0; - heightFieldData[dstIndex] = sample; } } @@ -718,38 +783,23 @@ bool ModifyCollision(const TerrainDataUpdateInfo& info, TextureBase::InitData* i bool TerrainPatch::SetupHeightMap(int32 heightMapLength, const float* heightMap, const byte* holesMask, bool forceUseVirtualStorage) { - // Validate input + PROFILE_CPU_NAMED("Terrain.Setup"); if (heightMap == nullptr) { LOG(Warning, "Cannot create terrain without a heightmap specified."); return true; } - const int32 chunkSize = _terrain->_chunkSize; - const int32 vertexCountEdge = chunkSize + 1; - const int32 heightMapSize = chunkSize * CHUNKS_COUNT_EDGE + 1; - if (heightMapLength != heightMapSize * heightMapSize) + TerrainDataUpdateInfo info(this); + if (heightMapLength != info.HeightmapLength) { - LOG(Warning, "Invalid heightmap length. Terrain of chunk size equal {0} uses heightmap of size {1}x{1} (heightmap array length must be {2}). Input heightmap has length {3}.", chunkSize, heightMapSize, heightMapSize * heightMapSize, heightMapLength); + LOG(Warning, "Invalid heightmap length. Terrain of chunk size equal {0} uses heightmap of size {1}x{1} (heightmap array length must be {2}). Input heightmap has length {3}.", info.ChunkSize, info.HeightmapSize, info.HeightmapLength, heightMapLength); return true; } const PixelFormat pixelFormat = PixelFormat::R8G8B8A8_UNorm; - PROFILE_CPU_NAMED("Terrain.Setup"); - // Input heightmap data overlaps on chunk edges but it needs to be duplicated for chunks (each chunk has own scale-bias for height values normalization) - const int32 textureSize = vertexCountEdge * CHUNKS_COUNT_EDGE; const int32 pixelStride = PixelFormatExtensions::SizeInBytes(pixelFormat); - const int32 lodCount = Math::Min(_terrain->_lodCount, MipLevelsCount(vertexCountEdge) - 2); - - // Setup patch data info - TerrainDataUpdateInfo info; - info.ChunkSize = chunkSize; - info.VertexCountEdge = vertexCountEdge; - info.HeightmapSize = heightMapSize; - info.HeightmapLength = heightMapLength; - info.TextureSize = textureSize; - info.PatchOffset = 0.0f; - info.PatchHeight = 1.0f; + const int32 lodCount = Math::Min(_terrain->_lodCount, MipLevelsCount(info.VertexCountEdge) - 2); // Process heightmap to get per-patch height normalization values float chunkOffsets[CHUNKS_COUNT]; @@ -782,18 +832,17 @@ bool TerrainPatch::SetupHeightMap(int32 heightMapLength, const float* heightMap, // Create heightmap texture data source container auto initData = New(); initData->Format = pixelFormat; - initData->Width = textureSize; - initData->Height = textureSize; + initData->Width = info.TextureSize; + initData->Height = info.TextureSize; initData->ArraySize = 1; initData->Mips.Resize(lodCount); // Allocate top mip data { PROFILE_CPU_NAMED("Terrain.AllocateHeightmap"); - auto& mip = initData->Mips[0]; - mip.RowPitch = textureSize * pixelStride; - mip.SlicePitch = mip.RowPitch * textureSize; + mip.RowPitch = info.TextureSize * pixelStride; + mip.SlicePitch = mip.RowPitch * info.TextureSize; mip.Data.Allocate(mip.SlicePitch); } @@ -945,21 +994,17 @@ bool TerrainPatch::SetupHeightMap(int32 heightMapLength, const float* heightMap, bool TerrainPatch::SetupSplatMap(int32 index, int32 splatMapLength, const Color32* splatMap, bool forceUseVirtualStorage) { + PROFILE_CPU_NAMED("Terrain.SetupSplatMap"); CHECK_RETURN(index >= 0 && index < TERRAIN_MAX_SPLATMAPS_COUNT, true); - - // Validate input if (splatMap == nullptr) { LOG(Warning, "Cannot create terrain without any splatmap specified."); return true; } - const int32 chunkSize = _terrain->_chunkSize; - const int32 vertexCountEdge = chunkSize + 1; - const int32 heightMapSize = chunkSize * CHUNKS_COUNT_EDGE + 1; - const int32 heightMapLength = heightMapSize * heightMapSize; - if (splatMapLength != heightMapLength) + TerrainDataUpdateInfo info(this, _yOffset, _yHeight); + if (splatMapLength != info.HeightmapLength) { - LOG(Warning, "Invalid splatmap length. Terrain of chunk size equal {0} uses heightmap of size {1}x{1} (heightmap array length must be {2}). Input heightmap has length {3}.", chunkSize, heightMapSize, heightMapLength, splatMapLength); + LOG(Warning, "Invalid splatmap length. Terrain of chunk size equal {0} uses heightmap of size {1}x{1} (heightmap array length must be {2}). Input heightmap has length {3}.", info.ChunkSize, info.HeightmapSize, info.HeightmapLength, splatMapLength); return true; } const PixelFormat pixelFormat = PixelFormat::R8G8B8A8_UNorm; @@ -974,22 +1019,9 @@ bool TerrainPatch::SetupSplatMap(int32 index, int32 splatMapLength, const Color3 } } - PROFILE_CPU_NAMED("Terrain.SetupSplatMap"); - // Input splatmap data overlaps on chunk edges but it needs to be duplicated for chunks - const int32 textureSize = vertexCountEdge * CHUNKS_COUNT_EDGE; const int32 pixelStride = PixelFormatExtensions::SizeInBytes(pixelFormat); - const int32 lodCount = Math::Min(_terrain->_lodCount, MipLevelsCount(vertexCountEdge) - 2); - - // Setup patch data info - TerrainDataUpdateInfo info; - info.ChunkSize = chunkSize; - info.VertexCountEdge = vertexCountEdge; - info.HeightmapSize = heightMapSize; - info.HeightmapLength = heightMapLength; - info.TextureSize = textureSize; - info.PatchOffset = _yOffset; - info.PatchHeight = _yHeight; + const int32 lodCount = Math::Min(_terrain->_lodCount, MipLevelsCount(info.VertexCountEdge) - 2); // Prepare #if USE_EDITOR @@ -1016,18 +1048,17 @@ bool TerrainPatch::SetupSplatMap(int32 index, int32 splatMapLength, const Color3 // Create heightmap texture data source container auto initData = New(); initData->Format = pixelFormat; - initData->Width = textureSize; - initData->Height = textureSize; + initData->Width = info.TextureSize; + initData->Height = info.TextureSize; initData->ArraySize = 1; initData->Mips.Resize(lodCount); // Allocate top mip data { PROFILE_CPU_NAMED("Terrain.AllocateSplatmap"); - auto& mip = initData->Mips[0]; - mip.RowPitch = textureSize * pixelStride; - mip.SlicePitch = mip.RowPitch * textureSize; + mip.RowPitch = info.TextureSize * pixelStride; + mip.SlicePitch = mip.RowPitch * info.TextureSize; mip.Data.Allocate(mip.SlicePitch); } @@ -1112,8 +1143,6 @@ bool TerrainPatch::SetupSplatMap(int32 index, int32 splatMapLength, const Color3 bool TerrainPatch::InitializeHeightMap() { PROFILE_CPU_NAMED("Terrain.InitializeHeightMap"); - - // Initialize with flat heightmap data const auto heightmapSize = _terrain->GetChunkSize() * TerrainPatch::CHUNKS_COUNT_EDGE + 1; Array heightmap; heightmap.Resize(heightmapSize * heightmapSize); @@ -1179,6 +1208,7 @@ void TerrainPatch::ClearCache() void TerrainPatch::CacheHeightData() { PROFILE_CPU_NAMED("Terrain.CacheHeightData"); + const TerrainDataUpdateInfo info(this); // Ensure that heightmap data is all loaded // TODO: disable streaming for heightmap texture if it's being modified by the editor @@ -1198,16 +1228,9 @@ void TerrainPatch::CacheHeightData() return; } - // Get texture input (note: this must match Setup method) - const int32 chunkSize = _terrain->_chunkSize; - const int32 vertexCountEdge = chunkSize + 1; - const int32 textureSize = vertexCountEdge * CHUNKS_COUNT_EDGE; - const int32 heightMapSize = chunkSize * CHUNKS_COUNT_EDGE + 1; - // Allocate data - const int32 heightMapLength = heightMapSize * heightMapSize; - _cachedHeightMap.Resize(heightMapLength); - _cachedHolesMask.Resize(heightMapLength); + _cachedHeightMap.Resize(info.HeightmapLength); + _cachedHolesMask.Resize(info.HeightmapLength); _wasHeightModified = false; // Extract heightmap data and denormalize it to get the pure height field @@ -1217,18 +1240,18 @@ void TerrainPatch::CacheHeightData() const auto holesMaskPtr = _cachedHolesMask.Get(); for (int32 chunkIndex = 0; chunkIndex < CHUNKS_COUNT; chunkIndex++) { - const int32 chunkTextureX = Chunks[chunkIndex]._x * vertexCountEdge; - const int32 chunkTextureZ = Chunks[chunkIndex]._z * vertexCountEdge; + const int32 chunkTextureX = Chunks[chunkIndex]._x * info.VertexCountEdge; + const int32 chunkTextureZ = Chunks[chunkIndex]._z * info.VertexCountEdge; - const int32 chunkHeightmapX = Chunks[chunkIndex]._x * chunkSize; - const int32 chunkHeightmapZ = Chunks[chunkIndex]._z * chunkSize; + const int32 chunkHeightmapX = Chunks[chunkIndex]._x * info.ChunkSize; + const int32 chunkHeightmapZ = Chunks[chunkIndex]._z * info.ChunkSize; - for (int32 z = 0; z < vertexCountEdge; z++) + for (int32 z = 0; z < info.VertexCountEdge; z++) { - const int32 tz = (chunkTextureZ + z) * textureSize; - const int32 sz = (chunkHeightmapZ + z) * heightMapSize; + const int32 tz = (chunkTextureZ + z) * info.TextureSize; + const int32 sz = (chunkHeightmapZ + z) * info.HeightmapSize; - for (int32 x = 0; x < vertexCountEdge; x++) + for (int32 x = 0; x < info.VertexCountEdge; x++) { const int32 tx = chunkTextureX + x; const int32 sx = chunkHeightmapX + x; @@ -1249,18 +1272,14 @@ void TerrainPatch::CacheHeightData() void TerrainPatch::CacheSplatData() { - // Prepare - const int32 chunkSize = _terrain->_chunkSize; - const int32 vertexCountEdge = chunkSize + 1; - const int32 textureSize = vertexCountEdge * CHUNKS_COUNT_EDGE; - const int32 heightMapSize = chunkSize * CHUNKS_COUNT_EDGE + 1; - const int32 heightMapLength = heightMapSize * heightMapSize; + PROFILE_CPU_NAMED("Terrain.CacheSplatData"); + const TerrainDataUpdateInfo info(this); // Cache all the splatmaps for (int32 index = 0; index < TERRAIN_MAX_SPLATMAPS_COUNT; index++) { // Allocate data - _cachedSplatMap[index].Resize(heightMapLength); + _cachedSplatMap[index].Resize(info.HeightmapLength); _wasSplatmapModified[index] = false; // Skip if has missing splatmap asset @@ -1272,8 +1291,6 @@ void TerrainPatch::CacheSplatData() continue; } - PROFILE_CPU_NAMED("Terrain.CacheSplatData"); - // Ensure that splatmap data is all loaded // TODO: disable streaming for heightmap texture if it's being modified by the editor if (Splatmap[index]->WaitForLoaded()) @@ -1296,18 +1313,18 @@ void TerrainPatch::CacheSplatData() const auto splatMapPtr = static_cast(_cachedSplatMap[index].Get()); for (int32 chunkIndex = 0; chunkIndex < CHUNKS_COUNT; chunkIndex++) { - const int32 chunkTextureX = Chunks[chunkIndex]._x * vertexCountEdge; - const int32 chunkTextureZ = Chunks[chunkIndex]._z * vertexCountEdge; + const int32 chunkTextureX = Chunks[chunkIndex]._x * info.VertexCountEdge; + const int32 chunkTextureZ = Chunks[chunkIndex]._z * info.VertexCountEdge; - const int32 chunkHeightmapX = Chunks[chunkIndex]._x * chunkSize; - const int32 chunkHeightmapZ = Chunks[chunkIndex]._z * chunkSize; + const int32 chunkHeightmapX = Chunks[chunkIndex]._x * info.ChunkSize; + const int32 chunkHeightmapZ = Chunks[chunkIndex]._z * info.ChunkSize; - for (int32 z = 0; z < vertexCountEdge; z++) + for (int32 z = 0; z < info.VertexCountEdge; z++) { - const int32 tz = (chunkTextureZ + z) * textureSize; - const int32 sz = (chunkHeightmapZ + z) * heightMapSize; + const int32 tz = (chunkTextureZ + z) * info.TextureSize; + const int32 sz = (chunkHeightmapZ + z) * info.HeightmapSize; - for (int32 x = 0; x < vertexCountEdge; x++) + for (int32 x = 0; x < info.VertexCountEdge; x++) { const int32 tx = chunkTextureX + x; const int32 sx = chunkHeightmapX + x; @@ -1324,9 +1341,7 @@ void TerrainPatch::CacheSplatData() bool TerrainPatch::ModifyHeightMap(const float* samples, const Int2& modifiedOffset, const Int2& modifiedSize) { // Validate input samples range - const int32 chunkSize = _terrain->_chunkSize; - const int32 vertexCountEdge = chunkSize + 1; - const int32 heightMapSize = chunkSize * CHUNKS_COUNT_EDGE + 1; + TerrainDataUpdateInfo info(this); if (samples == nullptr) { LOG(Warning, "Missing heightmap samples data."); @@ -1334,13 +1349,12 @@ bool TerrainPatch::ModifyHeightMap(const float* samples, const Int2& modifiedOff } if (modifiedOffset.X < 0 || modifiedOffset.Y < 0 || modifiedSize.X <= 0 || modifiedSize.Y <= 0 || - modifiedOffset.X + modifiedSize.X > heightMapSize || - modifiedOffset.Y + modifiedSize.Y > heightMapSize) + modifiedOffset.X + modifiedSize.X > info.HeightmapSize || + modifiedOffset.Y + modifiedSize.Y > info.HeightmapSize) { LOG(Warning, "Invalid heightmap samples range."); return true; } - PROFILE_CPU_NAMED("Terrain.ModifyHeightMap"); // Check if has no heightmap @@ -1364,28 +1378,17 @@ bool TerrainPatch::ModifyHeightMap(const float* samples, const Int2& modifiedOff // Modify heightmap data { PROFILE_CPU_NAMED("Terrain.WrtieCache"); - for (int32 z = 0; z < modifiedSize.Y; z++) { // TODO: use batches row mem copy for (int32 x = 0; x < modifiedSize.X; x++) { - heightMap[(z + modifiedOffset.Y) * heightMapSize + (x + modifiedOffset.X)] = samples[z * modifiedSize.X + x]; + heightMap[(z + modifiedOffset.Y) * info.HeightmapSize + (x + modifiedOffset.X)] = samples[z * modifiedSize.X + x]; } } } - // Setup patch data info - TerrainDataUpdateInfo info; - info.ChunkSize = chunkSize; - info.VertexCountEdge = vertexCountEdge; - info.HeightmapSize = heightMapSize; - info.HeightmapLength = heightMapSize * heightMapSize; - info.TextureSize = vertexCountEdge * CHUNKS_COUNT_EDGE; - info.PatchOffset = 0.0f; - info.PatchHeight = 1.0f; - // Process heightmap to get per-patch height normalization values float chunkOffsets[CHUNKS_COUNT]; float chunkHeights[CHUNKS_COUNT]; @@ -1426,15 +1429,13 @@ bool TerrainPatch::ModifyHeightMap(const float* samples, const Int2& modifiedOff chunk.UpdateTransform(); } _terrain->UpdateBounds(); - return UpdateHeightData(info, modifiedOffset, modifiedSize, wasHeightRangeChanged); + return UpdateHeightData(info, modifiedOffset, modifiedSize, wasHeightRangeChanged, true); } bool TerrainPatch::ModifyHolesMask(const byte* samples, const Int2& modifiedOffset, const Int2& modifiedSize) { // Validate input samples range - const int32 chunkSize = _terrain->_chunkSize; - const int32 vertexCountEdge = chunkSize + 1; - const int32 heightMapSize = chunkSize * CHUNKS_COUNT_EDGE + 1; + TerrainDataUpdateInfo info(this, _yOffset, _yHeight); if (samples == nullptr) { LOG(Warning, "Missing holes mask samples data."); @@ -1442,13 +1443,12 @@ bool TerrainPatch::ModifyHolesMask(const byte* samples, const Int2& modifiedOffs } if (modifiedOffset.X < 0 || modifiedOffset.Y < 0 || modifiedSize.X <= 0 || modifiedSize.Y <= 0 || - modifiedOffset.X + modifiedSize.X > heightMapSize || - modifiedOffset.Y + modifiedSize.Y > heightMapSize) + modifiedOffset.X + modifiedSize.X > info.HeightmapSize || + modifiedOffset.Y + modifiedSize.Y > info.HeightmapSize) { LOG(Warning, "Invalid holes mask samples range."); return true; } - PROFILE_CPU_NAMED("Terrain.ModifyHolesMask"); // Check if has no heightmap @@ -1472,28 +1472,17 @@ bool TerrainPatch::ModifyHolesMask(const byte* samples, const Int2& modifiedOffs // Modify holes mask data { PROFILE_CPU_NAMED("Terrain.WrtieCache"); - for (int32 z = 0; z < modifiedSize.Y; z++) { // TODO: use batches row mem copy for (int32 x = 0; x < modifiedSize.X; x++) { - holesMask[(z + modifiedOffset.Y) * heightMapSize + (x + modifiedOffset.X)] = samples[z * modifiedSize.X + x]; + holesMask[(z + modifiedOffset.Y) * info.HeightmapSize + (x + modifiedOffset.X)] = samples[z * modifiedSize.X + x]; } } } - // Setup patch data info - TerrainDataUpdateInfo info; - info.ChunkSize = chunkSize; - info.VertexCountEdge = vertexCountEdge; - info.HeightmapSize = heightMapSize; - info.HeightmapLength = heightMapSize * heightMapSize; - info.TextureSize = vertexCountEdge * CHUNKS_COUNT_EDGE; - info.PatchOffset = _yOffset; - info.PatchHeight = _yHeight; - // Check if has allocated texture if (_dataHeightmap) { @@ -1505,7 +1494,7 @@ bool TerrainPatch::ModifyHolesMask(const byte* samples, const Int2& modifiedOffs } // Update all the stuff - return UpdateHeightData(info, modifiedOffset, modifiedSize, false); + return UpdateHeightData(info, modifiedOffset, modifiedSize, false, true); } bool TerrainPatch::ModifySplatMap(int32 index, const Color32* samples, const Int2& modifiedOffset, const Int2& modifiedSize) @@ -1523,9 +1512,7 @@ bool TerrainPatch::ModifySplatMap(int32 index, const Color32* samples, const Int } // Validate input samples range - const int32 chunkSize = _terrain->_chunkSize; - const int32 vertexCountEdge = chunkSize + 1; - const int32 heightMapSize = chunkSize * CHUNKS_COUNT_EDGE + 1; + TerrainDataUpdateInfo info(this, _yOffset, _yHeight); if (samples == nullptr) { LOG(Warning, "Missing splatmap samples data."); @@ -1533,13 +1520,12 @@ bool TerrainPatch::ModifySplatMap(int32 index, const Color32* samples, const Int } if (modifiedOffset.X < 0 || modifiedOffset.Y < 0 || modifiedSize.X <= 0 || modifiedSize.Y <= 0 || - modifiedOffset.X + modifiedSize.X > heightMapSize || - modifiedOffset.Y + modifiedSize.Y > heightMapSize) + modifiedOffset.X + modifiedSize.X > info.HeightmapSize || + modifiedOffset.Y + modifiedSize.Y > info.HeightmapSize) { LOG(Warning, "Invalid heightmap samples range."); return true; } - PROFILE_CPU_NAMED("Terrain.ModifySplatMap"); // Get the current data to modify it @@ -1552,14 +1538,13 @@ bool TerrainPatch::ModifySplatMap(int32 index, const Color32* samples, const Int // Modify splat map data { PROFILE_CPU_NAMED("Terrain.WrtieCache"); - for (int32 z = 0; z < modifiedSize.Y; z++) { // TODO: use batches row mem copy for (int32 x = 0; x < modifiedSize.X; x++) { - splatMap[(z + modifiedOffset.Y) * heightMapSize + (x + modifiedOffset.X)] = samples[z * modifiedSize.X + x]; + splatMap[(z + modifiedOffset.Y) * info.HeightmapSize + (x + modifiedOffset.X)] = samples[z * modifiedSize.X + x]; } } } @@ -1570,7 +1555,6 @@ bool TerrainPatch::ModifySplatMap(int32 index, const Color32* samples, const Int if (dataSplatmap == nullptr) { PROFILE_CPU_NAMED("Terrain.InitDataStorage"); - if (Heightmap->WaitForLoaded()) { LOG(Error, "Failed to load heightmap."); @@ -1597,16 +1581,6 @@ bool TerrainPatch::ModifySplatMap(int32 index, const Color32* samples, const Int mip.Data.Allocate(mip.SlicePitch); } - // Setup patch data info - TerrainDataUpdateInfo info; - info.ChunkSize = chunkSize; - info.VertexCountEdge = vertexCountEdge; - info.HeightmapSize = heightMapSize; - info.HeightmapLength = heightMapSize * heightMapSize; - info.TextureSize = vertexCountEdge * CHUNKS_COUNT_EDGE; - info.PatchOffset = _yOffset; - info.PatchHeight = _yHeight; - // Update splat map storage data const bool hasSplatmap = splatmap; const auto splatmapData = dataSplatmap->Mips[0].Data.Get(); @@ -1712,12 +1686,18 @@ bool TerrainPatch::ModifySplatMap(int32 index, const Color32* samples, const Int // TODO: disable splatmap dynamic streaming - data on a GPU was modified and we don't want to override it with the old data stored in the asset container + // Update heightfield to reflect physical materials layering + if (info.UsePhysicalMaterials() && HasCollision()) + { + UpdateHeightData(info, modifiedOffset, modifiedSize, false, false); + } + return false; } -bool TerrainPatch::UpdateHeightData(const TerrainDataUpdateInfo& info, const Int2& modifiedOffset, const Int2& modifiedSize, bool wasHeightRangeChanged) +bool TerrainPatch::UpdateHeightData(TerrainDataUpdateInfo& info, const Int2& modifiedOffset, const Int2& modifiedSize, bool wasHeightRangeChanged, bool wasHeightChanged) { - // Prepare + PROFILE_CPU(); float* heightMap = GetHeightmapData(); byte* holesMask = GetHolesMaskData(); ASSERT(heightMap && holesMask); @@ -1753,9 +1733,7 @@ bool TerrainPatch::UpdateHeightData(const TerrainDataUpdateInfo& info, const Int // Downscale mip data for all lower LODs if (GenerateMips(_dataHeightmap)) - { return true; - } // Fix generated mip maps to keep the same values for chunk edges (reduce cracks on continuous LOD transitions) FixMips(info, _dataHeightmap, pixelStride); @@ -1779,9 +1757,7 @@ bool TerrainPatch::UpdateHeightData(const TerrainDataUpdateInfo& info, const Int } const auto collisionData = &_heightfield->Data; if (CookCollision(info, _dataHeightmap, _terrain->_collisionLod, collisionData)) - { return true; - } UpdateCollision(); } else @@ -1789,7 +1765,8 @@ bool TerrainPatch::UpdateHeightData(const TerrainDataUpdateInfo& info, const Int ScopeLock lock(_collisionLocker); if (ModifyCollision(info, _dataHeightmap, _terrain->_collisionLod, modifiedOffset, modifiedSize, _physicsHeightField)) return true; - UpdateCollisionScale(); + if (wasHeightChanged) + UpdateCollisionScale(); } #else // Modify heightfield samples (without cooking collision which is done on a separate async task) @@ -1811,6 +1788,9 @@ bool TerrainPatch::UpdateHeightData(const TerrainDataUpdateInfo& info, const Int } #endif + if (!wasHeightChanged) + return false; + // Invalidate cache #if TERRAIN_USE_PHYSICS_DEBUG _debugLines.Resize(0); @@ -1843,18 +1823,8 @@ void TerrainPatch::SaveHeightData() { return; } - PROFILE_CPU_NAMED("Terrain.Save"); - - // Setup patch data info - TerrainDataUpdateInfo info; - info.ChunkSize = _terrain->_chunkSize; - info.VertexCountEdge = info.ChunkSize + 1; - info.HeightmapSize = info.ChunkSize * CHUNKS_COUNT_EDGE + 1; - info.HeightmapLength = info.HeightmapSize * info.HeightmapSize; - info.TextureSize = info.VertexCountEdge * CHUNKS_COUNT_EDGE; - info.PatchOffset = _yOffset; - info.PatchHeight = _yHeight; + TerrainDataUpdateInfo info(this, _yOffset, _yHeight); // Save heightmap to asset if (Heightmap->WaitForLoaded()) @@ -1913,7 +1883,6 @@ void TerrainPatch::SaveSplatData(int32 index) { return; } - PROFILE_CPU_NAMED("Terrain.Save"); // Save splatmap to asset @@ -1937,6 +1906,7 @@ void TerrainPatch::SaveSplatData(int32 index) bool TerrainPatch::UpdateCollision() { + PROFILE_CPU(); ScopeLock lock(_collisionLocker); // Update collision @@ -2111,6 +2081,7 @@ void TerrainPatch::UpdatePostManualDeserialization() void TerrainPatch::CreateCollision() { + PROFILE_CPU(); ASSERT(!HasCollision()); if (CreateHeightField()) return; @@ -2125,7 +2096,10 @@ void TerrainPatch::CreateCollision() shape.SetHeightField(_physicsHeightField, heightScale, rowScale, columnScale); // Create shape - _physicsShape = PhysicsBackend::CreateShape(_terrain, shape, _terrain->PhysicalMaterial, _terrain->IsActiveInHierarchy(), false); + JsonAsset* materials[8]; + for (int32 i = 0;i<8;i++) + materials[i] = _terrain->GetPhysicalMaterials()[i]; + _physicsShape = PhysicsBackend::CreateShape(_terrain, shape, ToSpan(materials, 8), _terrain->IsActiveInHierarchy(), false); PhysicsBackend::SetShapeLocalPose(_physicsShape, Vector3(0, _yOffset * terrainTransform.Scale.Y, 0), Quaternion::Identity); // Create static actor @@ -2137,6 +2111,7 @@ void TerrainPatch::CreateCollision() bool TerrainPatch::CreateHeightField() { + PROFILE_CPU(); ASSERT(_physicsHeightField == nullptr); // Skip if height field data is missing but warn on loading failed @@ -2162,6 +2137,7 @@ bool TerrainPatch::CreateHeightField() void TerrainPatch::UpdateCollisionScale() const { + PROFILE_CPU(); ASSERT(HasCollision()); // Create geometry @@ -2179,6 +2155,7 @@ void TerrainPatch::UpdateCollisionScale() const void TerrainPatch::DestroyCollision() { + PROFILE_CPU(); ScopeLock lock(_collisionLocker); ASSERT(HasCollision()); @@ -2205,6 +2182,7 @@ void TerrainPatch::DestroyCollision() void TerrainPatch::CacheDebugLines() { + PROFILE_CPU(); ASSERT(_debugLines.IsEmpty() && _physicsHeightField); int32 rows, cols; @@ -2213,12 +2191,21 @@ void TerrainPatch::CacheDebugLines() _debugLines.Resize((rows - 1) * (cols - 1) * 6 + (cols + rows - 2) * 2); Vector3* data = _debugLines.Get(); -#define GET_VERTEX(x, y) const Vector3 v##x##y((float)(row + (x)), PhysicsBackend::GetHeightFieldHeight(_physicsHeightField, (float)(row + (x)), (float)(col + (y))) / TERRAIN_PATCH_COLLISION_QUANTIZATION, (float)(col + (y))) +#define GET_VERTEX(x, y) const Vector3 v##x##y((float)(row + (x)), PhysicsBackend::GetHeightFieldHeight(_physicsHeightField, row + (x), col + (y)) / TERRAIN_PATCH_COLLISION_QUANTIZATION, (float)(col + (y))) for (int32 row = 0; row < rows - 1; row++) { for (int32 col = 0; col < cols - 1; col++) { + // Skip holes + const auto sample = PhysicsBackend::GetHeightFieldSample(_physicsHeightField, row, col); + if (sample.MaterialIndex0 == (uint8)PhysicsBackend::HeightFieldMaterial::Hole) + { + for (int32 i = 0; i < 6; i++) + *data++ = Vector3::Zero; + continue; + } + GET_VERTEX(0, 0); GET_VERTEX(0, 1); GET_VERTEX(1, 0); @@ -2294,6 +2281,7 @@ const Array& TerrainPatch::GetCollisionTriangles() ScopeLock lock(_collisionLocker); if (!_physicsShape || _collisionTriangles.HasItems()) return _collisionTriangles; + PROFILE_CPU(); int32 rows, cols; PhysicsBackend::GetHeightFieldSize(_physicsHeightField, rows, cols); @@ -2301,7 +2289,7 @@ const Array& TerrainPatch::GetCollisionTriangles() _collisionTriangles.Resize((rows - 1) * (cols - 1) * 6); Vector3* data = _collisionTriangles.Get(); -#define GET_VERTEX(x, y) Vector3 v##x##y((float)(row + (x)), PhysicsBackend::GetHeightFieldHeight(_physicsHeightField, (float)(row + (x)), (float)(col + (y))) / TERRAIN_PATCH_COLLISION_QUANTIZATION, (float)(col + (y))); Vector3::Transform(v##x##y, world, v##x##y) +#define GET_VERTEX(x, y) Vector3 v##x##y((float)(row + (x)), PhysicsBackend::GetHeightFieldHeight(_physicsHeightField, row + (x), col + (y)) / TERRAIN_PATCH_COLLISION_QUANTIZATION, (float)(col + (y))); Vector3::Transform(v##x##y, world, v##x##y) const float size = _terrain->_chunkSize * TERRAIN_UNITS_PER_VERTEX * CHUNKS_COUNT_EDGE; const Transform terrainTransform = _terrain->_transform; @@ -2312,6 +2300,15 @@ const Array& TerrainPatch::GetCollisionTriangles() { for (int32 col = 0; col < cols - 1; col++) { + // Skip holes + const auto sample = PhysicsBackend::GetHeightFieldSample(_physicsHeightField, row, col); + if (sample.MaterialIndex0 == (uint8)PhysicsBackend::HeightFieldMaterial::Hole) + { + for (int32 i = 0; i < 6; i++) + *data++ = Vector3::Zero; + continue; + } + GET_VERTEX(0, 0); GET_VERTEX(0, 1); GET_VERTEX(1, 0); @@ -2334,6 +2331,7 @@ const Array& TerrainPatch::GetCollisionTriangles() void TerrainPatch::GetCollisionTriangles(const BoundingSphere& bounds, Array& result) { + PROFILE_CPU(); result.Clear(); // Skip if no intersection with patch @@ -2430,6 +2428,7 @@ void TerrainPatch::GetCollisionTriangles(const BoundingSphere& bounds, Array& vertexBuffer, Array& indexBuffer) { + PROFILE_CPU(); vertexBuffer.Clear(); indexBuffer.Clear(); @@ -2459,7 +2458,7 @@ void TerrainPatch::ExtractCollisionGeometry(Array& vertexBuffer, Array _debugLines; // TODO: large-worlds + Array _debugLines; // TODO: large-worlds #endif #if USE_EDITOR Array _collisionTriangles; // TODO: large-worlds @@ -62,14 +60,12 @@ private: void Init(Terrain* terrain, int16 x, int16 z); public: - /// /// Finalizes an instance of the class. /// ~TerrainPatch(); public: - /// /// The chunks contained within the patch. Organized in 4x4 square. /// @@ -86,11 +82,9 @@ public: AssetReference Splatmap[TERRAIN_MAX_SPLATMAPS_COUNT]; public: - /// /// Gets the Y axis heightmap offset from terrain origin. /// - /// The offset. FORCE_INLINE float GetOffsetY() const { return _yOffset; @@ -99,7 +93,6 @@ public: /// /// Gets the Y axis heightmap height. /// - /// The height. FORCE_INLINE float GetHeightY() const { return _yHeight; @@ -108,7 +101,6 @@ public: /// /// Gets the x coordinate. /// - /// The x position. FORCE_INLINE int32 GetX() const { return _x; @@ -117,7 +109,6 @@ public: /// /// Gets the z coordinate. /// - /// The z position. FORCE_INLINE int32 GetZ() const { return _z; @@ -126,7 +117,6 @@ public: /// /// Gets the terrain. /// - /// The terrain, FORCE_INLINE Terrain* GetTerrain() const { return _terrain; @@ -168,14 +158,12 @@ public: /// /// Gets the patch world bounds. /// - /// The bounding box. FORCE_INLINE const BoundingBox& GetBounds() const { return _bounds; } public: - /// /// Removes the lightmap data from the terrain patch. /// @@ -192,7 +180,6 @@ public: void UpdateTransform(); #if TERRAIN_EDITING - /// /// Initializes the patch heightmap and collision to the default flat level. /// @@ -218,11 +205,9 @@ public: /// If set to true patch will use virtual storage by force. Otherwise it can use normal texture asset storage on drive (valid only during Editor). Runtime-created terrain can only use virtual storage (in RAM). /// True if failed, otherwise false. bool SetupSplatMap(int32 index, int32 splatMapLength, const Color32* splatMap, bool forceUseVirtualStorage = false); - #endif #if TERRAIN_UPDATING - /// /// Gets the raw pointer to the heightmap data. /// @@ -291,18 +276,15 @@ public: bool ModifySplatMap(int32 index, const Color32* samples, const Int2& modifiedOffset, const Int2& modifiedSize); private: - - bool UpdateHeightData(const struct TerrainDataUpdateInfo& info, const Int2& modifiedOffset, const Int2& modifiedSize, bool wasHeightRangeChanged); + bool UpdateHeightData(struct TerrainDataUpdateInfo& info, const Int2& modifiedOffset, const Int2& modifiedSize, bool wasHeightRangeChanged, bool wasHeightChanged); void SaveHeightData(); void CacheHeightData(); void SaveSplatData(); void SaveSplatData(int32 index); void CacheSplatData(); - #endif public: - /// /// Performs a raycast against this terrain collision shape. /// @@ -353,18 +335,14 @@ public: void ClosestPoint(const Vector3& position, Vector3& result) const; #if USE_EDITOR - /// /// Updates the patch data after manual deserialization called at runtime (eg. by editor undo). /// void UpdatePostManualDeserialization(); - #endif public: - #if USE_EDITOR - /// /// Gets the collision mesh triangles array (3 vertices per triangle in linear list). Cached internally to reuse data. /// @@ -377,7 +355,6 @@ public: /// The world-space bounds to find terrain triangles that intersect with it. /// The result triangles that intersect with the given bounds (in world-space). void GetCollisionTriangles(const BoundingSphere& bounds, Array& result); - #endif /// @@ -388,7 +365,6 @@ public: void ExtractCollisionGeometry(Array& vertexBuffer, Array& indexBuffer); private: - /// /// Determines whether this patch has created collision representation. /// @@ -419,8 +395,8 @@ private: void DestroyCollision(); #if TERRAIN_USE_PHYSICS_DEBUG - void CacheDebugLines(); - void DrawPhysicsDebug(RenderView& view); + void CacheDebugLines(); + void DrawPhysicsDebug(RenderView& view); #endif /// @@ -430,8 +406,8 @@ private: bool UpdateCollision(); void OnPhysicsSceneChanged(PhysicsScene* previous); -public: +public: // [ISerializable] void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; From 85f291071878701cd6b56ea8076cf3e717911eac Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 16 Feb 2024 17:19:51 +0100 Subject: [PATCH 103/139] Move collider shape raycasting utilities to the `PhysicsColliderActor` class --- .../Physics/Actors/PhysicsColliderActor.h | 35 +++++++++++++++++ Source/Engine/Physics/Colliders/Collider.h | 38 ++----------------- .../Physics/PhysX/PhysicsBackendPhysX.cpp | 1 + .../PhysX/SimulationEventCallbackPhysX.cpp | 9 ++--- Source/Engine/Terrain/Terrain.cpp | 11 ++++-- Source/Engine/Terrain/Terrain.h | 31 ++------------- 6 files changed, 55 insertions(+), 70 deletions(-) diff --git a/Source/Engine/Physics/Actors/PhysicsColliderActor.h b/Source/Engine/Physics/Actors/PhysicsColliderActor.h index caea76c79..92693695d 100644 --- a/Source/Engine/Physics/Actors/PhysicsColliderActor.h +++ b/Source/Engine/Physics/Actors/PhysicsColliderActor.h @@ -5,6 +5,7 @@ #include "Engine/Level/Actor.h" #include "Engine/Physics/Collisions.h" +struct RayCastHit; struct Collision; /// @@ -42,6 +43,40 @@ public: /// The rigid body or null. API_PROPERTY() virtual RigidBody* GetAttachedRigidBody() const = 0; + /// + /// Performs a raycast against this collider shape. + /// + /// The origin of the ray. + /// The normalized direction of the ray. + /// The raycast result hit position distance from the ray origin. Valid only if raycast hits anything. + /// The maximum distance the ray should check for collisions. + /// True if ray hits an object, otherwise false. + API_FUNCTION(Sealed) virtual bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) float& resultHitDistance, float maxDistance = MAX_float) const = 0; + + /// + /// Performs a raycast against this collider, returns results in a RaycastHit structure. + /// + /// The origin of the ray. + /// The normalized direction of the ray. + /// The result hit information. Valid only when method returns true. + /// The maximum distance the ray should check for collisions. + /// True if ray hits an object, otherwise false. + API_FUNCTION(Sealed) virtual bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) RayCastHit& hitInfo, float maxDistance = MAX_float) const = 0; + + /// + /// Gets a point on the collider that is closest to a given location. Can be used to find a hit location or position to apply explosion force or any other special effects. + /// + /// The position to find the closest point to it. + /// The result point on the collider that is closest to the specified location. + API_FUNCTION(Sealed) virtual void ClosestPoint(const Vector3& point, API_PARAM(Out) Vector3& result) const = 0; + + /// + /// Checks if a point is inside the collider. + /// + /// The point to check if is contained by the collider shape (in world-space). + /// True if collider shape contains a given point, otherwise false. + API_FUNCTION(Sealed) virtual bool ContainsPoint(const Vector3& point) const = 0; + public: /// /// Called when a collision start gets registered for this collider (it collides with something). diff --git a/Source/Engine/Physics/Colliders/Collider.h b/Source/Engine/Physics/Colliders/Collider.h index cbbb7e522..68f5fe346 100644 --- a/Source/Engine/Physics/Colliders/Collider.h +++ b/Source/Engine/Physics/Colliders/Collider.h @@ -84,40 +84,6 @@ public: JsonAssetReference Material; public: - /// - /// Performs a raycast against this collider shape. - /// - /// The origin of the ray. - /// The normalized direction of the ray. - /// The raycast result hit position distance from the ray origin. Valid only if raycast hits anything. - /// The maximum distance the ray should check for collisions. - /// True if ray hits an object, otherwise false. - API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) float& resultHitDistance, float maxDistance = MAX_float) const; - - /// - /// Performs a raycast against this collider, returns results in a RaycastHit structure. - /// - /// The origin of the ray. - /// The normalized direction of the ray. - /// The result hit information. Valid only when method returns true. - /// The maximum distance the ray should check for collisions. - /// True if ray hits an object, otherwise false. - API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) RayCastHit& hitInfo, float maxDistance = MAX_float) const; - - /// - /// Gets a point on the collider that is closest to a given location. Can be used to find a hit location or position to apply explosion force or any other special effects. - /// - /// The position to find the closest point to it. - /// The result point on the collider that is closest to the specified location. - API_FUNCTION() void ClosestPoint(const Vector3& point, API_PARAM(Out) Vector3& result) const; - - /// - /// Checks if a point is inside the collider. - /// - /// The point to check if is contained by the collider shape (in world-space). - /// True if collider shape contains a given point, otherwise false. - API_FUNCTION() bool ContainsPoint(const Vector3& point) const; - /// /// Computes minimum translational distance between two geometry objects. /// Translating the first collider by direction * distance will separate the colliders apart if the function returned true. Otherwise, direction and distance are not defined. @@ -198,6 +164,10 @@ private: public: // [PhysicsColliderActor] RigidBody* GetAttachedRigidBody() const override; + bool RayCast(const Vector3& origin, const Vector3& direction, float& resultHitDistance, float maxDistance = MAX_float) const final; + bool RayCast(const Vector3& origin, const Vector3& direction, RayCastHit& hitInfo, float maxDistance = MAX_float) const final; + void ClosestPoint(const Vector3& point, Vector3& result) const final; + bool ContainsPoint(const Vector3& point) const final; protected: // [PhysicsColliderActor] diff --git a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp index cb2f3e736..9d0d2ea16 100644 --- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp +++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp @@ -2630,6 +2630,7 @@ bool PhysicsBackend::RayCastShape(void* shape, const Vector3& position, const Qu PxRaycastHit hit; if (PxGeometryQuery::raycast(C2P(origin - sceneOrigin), C2P(direction), shapePhysX->getGeometry(), trans, maxDistance, SCENE_QUERY_FLAGS, 1, &hit) == 0) return false; + hit.shape = shapePhysX; P2C(hit, hitInfo); hitInfo.Point += sceneOrigin; return true; diff --git a/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp b/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp index fc33b0dfe..62e588771 100644 --- a/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp +++ b/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp @@ -163,14 +163,11 @@ void SimulationEventCallback::onContact(const PxContactPairHeader& pairHeader, c c.ThisVelocity = c.OtherVelocity = Vector3::Zero; if (hasPostVelocities && j.nextItemSet()) { - ASSERT(j.contactPairIndex == pairIndex); + ASSERT_LOW_LAYER(j.contactPairIndex == pairIndex); if (j.postSolverVelocity) { - const PxVec3 linearVelocityActor0 = j.postSolverVelocity->linearVelocity[0]; - const PxVec3 linearVelocityActor1 = j.postSolverVelocity->linearVelocity[1]; - - c.ThisVelocity = P2C(linearVelocityActor0); - c.OtherVelocity = P2C(linearVelocityActor1); + c.ThisVelocity = P2C(j.postSolverVelocity->linearVelocity[0]); + c.OtherVelocity = P2C(j.postSolverVelocity->linearVelocity[1]); } } diff --git a/Source/Engine/Terrain/Terrain.cpp b/Source/Engine/Terrain/Terrain.cpp index 3f71760c3..497e9bf80 100644 --- a/Source/Engine/Terrain/Terrain.cpp +++ b/Source/Engine/Terrain/Terrain.cpp @@ -185,7 +185,7 @@ bool Terrain::RayCast(const Vector3& origin, const Vector3& direction, RayCastHi return result; } -void Terrain::ClosestPoint(const Vector3& position, Vector3& result) const +void Terrain::ClosestPoint(const Vector3& point, Vector3& result) const { Real minDistance = MAX_Real; Vector3 tmp; @@ -194,8 +194,8 @@ void Terrain::ClosestPoint(const Vector3& position, Vector3& result) const const auto patch = _patches[pathIndex]; if (patch->HasCollision()) { - patch->ClosestPoint(position, tmp); - const auto distance = Vector3::DistanceSquared(position, tmp); + patch->ClosestPoint(point, tmp); + const auto distance = Vector3::DistanceSquared(point, tmp); if (distance < minDistance) { minDistance = distance; @@ -205,6 +205,11 @@ void Terrain::ClosestPoint(const Vector3& position, Vector3& result) const } } +bool Terrain::ContainsPoint(const Vector3& point) const +{ + return false; +} + void Terrain::DrawPatch(const RenderContext& renderContext, const Int2& patchCoord, MaterialBase* material, int32 lodIndex) const { auto patch = GetPatch(patchCoord); diff --git a/Source/Engine/Terrain/Terrain.h b/Source/Engine/Terrain/Terrain.h index aad0e4e1b..dd9dc1926 100644 --- a/Source/Engine/Terrain/Terrain.h +++ b/Source/Engine/Terrain/Terrain.h @@ -367,16 +367,6 @@ public: void RemoveLightmap(); public: - /// - /// Performs a raycast against this terrain collision shape. - /// - /// The origin of the ray. - /// The normalized direction of the ray. - /// The raycast result hit position distance from the ray origin. Valid only if raycast hits anything. - /// The maximum distance the ray should check for collisions. - /// True if ray hits an object, otherwise false. - API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, float& resultHitDistance, float maxDistance = MAX_float) const; - /// /// Performs a raycast against this terrain collision shape. Returns the hit chunk. /// @@ -399,23 +389,6 @@ public: /// True if ray hits an object, otherwise false. API_FUNCTION() bool RayCast(const Ray& ray, API_PARAM(Out) float& resultHitDistance, API_PARAM(Out) Int2& resultPatchCoord, API_PARAM(Out) Int2& resultChunkCoord, float maxDistance = MAX_float) const; - /// - /// Performs a raycast against terrain collision, returns results in a RayCastHit structure. - /// - /// The origin of the ray. - /// The normalized direction of the ray. - /// The result hit information. Valid only when method returns true. - /// The maximum distance the ray should check for collisions. - /// True if ray hits an object, otherwise false. - API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) RayCastHit& hitInfo, float maxDistance = MAX_float) const; - - /// - /// Gets a point on the terrain collider that is closest to a given location. Can be used to find a hit location or position to apply explosion force or any other special effects. - /// - /// The position to find the closest point to it. - /// The result point on the collider that is closest to the specified location. - API_FUNCTION() void ClosestPoint(const Vector3& position, API_PARAM(Out) Vector3& result) const; - /// /// Draws the terrain patch. /// @@ -451,6 +424,10 @@ public: void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; RigidBody* GetAttachedRigidBody() const override; + bool RayCast(const Vector3& origin, const Vector3& direction, float& resultHitDistance, float maxDistance = MAX_float) const final; + bool RayCast(const Vector3& origin, const Vector3& direction, RayCastHit& hitInfo, float maxDistance = MAX_float) const final; + void ClosestPoint(const Vector3& point, Vector3& result) const final; + bool ContainsPoint(const Vector3& point) const final; protected: // [PhysicsColliderActor] From c140cc4e7c755c0ce432041d0fc70a5fc8738903 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 16 Feb 2024 17:39:35 +0100 Subject: [PATCH 104/139] Add `Material` to `RayCastHit` for surface detection logic --- Source/Engine/Physics/PhysX/Types.h | 18 ++++++++++++++++++ Source/Engine/Physics/PhysicalMaterial.h | 5 +++-- Source/Engine/Physics/Types.h | 8 +++++++- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Physics/PhysX/Types.h b/Source/Engine/Physics/PhysX/Types.h index d95d6b140..c86a03a62 100644 --- a/Source/Engine/Physics/PhysX/Types.h +++ b/Source/Engine/Physics/PhysX/Types.h @@ -17,6 +17,7 @@ #include #include #include +#include #include namespace physx @@ -233,12 +234,28 @@ inline float RadPerSToRpm(float v) return v * (30.0f / PI); } +inline PhysicalMaterial* GetMaterial(const PxShape* shape, PxU32 faceIndex) +{ + if (faceIndex != 0xFFFFffff) + { + PxBaseMaterial* mat = shape->getMaterialFromInternalFaceIndex(faceIndex); + return mat ? (PhysicalMaterial*)mat->userData : nullptr; + } + else + { + PxMaterial* mat; + shape->getMaterials(&mat, 1); + return mat ? (PhysicalMaterial*)mat->userData : nullptr; + } +} + inline void P2C(const PxRaycastHit& hit, RayCastHit& result) { result.Point = P2C(hit.position); result.Normal = P2C(hit.normal); result.Distance = hit.distance; result.Collider = hit.shape ? static_cast(hit.shape->userData) : nullptr; + result.Material = hit.shape ? GetMaterial(hit.shape, hit.faceIndex) : nullptr; result.FaceIndex = hit.faceIndex; result.UV.X = hit.u; result.UV.Y = hit.v; @@ -250,6 +267,7 @@ inline void P2C(const PxSweepHit& hit, RayCastHit& result) result.Normal = P2C(hit.normal); result.Distance = hit.distance; result.Collider = hit.shape ? static_cast(hit.shape->userData) : nullptr; + result.Material = hit.shape ? GetMaterial(hit.shape, hit.faceIndex) : nullptr; result.FaceIndex = hit.faceIndex; result.UV = Vector2::Zero; } diff --git a/Source/Engine/Physics/PhysicalMaterial.h b/Source/Engine/Physics/PhysicalMaterial.h index f1aa0c1fd..cb8202876 100644 --- a/Source/Engine/Physics/PhysicalMaterial.h +++ b/Source/Engine/Physics/PhysicalMaterial.h @@ -4,16 +4,17 @@ #include "Types.h" #include "Engine/Core/ISerializable.h" +#include "Engine/Scripting/ScriptingObject.h" #include "Engine/Level/Tags.h" /// /// Physical materials are used to define the response of a physical object when interacting dynamically with the world. /// API_CLASS(Attributes = "ContentContextMenu(\"New/Physics/Physical Material\")") -class FLAXENGINE_API PhysicalMaterial final : public ISerializable +class FLAXENGINE_API PhysicalMaterial final : public ScriptingObject, public ISerializable { API_AUTO_SERIALIZATION(); - DECLARE_SCRIPTING_TYPE_MINIMAL(PhysicalMaterial); + DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(PhysicalMaterial, ScriptingObject); private: void* _material = nullptr; diff --git a/Source/Engine/Physics/Types.h b/Source/Engine/Physics/Types.h index 1631f694b..f7cb9f89c 100644 --- a/Source/Engine/Physics/Types.h +++ b/Source/Engine/Physics/Types.h @@ -10,6 +10,7 @@ struct PhysicsStatistics; class PhysicsColliderActor; class PhysicsScene; +class PhysicalMaterial; class Joint; class Collider; class CollisionData; @@ -132,7 +133,7 @@ DECLARE_ENUM_OPERATORS(RigidbodyConstraints); /// /// Raycast hit result data. /// -API_STRUCT() struct RayCastHit +API_STRUCT(NoDefault) struct RayCastHit { DECLARE_SCRIPTING_TYPE_NO_SPAWN(RayCastHit); @@ -141,6 +142,11 @@ API_STRUCT() struct RayCastHit /// API_FIELD() PhysicsColliderActor* Collider = nullptr; + /// + /// The physical material of the surface that was hit. + /// + API_FIELD() PhysicalMaterial* Material = nullptr; + /// /// The normal of the surface the ray hit. /// From 12f7370caffe4893f8d25744ea47a8cb5fd1e0e3 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 16 Feb 2024 23:16:18 +0100 Subject: [PATCH 105/139] Fix missing forward type decl --- Source/Engine/Terrain/Terrain.h | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Engine/Terrain/Terrain.h b/Source/Engine/Terrain/Terrain.h index dd9dc1926..b5bf0ae5c 100644 --- a/Source/Engine/Terrain/Terrain.h +++ b/Source/Engine/Terrain/Terrain.h @@ -10,6 +10,7 @@ class Terrain; class TerrainChunk; class TerrainPatch; class TerrainManager; +class PhysicalMaterial; struct RayCastHit; struct RenderView; From c3faabaa0f241863754f8bbd8b4a357b84440fdc Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 16 Feb 2024 23:29:01 +0100 Subject: [PATCH 106/139] Fix game build --- Source/Engine/Terrain/TerrainPatch.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Terrain/TerrainPatch.cpp b/Source/Engine/Terrain/TerrainPatch.cpp index d30f65ec7..aa369f2ed 100644 --- a/Source/Engine/Terrain/TerrainPatch.cpp +++ b/Source/Engine/Terrain/TerrainPatch.cpp @@ -27,7 +27,7 @@ #include "Engine/ContentImporters/AssetsImportingManager.h" #endif #endif -#if TERRAIN_UPDATING +#if TERRAIN_EDITING || TERRAIN_UPDATING #include "Engine/Core/Collections/ArrayExtensions.h" #endif #if USE_EDITOR @@ -181,6 +181,7 @@ struct TerrainDataUpdateInfo // When using physical materials, then get splatmaps data required for per-triangle material indices void GetSplatMaps() { +#if TERRAIN_UPDATING if (SplatMaps[0]) return; if (UsePhysicalMaterials()) @@ -188,6 +189,9 @@ struct TerrainDataUpdateInfo for (int32 i = 0; i < TERRAIN_MAX_SPLATMAPS_COUNT; i++) SplatMaps[i] = Patch->GetSplatMapData(i); } +#else + LOG(Warning, "Splatmaps reading not implemented for physical layers updating."); +#endif } }; From b931020e5c97aab4385e11104d72d4de6bf54518 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 16 Feb 2024 23:32:46 +0100 Subject: [PATCH 107/139] Enable terrain updating in cooked build #2068 --- Source/Engine/Terrain/Terrain.h | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Source/Engine/Terrain/Terrain.h b/Source/Engine/Terrain/Terrain.h index b5bf0ae5c..7db9b8500 100644 --- a/Source/Engine/Terrain/Terrain.h +++ b/Source/Engine/Terrain/Terrain.h @@ -24,10 +24,7 @@ struct RenderView; #define TERRAIN_EDITING 1 // Enable/disable terrain heightmap samples modification and gather. Used by the editor to modify the terrain with the brushes. -#define TERRAIN_UPDATING (USE_EDITOR) - -// Enable/disable precise terrain geometry collision testing (with in-build vertex buffer caching, this will increase memory usage) -#define USE_PRECISE_TERRAIN_INTERSECTS (USE_EDITOR) +#define TERRAIN_UPDATING 1 // Enable/disable terrain physics collision drawing #define TERRAIN_USE_PHYSICS_DEBUG (USE_EDITOR && 1) From 09532acf29f4ddf2175ab132deff39d5e76eb52e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 17 Feb 2024 07:12:52 +0100 Subject: [PATCH 108/139] Fix compilation with Clang --- Source/Engine/Content/JsonAssetReference.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Content/JsonAssetReference.h b/Source/Engine/Content/JsonAssetReference.h index 965a0aaa0..201d0b3a3 100644 --- a/Source/Engine/Content/JsonAssetReference.h +++ b/Source/Engine/Content/JsonAssetReference.h @@ -25,7 +25,7 @@ API_STRUCT(NoDefault, Template, MarshalAs=JsonAsset*) struct JsonAssetReference /// The asset instance object or null. FORCE_INLINE T* GetInstance() const { - return _asset ? Get()->GetInstance() : nullptr; + return _asset ? Get()->template GetInstance() : nullptr; } JsonAssetReference& operator=(JsonAsset* asset) noexcept From 368dac5e4b1cae13c1487015227b93f724b4e6f4 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 17 Feb 2024 11:24:56 +0100 Subject: [PATCH 109/139] Simplify splatmap data management #1739 --- .../Tools/Terrain/PaintTerrainGizmoMode.cs | 30 +++++-------------- 1 file changed, 7 insertions(+), 23 deletions(-) diff --git a/Source/Editor/Tools/Terrain/PaintTerrainGizmoMode.cs b/Source/Editor/Tools/Terrain/PaintTerrainGizmoMode.cs index 05aaa48b5..4e7925dd9 100644 --- a/Source/Editor/Tools/Terrain/PaintTerrainGizmoMode.cs +++ b/Source/Editor/Tools/Terrain/PaintTerrainGizmoMode.cs @@ -36,35 +36,26 @@ namespace FlaxEditor.Tools.Terrain "Layer 7", }; - private class SplatmapData + private struct SplatmapData { - public IntPtr DataPtr { get; set; } = IntPtr.Zero; - public int Size { get; set; } = 0; - - public SplatmapData(int size) - { - EnsureCapacity(size); - } + public IntPtr DataPtr; + public int Size; public void EnsureCapacity(int size) { if (Size < size) { if (DataPtr != IntPtr.Zero) - { Marshal.FreeHGlobal(DataPtr); - } DataPtr = Marshal.AllocHGlobal(size); Size = size; } } - /// public void Free() { if (DataPtr == IntPtr.Zero) return; - Marshal.FreeHGlobal(DataPtr); DataPtr = IntPtr.Zero; Size = 0; @@ -72,7 +63,7 @@ namespace FlaxEditor.Tools.Terrain } private EditTerrainMapAction _activeAction; - private List _cachedSplatmapData = new(); + private SplatmapData[] _cachedSplatmapData = new SplatmapData[2]; /// /// The terrain painting gizmo. @@ -268,14 +259,9 @@ namespace FlaxEditor.Tools.Terrain /// The allocated memory using interface. public IntPtr GetSplatmapTempBuffer(int size, int splatmapIndex) { - if (_cachedSplatmapData.Count <= splatmapIndex) - { - _cachedSplatmapData.Add(new SplatmapData(size)); - return _cachedSplatmapData[splatmapIndex].DataPtr; - } - - _cachedSplatmapData[splatmapIndex].EnsureCapacity(size); - return _cachedSplatmapData[splatmapIndex].DataPtr; + ref var splatmapData = ref _cachedSplatmapData[splatmapIndex]; + splatmapData.EnsureCapacity(size); + return splatmapData.DataPtr; } /// @@ -309,9 +295,7 @@ namespace FlaxEditor.Tools.Terrain // Free temporary memory buffer foreach (var splatmapData in _cachedSplatmapData) - { splatmapData.Free(); - } } /// From 0cf39c9f8d78ce2efec36ed51f73a514078575dc Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 17 Feb 2024 11:36:13 +0100 Subject: [PATCH 110/139] Further improve terrain painting logic #1739r --- Source/Editor/Tools/Terrain/Paint/Mode.cs | 6 +-- .../Tools/Terrain/Paint/SingleLayerMode.cs | 39 +++++++++++-------- Source/Engine/Core/Math/Color.cs | 15 +++++++ 3 files changed, 39 insertions(+), 21 deletions(-) diff --git a/Source/Editor/Tools/Terrain/Paint/Mode.cs b/Source/Editor/Tools/Terrain/Paint/Mode.cs index bf9857bea..03e4a4690 100644 --- a/Source/Editor/Tools/Terrain/Paint/Mode.cs +++ b/Source/Editor/Tools/Terrain/Paint/Mode.cs @@ -209,8 +209,7 @@ namespace FlaxEditor.Tools.Terrain.Paint public int SplatmapIndex; /// - /// The splatmap texture index. If is 0, this will be 1. - /// If is 1, this will be 0. + /// The splatmap texture index. If is 0, this will be 1. If is 1, this will be 0. /// public int SplatmapIndexOther; @@ -220,8 +219,7 @@ namespace FlaxEditor.Tools.Terrain.Paint public Color32* TempBuffer; /// - /// The 'other" temporary data buffer (for modified data). If refers - /// to the splatmap with index 0, this one will refer to the one with index 1. + /// The 'other' temporary data buffer (for modified data). If refersto the splatmap with index 0, this one will refer to the one with index 1. /// public Color32* TempBufferOther; diff --git a/Source/Editor/Tools/Terrain/Paint/SingleLayerMode.cs b/Source/Editor/Tools/Terrain/Paint/SingleLayerMode.cs index d0c76a830..5921f7d10 100644 --- a/Source/Editor/Tools/Terrain/Paint/SingleLayerMode.cs +++ b/Source/Editor/Tools/Terrain/Paint/SingleLayerMode.cs @@ -76,6 +76,7 @@ namespace FlaxEditor.Tools.Terrain.Paint // Apply brush modification Profiler.BeginEvent("Apply Brush"); + bool otherModified = false; for (int z = 0; z < p.ModifiedSize.Y; z++) { var zz = z + p.ModifiedOffset.Y; @@ -86,34 +87,38 @@ namespace FlaxEditor.Tools.Terrain.Paint var samplePositionLocal = p.PatchPositionLocal + new Vector3(xx * FlaxEngine.Terrain.UnitsPerVertex, 0, zz * FlaxEngine.Terrain.UnitsPerVertex); Vector3.Transform(ref samplePositionLocal, ref p.TerrainWorld, out Vector3 samplePositionWorld); + var sample = Mathf.Saturate(p.Brush.Sample(ref brushPosition, ref samplePositionWorld)); + + var paintAmount = sample * strength; + if (paintAmount < 0.0f) + continue; // Skip when pixel won't be affected - var sample = Mathf.Clamp(p.Brush.Sample(ref brushPosition, ref samplePositionWorld), 0f, 1f); - var paintAmount = sample * strength * (1f - src[c]); - // Paint on the active splatmap texture - src[c] = Mathf.Clamp(src[c] + paintAmount, 0, 1f); - src[(c + 1) % 4] = Mathf.Clamp(src[(c + 1) % 4] - paintAmount, 0, 1f); - src[(c + 2) % 4] = Mathf.Clamp(src[(c + 2) % 4] - paintAmount, 0, 1f); - src[(c + 3) % 4] = Mathf.Clamp(src[(c + 3) % 4] - paintAmount, 0, 1f); - + src[c] = Mathf.Saturate(src[c] + paintAmount); + src[(c + 1) % 4] = Mathf.Saturate(src[(c + 1) % 4] - paintAmount); + src[(c + 2) % 4] = Mathf.Saturate(src[(c + 2) % 4] - paintAmount); + src[(c + 3) % 4] = Mathf.Saturate(src[(c + 3) % 4] - paintAmount); p.TempBuffer[z * p.ModifiedSize.X + x] = src; - // Remove 'paint' from the other splatmap texture var other = (Color)p.SourceDataOther[zz * p.HeightmapSize + xx]; - - other[c] = Mathf.Clamp(other[c] - paintAmount, 0, 1f); - other[(c + 1) % 4] = Mathf.Clamp(other[(c + 1) % 4] - paintAmount, 0, 1f); - other[(c + 2) % 4] = Mathf.Clamp(other[(c + 2) % 4] - paintAmount, 0, 1f); - other[(c + 3) % 4] = Mathf.Clamp(other[(c + 3) % 4] - paintAmount, 0, 1f); - - p.TempBufferOther[z * p.ModifiedSize.X + x] = other; + //if (other.ValuesSum > 0.0f) // Skip editing the other splatmap if it's empty + { + // Remove 'paint' from the other splatmap texture + other[c] = Mathf.Saturate(other[c] - paintAmount); + other[(c + 1) % 4] = Mathf.Saturate(other[(c + 1) % 4] - paintAmount); + other[(c + 2) % 4] = Mathf.Saturate(other[(c + 2) % 4] - paintAmount); + other[(c + 3) % 4] = Mathf.Saturate(other[(c + 3) % 4] - paintAmount); + p.TempBufferOther[z * p.ModifiedSize.X + x] = other; + otherModified = true; + } } } Profiler.EndEvent(); // Update terrain patch TerrainTools.ModifySplatMap(p.Terrain, ref p.PatchCoord, p.SplatmapIndex, p.TempBuffer, ref p.ModifiedOffset, ref p.ModifiedSize); - TerrainTools.ModifySplatMap(p.Terrain, ref p.PatchCoord, p.SplatmapIndexOther, p.TempBufferOther, ref p.ModifiedOffset, ref p.ModifiedSize); + if (otherModified) + TerrainTools.ModifySplatMap(p.Terrain, ref p.PatchCoord, p.SplatmapIndexOther, p.TempBufferOther, ref p.ModifiedOffset, ref p.ModifiedSize); } } } diff --git a/Source/Engine/Core/Math/Color.cs b/Source/Engine/Core/Math/Color.cs index d585177a0..7474b913d 100644 --- a/Source/Engine/Core/Math/Color.cs +++ b/Source/Engine/Core/Math/Color.cs @@ -92,6 +92,21 @@ namespace FlaxEngine /// public float MaxColorComponent => Mathf.Max(Mathf.Max(R, G), B); + /// + /// Gets a minimum component value (max of r,g,b,a). + /// + public float MinValue => Math.Min(R, Math.Min(G, Math.Min(B, A))); + + /// + /// Gets a maximum component value (min of r,g,b,a). + /// + public float MaxValue => Math.Max(R, Math.Max(G, Math.Max(B, A))); + + /// + /// Gets a sum of the component values. + /// + public float ValuesSum => R + G + B + A; + /// /// Constructs a new Color with given r,g,b,a component. /// From 37dfdad7e2d45403ded63e37026c0c3cfe8d8222 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 17 Feb 2024 20:22:40 +0100 Subject: [PATCH 111/139] Minor improvements to character controller #1935 --- Source/Engine/Physics/Colliders/CharacterController.cpp | 8 ++------ Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/Source/Engine/Physics/Colliders/CharacterController.cpp b/Source/Engine/Physics/Colliders/CharacterController.cpp index 38ab0394d..e9f86ebab 100644 --- a/Source/Engine/Physics/Colliders/CharacterController.cpp +++ b/Source/Engine/Physics/Colliders/CharacterController.cpp @@ -280,12 +280,8 @@ void CharacterController::OnActiveTransformChanged() // Change actor transform (but with locking) ASSERT(!_isUpdatingTransform); _isUpdatingTransform = true; - Transform transform; - PhysicsBackend::GetRigidActorPose(PhysicsBackend::GetShapeActor(_shape), transform.Translation, transform.Orientation); - transform.Translation -= _center; - transform.Orientation = _transform.Orientation; - transform.Scale = _transform.Scale; - SetTransform(transform); + const Vector3 position = PhysicsBackend::GetControllerPosition(_controller) - _center; + SetPosition(position); _isUpdatingTransform = false; UpdateBounds(); diff --git a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp index 9d0d2ea16..75c088db7 100644 --- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp +++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp @@ -3008,7 +3008,7 @@ void* PhysicsBackend::CreateController(void* scene, IPhysicsActor* actor, Physic desc.material = DefaultMaterial; const float minSize = 0.001f; desc.height = Math::Max(height, minSize); - desc.radius = Math::Max(radius - desc.contactOffset, minSize); + desc.radius = Math::Max(radius - Math::Max(contactOffset, 0.0f), minSize); desc.stepOffset = Math::Min(stepOffset, desc.height + desc.radius * 2.0f - minSize); auto controllerPhysX = (PxCapsuleController*)scenePhysX->ControllerManager->createController(desc); PxRigidActor* actorPhysX = controllerPhysX->getActor(); From c305bed829f8a973337e054b87b1af898ff371f3 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 17 Feb 2024 23:42:57 +0100 Subject: [PATCH 112/139] Add sub-groups expanded state restoring in Properties window #1049 --- .../CustomEditors/Dedicated/ScriptsEditor.cs | 10 ++-- .../CustomEditors/LayoutElementsContainer.cs | 60 ++++++++++++++----- Source/Editor/Modules/ProjectCacheModule.cs | 39 ++++++------ 3 files changed, 67 insertions(+), 42 deletions(-) diff --git a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs index f783822a5..e1a358b6e 100644 --- a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs @@ -591,14 +591,14 @@ namespace FlaxEditor.CustomEditors.Dedicated var group = layout.Group(title, editor); if ((Presenter.Features & FeatureFlags.CacheExpandedGroups) != 0) { - if (Editor.Instance.ProjectCache.IsCollapsedGroup(title)) - group.Panel.Close(false); + if (Editor.Instance.ProjectCache.IsGroupToggled(title)) + group.Panel.Close(); else - group.Panel.Open(false); - group.Panel.IsClosedChanged += panel => Editor.Instance.ProjectCache.SetCollapsedGroup(panel.HeaderText, panel.IsClosed); + group.Panel.Open(); + group.Panel.IsClosedChanged += panel => Editor.Instance.ProjectCache.SetGroupToggle(panel.HeaderText, panel.IsClosed); } else - group.Panel.Open(false); + group.Panel.Open(); // Customize group.Panel.TooltipText = Editor.Instance.CodeDocs.GetTooltip(scriptType); diff --git a/Source/Editor/CustomEditors/LayoutElementsContainer.cs b/Source/Editor/CustomEditors/LayoutElementsContainer.cs index 936851b15..e9a501bec 100644 --- a/Source/Editor/CustomEditors/LayoutElementsContainer.cs +++ b/Source/Editor/CustomEditors/LayoutElementsContainer.cs @@ -25,6 +25,11 @@ namespace FlaxEditor.CustomEditors /// internal bool isRootGroup = true; + /// + /// Parent container who created this one. + /// + internal LayoutElementsContainer _parent; + /// /// The children. /// @@ -40,6 +45,24 @@ namespace FlaxEditor.CustomEditors /// public abstract ContainerControl ContainerControl { get; } + /// + /// Gets the Custom Editors layout presenter. + /// + internal CustomEditorPresenter Presenter + { + get + { + CustomEditorPresenter result; + var container = this; + do + { + result = container as CustomEditorPresenter; + container = container._parent; + } while (container != null); + return result; + } + } + /// /// Adds new group element. /// @@ -81,17 +104,31 @@ namespace FlaxEditor.CustomEditors public GroupElement Group(string title, bool useTransparentHeader = false) { var element = new GroupElement(); - if (!isRootGroup) + var presenter = Presenter; + var isSubGroup = !isRootGroup; + if (isSubGroup) + element.Panel.Close(); + if (presenter != null && (presenter.Features & FeatureFlags.CacheExpandedGroups) != 0) { - element.Panel.Close(false); - } - else if (this is CustomEditorPresenter presenter && (presenter.Features & FeatureFlags.CacheExpandedGroups) != 0) - { - if (Editor.Instance.ProjectCache.IsCollapsedGroup(title)) - element.Panel.Close(false); - element.Panel.IsClosedChanged += OnPanelIsClosedChanged; + // Build group identifier (made of path from group titles) + var expandPath = title; + var container = this; + while (container != null && !(container is CustomEditorPresenter)) + { + if (container.ContainerControl is DropPanel dropPanel) + expandPath = dropPanel.HeaderText + "/" + expandPath; + container = container._parent; + } + + // Caching/restoring expanded groups (non-root groups cache expanded state so invert boolean expression) + if (Editor.Instance.ProjectCache.IsGroupToggled(expandPath) ^ isSubGroup) + element.Panel.Close(); + else + element.Panel.Open(); + element.Panel.IsClosedChanged += panel => Editor.Instance.ProjectCache.SetGroupToggle(expandPath, panel.IsClosed ^ isSubGroup); } element.isRootGroup = false; + element._parent = this; element.Panel.HeaderText = title; if (useTransparentHeader) { @@ -103,11 +140,6 @@ namespace FlaxEditor.CustomEditors return element; } - private void OnPanelIsClosedChanged(DropPanel panel) - { - Editor.Instance.ProjectCache.SetCollapsedGroup(panel.HeaderText, panel.IsClosed); - } - /// /// Adds new horizontal panel element. /// @@ -627,7 +659,6 @@ namespace FlaxEditor.CustomEditors if (style == DisplayStyle.Group) { var group = Group(name, editor, true); - group.Panel.Close(false); group.Panel.TooltipText = tooltip; return group.Object(values, editor); } @@ -657,7 +688,6 @@ namespace FlaxEditor.CustomEditors if (style == DisplayStyle.Group) { var group = Group(label.Text, editor, true); - group.Panel.Close(false); group.Panel.TooltipText = tooltip; return group.Object(values, editor); } diff --git a/Source/Editor/Modules/ProjectCacheModule.cs b/Source/Editor/Modules/ProjectCacheModule.cs index acb6e997e..eebea3ba0 100644 --- a/Source/Editor/Modules/ProjectCacheModule.cs +++ b/Source/Editor/Modules/ProjectCacheModule.cs @@ -18,7 +18,7 @@ namespace FlaxEditor.Modules private DateTime _lastSaveTime; private readonly HashSet _expandedActors = new HashSet(); - private readonly HashSet _collapsedGroups = new HashSet(); + private readonly HashSet _toggledGroups = new HashSet(); private readonly Dictionary _customData = new Dictionary(); /// @@ -62,26 +62,26 @@ namespace FlaxEditor.Modules } /// - /// Determines whether group identified by the given title is collapsed in the UI. + /// Determines whether group identified by the given title is collapsed/opened in the UI. /// /// The group title. - /// true if group is collapsed; otherwise, false. - public bool IsCollapsedGroup(string title) + /// true if group is toggled; otherwise, false. + public bool IsGroupToggled(string title) { - return _collapsedGroups.Contains(title); + return _toggledGroups.Contains(title); } /// - /// Sets the group collapsed cached value. + /// Sets the group collapsed/opened cached value. /// /// The group title. - /// If set to true group will be cached as an collapsed, otherwise false. - public void SetCollapsedGroup(string title, bool isCollapsed) + /// If set to true group will be cached as a toggled, otherwise false. + public void SetGroupToggle(string title, bool isToggled) { - if (isCollapsed) - _collapsedGroups.Add(title); + if (isToggled) + _toggledGroups.Add(title); else - _collapsedGroups.Remove(title); + _toggledGroups.Remove(title); _isDirty = true; } @@ -160,7 +160,7 @@ namespace FlaxEditor.Modules _expandedActors.Add(new Guid(bytes16)); } - _collapsedGroups.Clear(); + _toggledGroups.Clear(); _customData.Clear(); break; @@ -176,7 +176,7 @@ namespace FlaxEditor.Modules _expandedActors.Add(new Guid(bytes16)); } - _collapsedGroups.Clear(); + _toggledGroups.Clear(); _customData.Clear(); int customDataCount = reader.ReadInt32(); @@ -201,11 +201,9 @@ namespace FlaxEditor.Modules } int collapsedGroupsCount = reader.ReadInt32(); - _collapsedGroups.Clear(); + _toggledGroups.Clear(); for (int i = 0; i < collapsedGroupsCount; i++) - { - _collapsedGroups.Add(reader.ReadString()); - } + _toggledGroups.Add(reader.ReadString()); _customData.Clear(); int customDataCount = reader.ReadInt32(); @@ -259,11 +257,9 @@ namespace FlaxEditor.Modules writer.Write(e.ToByteArray()); } - writer.Write(_collapsedGroups.Count); - foreach (var e in _collapsedGroups) - { + writer.Write(_toggledGroups.Count); + foreach (var e in _toggledGroups) writer.Write(e); - } writer.Write(_customData.Count); foreach (var e in _customData) @@ -284,7 +280,6 @@ namespace FlaxEditor.Modules try { SaveGuarded(); - _isDirty = false; } catch (Exception ex) From c81ef9b26f3be4d86861c317f4e396c5bfed3e04 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 17 Feb 2024 23:48:30 +0100 Subject: [PATCH 113/139] Add hiding Actor's Transform for `UIControl`Actor #382 --- Source/Engine/Level/Actor.cs | 6 +++++- Source/Engine/Level/Actor.h | 10 ++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Source/Engine/Level/Actor.cs b/Source/Engine/Level/Actor.cs index dbe8a89b5..1d64ff5ae 100644 --- a/Source/Engine/Level/Actor.cs +++ b/Source/Engine/Level/Actor.cs @@ -269,7 +269,7 @@ namespace FlaxEngine { return FindActor(typeof(T), name) as T; } - + /// /// Tries to find actor of the given type and tag in this actor hierarchy (checks this actor and all children hierarchy). /// @@ -386,5 +386,9 @@ namespace FlaxEngine { return $"{Name} ({GetType().Name})"; } + +#if FLAX_EDITOR + internal bool ShowTransform => !(this is UIControl); +#endif } } diff --git a/Source/Engine/Level/Actor.h b/Source/Engine/Level/Actor.h index 0ce9a0dbc..f66d13f40 100644 --- a/Source/Engine/Level/Actor.h +++ b/Source/Engine/Level/Actor.h @@ -534,9 +534,7 @@ public: /// /// Gets actor direction vector (forward vector). /// - /// The result value. - API_PROPERTY(Attributes="HideInEditor, NoSerialize") - FORCE_INLINE Float3 GetDirection() const + API_PROPERTY(Attributes="HideInEditor, NoSerialize") FORCE_INLINE Float3 GetDirection() const { return Float3::Transform(Float3::Forward, GetOrientation()); } @@ -571,7 +569,7 @@ public: /// /// Gets local position of the actor in parent actor space. /// - API_PROPERTY(Attributes="EditorDisplay(\"Transform\", \"Position\"), DefaultValue(typeof(Vector3), \"0,0,0\"), EditorOrder(-30), NoSerialize, CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ActorTransformEditor+PositionEditor\")") + API_PROPERTY(Attributes="EditorDisplay(\"Transform\", \"Position\"), VisibleIf(\"ShowTransform\"), DefaultValue(typeof(Vector3), \"0,0,0\"), EditorOrder(-30), NoSerialize, CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ActorTransformEditor+PositionEditor\")") FORCE_INLINE Vector3 GetLocalPosition() const { return _localTransform.Translation; @@ -587,7 +585,7 @@ public: /// Gets local rotation of the actor in parent actor space. /// /// Actor.LocalOrientation *= Quaternion.Euler(0, 10 * Time.DeltaTime, 0) - API_PROPERTY(Attributes="EditorDisplay(\"Transform\", \"Rotation\"), DefaultValue(typeof(Quaternion), \"0,0,0,1\"), EditorOrder(-20), NoSerialize, CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ActorTransformEditor+OrientationEditor\")") + API_PROPERTY(Attributes="EditorDisplay(\"Transform\", \"Rotation\"), VisibleIf(\"ShowTransform\"), DefaultValue(typeof(Quaternion), \"0,0,0,1\"), EditorOrder(-20), NoSerialize, CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ActorTransformEditor+OrientationEditor\")") FORCE_INLINE Quaternion GetLocalOrientation() const { return _localTransform.Orientation; @@ -602,7 +600,7 @@ public: /// /// Gets local scale vector of the actor in parent actor space. /// - API_PROPERTY(Attributes="EditorDisplay(\"Transform\", \"Scale\"), DefaultValue(typeof(Float3), \"1,1,1\"), Limit(float.MinValue, float.MaxValue, 0.01f), EditorOrder(-10), NoSerialize, CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ActorTransformEditor+ScaleEditor\")") + API_PROPERTY(Attributes="EditorDisplay(\"Transform\", \"Scale\"), VisibleIf(\"ShowTransform\"), DefaultValue(typeof(Float3), \"1,1,1\"), Limit(float.MinValue, float.MaxValue, 0.01f), EditorOrder(-10), NoSerialize, CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ActorTransformEditor+ScaleEditor\")") FORCE_INLINE Float3 GetLocalScale() const { return _localTransform.Scale; From 5e218c8da92b942e18849f3f0c62596c0214cc82 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 18 Feb 2024 00:03:27 +0100 Subject: [PATCH 114/139] Improve #2100 and fix undo --- Source/Editor/Modules/SceneEditingModule.cs | 21 +++++++++++-------- Source/Editor/Modules/UIModule.cs | 4 +--- .../Windows/SceneTreeWindow.ContextMenu.cs | 6 +----- 3 files changed, 14 insertions(+), 17 deletions(-) diff --git a/Source/Editor/Modules/SceneEditingModule.cs b/Source/Editor/Modules/SceneEditingModule.cs index 3b982d7ad..35b85bfa8 100644 --- a/Source/Editor/Modules/SceneEditingModule.cs +++ b/Source/Editor/Modules/SceneEditingModule.cs @@ -542,24 +542,27 @@ namespace FlaxEditor.Modules Actor actor = new EmptyActor(); Editor.SceneEditing.Spawn(actor, null, false); List selection = Editor.SceneEditing.Selection; - for (int i = 0; i < selection.Count; i++) + var actors = selection.Where(x => x is ActorNode).Select(x => ((ActorNode)x).Actor); + using (new UndoMultiBlock(Undo, actors, "Reparent actors")) { - if (selection[i] is ActorNode node) + for (int i = 0; i < selection.Count; i++) { - if (node.ParentNode != node.ParentScene) // if parent node is not Scene + if (selection[i] is ActorNode node) { - if (selection.Contains(node.ParentNode)) + if (node.ParentNode != node.ParentScene) // If parent node is not a scene { - return; // if parent and child nodes selected together, don't touch child nodes - } - else - { // put created node as child of the Parent Node of node + if (selection.Contains(node.ParentNode)) + { + return; // If parent and child nodes selected together, don't touch child nodes + } + + // Put created node as child of the Parent Node of node int parentOrder = node.Actor.OrderInParent; actor.Parent = node.Actor.Parent; actor.OrderInParent = parentOrder; } + node.Actor.Parent = actor; } - node.Actor.Parent = actor; } } Editor.SceneEditing.Select(actor); diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs index 7920d5de4..4537d732e 100644 --- a/Source/Editor/Modules/UIModule.cs +++ b/Source/Editor/Modules/UIModule.cs @@ -549,13 +549,11 @@ namespace FlaxEditor.Modules _menuEditCut = cm.AddButton("Cut", inputOptions.Cut, Editor.SceneEditing.Cut); _menuEditCopy = cm.AddButton("Copy", inputOptions.Copy, Editor.SceneEditing.Copy); _menuEditPaste = cm.AddButton("Paste", inputOptions.Paste, Editor.SceneEditing.Paste); - cm.AddSeparator(); - _menuCreateParentForSelectedActors = cm.AddButton("Create parent for selected actors", Editor.SceneEditing.CreateParentForSelectedActors); - cm.AddSeparator(); _menuEditDelete = cm.AddButton("Delete", inputOptions.Delete, Editor.SceneEditing.Delete); _menuEditDuplicate = cm.AddButton("Duplicate", inputOptions.Duplicate, Editor.SceneEditing.Duplicate); cm.AddSeparator(); _menuEditSelectAll = cm.AddButton("Select all", inputOptions.SelectAll, Editor.SceneEditing.SelectAllScenes); + _menuCreateParentForSelectedActors = cm.AddButton("Create parent for selected actors", Editor.SceneEditing.CreateParentForSelectedActors); _menuEditFind = cm.AddButton("Find", inputOptions.Search, Editor.Windows.SceneWin.Search); cm.AddSeparator(); cm.AddButton("Game Settings", () => diff --git a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs index fa4abfeca..abad36829 100644 --- a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs +++ b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs @@ -132,17 +132,13 @@ namespace FlaxEditor.Windows b = contextMenu.AddButton("Cut", inputOptions.Cut, Editor.SceneEditing.Cut); b.Enabled = canEditScene; - // Create a new hierarchy from selected actors + // Create option contextMenu.AddSeparator(); b = contextMenu.AddButton("Create parent for selected actors", Editor.SceneEditing.CreateParentForSelectedActors); b.Enabled = canEditScene && hasSthSelected; - // Prefab options - - contextMenu.AddSeparator(); - b = contextMenu.AddButton("Create Prefab", Editor.Prefabs.CreatePrefab); b.Enabled = isSingleActorSelected && ((ActorNode)Editor.SceneEditing.Selection[0]).CanCreatePrefab && From e165c87e5a1d5088b278861f8dc3bd2eb1cf92b3 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 18 Feb 2024 00:03:54 +0100 Subject: [PATCH 115/139] Format code #1871 --- Source/Engine/UI/GUI/Common/Slider.cs | 56 ++++++++++++--------------- 1 file changed, 25 insertions(+), 31 deletions(-) diff --git a/Source/Engine/UI/GUI/Common/Slider.cs b/Source/Engine/UI/GUI/Common/Slider.cs index d9d998bc2..efffa6728 100644 --- a/Source/Engine/UI/GUI/Common/Slider.cs +++ b/Source/Engine/UI/GUI/Common/Slider.cs @@ -1,4 +1,6 @@ -using System; +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +using System; namespace FlaxEngine.GUI; @@ -81,14 +83,14 @@ public class Slider : ContainerControl /// Gets or sets the slider direction. /// [EditorOrder(40), Tooltip("Slider Direction.")] - public SliderDirection Direction - { + public SliderDirection Direction + { get => _direction; set { _direction = value; UpdateThumb(); - } + } } private SliderDirection _direction = SliderDirection.HorizontalRight; @@ -138,14 +140,10 @@ public class Slider : ContainerControl { switch (Direction) { - case SliderDirection.HorizontalRight: - return new Float2(_thumbSize.X / 2, Height / 2); - case SliderDirection.HorizontalLeft: - return new Float2(Width - _thumbSize.X / 2, Height / 2); - case SliderDirection.VerticalUp: - return new Float2(Width / 2, Height - _thumbSize.Y / 2); - case SliderDirection.VerticalDown: - return new Float2(Width / 2, _thumbSize.Y / 2); + case SliderDirection.HorizontalRight: return new Float2(_thumbSize.X / 2, Height / 2); + case SliderDirection.HorizontalLeft: return new Float2(Width - _thumbSize.X / 2, Height / 2); + case SliderDirection.VerticalUp: return new Float2(Width / 2, Height - _thumbSize.Y / 2); + case SliderDirection.VerticalDown: return new Float2(Width / 2, _thumbSize.Y / 2); default: break; } return Float2.Zero; @@ -162,14 +160,10 @@ public class Slider : ContainerControl { switch (Direction) { - case SliderDirection.HorizontalRight: - return new Float2(Width - _thumbSize.X / 2, Height / 2); - case SliderDirection.HorizontalLeft: - return new Float2(_thumbSize.X / 2, Height / 2); - case SliderDirection.VerticalUp: - return new Float2(Width / 2, _thumbSize.Y / 2); - case SliderDirection.VerticalDown: - return new Float2(Width / 2, Height - _thumbSize.Y / 2); + case SliderDirection.HorizontalRight: return new Float2(Width - _thumbSize.X / 2, Height / 2); + case SliderDirection.HorizontalLeft: return new Float2(_thumbSize.X / 2, Height / 2); + case SliderDirection.VerticalUp: return new Float2(Width / 2, _thumbSize.Y / 2); + case SliderDirection.VerticalDown: return new Float2(Width / 2, Height - _thumbSize.Y / 2); default: break; } return Float2.Zero; @@ -186,7 +180,8 @@ public class Slider : ContainerControl /// The thumb size. /// [EditorOrder(41), Tooltip("The size of the thumb.")] - public Float2 ThumbSize { + public Float2 ThumbSize + { get => _thumbSize; set { @@ -200,7 +195,7 @@ public class Slider : ContainerControl /// [EditorOrder(42), Tooltip("Fill the track.")] public bool FillTrack = true; - + /// /// Whether to use whole numbers. /// @@ -223,7 +218,7 @@ public class Slider : ContainerControl /// Gets the width of the track. /// private float TrackWidth => (Direction is SliderDirection.HorizontalLeft or SliderDirection.HorizontalRight) ? Width - _thumbSize.X : TrackThickness; - + /// /// Gets the height of the track. /// @@ -246,7 +241,7 @@ public class Slider : ContainerControl /// [EditorDisplay("Thumb Style"), EditorOrder(2030), Tooltip("The color of the slider thumb when it's not selected."), ExpandGroups] public Color ThumbColor { get; set; } - + /// /// The color of the slider thumb when it's highlighted. /// @@ -280,12 +275,12 @@ public class Slider : ContainerControl /// Occurs when sliding ends. /// public event Action SlidingEnd; - + /// /// Occurs when value gets changed. /// public event Action ValueChanged; - + /// /// Initializes a new instance of the class. /// @@ -359,17 +354,16 @@ public class Slider : ContainerControl var fillLineRect = new Rectangle(_thumbSize.X / 2 - 1, (Height - TrackThickness - 2) / 2, Width - (Width - _thumbCenter) - _thumbSize.X / 2 + 1, TrackThickness + 2); switch (Direction) { - case SliderDirection.HorizontalRight: - break; + case SliderDirection.HorizontalRight: break; case SliderDirection.VerticalDown: - lineRect = new Rectangle((Width - TrackThickness) / 2 , _thumbSize.Y / 2, TrackThickness, Height - _thumbSize.Y); + lineRect = new Rectangle((Width - TrackThickness) / 2, _thumbSize.Y / 2, TrackThickness, Height - _thumbSize.Y); fillLineRect = new Rectangle((Width - TrackThickness - 2) / 2, _thumbSize.Y / 2 - 1, TrackThickness + 2, Height - (Height - _thumbCenter) - _thumbSize.Y / 2 + 1); break; case SliderDirection.HorizontalLeft: fillLineRect = new Rectangle(Width - (Width - _thumbCenter) - 1, (Height - TrackThickness - 2) / 2, Width - _thumbCenter + 1, TrackThickness + 2); break; case SliderDirection.VerticalUp: - lineRect = new Rectangle((Width - TrackThickness) / 2 , _thumbSize.Y / 2, TrackThickness, Height - _thumbSize.Y); + lineRect = new Rectangle((Width - TrackThickness) / 2, _thumbSize.Y / 2, TrackThickness, Height - _thumbSize.Y); fillLineRect = new Rectangle((Width - TrackThickness - 2) / 2, Height - (Height - _thumbCenter) - 1, TrackThickness + 2, Height - _thumbCenter + 1); break; default: break; @@ -379,7 +373,7 @@ public class Slider : ContainerControl if (TrackBrush != null) TrackBrush.Draw(lineRect, TrackLineColor); else - Render2D.FillRectangle(lineRect, TrackLineColor); + Render2D.FillRectangle(lineRect, TrackLineColor); // Draw track fill if (FillTrack) From 912437c456a85fc104d373fc2928de5d71d50a27 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 18 Feb 2024 10:27:17 +0100 Subject: [PATCH 116/139] Fix `CreateParentForSelectedActors` --- Source/Editor/Modules/SceneEditingModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/Modules/SceneEditingModule.cs b/Source/Editor/Modules/SceneEditingModule.cs index 35b85bfa8..1657cb3a1 100644 --- a/Source/Editor/Modules/SceneEditingModule.cs +++ b/Source/Editor/Modules/SceneEditingModule.cs @@ -553,7 +553,7 @@ namespace FlaxEditor.Modules { if (selection.Contains(node.ParentNode)) { - return; // If parent and child nodes selected together, don't touch child nodes + continue; // If parent and child nodes selected together, don't touch child nodes } // Put created node as child of the Parent Node of node From dfbde5f8ebd07e1bc04985197eef5eaf3be90607 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 18 Feb 2024 10:47:24 +0100 Subject: [PATCH 117/139] Remove debug draw --- Source/Editor/Viewport/ViewportDraggingHelper.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Source/Editor/Viewport/ViewportDraggingHelper.cs b/Source/Editor/Viewport/ViewportDraggingHelper.cs index 9f8977b10..dce2867c3 100644 --- a/Source/Editor/Viewport/ViewportDraggingHelper.cs +++ b/Source/Editor/Viewport/ViewportDraggingHelper.cs @@ -234,7 +234,6 @@ namespace FlaxEditor.Viewport LocalOrientation = RootNode.RaycastNormalRotation(ref hitNormal), Name = item.ShortName }; - DebugDraw.DrawWireArrow(PostProcessSpawnedActorLocation(actor, ref hitNormal), actor.LocalOrientation, 1.0f, 0.5f, Color.Red, 1000000); Spawn(actor, ref hitLocation, ref hitNormal); } else if (hit is StaticModelNode staticModelNode) From d76b5234c5f5b5855d8b7e32e1f104ca91ccf6ef Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 18 Feb 2024 11:06:42 +0100 Subject: [PATCH 118/139] Expose and document various APIs of Visject for plugins to use --- .../Archetypes/Animation.StateMachine.cs | 40 ++----------------- Source/Editor/Surface/BehaviorTreeSurface.cs | 7 +--- Source/Editor/Surface/SurfaceStyle.cs | 33 +++++++++++++++ .../Surface/VisjectSurface.ContextMenu.cs | 23 ++++++++++- Source/Editor/Surface/VisjectSurfaceWindow.cs | 2 +- Source/Editor/Windows/SceneTreeWindow.cs | 9 +++-- Source/Engine/Visject/VisjectGraph.h | 2 +- Source/Engine/Visject/VisjectMeta.cpp | 4 -- Source/Engine/Visject/VisjectMeta.h | 15 +------ 9 files changed, 68 insertions(+), 67 deletions(-) diff --git a/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs b/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs index bb4bc724d..f96ac8e4b 100644 --- a/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs +++ b/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs @@ -482,7 +482,7 @@ namespace FlaxEditor.Surface.Archetypes var startPos = PointToParent(ref center); targetState.GetConnectionEndPoint(ref startPos, out var endPos); var color = style.Foreground; - StateMachineState.DrawConnection(ref startPos, ref endPos, ref color); + SurfaceStyle.DrawStraightConnection(startPos, endPos, color); } } @@ -512,7 +512,7 @@ namespace FlaxEditor.Surface.Archetypes /// public void DrawConnectingLine(ref Float2 startPos, ref Float2 endPos, ref Color color) { - StateMachineState.DrawConnection(ref startPos, ref endPos, ref color); + SurfaceStyle.DrawStraightConnection(startPos, endPos, color); } /// @@ -676,38 +676,6 @@ namespace FlaxEditor.Surface.Archetypes { } - /// - /// Draws the connection between two state machine nodes. - /// - /// The start position. - /// The end position. - /// The line color. - public static void DrawConnection(ref Float2 startPos, ref Float2 endPos, ref Color color) - { - var sub = endPos - startPos; - var length = sub.Length; - if (length > Mathf.Epsilon) - { - var dir = sub / length; - var arrowRect = new Rectangle(0, 0, 16.0f, 16.0f); - float rotation = Float2.Dot(dir, Float2.UnitY); - if (endPos.X < startPos.X) - rotation = 2 - rotation; - var sprite = Editor.Instance.Icons.VisjectArrowClosed32; - var arrowTransform = - Matrix3x3.Translation2D(-6.5f, -8) * - Matrix3x3.RotationZ(rotation * Mathf.PiOverTwo) * - Matrix3x3.Translation2D(endPos - dir * 8); - - Render2D.PushTransform(ref arrowTransform); - Render2D.DrawSprite(sprite, arrowRect, color); - Render2D.PopTransform(); - - endPos -= dir * 4.0f; - } - Render2D.DrawLine(startPos, endPos, color); - } - /// /// Gets the connection end point for the given input position. Puts the end point near the edge of the node bounds. /// @@ -1308,7 +1276,7 @@ namespace FlaxEditor.Surface.Archetypes isMouseOver = Float2.DistanceSquared(ref mousePosition, ref point) < 25.0f; } var color = isMouseOver ? Color.Wheat : t.LineColor; - DrawConnection(ref t.StartPos, ref t.EndPos, ref color); + SurfaceStyle.DrawStraightConnection(t.StartPos, t.EndPos, color); } } @@ -1337,7 +1305,7 @@ namespace FlaxEditor.Surface.Archetypes /// public void DrawConnectingLine(ref Float2 startPos, ref Float2 endPos, ref Color color) { - DrawConnection(ref startPos, ref endPos, ref color); + SurfaceStyle.DrawStraightConnection(startPos, endPos, color); } /// diff --git a/Source/Editor/Surface/BehaviorTreeSurface.cs b/Source/Editor/Surface/BehaviorTreeSurface.cs index 9b201d3f8..760110769 100644 --- a/Source/Editor/Surface/BehaviorTreeSurface.cs +++ b/Source/Editor/Surface/BehaviorTreeSurface.cs @@ -31,7 +31,7 @@ namespace FlaxEditor.Surface var editor = Editor.Instance; var style = SurfaceStyle.CreateStyleHandler(editor); style.DrawBox = DrawBox; - style.DrawConnection = DrawConnection; + style.DrawConnection = SurfaceStyle.DrawStraightConnection; return style; } @@ -49,11 +49,6 @@ namespace FlaxEditor.Surface Render2D.FillRectangle(rect, color); } - private static void DrawConnection(Float2 start, Float2 end, Color color, float thickness) - { - Archetypes.Animation.StateMachineStateBase.DrawConnection(ref start, ref end, ref color); - } - private void OnActiveContextMenuVisibleChanged(Control activeCM) { _nodesCache.Wait(); diff --git a/Source/Editor/Surface/SurfaceStyle.cs b/Source/Editor/Surface/SurfaceStyle.cs index 2b8e97f62..5a67d6fb2 100644 --- a/Source/Editor/Surface/SurfaceStyle.cs +++ b/Source/Editor/Surface/SurfaceStyle.cs @@ -295,5 +295,38 @@ namespace FlaxEditor.Surface Background = editor.UI.VisjectSurfaceBackground, }; } + + /// + /// Draws a simple straight connection between two locations. + /// + /// The start position. + /// The end position. + /// The line color. + /// The line thickness. + public static void DrawStraightConnection(Float2 startPos, Float2 endPos, Color color, float thickness = 1.0f) + { + var sub = endPos - startPos; + var length = sub.Length; + if (length > Mathf.Epsilon) + { + var dir = sub / length; + var arrowRect = new Rectangle(0, 0, 16.0f, 16.0f); + float rotation = Float2.Dot(dir, Float2.UnitY); + if (endPos.X < startPos.X) + rotation = 2 - rotation; + var sprite = Editor.Instance.Icons.VisjectArrowClosed32; + var arrowTransform = + Matrix3x3.Translation2D(-6.5f, -8) * + Matrix3x3.RotationZ(rotation * Mathf.PiOverTwo) * + Matrix3x3.Translation2D(endPos - dir * 8); + + Render2D.PushTransform(ref arrowTransform); + Render2D.DrawSprite(sprite, arrowRect, color); + Render2D.PopTransform(); + + endPos -= dir * 4.0f; + } + Render2D.DrawLine(startPos, endPos, color); + } } } diff --git a/Source/Editor/Surface/VisjectSurface.ContextMenu.cs b/Source/Editor/Surface/VisjectSurface.ContextMenu.cs index 3ca7ddfc7..51339d6bc 100644 --- a/Source/Editor/Surface/VisjectSurface.ContextMenu.cs +++ b/Source/Editor/Surface/VisjectSurface.ContextMenu.cs @@ -20,8 +20,15 @@ namespace FlaxEditor.Surface /// /// Utility for easy nodes archetypes generation for Visject Surface based on scripting types. /// - internal class NodesCache + [HideInEditor] + public class NodesCache { + /// + /// Delegate for scripting types filtering into cache. + /// + /// The input type to process. + /// Node groups cache that can be used for reusing groups for different nodes. + /// The cache version number. Can be used to reject any cached data after rebuilt. public delegate void IterateType(ScriptType scriptType, Dictionary, GroupArchetype> cache, int version); internal static readonly List Caches = new List(8); @@ -33,11 +40,18 @@ namespace FlaxEditor.Surface private VisjectCM _taskContextMenu; private Dictionary, GroupArchetype> _cache; + /// + /// Initializes a new instance of the class. + /// + /// The iterator callback to build node types from Scripting. public NodesCache(IterateType iterator) { _iterator = iterator; } + /// + /// Waits for the async caching job to finish. + /// public void Wait() { if (_task != null) @@ -48,6 +62,9 @@ namespace FlaxEditor.Surface } } + /// + /// Clears cache. + /// public void Clear() { Wait(); @@ -62,6 +79,10 @@ namespace FlaxEditor.Surface } } + /// + /// Updates the Visject Context Menu to contain current nodes. + /// + /// The output context menu to setup. public void Get(VisjectCM contextMenu) { Profiler.BeginEvent("Setup Context Menu"); diff --git a/Source/Editor/Surface/VisjectSurfaceWindow.cs b/Source/Editor/Surface/VisjectSurfaceWindow.cs index cce86d5c0..6a9cba841 100644 --- a/Source/Editor/Surface/VisjectSurfaceWindow.cs +++ b/Source/Editor/Surface/VisjectSurfaceWindow.cs @@ -25,7 +25,7 @@ namespace FlaxEditor.Surface /// The base interface for editor windows that use for content editing. /// /// - interface IVisjectSurfaceWindow : IVisjectSurfaceOwner + public interface IVisjectSurfaceWindow : IVisjectSurfaceOwner { /// /// Gets the asset edited by the window. diff --git a/Source/Editor/Windows/SceneTreeWindow.cs b/Source/Editor/Windows/SceneTreeWindow.cs index 1eb80c9ea..c5d790a3d 100644 --- a/Source/Editor/Windows/SceneTreeWindow.cs +++ b/Source/Editor/Windows/SceneTreeWindow.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; -using System.Linq; using FlaxEditor.Gizmo; using FlaxEditor.Content; using FlaxEditor.GUI.Tree; @@ -14,7 +13,6 @@ using FlaxEditor.Scripting; using FlaxEditor.States; using FlaxEngine; using FlaxEngine.GUI; -using static FlaxEditor.GUI.ItemsListContextMenu; namespace FlaxEditor.Windows { @@ -24,8 +22,6 @@ namespace FlaxEditor.Windows /// public partial class SceneTreeWindow : SceneEditorWindow { - public Panel SceneTreePanel => _sceneTreePanel; - private TextBox _searchBox; private Tree _tree; private Panel _sceneTreePanel; @@ -37,6 +33,11 @@ namespace FlaxEditor.Windows private DragScriptItems _dragScriptItems; private DragHandlers _dragHandlers; + /// + /// Scene tree panel. + /// + public Panel SceneTreePanel => _sceneTreePanel; + /// /// Initializes a new instance of the class. /// diff --git a/Source/Engine/Visject/VisjectGraph.h b/Source/Engine/Visject/VisjectGraph.h index 085c05de0..5054bd54d 100644 --- a/Source/Engine/Visject/VisjectGraph.h +++ b/Source/Engine/Visject/VisjectGraph.h @@ -101,7 +101,7 @@ public: /// Visject graph parameter. /// /// -API_CLASS() class VisjectGraphParameter : public GraphParameter +API_CLASS() class FLAXENGINE_API VisjectGraphParameter : public GraphParameter { DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(VisjectGraphParameter, GraphParameter); public: diff --git a/Source/Engine/Visject/VisjectMeta.cpp b/Source/Engine/Visject/VisjectMeta.cpp index 37631df55..624345813 100644 --- a/Source/Engine/Visject/VisjectMeta.cpp +++ b/Source/Engine/Visject/VisjectMeta.cpp @@ -5,10 +5,6 @@ #include "Engine/Serialization/ReadStream.h" #include "Engine/Serialization/WriteStream.h" -VisjectMeta::VisjectMeta() -{ -} - bool VisjectMeta::Load(ReadStream* stream, bool loadData) { Release(); diff --git a/Source/Engine/Visject/VisjectMeta.h b/Source/Engine/Visject/VisjectMeta.h index c417b7030..b35add815 100644 --- a/Source/Engine/Visject/VisjectMeta.h +++ b/Source/Engine/Visject/VisjectMeta.h @@ -8,7 +8,7 @@ /// /// Visject metadata container /// -class VisjectMeta +class FLAXENGINE_API VisjectMeta { public: /// @@ -27,19 +27,6 @@ public: /// Array> Entries; -public: - /// - /// Initializes a new instance of the class. - /// - VisjectMeta(); - - /// - /// Finalizes an instance of the class. - /// - ~VisjectMeta() - { - } - public: /// /// Load from the stream From 636b2c91ccfe2d7e0483a2ac7cf6ca35ab0cac67 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 18 Feb 2024 11:22:35 +0100 Subject: [PATCH 119/139] Refactor `Editor.CreateAsset` to use named tags for better extensibility with custom assets in plugins --- .../Create/ParticleEmitterCreateEntry.cs | 2 +- .../Proxy/AnimationGraphFunctionProxy.cs | 2 +- .../Content/Proxy/AnimationGraphProxy.cs | 2 +- Source/Editor/Content/Proxy/AnimationProxy.cs | 2 +- .../Editor/Content/Proxy/BehaviorTreeProxy.cs | 2 +- .../Content/Proxy/CollisionDataProxy.cs | 2 +- .../Content/Proxy/MaterialFunctionProxy.cs | 2 +- .../Content/Proxy/MaterialInstanceProxy.cs | 2 +- Source/Editor/Content/Proxy/MaterialProxy.cs | 2 +- .../Proxy/ParticleEmitterFunctionProxy.cs | 2 +- .../Content/Proxy/ParticleSystemProxy.cs | 2 +- .../Content/Proxy/SceneAnimationProxy.cs | 2 +- .../Editor/Content/Proxy/SkeletonMaskProxy.cs | 2 +- Source/Editor/Editor.cs | 55 ++++++++++-- .../Editor/Managed/ManagedEditor.Internal.cpp | 83 ++----------------- Source/Editor/Managed/ManagedEditor.h | 7 ++ .../AssetsImportingManager.cpp | 1 - Source/Engine/ContentImporters/Types.h | 4 +- 18 files changed, 79 insertions(+), 97 deletions(-) diff --git a/Source/Editor/Content/Create/ParticleEmitterCreateEntry.cs b/Source/Editor/Content/Create/ParticleEmitterCreateEntry.cs index 1be5330e7..76edde777 100644 --- a/Source/Editor/Content/Create/ParticleEmitterCreateEntry.cs +++ b/Source/Editor/Content/Create/ParticleEmitterCreateEntry.cs @@ -81,7 +81,7 @@ namespace FlaxEditor.Content.Create switch (_options.Template) { case Templates.Empty: - return Editor.CreateAsset(Editor.NewAssetType.ParticleEmitter, ResultUrl); + return Editor.CreateAsset("ParticleEmitter", ResultUrl); case Templates.ConstantBurst: templateName = "Constant Burst"; break; diff --git a/Source/Editor/Content/Proxy/AnimationGraphFunctionProxy.cs b/Source/Editor/Content/Proxy/AnimationGraphFunctionProxy.cs index f714ddbeb..7efc02368 100644 --- a/Source/Editor/Content/Proxy/AnimationGraphFunctionProxy.cs +++ b/Source/Editor/Content/Proxy/AnimationGraphFunctionProxy.cs @@ -38,7 +38,7 @@ namespace FlaxEditor.Content /// public override void Create(string outputPath, object arg) { - if (Editor.CreateAsset(Editor.NewAssetType.AnimationGraphFunction, outputPath)) + if (Editor.CreateAsset("AnimationGraphFunction", outputPath)) throw new Exception("Failed to create new asset."); } } diff --git a/Source/Editor/Content/Proxy/AnimationGraphProxy.cs b/Source/Editor/Content/Proxy/AnimationGraphProxy.cs index 3e6c35c6d..20d3c5a2c 100644 --- a/Source/Editor/Content/Proxy/AnimationGraphProxy.cs +++ b/Source/Editor/Content/Proxy/AnimationGraphProxy.cs @@ -38,7 +38,7 @@ namespace FlaxEditor.Content /// public override void Create(string outputPath, object arg) { - if (Editor.CreateAsset(Editor.NewAssetType.AnimationGraph, outputPath)) + if (Editor.CreateAsset("AnimationGraph", outputPath)) throw new Exception("Failed to create new asset."); } } diff --git a/Source/Editor/Content/Proxy/AnimationProxy.cs b/Source/Editor/Content/Proxy/AnimationProxy.cs index 2cf46d3a3..6636fccb8 100644 --- a/Source/Editor/Content/Proxy/AnimationProxy.cs +++ b/Source/Editor/Content/Proxy/AnimationProxy.cs @@ -47,7 +47,7 @@ namespace FlaxEditor.Content /// public override void Create(string outputPath, object arg) { - if (Editor.CreateAsset(Editor.NewAssetType.Animation, outputPath)) + if (Editor.CreateAsset("Animation", outputPath)) throw new Exception("Failed to create new asset."); } diff --git a/Source/Editor/Content/Proxy/BehaviorTreeProxy.cs b/Source/Editor/Content/Proxy/BehaviorTreeProxy.cs index 33ad0862f..234dfaf7d 100644 --- a/Source/Editor/Content/Proxy/BehaviorTreeProxy.cs +++ b/Source/Editor/Content/Proxy/BehaviorTreeProxy.cs @@ -47,7 +47,7 @@ namespace FlaxEditor.Content /// public override void Create(string outputPath, object arg) { - if (Editor.CreateAsset(Editor.NewAssetType.BehaviorTree, outputPath)) + if (Editor.CreateAsset("BehaviorTree", outputPath)) throw new Exception("Failed to create new asset."); } diff --git a/Source/Editor/Content/Proxy/CollisionDataProxy.cs b/Source/Editor/Content/Proxy/CollisionDataProxy.cs index 55e8c6327..df26dca75 100644 --- a/Source/Editor/Content/Proxy/CollisionDataProxy.cs +++ b/Source/Editor/Content/Proxy/CollisionDataProxy.cs @@ -71,7 +71,7 @@ namespace FlaxEditor.Content /// public override void Create(string outputPath, object arg) { - if (Editor.CreateAsset(Editor.NewAssetType.CollisionData, outputPath)) + if (Editor.CreateAsset("CollisionData", outputPath)) throw new Exception("Failed to create new asset."); } diff --git a/Source/Editor/Content/Proxy/MaterialFunctionProxy.cs b/Source/Editor/Content/Proxy/MaterialFunctionProxy.cs index ab59f11f3..ca012f70a 100644 --- a/Source/Editor/Content/Proxy/MaterialFunctionProxy.cs +++ b/Source/Editor/Content/Proxy/MaterialFunctionProxy.cs @@ -38,7 +38,7 @@ namespace FlaxEditor.Content /// public override void Create(string outputPath, object arg) { - if (Editor.CreateAsset(Editor.NewAssetType.MaterialFunction, outputPath)) + if (Editor.CreateAsset("MaterialFunction", outputPath)) throw new Exception("Failed to create new asset."); } } diff --git a/Source/Editor/Content/Proxy/MaterialInstanceProxy.cs b/Source/Editor/Content/Proxy/MaterialInstanceProxy.cs index 331ff81c3..cd245b149 100644 --- a/Source/Editor/Content/Proxy/MaterialInstanceProxy.cs +++ b/Source/Editor/Content/Proxy/MaterialInstanceProxy.cs @@ -43,7 +43,7 @@ namespace FlaxEditor.Content /// public override void Create(string outputPath, object arg) { - if (Editor.CreateAsset(Editor.NewAssetType.MaterialInstance, outputPath)) + if (Editor.CreateAsset("MaterialInstance", outputPath)) throw new Exception("Failed to create new asset."); } diff --git a/Source/Editor/Content/Proxy/MaterialProxy.cs b/Source/Editor/Content/Proxy/MaterialProxy.cs index a7fcfecc8..f7db2c83d 100644 --- a/Source/Editor/Content/Proxy/MaterialProxy.cs +++ b/Source/Editor/Content/Proxy/MaterialProxy.cs @@ -44,7 +44,7 @@ namespace FlaxEditor.Content /// public override void Create(string outputPath, object arg) { - if (Editor.CreateAsset(Editor.NewAssetType.Material, outputPath)) + if (Editor.CreateAsset("Material", outputPath)) throw new Exception("Failed to create new asset."); } diff --git a/Source/Editor/Content/Proxy/ParticleEmitterFunctionProxy.cs b/Source/Editor/Content/Proxy/ParticleEmitterFunctionProxy.cs index 3a2ed749f..aaf9445e2 100644 --- a/Source/Editor/Content/Proxy/ParticleEmitterFunctionProxy.cs +++ b/Source/Editor/Content/Proxy/ParticleEmitterFunctionProxy.cs @@ -38,7 +38,7 @@ namespace FlaxEditor.Content /// public override void Create(string outputPath, object arg) { - if (Editor.CreateAsset(Editor.NewAssetType.ParticleEmitterFunction, outputPath)) + if (Editor.CreateAsset("ParticleEmitterFunction", outputPath)) throw new Exception("Failed to create new asset."); } } diff --git a/Source/Editor/Content/Proxy/ParticleSystemProxy.cs b/Source/Editor/Content/Proxy/ParticleSystemProxy.cs index 047853f0b..74f391513 100644 --- a/Source/Editor/Content/Proxy/ParticleSystemProxy.cs +++ b/Source/Editor/Content/Proxy/ParticleSystemProxy.cs @@ -75,7 +75,7 @@ namespace FlaxEditor.Content /// public override void Create(string outputPath, object arg) { - if (Editor.CreateAsset(Editor.NewAssetType.ParticleSystem, outputPath)) + if (Editor.CreateAsset("ParticleSystem", outputPath)) throw new Exception("Failed to create new asset."); } diff --git a/Source/Editor/Content/Proxy/SceneAnimationProxy.cs b/Source/Editor/Content/Proxy/SceneAnimationProxy.cs index fe31f0e34..5f7fa800a 100644 --- a/Source/Editor/Content/Proxy/SceneAnimationProxy.cs +++ b/Source/Editor/Content/Proxy/SceneAnimationProxy.cs @@ -69,7 +69,7 @@ namespace FlaxEditor.Content /// public override void Create(string outputPath, object arg) { - if (Editor.CreateAsset(Editor.NewAssetType.SceneAnimation, outputPath)) + if (Editor.CreateAsset("SceneAnimation", outputPath)) throw new Exception("Failed to create new asset."); } } diff --git a/Source/Editor/Content/Proxy/SkeletonMaskProxy.cs b/Source/Editor/Content/Proxy/SkeletonMaskProxy.cs index f300a4c61..560df0a7c 100644 --- a/Source/Editor/Content/Proxy/SkeletonMaskProxy.cs +++ b/Source/Editor/Content/Proxy/SkeletonMaskProxy.cs @@ -38,7 +38,7 @@ namespace FlaxEditor.Content /// public override void Create(string outputPath, object arg) { - if (Editor.CreateAsset(Editor.NewAssetType.SkeletonMask, outputPath)) + if (Editor.CreateAsset("SkeletonMask", outputPath)) throw new Exception("Failed to create new asset."); } } diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs index 393bf564e..266d8235f 100644 --- a/Source/Editor/Editor.cs +++ b/Source/Editor/Editor.cs @@ -869,7 +869,9 @@ namespace FlaxEditor /// /// New asset types allowed to create. + /// [Deprecated in v1.8] /// + [Obsolete("Use CreateAsset with named tag.")] public enum NewAssetType { /// @@ -1046,12 +1048,59 @@ namespace FlaxEditor /// /// Creates new asset at the target location. + /// [Deprecated in v1.8] /// /// New asset type. /// Output asset path. + [Obsolete("Use CreateAsset with named tag.")] public static bool CreateAsset(NewAssetType type, string outputPath) { - return Internal_CreateAsset(type, outputPath); + // [Deprecated on 18.02.2024, expires on 18.02.2025] + string tag; + switch (type) + { + case NewAssetType.Material: + tag = "Material"; + break; + case NewAssetType.MaterialInstance: + tag = "MaterialInstance"; + break; + case NewAssetType.CollisionData: + tag = "CollisionData"; + break; + case NewAssetType.AnimationGraph: + tag = "AnimationGraph"; + break; + case NewAssetType.SkeletonMask: + tag = "SkeletonMask"; + break; + case NewAssetType.ParticleEmitter: + tag = "ParticleEmitter"; + break; + case NewAssetType.ParticleSystem: + tag = "ParticleSystem"; + break; + case NewAssetType.SceneAnimation: + tag = "SceneAnimation"; + break; + case NewAssetType.MaterialFunction: + tag = "MaterialFunction"; + break; + case NewAssetType.ParticleEmitterFunction: + tag = "ParticleEmitterFunction"; + break; + case NewAssetType.AnimationGraphFunction: + tag = "AnimationGraphFunction"; + break; + case NewAssetType.Animation: + tag = "Animation"; + break; + case NewAssetType.BehaviorTree: + tag = "BehaviorTree"; + break; + default: return true; + } + return CreateAsset(tag, outputPath); } /// @@ -1588,10 +1637,6 @@ namespace FlaxEditor [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_CloseSplashScreen", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] internal static partial void Internal_CloseSplashScreen(); - [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_CreateAsset", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] - [return: MarshalAs(UnmanagedType.U1)] - internal static partial bool Internal_CreateAsset(NewAssetType type, string outputPath); - [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_CreateVisualScript", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] [return: MarshalAs(UnmanagedType.U1)] internal static partial bool Internal_CreateVisualScript(string outputPath, string baseTypename); diff --git a/Source/Editor/Managed/ManagedEditor.Internal.cpp b/Source/Editor/Managed/ManagedEditor.Internal.cpp index ae8b71ee2..53c0fbab2 100644 --- a/Source/Editor/Managed/ManagedEditor.Internal.cpp +++ b/Source/Editor/Managed/ManagedEditor.Internal.cpp @@ -170,78 +170,6 @@ DEFINE_INTERNAL_CALL(bool) EditorInternal_CloneAssetFile(MString* dstPathObj, MS return Content::CloneAssetFile(dstPath, srcPath, *dstId); } -enum class NewAssetType -{ - Material = 0, - MaterialInstance = 1, - CollisionData = 2, - AnimationGraph = 3, - SkeletonMask = 4, - ParticleEmitter = 5, - ParticleSystem = 6, - SceneAnimation = 7, - MaterialFunction = 8, - ParticleEmitterFunction = 9, - AnimationGraphFunction = 10, - Animation = 11, - BehaviorTree = 12, -}; - -DEFINE_INTERNAL_CALL(bool) EditorInternal_CreateAsset(NewAssetType type, MString* outputPathObj) -{ - String tag; - switch (type) - { - case NewAssetType::Material: - tag = AssetsImportingManager::CreateMaterialTag; - break; - case NewAssetType::MaterialInstance: - tag = AssetsImportingManager::CreateMaterialInstanceTag; - break; - case NewAssetType::CollisionData: - tag = AssetsImportingManager::CreateCollisionDataTag; - break; - case NewAssetType::AnimationGraph: - tag = AssetsImportingManager::CreateAnimationGraphTag; - break; - case NewAssetType::SkeletonMask: - tag = AssetsImportingManager::CreateSkeletonMaskTag; - break; - case NewAssetType::ParticleEmitter: - tag = AssetsImportingManager::CreateParticleEmitterTag; - break; - case NewAssetType::ParticleSystem: - tag = AssetsImportingManager::CreateParticleSystemTag; - break; - case NewAssetType::SceneAnimation: - tag = AssetsImportingManager::CreateSceneAnimationTag; - break; - case NewAssetType::MaterialFunction: - tag = AssetsImportingManager::CreateMaterialFunctionTag; - break; - case NewAssetType::ParticleEmitterFunction: - tag = AssetsImportingManager::CreateParticleEmitterFunctionTag; - break; - case NewAssetType::AnimationGraphFunction: - tag = AssetsImportingManager::CreateAnimationGraphFunctionTag; - break; - case NewAssetType::Animation: - tag = AssetsImportingManager::CreateAnimationTag; - break; - case NewAssetType::BehaviorTree: - tag = AssetsImportingManager::CreateBehaviorTreeTag; - break; - default: - return true; - } - - String outputPath; - MUtils::ToString(outputPathObj, outputPath); - FileSystem::NormalizePath(outputPath); - - return AssetsImportingManager::Create(tag, outputPath); -} - DEFINE_INTERNAL_CALL(bool) EditorInternal_CreateVisualScript(MString* outputPathObj, MString* baseTypenameObj) { String outputPath; @@ -634,13 +562,11 @@ bool ManagedEditor::Import(const String& inputPath, const String& outputPath, co bool ManagedEditor::TryRestoreImportOptions(ModelTool::Options& options, String assetPath) { - // Initialize defaults + // Initialize defaults if (const auto* graphicsSettings = GraphicsSettings::Get()) { options.GenerateSDF = graphicsSettings->GenerateSDFOnModelImport; } - - // Get options from model FileSystem::NormalizePath(assetPath); return ImportModel::TryGetImportOptions(assetPath, options); } @@ -652,7 +578,12 @@ bool ManagedEditor::Import(const String& inputPath, const String& outputPath, co bool ManagedEditor::TryRestoreImportOptions(AudioTool::Options& options, String assetPath) { - // Get options from model FileSystem::NormalizePath(assetPath); return ImportAudio::TryGetImportOptions(assetPath, options); } + +bool ManagedEditor::CreateAsset(const String& tag, String outputPath) +{ + FileSystem::NormalizePath(outputPath); + return AssetsImportingManager::Create(tag, outputPath); +} diff --git a/Source/Editor/Managed/ManagedEditor.h b/Source/Editor/Managed/ManagedEditor.h index 8c9571cfd..6aa7514b0 100644 --- a/Source/Editor/Managed/ManagedEditor.h +++ b/Source/Editor/Managed/ManagedEditor.h @@ -210,6 +210,13 @@ public: API_FUNCTION() static bool TryRestoreImportOptions(API_PARAM(Ref) AudioTool::Options& options, String assetPath); #endif + /// + /// Creates a new asset at the target location. + /// + /// New asset type. + /// Output asset path. + API_FUNCTION() static bool CreateAsset(const String& tag, String outputPath); + public: API_STRUCT(Internal, NoDefault) struct VisualScriptStackFrame { diff --git a/Source/Engine/ContentImporters/AssetsImportingManager.cpp b/Source/Engine/ContentImporters/AssetsImportingManager.cpp index a0b28f4f8..5fb5ff0c0 100644 --- a/Source/Engine/ContentImporters/AssetsImportingManager.cpp +++ b/Source/Engine/ContentImporters/AssetsImportingManager.cpp @@ -234,7 +234,6 @@ bool AssetsImportingManager::Create(const String& tag, const StringView& outputP LOG(Warning, "Cannot find asset creator object for tag \'{0}\'.", tag); return true; } - return Create(creator->Callback, outputPath, assetId, arg); } diff --git a/Source/Engine/ContentImporters/Types.h b/Source/Engine/ContentImporters/Types.h index 388c533a8..0275edc35 100644 --- a/Source/Engine/ContentImporters/Types.h +++ b/Source/Engine/ContentImporters/Types.h @@ -113,7 +113,7 @@ private: /// /// Asset importer entry /// -struct AssetImporter +struct FLAXENGINE_API AssetImporter { public: /// @@ -135,7 +135,7 @@ public: /// /// Asset creator entry /// -struct AssetCreator +struct FLAXENGINE_API AssetCreator { public: /// From a2b8312fbaed18af9bad302fe15b6d21fd36988f Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 18 Feb 2024 19:48:43 +0100 Subject: [PATCH 120/139] Cleanup stuff in #2019 --- .gitignore | 2 - Content/Editor/Fonts/NotoSansSC-Regular.flax | 3 + Source/Editor/EditorAssets.cs | 5 +- Source/Editor/GUI/Row.cs | 6 +- .../Editor/GUI/Timeline/GUI/PositionHandle.cs | 20 +- Source/Editor/Options/InterfaceOptions.cs | 12 +- Source/Editor/Options/OptionsModule.cs | 11 +- Source/Editor/Windows/OutputLogWindow.cs | 12 +- Source/Editor/Windows/SplashScreen.cpp | 8 +- Source/Engine/Core/Config/GameSettings.h | 2 - Source/Engine/Core/Config/GraphicsSettings.h | 15 +- Source/Engine/Graphics/Graphics.cpp | 8 +- Source/Engine/Render2D/FallbackFonts.cpp | 11 - Source/Engine/Render2D/FallbackFonts.h | 119 ---- Source/Engine/Render2D/Font.cpp | 509 +--------------- Source/Engine/Render2D/Font.h | 572 ++---------------- Source/Engine/Render2D/FontAsset.cpp | 28 +- Source/Engine/Render2D/FontAsset.h | 7 +- Source/Engine/Render2D/FontManager.cpp | 7 +- Source/Engine/Render2D/Render2D.cpp | 368 +---------- Source/Engine/Render2D/Render2D.cs | 6 +- Source/Engine/Render2D/Render2D.h | 144 +---- Source/Engine/Scripting/Scripting.cs | 14 +- Source/Engine/UI/GUI/Common/Label.cs | 27 +- .../UI/GUI/Common/RichTextBox.Parsing.cs | 1 - .../Engine/UI/GUI/Common/RichTextBox.Tags.cs | 42 +- .../Engine/UI/GUI/Common/RichTextBoxBase.cs | 1 - Source/Engine/UI/GUI/Common/TextBox.cs | 42 +- Source/Engine/UI/GUI/Common/TextBoxBase.cs | 4 +- Source/Engine/UI/TextRender.cpp | 2 +- 30 files changed, 252 insertions(+), 1756 deletions(-) create mode 100644 Content/Editor/Fonts/NotoSansSC-Regular.flax delete mode 100644 Source/Engine/Render2D/FallbackFonts.cpp delete mode 100644 Source/Engine/Render2D/FallbackFonts.h diff --git a/.gitignore b/.gitignore index b653b7f77..30c2caeb2 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,6 @@ Source/*.Gen.* Source/*.csproj /Package_*/ !Source/Engine/Debug -/Source/Platforms/Editor/Linux/Mono/etc/mono/registry PackageEditor_Cert.command PackageEditor_Cert.bat PackagePlatforms_Cert.bat @@ -157,4 +156,3 @@ obj/ .idea/ *.code-workspace omnisharp.json -Content/Editor/Fonts/NotoSansSC-Regular.flax diff --git a/Content/Editor/Fonts/NotoSansSC-Regular.flax b/Content/Editor/Fonts/NotoSansSC-Regular.flax new file mode 100644 index 000000000..39964e6c5 --- /dev/null +++ b/Content/Editor/Fonts/NotoSansSC-Regular.flax @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:19fa43eb7b31ee3936348b1f271c464c79d7020a21d33e3cdbe54f98c3b14304 +size 10560899 diff --git a/Source/Editor/EditorAssets.cs b/Source/Editor/EditorAssets.cs index c894abe6b..8b2049ebd 100644 --- a/Source/Editor/EditorAssets.cs +++ b/Source/Editor/EditorAssets.cs @@ -54,7 +54,10 @@ namespace FlaxEditor /// public static string PrimaryFont = "Editor/Fonts/Roboto-Regular"; - public static string CjkFont = "Editor/Fonts/NotoSansSC-Regular"; + /// + /// The secondary (fallback) font to use for missing characters rendering (CJK - Chinese/Japanese/Korean characters). + /// + public static string FallbackFont = "Editor/Fonts/NotoSansSC-Regular"; /// /// The Inconsolata Regular font. diff --git a/Source/Editor/GUI/Row.cs b/Source/Editor/GUI/Row.cs index f75f11922..3e2bb63bd 100644 --- a/Source/Editor/GUI/Row.cs +++ b/Source/Editor/GUI/Row.cs @@ -43,9 +43,9 @@ namespace FlaxEditor.GUI { Depth = -1; - var mediumHeight = Style.Current.FontMedium.GetMaxHeight(); - if (Height < mediumHeight) - Height = mediumHeight + 4; + var fontHeight = Style.Current.FontMedium.Height; + if (Height < fontHeight) + Height = fontHeight + 4; } /// diff --git a/Source/Editor/GUI/Timeline/GUI/PositionHandle.cs b/Source/Editor/GUI/Timeline/GUI/PositionHandle.cs index 3f835a35f..bedb61a5e 100644 --- a/Source/Editor/GUI/Timeline/GUI/PositionHandle.cs +++ b/Source/Editor/GUI/Timeline/GUI/PositionHandle.cs @@ -36,16 +36,16 @@ namespace FlaxEditor.GUI.Timeline.GUI string labelText; switch (_timeline.TimeShowMode) { - case Timeline.TimeShowModes.Frames: - labelText = _timeline.CurrentFrame.ToString("###0", CultureInfo.InvariantCulture); - break; - case Timeline.TimeShowModes.Seconds: - labelText = _timeline.CurrentTime.ToString("###0.##'s'", CultureInfo.InvariantCulture); - break; - case Timeline.TimeShowModes.Time: - labelText = TimeSpan.FromSeconds(_timeline.CurrentTime).ToString("g"); - break; - default: throw new ArgumentOutOfRangeException(); + case Timeline.TimeShowModes.Frames: + labelText = _timeline.CurrentFrame.ToString("###0", CultureInfo.InvariantCulture); + break; + case Timeline.TimeShowModes.Seconds: + labelText = _timeline.CurrentTime.ToString("###0.##'s'", CultureInfo.InvariantCulture); + break; + case Timeline.TimeShowModes.Time: + labelText = TimeSpan.FromSeconds(_timeline.CurrentTime).ToString("g"); + break; + default: throw new ArgumentOutOfRangeException(); } var color = (_timeline.IsMovingPositionHandle ? style.ProgressNormal : style.Foreground).AlphaMultiplied(0.6f); Matrix3x3.RotationZ(Mathf.PiOverTwo, out var m1); diff --git a/Source/Editor/Options/InterfaceOptions.cs b/Source/Editor/Options/InterfaceOptions.cs index af432baa7..6180b5a66 100644 --- a/Source/Editor/Options/InterfaceOptions.cs +++ b/Source/Editor/Options/InterfaceOptions.cs @@ -172,9 +172,9 @@ namespace FlaxEditor.Options set { if (value == null) - _outputLogFont = new FontReference(FlaxEngine.Content.LoadAsyncInternal(EditorAssets.InconsolataRegularFont), 10); + _outputLogFont = new FontReference(ConsoleFont, 10); else if (!value.Font) - _outputLogFont.Font = FlaxEngine.Content.LoadAsyncInternal(EditorAssets.InconsolataRegularFont); + _outputLogFont.Font = ConsoleFont; else _outputLogFont = value; } @@ -237,8 +237,7 @@ namespace FlaxEditor.Options public int NumberOfGameClientsToLaunch = 1; private static FontAsset DefaultFont => FlaxEngine.Content.LoadAsyncInternal(EditorAssets.PrimaryFont); - - private static FontAsset ConsoleFont => FlaxEngine.Content.LoadAsyncInternal(EditorAssets.PrimaryFont); + private static FontAsset ConsoleFont => FlaxEngine.Content.LoadAsyncInternal(EditorAssets.InconsolataRegularFont); private FontReference _titleFont = new FontReference(DefaultFont, 18); private FontReference _largeFont = new FontReference(DefaultFont, 14); @@ -247,9 +246,10 @@ namespace FlaxEditor.Options private FontReference _outputLogFont = new FontReference(ConsoleFont, 10); /// - /// The fallback fonts. + /// The list of fallback fonts to use when main text font is missing certain characters. Empty to use fonts from GraphicsSettings. /// - public FontAsset[] Fallbacks = [FlaxEngine.Content.LoadAsyncInternal(EditorAssets.CjkFont)]; + [EditorDisplay("Fonts"), EditorOrder(650)] + public FontAsset[] FallbackFonts = [FlaxEngine.Content.LoadAsyncInternal(EditorAssets.FallbackFont)]; /// /// Gets or sets the title font for editor UI. diff --git a/Source/Editor/Options/OptionsModule.cs b/Source/Editor/Options/OptionsModule.cs index bf35105a5..bb90be6d9 100644 --- a/Source/Editor/Options/OptionsModule.cs +++ b/Source/Editor/Options/OptionsModule.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using FlaxEditor.Content.Settings; using FlaxEditor.Modules; using FlaxEngine; @@ -225,11 +226,11 @@ namespace FlaxEditor.Options } } - var graphicsSetttings = GameSettings.Load(); - if (graphicsSetttings.EnableFontFallback && graphicsSetttings.FallbackFonts == null) - { - Render2D.FallbackFonts = graphicsSetttings.FallbackFonts = FontFallbackList.Create(Options.Interface.Fallbacks); - } + // Set fallback fonts + var fallbackFonts = Options.Interface.FallbackFonts; + if (fallbackFonts == null || fallbackFonts.Length == 0 || fallbackFonts.All(x => x == null)) + fallbackFonts = GameSettings.Load().FallbackFonts; + Font.FallbackFonts = fallbackFonts; } /// diff --git a/Source/Editor/Windows/OutputLogWindow.cs b/Source/Editor/Windows/OutputLogWindow.cs index f168d8d45..6526d7c8a 100644 --- a/Source/Editor/Windows/OutputLogWindow.cs +++ b/Source/Editor/Windows/OutputLogWindow.cs @@ -562,12 +562,12 @@ namespace FlaxEditor.Windows { switch (match.Groups["level"].Value) { - case "error": - textBlock.Style = _output.ErrorStyle; - break; - case "warning": - textBlock.Style = _output.WarningStyle; - break; + case "error": + textBlock.Style = _output.ErrorStyle; + break; + case "warning": + textBlock.Style = _output.WarningStyle; + break; } textBlock.Tag = new TextBlockTag { diff --git a/Source/Editor/Windows/SplashScreen.cpp b/Source/Editor/Windows/SplashScreen.cpp index cba59191e..92894b537 100644 --- a/Source/Editor/Windows/SplashScreen.cpp +++ b/Source/Editor/Windows/SplashScreen.cpp @@ -258,13 +258,13 @@ void SplashScreen::OnDraw() return; // Title - const auto titleLength = _titleFont->MeasureTextInternal(GetTitle()); + const auto titleLength = _titleFont->MeasureText(GetTitle()); TextLayoutOptions layout; layout.Bounds = Rectangle(10 * s, 10 * s, width - 10 * s, 50 * s); layout.HorizontalAlignment = TextAlignment::Near; layout.VerticalAlignment = TextAlignment::Near; layout.Scale = Math::Min((width - 20 * s) / titleLength.X, 1.0f); - Render2D::DrawTextInternal(_titleFont, GetTitle(), Color::White, layout); + Render2D::DrawText(_titleFont, GetTitle(), Color::White, layout); // Subtitle String subtitle(_quote); @@ -279,14 +279,14 @@ void SplashScreen::OnDraw() layout.Scale = 1.0f; layout.HorizontalAlignment = TextAlignment::Far; layout.VerticalAlignment = TextAlignment::Far; - Render2D::DrawTextInternal(_subtitleFont, subtitle, Color::FromRGB(0x8C8C8C), layout); + Render2D::DrawText(_subtitleFont, subtitle, Color::FromRGB(0x8C8C8C), layout); // Additional info const float infoMargin = 6 * s; layout.Bounds = Rectangle(infoMargin, lightBarHeight + infoMargin, width - (2 * infoMargin), height - lightBarHeight - (2 * infoMargin)); layout.HorizontalAlignment = TextAlignment::Near; layout.VerticalAlignment = TextAlignment::Center; - Render2D::DrawTextInternal(_subtitleFont, _infoText, Color::FromRGB(0xFFFFFF) * 0.9f, layout); + Render2D::DrawText(_subtitleFont, _infoText, Color::FromRGB(0xFFFFFF) * 0.9f, layout); } bool SplashScreen::HasLoadedFonts() const diff --git a/Source/Engine/Core/Config/GameSettings.h b/Source/Engine/Core/Config/GameSettings.h index 675c26bd1..54ad29a7b 100644 --- a/Source/Engine/Core/Config/GameSettings.h +++ b/Source/Engine/Core/Config/GameSettings.h @@ -7,8 +7,6 @@ #include "Engine/Core/Types/String.h" #include "Engine/Core/Collections/Dictionary.h" -class FontFallbackList; - /// /// The main game engine configuration service. Loads and applies game configuration. /// diff --git a/Source/Engine/Core/Config/GraphicsSettings.h b/Source/Engine/Core/Config/GraphicsSettings.h index e109a55e4..b639b9e64 100644 --- a/Source/Engine/Core/Config/GraphicsSettings.h +++ b/Source/Engine/Core/Config/GraphicsSettings.h @@ -6,7 +6,7 @@ #include "Engine/Graphics/Enums.h" #include "Engine/Graphics/PostProcessSettings.h" -class FontFallbackList; +class FontAsset; /// /// Graphics rendering settings. @@ -15,6 +15,7 @@ API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings", NoConstructor) class { API_AUTO_SERIALIZATION(); DECLARE_SCRIPTING_TYPE_MINIMAL(GraphicsSettings); + public: /// /// Enables rendering synchronization with the refresh rate of the display device to avoid "tearing" artifacts. @@ -121,16 +122,10 @@ public: PostProcessSettings PostProcessSettings; /// - /// Whether to enable font fallbacking globally. + /// The list of fallback fonts used for text rendering. Ignored if empty. /// - API_FIELD(Attributes = "EditorOrder(12000), EditorDisplay(\"Text Render Settings\", EditorDisplayAttribute.InlineStyle)") - bool EnableFontFallback = true; - - /// - /// The fallback fonts used for text rendering, ignored if null. - /// - API_FIELD(Attributes = "EditorOrder(12005), EditorDisplay(\"Text Render Settings\", EditorDisplayAttribute.InlineStyle)") - FontFallbackList* FallbackFonts; + API_FIELD(Attributes="EditorOrder(5000), EditorDisplay(\"Text\")") + Array> FallbackFonts; private: /// diff --git a/Source/Engine/Graphics/Graphics.cpp b/Source/Engine/Graphics/Graphics.cpp index 20f29288b..a9bfef470 100644 --- a/Source/Engine/Graphics/Graphics.cpp +++ b/Source/Engine/Graphics/Graphics.cpp @@ -8,7 +8,7 @@ #include "Engine/Core/Config/GraphicsSettings.h" #include "Engine/Engine/CommandLine.h" #include "Engine/Engine/EngineService.h" -#include "Engine/Render2D/Render2D.h" +#include "Engine/Render2D/Font.h" bool Graphics::UseVSync = false; Quality Graphics::AAQuality = Quality::Medium; @@ -70,9 +70,9 @@ void GraphicsSettings::Apply() Graphics::GIQuality = GIQuality; Graphics::PostProcessSettings = ::PostProcessSettings(); Graphics::PostProcessSettings.BlendWith(PostProcessSettings, 1.0f); - - Render2D::EnableFontFallback = EnableFontFallback; - Render2D::FallbackFonts = FallbackFonts; +#if !USE_EDITOR // OptionsModule handles fallback fonts in Editor + Font::FallbackFonts = FallbackFonts; +#endif } void Graphics::DisposeDevice() diff --git a/Source/Engine/Render2D/FallbackFonts.cpp b/Source/Engine/Render2D/FallbackFonts.cpp deleted file mode 100644 index 7f81a42bb..000000000 --- a/Source/Engine/Render2D/FallbackFonts.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include "FallbackFonts.h" -#include "FontManager.h" -#include "Engine/Core/Math/Math.h" - -FontFallbackList::FontFallbackList(const Array& fonts) - : ManagedScriptingObject(SpawnParams(Guid::New(), Font::TypeInitializer)), - _fontAssets(fonts) -{ - -} - diff --git a/Source/Engine/Render2D/FallbackFonts.h b/Source/Engine/Render2D/FallbackFonts.h deleted file mode 100644 index fb247e4e2..000000000 --- a/Source/Engine/Render2D/FallbackFonts.h +++ /dev/null @@ -1,119 +0,0 @@ -#pragma once - -#include "Engine/Core/Collections/Array.h" -#include "Engine/Core/Collections/Dictionary.h" -#include "Font.h" -#include "FontAsset.h" - -struct TextRange; -class Font; -class FontAsset; - -/// -/// Defines a list of fonts that can be used as a fallback, ordered by priority. -/// -API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API FontFallbackList : public ManagedScriptingObject -{ - DECLARE_SCRIPTING_TYPE_NO_SPAWN(FontFallbackList); -private: - Array _fontAssets; - - // Cache fallback fonts of various sizes - Dictionary*> _cache; - -public: - /// - /// Initializes a new instance of the class. - /// - /// The fallback font assets. - FontFallbackList(const Array& fonts); - - /// - /// Initializes a new instance of the class, exposed for C#. - /// - /// The fallback font assets. - /// The new instance. - API_FUNCTION() FORCE_INLINE static FontFallbackList* Create(const Array& fonts) { - return New(fonts); - } - - /// - /// Get the parent assets of fallback fonts. - /// - /// The font assets. - API_PROPERTY() FORCE_INLINE Array& GetFonts() { - return _fontAssets; - } - - /// - /// Set the fallback fonts. - /// - /// The parent assets of the new fonts. - API_PROPERTY() FORCE_INLINE void SetFonts(const Array& val) { - _fontAssets = val; - } - - /// - /// Gets the fallback fonts with the given size. - /// - /// The size. - /// The generated fonts. - API_FUNCTION() FORCE_INLINE Array& GetFontList(float size) { - Array* result; - if (_cache.TryGet(size, result)) { - return *result; - } - - result = New>(_fontAssets.Count()); - auto& arr = *result; - for (int32 i = 0; i < _fontAssets.Count(); i++) - { - arr.Add(_fontAssets[i]->CreateFont(size)); - } - - _cache[size] = result; - return *result; - } - - - /// - /// Gets the index of the fallback font that should be used to render the char - /// - /// The char. - /// The primary font. - /// The number to return if none of the fonts can render. - /// -1 if char can be rendered with primary font, index if it matches a fallback font. - API_FUNCTION() FORCE_INLINE int32 GetCharFallbackIndex(Char c, Font* primaryFont = nullptr, int32 missing = -1) { - if (primaryFont && primaryFont->GetAsset()->ContainsChar(c)) { - return -1; - } - - int32 fontIndex = 0; - while (fontIndex < _fontAssets.Count() && _fontAssets[fontIndex] && !_fontAssets[fontIndex]->ContainsChar(c)) - { - fontIndex++; - } - - if (fontIndex < _fontAssets.Count()) { - return fontIndex; - - } - - return missing; - } - - /// - /// Checks if every font is properly loaded. - /// - /// True if every font asset is non-null, otherwise false. - API_FUNCTION() FORCE_INLINE bool Verify() { - for (int32 i = 0; i < _fontAssets.Count(); i++) - { - if (!_fontAssets[i]) { - return false; - } - } - - return true; - } -}; diff --git a/Source/Engine/Render2D/Font.cpp b/Source/Engine/Render2D/Font.cpp index 9a2037b3f..71eca1f59 100644 --- a/Source/Engine/Render2D/Font.cpp +++ b/Source/Engine/Render2D/Font.cpp @@ -6,7 +6,8 @@ #include "Engine/Core/Log.h" #include "Engine/Threading/Threading.h" #include "IncludeFreeType.h" -#include "FallbackFonts.h" + +Array, HeapAllocation> Font::FallbackFonts; Font::Font(FontAsset* parentAsset, float size) : ManagedScriptingObject(SpawnParams(Guid::New(), Font::TypeInitializer)) @@ -33,7 +34,7 @@ Font::~Font() _asset->_fonts.Remove(this); } -void Font::GetCharacter(Char c, FontCharacterEntry& result) +void Font::GetCharacter(Char c, FontCharacterEntry& result, bool enableFallback) { // Try to get the character or cache it if cannot be found if (!_characters.TryGet(c, result)) @@ -45,6 +46,20 @@ void Font::GetCharacter(Char c, FontCharacterEntry& result) if (_characters.TryGet(c, result)) return; + // Try to use fallback font if character is missing + if (enableFallback && !_asset->ContainsChar(c)) + { + for (int32 fallbackIndex = 0; fallbackIndex < FallbackFonts.Count(); fallbackIndex++) + { + FontAsset* fallbackFont = FallbackFonts.Get()[fallbackIndex].Get(); + if (fallbackFont && fallbackFont->ContainsChar(c)) + { + fallbackFont->CreateFont(GetSize())->GetCharacter(c, result, enableFallback); + return; + } + } + } + // Create character cache FontManager::AddNewEntry(this, c, result); @@ -88,7 +103,7 @@ void Font::CacheText(const StringView& text) FontCharacterEntry entry; for (int32 i = 0; i < text.Length(); i++) { - GetCharacter(text[i], entry); + GetCharacter(text[i], entry, false); } } @@ -103,26 +118,16 @@ void Font::Invalidate() _characters.Clear(); } -float Font::GetMaxHeight(FontFallbackList* fallbacks) const -{ - float height = GetHeight(); - auto& fallbackFonts = fallbacks->GetFontList(GetSize()); - for (int32 i = 0; i < fallbackFonts.Count(); i++) - { - height = Math::Max(height, static_cast(fallbackFonts[i]->GetHeight())); - } - - return height; -} - void Font::ProcessText(const StringView& text, Array& outputLines, const TextLayoutOptions& layout) { + int32 textLength = text.Length(); + if (textLength == 0) + return; float cursorX = 0; int32 kerning; FontLineCache tmpLine; FontCharacterEntry entry; FontCharacterEntry previous; - int32 textLength = text.Length(); float scale = layout.Scale / FontManager::FontScale; float boundsWidth = layout.Bounds.GetWidth(); float baseLinesDistance = static_cast(_height) * layout.BaseLinesGapScale * scale; @@ -131,10 +136,6 @@ void Font::ProcessText(const StringView& text, Array& outputLines tmpLine.FirstCharIndex = 0; tmpLine.LastCharIndex = -1; - if (textLength == 0) { - return; - } - int32 lastWrapCharIndex = INVALID_INDEX; float lastWrapCharX = 0; bool lastMoveLine = false; @@ -146,11 +147,6 @@ void Font::ProcessText(const StringView& text, Array& outputLines float xAdvance = 0; int32 nextCharIndex = currentIndex + 1; - // Submit line if text ends - if (nextCharIndex == textLength) { - moveLine = true; - } - // Cache current character const Char currentChar = text[currentIndex]; const bool isWhitespace = StringUtils::IsWhitespace(currentChar); @@ -168,6 +164,7 @@ void Font::ProcessText(const StringView& text, Array& outputLines { // Break line moveLine = true; + currentIndex++; tmpLine.LastCharIndex++; } else @@ -178,7 +175,7 @@ void Font::ProcessText(const StringView& text, Array& outputLines // Get kerning if (!isWhitespace && previous.IsValid) { - kerning = GetKerning(previous.Character, entry.Character); + kerning = entry.Font->GetKerning(previous.Character, entry.Character); } else { @@ -247,8 +244,8 @@ void Font::ProcessText(const StringView& text, Array& outputLines // Reset line tmpLine.Location.Y += baseLinesDistance; - tmpLine.FirstCharIndex = nextCharIndex; - tmpLine.LastCharIndex = nextCharIndex - 1; + tmpLine.FirstCharIndex = currentIndex; + tmpLine.LastCharIndex = currentIndex - 1; cursorX = 0; lastWrapCharIndex = INVALID_INDEX; lastWrapCharX = 0; @@ -260,7 +257,7 @@ void Font::ProcessText(const StringView& text, Array& outputLines } // Check if an additional line should be created - if (text[textLength - 1] == '\n') + if (tmpLine.LastCharIndex >= tmpLine.FirstCharIndex || text[textLength - 1] == '\n') { // Add line tmpLine.Size.X = cursorX; @@ -305,262 +302,7 @@ void Font::ProcessText(const StringView& text, Array& outputLines } } -void Font::ProcessText(FontFallbackList* fallbacks, const StringView& text, Array& outputLines, API_PARAM(Ref) const TextLayoutOptions& layout) -{ - const Array& fallbackFonts = fallbacks->GetFontList(GetSize()); - float cursorX = 0; - int32 kerning; - BlockedTextLineCache tmpLine; - FontBlockCache tmpBlock; - FontCharacterEntry entry; - FontCharacterEntry previous; - int32 textLength = text.Length(); - float scale = layout.Scale / FontManager::FontScale; - float boundsWidth = layout.Bounds.GetWidth(); - float baseLinesDistanceScale = layout.BaseLinesGapScale * scale; - - tmpBlock.Location = Float2::Zero; - tmpBlock.Size = Float2::Zero; - tmpBlock.FirstCharIndex = 0; - tmpBlock.LastCharIndex = -1; - - tmpLine.Location = Float2::Zero; - tmpLine.Size = Float2::Zero; - tmpLine.Blocks = Array(); - - if (textLength == 0) { - return; - } - - int32 lastWrapCharIndex = INVALID_INDEX; - float lastWrapCharX = 0; - bool lastMoveLine = false; - // The index of the font used by the current block - int32 currentFontIndex = fallbacks->GetCharFallbackIndex(text[0], this); - // The maximum font height of the current line - float maxHeight = 0; - float maxAscender = 0; - float lastCursorX = 0; - - auto getFont = [&](int32 index)->Font* { - return index >= 0 ? fallbackFonts[index] : this; - }; - - // Process each character to split text into single blocks - for (int32 currentIndex = 0; currentIndex < textLength;) - { - bool moveLine = false; - bool moveBlock = false; - float xAdvance = 0; - int32 nextCharIndex = currentIndex + 1; - - // Submit line and block if text ends - if (nextCharIndex == textLength) { - moveLine = moveBlock = true; - } - - // Cache current character - const Char currentChar = text[currentIndex]; - const bool isWhitespace = StringUtils::IsWhitespace(currentChar); - - // Check if character can wrap words - const bool isWrapChar = !StringUtils::IsAlnum(currentChar) || isWhitespace || StringUtils::IsUpper(currentChar) || (currentChar >= 0x3040 && currentChar <= 0x9FFF); - if (isWrapChar && currentIndex != 0) - { - lastWrapCharIndex = currentIndex; - lastWrapCharX = cursorX; - } - - int32 nextFontIndex = currentFontIndex; - // Check if it's a newline character - if (currentChar == '\n') - { - // Break line - moveLine = moveBlock = true; - tmpBlock.LastCharIndex++; - } - else - { - // Get character entry - if (nextCharIndex < textLength) { - nextFontIndex = fallbacks->GetCharFallbackIndex(text[nextCharIndex], this, currentFontIndex); - } - - // Get character entry - getFont(currentFontIndex)->GetCharacter(currentChar, entry); - - maxHeight = Math::Max(maxHeight, - static_cast(getFont(currentFontIndex)->GetHeight())); - maxAscender = Math::Max(maxAscender, - static_cast(getFont(currentFontIndex)->GetAscender())); - - // Move block if the font changes or text ends - if (nextFontIndex != currentFontIndex || nextCharIndex == textLength) { - moveBlock = true; - } - - // Get kerning, only when the font hasn't changed - if (!isWhitespace && previous.IsValid && !moveBlock) - { - kerning = getFont(currentFontIndex)->GetKerning(previous.Character, entry.Character); - } - else - { - kerning = 0; - } - previous = entry; - xAdvance = (kerning + entry.AdvanceX) * scale; - - // Check if character fits the line or skip wrapping - if (cursorX + xAdvance <= boundsWidth || layout.TextWrapping == TextWrapping::NoWrap) - { - // Move character - cursorX += xAdvance; - tmpBlock.LastCharIndex++; - } - else if (layout.TextWrapping == TextWrapping::WrapWords) - { - if (lastWrapCharIndex != INVALID_INDEX) - { - // Skip moving twice for the same character - int32 lastLineLastCharIndex = outputLines.HasItems() && outputLines.Last().Blocks.HasItems() ? outputLines.Last().Blocks.Last().LastCharIndex : -10000; - if (lastLineLastCharIndex == lastWrapCharIndex || lastLineLastCharIndex == lastWrapCharIndex - 1 || lastLineLastCharIndex == lastWrapCharIndex - 2) - { - currentIndex = nextCharIndex; - lastMoveLine = moveLine; - continue; - } - - // Move line - const Char wrapChar = text[lastWrapCharIndex]; - moveLine = true; - moveBlock = tmpBlock.FirstCharIndex < lastWrapCharIndex; - - cursorX = lastWrapCharX; - if (StringUtils::IsWhitespace(wrapChar)) - { - // Skip whitespaces - tmpBlock.LastCharIndex = lastWrapCharIndex - 1; - nextCharIndex = currentIndex = lastWrapCharIndex + 1; - } - else - { - tmpBlock.LastCharIndex = lastWrapCharIndex - 1; - nextCharIndex = currentIndex = lastWrapCharIndex; - } - } - } - else if (layout.TextWrapping == TextWrapping::WrapChars) - { - // Move line - moveLine = true; - moveBlock = tmpBlock.FirstCharIndex < currentChar; - nextCharIndex = currentIndex; - - // Skip moving twice for the same character - if (lastMoveLine) - break; - } - } - - if (moveBlock) { - // Add block - tmpBlock.Size.X = lastCursorX - cursorX; - tmpBlock.Size.Y = baseLinesDistanceScale * getFont(currentFontIndex)->GetHeight(); - tmpBlock.LastCharIndex = Math::Max(tmpBlock.LastCharIndex, tmpBlock.FirstCharIndex); - tmpBlock.FallbackFontIndex = currentFontIndex; - tmpLine.Blocks.Add(tmpBlock); - - // Reset block - tmpBlock.Location.X = cursorX; - tmpBlock.FirstCharIndex = nextCharIndex; - tmpBlock.LastCharIndex = nextCharIndex - 1; - - currentFontIndex = nextFontIndex; - lastCursorX = cursorX; - } - - // Check if move to another line - if (moveLine) - { - // Add line - tmpLine.Size.X = cursorX; - tmpLine.Size.Y = baseLinesDistanceScale * maxHeight; - tmpLine.MaxAscender = maxAscender; - outputLines.Add(tmpLine); - - // Reset line - tmpLine.Blocks.Clear(); - tmpLine.Location.Y += baseLinesDistanceScale * maxHeight; - cursorX = 0; - tmpBlock.Location.X = cursorX; - lastWrapCharIndex = INVALID_INDEX; - lastWrapCharX = 0; - previous.IsValid = false; - - // Reset max font height - maxHeight = 0; - maxAscender = 0; - lastCursorX = 0; - } - - currentIndex = nextCharIndex; - lastMoveLine = moveLine; - } - - // Check if an additional line should be created - if (text[textLength - 1] == '\n') - { - // Add line - tmpLine.Size.X = cursorX; - tmpLine.Size.Y = baseLinesDistanceScale * maxHeight; - outputLines.Add(tmpLine); - - tmpLine.Location.Y += baseLinesDistanceScale * maxHeight; - } - - // Check amount of lines - if (outputLines.IsEmpty()) - return; - - float totalHeight = tmpLine.Location.Y; - - Float2 offset = Float2::Zero; - if (layout.VerticalAlignment == TextAlignment::Center) - { - offset.Y += (layout.Bounds.GetHeight() - totalHeight) * 0.5f; - } - else if (layout.VerticalAlignment == TextAlignment::Far) - { - offset.Y += layout.Bounds.GetHeight() - totalHeight; - } - for (int32 i = 0; i < outputLines.Count(); i++) - { - BlockedTextLineCache& line = outputLines[i]; - Float2 rootPos = line.Location + offset; - - // Fix upper left line corner to match desire text alignment - if (layout.HorizontalAlignment == TextAlignment::Center) - { - rootPos.X += (layout.Bounds.GetWidth() - line.Size.X) * 0.5f; - } - else if (layout.HorizontalAlignment == TextAlignment::Far) - { - rootPos.X += layout.Bounds.GetWidth() - line.Size.X; - } - - line.Location = rootPos; - - // Align all blocks to center in case they have different heights - for (int32 j = 0; j < line.Blocks.Count(); j++) - { - FontBlockCache& block = line.Blocks[j]; - block.Location.Y += (line.MaxAscender - getFont(block.FallbackFontIndex)->GetAscender()) / 2; - } - } -} - -Float2 Font::MeasureTextInternal(const StringView& text, const TextLayoutOptions& layout) +Float2 Font::MeasureText(const StringView& text, const TextLayoutOptions& layout) { // Check if there is no need to do anything if (text.IsEmpty()) @@ -581,28 +323,7 @@ Float2 Font::MeasureTextInternal(const StringView& text, const TextLayoutOptions return max; } -Float2 Font::MeasureTextInternal(FontFallbackList* fallbacks, const StringView& text, const TextLayoutOptions& layout) -{ - // Check if there is no need to do anything - if (text.IsEmpty()) - return Float2::Zero; - - // Process text - Array lines; - ProcessText(fallbacks, text, lines, layout); - - // Calculate bounds - Float2 max = Float2::Zero; - for (int32 i = 0; i < lines.Count(); i++) - { - const BlockedTextLineCache& line = lines[i]; - max = Float2::Max(max, line.Location + line.Size); - } - - return max; -} - -int32 Font::HitTestTextInternal(const StringView& text, const Float2& location, const TextLayoutOptions& layout) +int32 Font::HitTestText(const StringView& text, const Float2& location, const TextLayoutOptions& layout) { // Check if there is no need to do anything if (text.Length() <= 0) @@ -639,7 +360,7 @@ int32 Font::HitTestTextInternal(const StringView& text, const Float2& location, // Apply kerning if (!isWhitespace && previous.IsValid) { - x += GetKerning(previous.Character, entry.Character); + x += entry.Font->GetKerning(previous.Character, entry.Character); } previous = entry; @@ -676,107 +397,7 @@ int32 Font::HitTestTextInternal(const StringView& text, const Float2& location, return smallestIndex; } -int32 Font::HitTestTextInternal(FontFallbackList* fallbacks, const StringView& text, const Float2& location, const TextLayoutOptions& layout) -{ - // Check if there is no need to do anything - if (text.Length() <= 0) - return 0; - - // Process text - const Array& fallbackFonts = fallbacks->GetFontList(GetSize()); - Array lines; - ProcessText(fallbacks, text, lines, layout); - ASSERT(lines.HasItems()); - float scale = layout.Scale / FontManager::FontScale; - - // Offset position to match lines origin space - Float2 rootOffset = layout.Bounds.Location + lines.First().Location; - Float2 testPoint = location - rootOffset; - - // Get block which may intersect with the position (it's possible because lines have fixed height) - int32 lineIndex = 0; - while (lineIndex < lines.Count()) - { - if (lines[lineIndex].Location.Y + lines[lineIndex].Size.Y >= location.Y) { - break; - } - - lineIndex++; - } - lineIndex = Math::Clamp(lineIndex, 0, lines.Count() - 1); - const BlockedTextLineCache& line = lines[lineIndex]; - - int32 blockIndex = 0; - while (blockIndex < line.Blocks.Count() - 1) - { - if (line.Location.X + line.Blocks[blockIndex + 1].Location.X >= location.X) { - break; - } - - blockIndex++; - } - const FontBlockCache& block = line.Blocks[blockIndex]; - float x = line.Location.X; - - // Check all characters in the line to find hit point - FontCharacterEntry previous; - FontCharacterEntry entry; - int32 smallestIndex = INVALID_INDEX; - float dst, smallestDst = MAX_float; - - auto getFont = [&](int32 index)->Font* { - return index >= 0 ? fallbackFonts[index] : this; - }; - - for (int32 currentIndex = block.FirstCharIndex; currentIndex <= block.LastCharIndex; currentIndex++) - { - // Cache current character - const Char currentChar = text[currentIndex]; - - getFont(block.FallbackFontIndex)->GetCharacter(currentChar, entry); - const bool isWhitespace = StringUtils::IsWhitespace(currentChar); - - // Apply kerning - if (!isWhitespace && previous.IsValid) - { - x += getFont(block.FallbackFontIndex)->GetKerning(previous.Character, entry.Character); - } - previous = entry; - - // Test - dst = Math::Abs(testPoint.X - x); - if (dst < smallestDst) - { - // Found closer character - smallestIndex = currentIndex; - smallestDst = dst; - } - else if (dst > smallestDst) - { - // Current char is worse so return the best result - return smallestIndex; - } - - // Move - x += entry.AdvanceX * scale; - } - - // Test line end edge - dst = Math::Abs(testPoint.X - x); - if (dst < smallestDst) - { - // Pointer is behind the last character in the line - smallestIndex = block.LastCharIndex; - - // Fix for last line - if (lineIndex == lines.Count() - 1) - smallestIndex++; - } - - return smallestIndex; -} - -Float2 Font::GetCharPositionInternal(const StringView& text, int32 index, const TextLayoutOptions& layout) +Float2 Font::GetCharPosition(const StringView& text, int32 index, const TextLayoutOptions& layout) { // Check if there is no need to do anything if (text.IsEmpty()) @@ -788,7 +409,7 @@ Float2 Font::GetCharPositionInternal(const StringView& text, int32 index, const ASSERT(lines.HasItems()); float scale = layout.Scale / FontManager::FontScale; float baseLinesDistance = static_cast(_height) * layout.BaseLinesGapScale * scale; - Float2 rootOffset = layout.Bounds.Location; + Float2 rootOffset = layout.Bounds.Location + lines.First().Location; // Find line with that position FontCharacterEntry previous; @@ -813,7 +434,7 @@ Float2 Font::GetCharPositionInternal(const StringView& text, int32 index, const // Apply kerning if (!isWhitespace && previous.IsValid) { - x += GetKerning(previous.Character, entry.Character); + x += entry.Font->GetKerning(previous.Character, entry.Character); } previous = entry; @@ -827,71 +448,7 @@ Float2 Font::GetCharPositionInternal(const StringView& text, int32 index, const } // Position after last character in the last line - return rootOffset + Float2(lines.Last().Location.X + lines.Last().Size.X, static_cast((lines.Count() - 1) * baseLinesDistance)); -} - -Float2 Font::GetCharPositionInternal(FontFallbackList* fallbacks, const StringView& text, int32 index, const TextLayoutOptions& layout) -{ - // Check if there is no need to do anything - if (text.IsEmpty()) - return layout.Bounds.Location; - - // Process text - const Array& fallbackFonts = fallbacks->GetFontList(GetSize()); - Array lines; - ProcessText(fallbacks, text, lines, layout); - ASSERT(lines.HasItems()); - float scale = layout.Scale / FontManager::FontScale; - float baseLinesDistance = layout.BaseLinesGapScale * scale; - Float2 rootOffset = layout.Bounds.Location; - - // Find line with that position - FontCharacterEntry previous; - FontCharacterEntry entry; - - auto getFont = [&](int32 index)->Font* { - return index >= 0 ? fallbackFonts[index] : this; - }; - - for (int32 lineIndex = 0; lineIndex < lines.Count(); lineIndex++) - { - const BlockedTextLineCache& line = lines[lineIndex]; - for (int32 blockIndex = 0; blockIndex < line.Blocks.Count(); blockIndex++) - { - const FontBlockCache& block = line.Blocks[blockIndex]; - // Check if desire position is somewhere inside characters in line range - if (Math::IsInRange(index, block.FirstCharIndex, block.LastCharIndex)) - { - float x = line.Location.X + block.Location.X; - float y = line.Location.Y + block.Location.Y; - - // Check all characters in the line - for (int32 currentIndex = block.FirstCharIndex; currentIndex < index; currentIndex++) - { - // Cache current character - const Char currentChar = text[currentIndex]; - getFont(block.FallbackFontIndex)->GetCharacter(currentChar, entry); - const bool isWhitespace = StringUtils::IsWhitespace(currentChar); - - // Apply kerning - if (!isWhitespace && previous.IsValid) - { - x += getFont(block.FallbackFontIndex)->GetKerning(previous.Character, entry.Character); - } - previous = entry; - - // Move - x += entry.AdvanceX * scale; - } - - // Upper left corner of the character - return rootOffset + Float2(x, y); - } - } - } - - // Position after last character in the last line - return rootOffset + Float2(lines.Last().Location.X + lines.Last().Size.X, lines.Last().Location.Y); + return rootOffset + Float2(lines.Last().Size.X, static_cast((lines.Count() - 1) * baseLinesDistance)); } void Font::FlushFaceSize() const diff --git a/Source/Engine/Render2D/Font.h b/Source/Engine/Render2D/Font.h index 1fb41d032..cd92103cb 100644 --- a/Source/Engine/Render2D/Font.h +++ b/Source/Engine/Render2D/Font.h @@ -8,12 +8,9 @@ #include "Engine/Content/AssetReference.h" #include "Engine/Scripting/ScriptingObject.h" #include "TextLayoutOptions.h" -#include "Render2D.h" class FontAsset; -class FontFallbackList; struct FontTextureAtlasSlot; -struct BlockedTextLineCache; // The default DPI that engine is using #define DefaultDPI 96 @@ -38,7 +35,7 @@ API_STRUCT(NoDefault) struct TextRange /// /// Gets the range length. /// - int32 Length() const + FORCE_INLINE int32 Length() const { return EndIndex - StartIndex; } @@ -46,7 +43,7 @@ API_STRUCT(NoDefault) struct TextRange /// /// Gets a value indicating whether range is empty. /// - bool IsEmpty() const + FORCE_INLINE bool IsEmpty() const { return (EndIndex - StartIndex) <= 0; } @@ -56,7 +53,7 @@ API_STRUCT(NoDefault) struct TextRange /// /// The index. /// true if range contains the specified character index; otherwise, false. - bool Contains(int32 index) const + FORCE_INLINE bool Contains(int32 index) const { return index >= StartIndex && index < EndIndex; } @@ -122,74 +119,6 @@ struct TIsPODType enum { Value = true }; }; -/// -/// The font block info generated during text processing. -/// A block means a range of text that belongs to the same line and can be rendered with the same font. -/// -API_STRUCT(NoDefault) struct FontBlockCache -{ - DECLARE_SCRIPTING_TYPE_MINIMAL(FontBlockCache); - - /// - /// The root position of the block (upper left corner), relative to line. - /// - API_FIELD() Float2 Location; - - /// - /// The size of the current block - /// - API_FIELD() Float2 Size; - - /// - /// The first character index (from the input text). - /// - API_FIELD() int32 FirstCharIndex; - - /// - /// The last character index (from the input text), inclusive. - /// - API_FIELD() int32 LastCharIndex; - - /// - /// Indicates the fallback font to render this block with, -1 if doesn't require fallback. - /// - API_FIELD() int32 FallbackFontIndex; -}; - -template<> -struct TIsPODType -{ - enum { Value = true }; -}; - -/// -/// Line of font blocks info generated during text processing. -/// -API_STRUCT(NoDefault) struct BlockedTextLineCache -{ - DECLARE_SCRIPTING_TYPE_MINIMAL(BlockedTextLineCache); - - /// - /// The root position of the line (upper left corner). - /// - API_FIELD() Float2 Location; - - /// - /// The line bounds (width and height). - /// - API_FIELD() Float2 Size; - - /// - /// The maximum ascender of the line. - /// - API_FIELD() float MaxAscender; - - /// - /// The blocks that belongs to this line - /// - API_FIELD() Array Blocks; -}; - // Font glyph metrics: // // xmin xmax @@ -281,6 +210,11 @@ API_STRUCT(NoDefault) struct FontCharacterEntry /// The slot in texture atlas, containing the pixel data of the glyph. /// API_FIELD() const FontTextureAtlasSlot* Slot; + + /// + /// The owner font. + /// + API_FIELD() const class Font* Font; }; template<> @@ -296,8 +230,8 @@ API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API Font : public ManagedScriptingOb { DECLARE_SCRIPTING_TYPE_NO_SPAWN(Font); friend FontAsset; -private: +private: FontAsset* _asset; float _size; int32 _height; @@ -309,7 +243,6 @@ private: mutable Dictionary _kerningTable; public: - /// /// Initializes a new instance of the class. /// @@ -323,6 +256,10 @@ public: ~Font(); public: + /// + /// The active fallback fonts. + /// + API_FIELD() static Array, HeapAllocation> FallbackFonts; /// /// Gets parent font asset that contains font family used by this font. @@ -373,13 +310,13 @@ public: } public: - /// /// Gets character entry. /// /// The character. /// The output character entry. - void GetCharacter(Char c, FontCharacterEntry& result); + /// True if fallback to secondary font when the primary font doesn't contains this character. + void GetCharacter(Char c, FontCharacterEntry& result, bool enableFallback = true); /// /// Gets the kerning amount for a pair of characters. @@ -401,33 +338,8 @@ public: API_FUNCTION() void Invalidate(); public: - /// - /// Gets the maximum height among the font and the fallback fonts. - /// - /// The fallback fonts. - /// The maximum height. - API_FUNCTION() float GetMaxHeight(FontFallbackList* fallbacks) const; - - /// - /// Gets the maximum height among the font and the fallback fonts, uses the default font defined in . - /// - /// The fallback fonts. - /// The maximum height. - API_FUNCTION() FORCE_INLINE float GetMaxHeight() const - { - if (Render2D::EnableFontFallback && Render2D::FallbackFonts) - { - return GetMaxHeight(Render2D::FallbackFonts); - } - else - { - return GetHeight(); - } - } - - /// - /// Processes text to get cached lines for rendering, with font fallbacking disabled. + /// Processes text to get cached lines for rendering. /// /// The input text. /// The layout properties. @@ -435,12 +347,12 @@ public: void ProcessText(const StringView& text, Array& outputLines, API_PARAM(Ref) const TextLayoutOptions& layout); /// - /// Processes text to get cached lines for rendering, with font fallbacking disabled. + /// Processes text to get cached lines for rendering. /// /// The input text. /// The layout properties. /// The output lines list. - API_FUNCTION() FORCE_INLINE Array ProcessText(const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout) + API_FUNCTION() Array ProcessText(const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout) { Array lines; ProcessText(text, lines, layout); @@ -448,13 +360,13 @@ public: } /// - /// Processes text to get cached lines for rendering, with font fallbacking disabled. + /// Processes text to get cached lines for rendering. /// /// The input text. /// The input text range (substring range of the input text parameter). /// The layout properties. /// The output lines list. - API_FUNCTION() FORCE_INLINE Array ProcessText(const StringView& text, API_PARAM(Ref) const TextRange& textRange, API_PARAM(Ref) const TextLayoutOptions& layout) + API_FUNCTION() Array ProcessText(const StringView& text, API_PARAM(Ref) const TextRange& textRange, API_PARAM(Ref) const TextLayoutOptions& layout) { Array lines; ProcessText(textRange.Substring(text), lines, layout); @@ -462,7 +374,7 @@ public: } /// - /// Processes text to get cached lines for rendering, with font fallbacking disabled. + /// Processes text to get cached lines for rendering. /// /// The input text. /// The output lines list. @@ -472,7 +384,7 @@ public: } /// - /// Processes text to get cached lines for rendering, with font fallbacking disabled. + /// Processes text to get cached lines for rendering. /// /// The input text. /// The input text range (substring range of the input text parameter). @@ -483,349 +395,81 @@ public: } /// - /// Processes text to get cached lines for rendering, using custom fallback options. - /// - /// The input text. - /// The layout properties. - /// The output lines list. - void ProcessText(FontFallbackList* fallbacks, const StringView& text, Array& outputLines, API_PARAM(Ref) const TextLayoutOptions& layout); - - /// - /// Processes text to get cached lines for rendering, using custom fallback options. - /// - /// The input text. - /// The layout properties. - /// The output lines list. - API_FUNCTION() FORCE_INLINE Array ProcessText(FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout) - { - Array lines; - ProcessText(fallbacks, text, lines, layout); - return lines; - } - - /// - /// Processes text to get cached lines for rendering, using custom fallback options. - /// - /// The input text. - /// The input text range (substring range of the input text parameter). - /// The layout properties. - /// The output lines list. - API_FUNCTION() FORCE_INLINE Array ProcessText(FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange, API_PARAM(Ref) const TextLayoutOptions& layout) - { - Array lines; - ProcessText(fallbacks, textRange.Substring(text), lines, layout); - return lines; - } - - /// - /// Processes text to get cached lines for rendering, using custom fallback options. - /// - /// The input text. - /// The output lines list. - API_FUNCTION() FORCE_INLINE Array ProcessText(FontFallbackList* fallbacks, const StringView& text) - { - return ProcessText(fallbacks, text, TextLayoutOptions()); - } - - /// - /// Processes text to get cached lines for rendering, using custom fallback options. - /// - /// The input text. - /// The input text range (substring range of the input text parameter). - /// The output lines list. - API_FUNCTION() FORCE_INLINE Array ProcessText(FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange) - { - return ProcessText(fallbacks, textRange.Substring(text), TextLayoutOptions()); - } - - /// - /// Measures minimum size of the rectangle that will be needed to draw given text, with font fallbacking disabled. + /// Measures minimum size of the rectangle that will be needed to draw given text. /// /// The input text to test. /// The layout properties. /// The minimum size for that text and fot to render properly. - API_FUNCTION() Float2 MeasureTextInternal(const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout); + API_FUNCTION() Float2 MeasureText(const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout); /// - /// Measures minimum size of the rectangle that will be needed to draw given text, with font fallbacking disabled. + /// Measures minimum size of the rectangle that will be needed to draw given text. /// /// The input text to test. /// The input text range (substring range of the input text parameter). /// The layout properties. /// The minimum size for that text and fot to render properly. - API_FUNCTION() FORCE_INLINE Float2 MeasureTextInternal(const StringView& text, API_PARAM(Ref) const TextRange& textRange, API_PARAM(Ref) const TextLayoutOptions& layout) + API_FUNCTION() Float2 MeasureText(const StringView& text, API_PARAM(Ref) const TextRange& textRange, API_PARAM(Ref) const TextLayoutOptions& layout) { - return MeasureTextInternal(textRange.Substring(text), layout); + return MeasureText(textRange.Substring(text), layout); } /// - /// Measures minimum size of the rectangle that will be needed to draw given text, with font fallbacking disabled. - /// . - /// The input text to test. - /// The minimum size for that text and fot to render properly. - API_FUNCTION() FORCE_INLINE Float2 MeasureTextInternal(const StringView& text) - { - return MeasureTextInternal(text, TextLayoutOptions()); - } - - /// - /// Measures minimum size of the rectangle that will be needed to draw given text, with font fallbacking disabled. - /// . - /// The input text to test. - /// The input text range (substring range of the input text parameter). - /// The minimum size for that text and fot to render properly. - API_FUNCTION() FORCE_INLINE Float2 MeasureTextInternal(const StringView& text, API_PARAM(Ref) const TextRange& textRange) - { - return MeasureTextInternal(textRange.Substring(text), TextLayoutOptions()); - } - - /// - /// Measures minimum size of the rectangle that will be needed to draw given text, using custom fallback options. - /// - /// The input text to test. - /// The layout properties. - /// The minimum size for that text and fot to render properly. - API_FUNCTION() Float2 MeasureTextInternal(FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout); - - /// - /// Measures minimum size of the rectangle that will be needed to draw given text, using custom fallback options. - /// - /// The input text to test. - /// The input text range (substring range of the input text parameter). - /// The layout properties. - /// The minimum size for that text and fot to render properly. - API_FUNCTION() FORCE_INLINE Float2 MeasureTextInternal(FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange, API_PARAM(Ref) const TextLayoutOptions& layout) - { - return MeasureTextInternal(fallbacks, textRange.Substring(text), layout); - } - - /// - /// Measures minimum size of the rectangle that will be needed to draw given text, using custom fallback options. - /// . - /// The input text to test. - /// The minimum size for that text and fot to render properly. - API_FUNCTION() FORCE_INLINE Float2 MeasureTextInternal(FontFallbackList* fallbacks, const StringView& text) - { - return MeasureTextInternal(fallbacks, text, TextLayoutOptions()); - } - - /// - /// Measures minimum size of the rectangle that will be needed to draw given text, using custom fallback options. - /// . - /// The input text to test. - /// The input text range (substring range of the input text parameter). - /// The minimum size for that text and fot to render properly. - API_FUNCTION() FORCE_INLINE Float2 MeasureTextInternal(FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange) - { - return MeasureTextInternal(fallbacks, textRange.Substring(text), TextLayoutOptions()); - } - - /// - /// Measures minimum size of the rectangle that will be needed to draw given text, follows the fallback settings defined in . - /// - /// The input text to test. - /// The layout properties. - /// The minimum size for that text and fot to render properly. - API_FUNCTION() FORCE_INLINE Float2 MeasureText(const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout) { - if (Render2D::EnableFontFallback && Render2D::FallbackFonts) { - return MeasureTextInternal(Render2D::FallbackFonts, text, layout); - } - else { - return MeasureTextInternal(text, layout); - } - } - - /// - /// Measures minimum size of the rectangle that will be needed to draw given text, follows the fallback settings defined in . - /// - /// The input text to test. - /// The input text range (substring range of the input text parameter). - /// The layout properties. - /// The minimum size for that text and fot to render properly. - API_FUNCTION() FORCE_INLINE Float2 MeasureText(const StringView& text, API_PARAM(Ref) const TextRange& textRange, API_PARAM(Ref) const TextLayoutOptions& layout) - { - if (Render2D::EnableFontFallback && Render2D::FallbackFonts) { - return MeasureTextInternal(Render2D::FallbackFonts, textRange.Substring(text), layout); - } - else { - return MeasureTextInternal(textRange.Substring(text), layout); - } - } - - /// - /// Measures minimum size of the rectangle that will be needed to draw given text, follows the fallback settings defined in . + /// Measures minimum size of the rectangle that will be needed to draw given text /// . /// The input text to test. /// The minimum size for that text and fot to render properly. API_FUNCTION() FORCE_INLINE Float2 MeasureText(const StringView& text) { - if (Render2D::EnableFontFallback && Render2D::FallbackFonts) { - return MeasureTextInternal(Render2D::FallbackFonts, text, TextLayoutOptions()); - } - else { - return MeasureTextInternal(text, TextLayoutOptions()); - } + return MeasureText(text, TextLayoutOptions()); } /// - /// Measures minimum size of the rectangle that will be needed to draw given text, follows the fallback settings defined in . + /// Measures minimum size of the rectangle that will be needed to draw given text /// . /// The input text to test. /// The input text range (substring range of the input text parameter). /// The minimum size for that text and fot to render properly. API_FUNCTION() FORCE_INLINE Float2 MeasureText(const StringView& text, API_PARAM(Ref) const TextRange& textRange) { - if (Render2D::EnableFontFallback && Render2D::FallbackFonts) { - return MeasureTextInternal(Render2D::FallbackFonts, textRange.Substring(text), TextLayoutOptions()); - } - else { - return MeasureTextInternal(textRange.Substring(text), TextLayoutOptions()); - } + return MeasureText(textRange.Substring(text), TextLayoutOptions()); } /// - /// Calculates hit character index at given location, with font fallbacking disabled. - /// - /// The input text to test. - /// The input location to test. - /// The text layout properties. - /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). - API_FUNCTION() int32 HitTestTextInternal(const StringView& text, const Float2& location, API_PARAM(Ref) const TextLayoutOptions& layout); - - /// - /// Calculates hit character index at given location, with font fallbacking disabled. + /// Calculates hit character index at given location. /// /// The input text to test. /// The input text range (substring range of the input text parameter). /// The input location to test. /// The text layout properties. /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). - API_FUNCTION() FORCE_INLINE int32 HitTestTextInternal(const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Float2& location, API_PARAM(Ref) const TextLayoutOptions& layout) + API_FUNCTION() int32 HitTestText(const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Float2& location, API_PARAM(Ref) const TextLayoutOptions& layout) { - return HitTestTextInternal(textRange.Substring(text), location, layout); + return HitTestText(textRange.Substring(text), location, layout); } /// - /// Calculates hit character index at given location, with font fallbacking disabled. - /// - /// The input text to test. - /// The input location to test. - /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). - API_FUNCTION() FORCE_INLINE int32 HitTestTextInternal(const StringView& text, const Float2& location) - { - return HitTestTextInternal(text, location, TextLayoutOptions()); - } - - /// - /// Calculates hit character index at given location, with font fallbacking disabled. - /// - /// The input text to test. - /// The input text range (substring range of the input text parameter). - /// The input location to test. - /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). - API_FUNCTION() FORCE_INLINE int32 HitTestTextInternal(const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Float2& location) - { - return HitTestTextInternal(textRange.Substring(text), location, TextLayoutOptions()); - } - - /// - /// Calculates hit character index at given location, using custom fallback options. + /// Calculates hit character index at given location. /// /// The input text to test. /// The input location to test. /// The text layout properties. /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). - API_FUNCTION() int32 HitTestTextInternal(FontFallbackList* fallbacks, const StringView& text, const Float2& location, API_PARAM(Ref) const TextLayoutOptions& layout); + API_FUNCTION() int32 HitTestText(const StringView& text, const Float2& location, API_PARAM(Ref) const TextLayoutOptions& layout); /// - /// Calculates hit character index at given location, using custom fallback options. - /// - /// The input text to test. - /// The input text range (substring range of the input text parameter). - /// The input location to test. - /// The text layout properties. - /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). - API_FUNCTION() FORCE_INLINE int32 HitTestTextInternal(FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Float2& location, API_PARAM(Ref) const TextLayoutOptions& layout) - { - return HitTestTextInternal(fallbacks, textRange.Substring(text), location, layout); - } - - /// - /// Calculates hit character index at given location, using custom fallback options. - /// - /// The input text to test. - /// The input location to test. - /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). - API_FUNCTION() FORCE_INLINE int32 HitTestTextInternal(FontFallbackList* fallbacks, const StringView& text, const Float2& location) - { - return HitTestTextInternal(fallbacks, text, location, TextLayoutOptions()); - } - - /// - /// Calculates hit character index at given location, using custom fallback options. - /// - /// The input text to test. - /// The input text range (substring range of the input text parameter). - /// The input location to test. - /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). - API_FUNCTION() FORCE_INLINE int32 HitTestTextInternal(FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Float2& location) - { - return HitTestTextInternal(fallbacks, textRange.Substring(text), location, TextLayoutOptions()); - } - - /// - /// Calculates hit character index at given location, follows the fallback settings defined in . - /// - /// The input text to test. - /// The input location to test. - /// The text layout properties. - /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). - API_FUNCTION() FORCE_INLINE int32 HitTestText(const StringView& text, const Float2& location, API_PARAM(Ref) const TextLayoutOptions& layout) { - if (Render2D::EnableFontFallback && Render2D::FallbackFonts) { - return HitTestTextInternal(Render2D::FallbackFonts, text, location, layout); - } - else { - return HitTestTextInternal(text, location, layout); - } - } - - /// - /// Calculates hit character index at given location, follows the fallback settings defined in . - /// - /// The input text to test. - /// The input text range (substring range of the input text parameter). - /// The input location to test. - /// The text layout properties. - /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). - API_FUNCTION() FORCE_INLINE int32 HitTestText(const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Float2& location, API_PARAM(Ref) const TextLayoutOptions& layout) - { - if (Render2D::EnableFontFallback && Render2D::FallbackFonts) { - return HitTestTextInternal(Render2D::FallbackFonts, textRange.Substring(text), location, layout); - } - else { - return HitTestTextInternal(textRange.Substring(text), location, layout); - } - - } - - /// - /// Calculates hit character index at given location, follows the fallback settings defined in . + /// Calculates hit character index at given location. /// /// The input text to test. /// The input location to test. /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). API_FUNCTION() FORCE_INLINE int32 HitTestText(const StringView& text, const Float2& location) { - if (Render2D::EnableFontFallback && Render2D::FallbackFonts) { - return HitTestTextInternal(Render2D::FallbackFonts, text, location, TextLayoutOptions()); - } - else { - return HitTestTextInternal(text, location, TextLayoutOptions()); - } + return HitTestText(text, location, TextLayoutOptions()); } /// - /// Calculates hit character index at given location, follows the fallback settings defined in . + /// Calculates hit character index at given location. /// /// The input text to test. /// The input text range (substring range of the input text parameter). @@ -833,156 +477,44 @@ public: /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). API_FUNCTION() FORCE_INLINE int32 HitTestText(const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Float2& location) { - if (Render2D::EnableFontFallback && Render2D::FallbackFonts) { - return HitTestTextInternal(Render2D::FallbackFonts, textRange.Substring(text), location, TextLayoutOptions()); - } - else { - return HitTestTextInternal(textRange.Substring(text), location, TextLayoutOptions()); - } + return HitTestText(textRange.Substring(text), location, TextLayoutOptions()); } /// - /// Calculates character position for given text and character index, with font fallbacking disabled. + /// Calculates character position for given text and character index. /// /// The input text to test. /// The text position to get coordinates of. /// The text layout properties. /// The character position (upper left corner which can be used for a caret position). - API_FUNCTION() Float2 GetCharPositionInternal(const StringView& text, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout); + API_FUNCTION() Float2 GetCharPosition(const StringView& text, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout); /// - /// Calculates character position for given text and character index, with font fallbacking disabled. + /// Calculates character position for given text and character index. /// /// The input text to test. /// The input text range (substring range of the input text parameter). /// The text position to get coordinates of. /// The text layout properties. /// The character position (upper left corner which can be used for a caret position). - API_FUNCTION() FORCE_INLINE Float2 GetCharPositionInternal(const StringView& text, API_PARAM(Ref) const TextRange& textRange, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout) + API_FUNCTION() Float2 GetCharPosition(const StringView& text, API_PARAM(Ref) const TextRange& textRange, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout) { - return GetCharPositionInternal(textRange.Substring(text), index, layout); + return GetCharPosition(textRange.Substring(text), index, layout); } /// - /// Calculates character position for given text and character index, with font fallbacking disabled. - /// - /// The input text to test. - /// The text position to get coordinates of. - /// The character position (upper left corner which can be used for a caret position). - API_FUNCTION() FORCE_INLINE Float2 GetCharPositionInternal(const StringView& text, int32 index) - { - return GetCharPositionInternal(text, index, TextLayoutOptions()); - } - - /// - /// Calculates character position for given text and character index, with font fallbacking disabled. - /// - /// The input text to test. - /// The input text range (substring range of the input text parameter). - /// The text position to get coordinates of. - /// The character position (upper left corner which can be used for a caret position). - API_FUNCTION() FORCE_INLINE Float2 GetCharPositionInternal(const StringView& text, API_PARAM(Ref) const TextRange& textRange, int32 index) - { - return GetCharPositionInternal(textRange.Substring(text), index, TextLayoutOptions()); - } - - /// - /// Calculates character position for given text and character index, using custom fallback options. - /// - /// The input text to test. - /// The text position to get coordinates of. - /// The text layout properties. - /// The character position (upper left corner which can be used for a caret position). - API_FUNCTION() Float2 GetCharPositionInternal(FontFallbackList* fallbacks, const StringView& text, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout); - - /// - /// Calculates character position for given text and character index, using custom fallback options. - /// - /// The input text to test. - /// The input text range (substring range of the input text parameter). - /// The text position to get coordinates of. - /// The text layout properties. - /// The character position (upper left corner which can be used for a caret position). - API_FUNCTION() FORCE_INLINE Float2 GetCharPositionInternal(FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout) - { - return GetCharPositionInternal(fallbacks, textRange.Substring(text), index, layout); - } - - /// - /// Calculates character position for given text and character index, using custom fallback options. - /// - /// The input text to test. - /// The text position to get coordinates of. - /// The character position (upper left corner which can be used for a caret position). - API_FUNCTION() FORCE_INLINE Float2 GetCharPositionInternal(FontFallbackList* fallbacks, const StringView& text, int32 index) - { - return GetCharPositionInternal(fallbacks, text, index, TextLayoutOptions()); - } - - /// - /// Calculates character position for given text and character index, using custom fallback options. - /// - /// The input text to test. - /// The input text range (substring range of the input text parameter). - /// The text position to get coordinates of. - /// The character position (upper left corner which can be used for a caret position). - API_FUNCTION() FORCE_INLINE Float2 GetCharPositionInternal(FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange, int32 index) - { - return GetCharPositionInternal(fallbacks, textRange.Substring(text), index, TextLayoutOptions()); - } - - /// - /// Calculates character position for given text and character index, follows the fallback settings defined in . - /// - /// The input text to test. - /// The text position to get coordinates of. - /// The text layout properties. - /// The character position (upper left corner which can be used for a caret position). - API_FUNCTION() FORCE_INLINE Float2 GetCharPosition(const StringView& text, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout) { - if (Render2D::EnableFontFallback && Render2D::FallbackFonts) { - return GetCharPositionInternal(Render2D::FallbackFonts, text, index, layout); - } - else { - return GetCharPositionInternal(text, index, layout); - } - } - - /// - /// Calculates character position for given text and character index, follows the fallback settings defined in . - /// - /// The input text to test. - /// The input text range (substring range of the input text parameter). - /// The text position to get coordinates of. - /// The text layout properties. - /// The character position (upper left corner which can be used for a caret position). - API_FUNCTION() FORCE_INLINE Float2 GetCharPosition(const StringView& text, API_PARAM(Ref) const TextRange& textRange, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout) - { - if (Render2D::EnableFontFallback && Render2D::FallbackFonts) { - return GetCharPositionInternal(Render2D::FallbackFonts, textRange.Substring(text), index, layout); - } - else { - return GetCharPositionInternal(textRange.Substring(text), index, layout); - } - } - - /// - /// Calculates character position for given text and character index, follows the fallback settings defined in . + /// Calculates character position for given text and character index /// /// The input text to test. /// The text position to get coordinates of. /// The character position (upper left corner which can be used for a caret position). API_FUNCTION() FORCE_INLINE Float2 GetCharPosition(const StringView& text, int32 index) { - if (Render2D::EnableFontFallback && Render2D::FallbackFonts) { - return GetCharPositionInternal(Render2D::FallbackFonts, text, index, TextLayoutOptions()); - } - else { - return GetCharPositionInternal(text, index, TextLayoutOptions()); - } + return GetCharPosition(text, index, TextLayoutOptions()); } /// - /// Calculates character position for given text and character index, follows the fallback settings defined in . + /// Calculates character position for given text and character index /// /// The input text to test. /// The input text range (substring range of the input text parameter). @@ -990,12 +522,7 @@ public: /// The character position (upper left corner which can be used for a caret position). API_FUNCTION() FORCE_INLINE Float2 GetCharPosition(const StringView& text, API_PARAM(Ref) const TextRange& textRange, int32 index) { - if (Render2D::EnableFontFallback && Render2D::FallbackFonts) { - return GetCharPositionInternal(Render2D::FallbackFonts, textRange.Substring(text), index, TextLayoutOptions()); - } - else { - return GetCharPositionInternal(textRange.Substring(text), index, TextLayoutOptions()); - } + return GetCharPosition(textRange.Substring(text), index, TextLayoutOptions()); } /// @@ -1004,7 +531,6 @@ public: void FlushFaceSize() const; public: - // [Object] String ToString() const override; }; diff --git a/Source/Engine/Render2D/FontAsset.cpp b/Source/Engine/Render2D/FontAsset.cpp index ea629367f..53fbdc930 100644 --- a/Source/Engine/Render2D/FontAsset.cpp +++ b/Source/Engine/Render2D/FontAsset.cpp @@ -199,25 +199,29 @@ bool FontAsset::Save(const StringView& path) #endif - -/// -/// Check if the font contains the glyph of a char -/// -/// The char to test. -/// True if the font contains the glyph of the char, otherwise false. - -bool FontAsset::ContainsChar(Char c) const { - return FT_Get_Char_Index(GetFTFace(), c) > 0; +bool FontAsset::ContainsChar(Char c) const +{ + return FT_Get_Char_Index(_face, c) > 0; } void FontAsset::Invalidate() { ScopeLock lock(Locker); - for (auto font : _fonts) - { font->Invalidate(); - } +} + +uint64 FontAsset::GetMemoryUsage() const +{ + Locker.Lock(); + uint64 result = BinaryAsset::GetMemoryUsage(); + result += sizeof(FontAsset) - sizeof(BinaryAsset); + result += sizeof(FT_FaceRec); + result += _fontFile.Length(); + for (auto font : _fonts) + result += sizeof(Font); + Locker.Unlock(); + return result; } bool FontAsset::init(AssetInitData& initData) diff --git a/Source/Engine/Render2D/FontAsset.h b/Source/Engine/Render2D/FontAsset.h index f3c909911..a93276bf1 100644 --- a/Source/Engine/Render2D/FontAsset.h +++ b/Source/Engine/Render2D/FontAsset.h @@ -93,6 +93,7 @@ API_CLASS(NoSpawn) class FLAXENGINE_API FontAsset : public BinaryAsset { DECLARE_BINARY_ASSET_HEADER(FontAsset, 3); friend Font; + private: FT_Face _face; FontOptions _options; @@ -175,7 +176,7 @@ public: #endif /// - /// Check if the font contains the glyph of a char + /// Check if the font contains the glyph of a char. /// /// The char to test. /// True if the font contains the glyph of the char, otherwise false. @@ -186,6 +187,10 @@ public: /// API_FUNCTION() void Invalidate(); +public: + // [BinaryAsset] + uint64 GetMemoryUsage() const override; + protected: // [BinaryAsset] bool init(AssetInitData& initData) override; diff --git a/Source/Engine/Render2D/FontManager.cpp b/Source/Engine/Render2D/FontManager.cpp index 5b08c1d87..2e1ba8c6b 100644 --- a/Source/Engine/Render2D/FontManager.cpp +++ b/Source/Engine/Render2D/FontManager.cpp @@ -27,7 +27,6 @@ using namespace FontManagerImpl; class FontManagerService : public EngineService { public: - FontManagerService() : EngineService(TEXT("Font Manager"), -700) { @@ -155,9 +154,12 @@ bool FontManager::AddNewEntry(Font* font, Char c, FontCharacterEntry& entry) // Get the index to the glyph in the font face const FT_UInt glyphIndex = FT_Get_Char_Index(face, c); - if (glyphIndex == 0) { +#if !BUILD_RELEASE + if (glyphIndex == 0) + { LOG(Warning, "Font `{}` doesn't contain character `\\u{:x}`, consider choosing another font. ", String(face->family_name), c); } +#endif // Load the glyph const FT_Error error = FT_Load_Glyph(face, glyphIndex, glyphFlags); @@ -287,6 +289,7 @@ bool FontManager::AddNewEntry(Font* font, Char c, FontCharacterEntry& entry) entry.UVSize.X = static_cast(slot->Width - 2 * padding); entry.UVSize.Y = static_cast(slot->Height - 2 * padding); entry.Slot = slot; + entry.Font = font; return false; } diff --git a/Source/Engine/Render2D/Render2D.cpp b/Source/Engine/Render2D/Render2D.cpp index fa52baff0..34cb2c693 100644 --- a/Source/Engine/Render2D/Render2D.cpp +++ b/Source/Engine/Render2D/Render2D.cpp @@ -3,7 +3,6 @@ #include "Render2D.h" #include "Font.h" #include "FontManager.h" -#include "FallbackFonts.h" #include "FontTextureAtlas.h" #include "RotatedRectangle.h" #include "SpriteAtlas.h" @@ -55,7 +54,7 @@ const bool DownsampleForBlur = false; PACK_STRUCT(struct Data { Matrix ViewProjection; -}); + }); PACK_STRUCT(struct BlurData { Float2 InvBufferSize; @@ -63,7 +62,7 @@ PACK_STRUCT(struct BlurData { float Dummy0; Float4 Bounds; Float4 WeightAndOffsets[RENDER2D_BLUR_MAX_SAMPLES / 2]; -}); + }); enum class DrawCallType : byte { @@ -181,9 +180,7 @@ struct ClipMask Rectangle Bounds; }; -Render2D::RenderingFeatures Render2D::Features = RenderingFeatures::VertexSnapping; -bool Render2D::EnableFontFallback = true; -FontFallbackList* Render2D::FallbackFonts = nullptr; +Render2D::RenderingFeatures Render2D::Features = RenderingFeatures::VertexSnapping | RenderingFeatures::FallbackFonts; namespace { @@ -197,7 +194,6 @@ namespace // Drawing Array DrawCalls; Array Lines; - Array BlockedTextLines; Array Lines2; bool IsScissorsRectEmpty; bool IsScissorsRectEnabled; @@ -1141,12 +1137,12 @@ void DrawBatch(int32 startIndex, int32 count) } // Draw - Context->BindVB(ToSpan(&vb, 1)); // TODO: reduce bindings frequency - Context->BindIB(ib); // TODO: reduce bindings frequency + Context->BindVB(ToSpan(&vb, 1)); + Context->BindIB(ib); Context->DrawIndexed(countIb, 0, d.StartIB); } -void Render2D::DrawTextInternal(Font* font, const StringView& text, const Color& color, const Float2& location, MaterialBase* customMaterial) +void Render2D::DrawText(Font* font, const StringView& text, const Color& color, const Float2& location, MaterialBase* customMaterial) { RENDER2D_CHECK_RENDERING_STATE; @@ -1163,6 +1159,7 @@ void Render2D::DrawTextInternal(Font* font, const StringView& text, const Color& FontCharacterEntry previous; int32 kerning; float scale = 1.0f / FontManager::FontScale; + const bool enableFallbackFonts = EnumHasAllFlags(Features, RenderingFeatures::FallbackFonts); // Render all characters FontCharacterEntry entry; @@ -1178,7 +1175,7 @@ void Render2D::DrawTextInternal(Font* font, const StringView& text, const Color& drawCall.AsChar.Mat = nullptr; } Float2 pointer = location; - for (int32 currentIndex = 0; currentIndex < text.Length(); currentIndex++) + for (int32 currentIndex = 0; currentIndex <= text.Length(); currentIndex++) { // Cache current character const Char currentChar = text[currentIndex]; @@ -1187,7 +1184,7 @@ void Render2D::DrawTextInternal(Font* font, const StringView& text, const Color& if (currentChar != '\n') { // Get character entry - font->GetCharacter(currentChar, entry); + font->GetCharacter(currentChar, entry, enableFallbackFonts); // Check if need to select/change font atlas (since characters even in the same font may be located in different atlases) if (fontAtlas == nullptr || entry.TextureIndex != fontAtlasIndex) @@ -1214,7 +1211,7 @@ void Render2D::DrawTextInternal(Font* font, const StringView& text, const Color& // Get kerning if (!isWhitespace && previous.IsValid) { - kerning = font->GetKerning(previous.Character, entry.Character); + kerning = entry.Font->GetKerning(previous.Character, entry.Character); } else { @@ -1254,12 +1251,12 @@ void Render2D::DrawTextInternal(Font* font, const StringView& text, const Color& } } -void Render2D::DrawTextInternal(Font* font, const StringView& text, const TextRange& textRange, const Color& color, const Float2& location, MaterialBase* customMaterial) +void Render2D::DrawText(Font* font, const StringView& text, const TextRange& textRange, const Color& color, const Float2& location, MaterialBase* customMaterial) { - DrawTextInternal(font, textRange.Substring(text), color, location, customMaterial); + DrawText(font, textRange.Substring(text), color, location, customMaterial); } -void Render2D::DrawTextInternal(Font* font, const StringView& text, const Color& color, const TextLayoutOptions& layout, MaterialBase* customMaterial) +void Render2D::DrawText(Font* font, const StringView& text, const Color& color, const TextLayoutOptions& layout, MaterialBase* customMaterial) { RENDER2D_CHECK_RENDERING_STATE; @@ -1277,6 +1274,7 @@ void Render2D::DrawTextInternal(Font* font, const StringView& text, const Color& FontCharacterEntry previous; int32 kerning; float scale = layout.Scale / FontManager::FontScale; + const bool enableFallbackFonts = EnumHasAllFlags(Features, RenderingFeatures::FallbackFonts); // Process text to get lines Lines.Clear(); @@ -1303,10 +1301,14 @@ void Render2D::DrawTextInternal(Font* font, const StringView& text, const Color& // Render all characters from the line for (int32 charIndex = line.FirstCharIndex; charIndex <= line.LastCharIndex; charIndex++) { - const Char c = text[charIndex]; - if (c != '\n') + // Cache current character + const Char currentChar = text[charIndex]; + + // Check if it isn't a newline character + if (currentChar != '\n') { - font->GetCharacter(c, entry); + // Get character entry + font->GetCharacter(currentChar, entry, enableFallbackFonts); // Check if need to select/change font atlas (since characters even in the same font may be located in different atlases) if (fontAtlas == nullptr || entry.TextureIndex != fontAtlasIndex) @@ -1328,10 +1330,10 @@ void Render2D::DrawTextInternal(Font* font, const StringView& text, const Color& } // Get kerning - const bool isWhitespace = StringUtils::IsWhitespace(c); + const bool isWhitespace = StringUtils::IsWhitespace(currentChar); if (!isWhitespace && previous.IsValid) { - kerning = font->GetKerning(previous.Character, entry.Character); + kerning = entry.Font->GetKerning(previous.Character, entry.Character); } else { @@ -1367,317 +1369,9 @@ void Render2D::DrawTextInternal(Font* font, const StringView& text, const Color& } } -void Render2D::DrawTextInternal(Font* font, const StringView& text, const TextRange& textRange, const Color& color, const TextLayoutOptions& layout, MaterialBase* customMaterial) +void Render2D::DrawText(Font* font, const StringView& text, const TextRange& textRange, const Color& color, const TextLayoutOptions& layout, MaterialBase* customMaterial) { - DrawTextInternal(font, textRange.Substring(text), color, layout, customMaterial); -} - -void Render2D::DrawTextInternal(Font* font, FontFallbackList* fallbacks, const StringView& text, const Color& color, const Float2& location, MaterialBase* customMaterial) -{ - RENDER2D_CHECK_RENDERING_STATE; - - // Check if there is no need to do anything - if (font == nullptr || text.Length() < 0) - return; - - // Temporary data - const Array& fallbackFonts = fallbacks->GetFontList(font->GetSize()); - uint32 fontAtlasIndex = 0; - FontTextureAtlas* fontAtlas = nullptr; - Float2 invAtlasSize = Float2::One; - FontCharacterEntry previous; - int32 kerning; - float scale = 1.0f / FontManager::FontScale; - - // Process text to get lines - Array maxAscenders; - - // Render all characters - FontCharacterEntry entry; - Render2DDrawCall drawCall; - if (customMaterial) - { - drawCall.Type = DrawCallType::DrawCharMaterial; - drawCall.AsChar.Mat = customMaterial; - } - else - { - drawCall.Type = DrawCallType::DrawChar; - drawCall.AsChar.Mat = nullptr; - } - - int32 lineIndex = 0; - maxAscenders.Add(0); - - auto getFont = [&](int32 index)->Font* { - return index >= 0 ? fallbackFonts[index] : font; - }; - - // Preprocess the text to determine vertical offset of blocks - for (int32 currentIndex = 0; currentIndex < text.Length(); currentIndex++) - { - const Char c = text[currentIndex]; - if (c != '\n') { - int32 fontIndex = fallbacks->GetCharFallbackIndex(c, font); - maxAscenders[lineIndex] = Math::Max(maxAscenders[lineIndex], - static_cast(getFont(fontIndex)->GetAscender())); - } - else { - lineIndex++; - maxAscenders.Add(0); - } - } - - lineIndex = 0; - // The following code cut the text into blocks, according to the font used to render - Float2 pointer = location; - // The starting index of the current block - int32 startIndex = 0; - // The index of the font used by the current block - int32 currentFontIndex = fallbacks->GetCharFallbackIndex(text[0], font); - // The maximum font height of the current line - float maxHeight = 0; - for (int32 currentIndex = 0; currentIndex < text.Length(); currentIndex++) - { - // Cache current character - const Char currentChar = text[currentIndex]; - int32 nextCharIndex = currentIndex + 1; - bool moveBlock = false; - bool moveLine = false; - int32 nextFontIndex = currentFontIndex; - - // Submit block if text ends - if (nextCharIndex == text.Length()) { - moveBlock = true; - } - - // Check if it isn't a newline character - if (currentChar != '\n') - { - // Get character entry - if (nextCharIndex < text.Length()) { - nextFontIndex = fallbacks->GetCharFallbackIndex(text[nextCharIndex], font); - } - - if (nextFontIndex != currentFontIndex) { - moveBlock = true; - } - } - else - { - // Move - moveLine = moveBlock = true; - } - - if (moveBlock) { - // Render the pending block before beginning the new block - auto fontHeight = getFont(currentFontIndex)->GetHeight(); - maxHeight = Math::Max(maxHeight, static_cast(fontHeight)); - auto fontDescender = getFont(currentFontIndex)->GetDescender(); - for (int32 renderIndex = startIndex; renderIndex <= currentIndex; renderIndex++) - { - // Get character entry - getFont(currentFontIndex)->GetCharacter(text[renderIndex], entry); - - // Check if need to select/change font atlas (since characters even in the same font may be located in different atlases) - if (fontAtlas == nullptr || entry.TextureIndex != fontAtlasIndex) - { - // Get texture atlas that contains current character - fontAtlasIndex = entry.TextureIndex; - fontAtlas = FontManager::GetAtlas(fontAtlasIndex); - if (fontAtlas) - { - fontAtlas->EnsureTextureCreated(); - drawCall.AsChar.Tex = fontAtlas->GetTexture(); - invAtlasSize = 1.0f / fontAtlas->GetSize(); - } - else - { - drawCall.AsChar.Tex = nullptr; - invAtlasSize = 1.0f; - } - } - - // Check if character is a whitespace - const bool isWhitespace = StringUtils::IsWhitespace(text[renderIndex]); - - // Get kerning - if (!isWhitespace && previous.IsValid) - { - kerning = getFont(currentFontIndex)->GetKerning(previous.Character, entry.Character); - } - else - { - kerning = 0; - } - pointer.X += kerning * scale; - previous = entry; - - // Omit whitespace characters - if (!isWhitespace) - { - // Calculate character size and atlas coordinates - const float x = pointer.X + entry.OffsetX * scale; - const float y = pointer.Y + (fontHeight + fontDescender - entry.OffsetY) * scale; - - Rectangle charRect(x, y + (maxAscenders[lineIndex] - getFont(currentFontIndex)->GetAscender()) / 2, entry.UVSize.X * scale, entry.UVSize.Y * scale); - - Float2 upperLeftUV = entry.UV * invAtlasSize; - Float2 rightBottomUV = (entry.UV + entry.UVSize) * invAtlasSize; - - // Add draw call - drawCall.StartIB = IBIndex; - drawCall.CountIB = 6; - DrawCalls.Add(drawCall); - WriteRect(charRect, color, upperLeftUV, rightBottomUV); - } - - // Move - pointer.X += entry.AdvanceX * scale; - } - - if (moveLine) { - pointer.X = location.X; - pointer.Y += maxHeight * scale; - // Clear max height - maxHeight = 0; - lineIndex++; - } - - // Start new block - startIndex = nextCharIndex; - currentFontIndex = nextFontIndex; - } - } -} - -void Render2D::DrawTextInternal(Font* font, FontFallbackList* fallbacks, const StringView& text, const TextRange& textRange, const Color& color, const Float2& location, MaterialBase* customMaterial) -{ - DrawTextInternal(font, fallbacks, textRange.Substring(text), color, location, customMaterial); -} - -void Render2D::DrawTextInternal(Font* font, FontFallbackList* fallbacks, const StringView& text, const Color& color, const TextLayoutOptions& layout, MaterialBase* customMaterial) -{ - RENDER2D_CHECK_RENDERING_STATE; - - // Check if there is no need to do anything - if (font == nullptr || text.IsEmpty() || layout.Scale <= ZeroTolerance) - return; - - // Temporary data - const Array& fallbackFonts = fallbacks->GetFontList(font->GetSize()); - uint32 fontAtlasIndex = 0; - FontTextureAtlas* fontAtlas = nullptr; - Float2 invAtlasSize = Float2::One; - FontCharacterEntry previous; - int32 kerning; - float scale = layout.Scale / FontManager::FontScale; - - // Process text to get lines - BlockedTextLines.Clear(); - font->ProcessText(fallbacks, text, BlockedTextLines, layout); - - // Render all lines - FontCharacterEntry entry; - Render2DDrawCall drawCall; - if (customMaterial) - { - drawCall.Type = DrawCallType::DrawCharMaterial; - drawCall.AsChar.Mat = customMaterial; - } - else - { - drawCall.Type = DrawCallType::DrawChar; - drawCall.AsChar.Mat = nullptr; - } - - auto getFont = [&](int32 index)->Font* { - return index >= 0 ? fallbackFonts[index] : font; - }; - - for (int32 lineIndex = 0; lineIndex < BlockedTextLines.Count(); lineIndex++) - { - const BlockedTextLineCache& line = BlockedTextLines[lineIndex]; - for (int32 blockIndex = 0; blockIndex < line.Blocks.Count(); blockIndex++) - { - const FontBlockCache& block = BlockedTextLines[lineIndex].Blocks[blockIndex]; - auto fontHeight = getFont(block.FallbackFontIndex)->GetHeight(); - auto fontDescender = getFont(block.FallbackFontIndex)->GetDescender(); - Float2 pointer = line.Location + block.Location; - - for (int32 charIndex = block.FirstCharIndex; charIndex <= block.LastCharIndex; charIndex++) - { - Char c = text[charIndex]; - if (c == '\n') - { - continue; - } - - // Get character entry - getFont(block.FallbackFontIndex)->GetCharacter(c, entry); - - // Check if need to select/change font atlas (since characters even in the same font may be located in different atlases) - if (fontAtlas == nullptr || entry.TextureIndex != fontAtlasIndex) - { - // Get texture atlas that contains current character - fontAtlasIndex = entry.TextureIndex; - fontAtlas = FontManager::GetAtlas(fontAtlasIndex); - if (fontAtlas) - { - fontAtlas->EnsureTextureCreated(); - invAtlasSize = 1.0f / fontAtlas->GetSize(); - drawCall.AsChar.Tex = fontAtlas->GetTexture(); - } - else - { - invAtlasSize = 1.0f; - drawCall.AsChar.Tex = nullptr; - } - } - - // Get kerning - const bool isWhitespace = StringUtils::IsWhitespace(c); - if (!isWhitespace && previous.IsValid) - { - kerning = getFont(block.FallbackFontIndex)->GetKerning(previous.Character, entry.Character); - } - else - { - kerning = 0; - } - pointer.X += (float)kerning * scale; - previous = entry; - - // Omit whitespace characters - if (!isWhitespace) - { - // Calculate character size and atlas coordinates - const float x = pointer.X + entry.OffsetX * scale; - const float y = pointer.Y - entry.OffsetY * scale + Math::Ceil((fontHeight + fontDescender) * scale); - - Rectangle charRect(x, y, entry.UVSize.X * scale, entry.UVSize.Y * scale); - charRect.Offset(layout.Bounds.Location); - - Float2 upperLeftUV = entry.UV * invAtlasSize; - Float2 rightBottomUV = (entry.UV + entry.UVSize) * invAtlasSize; - - // Add draw call - drawCall.StartIB = IBIndex; - drawCall.CountIB = 6; - DrawCalls.Add(drawCall); - WriteRect(charRect, color, upperLeftUV, rightBottomUV); - } - - // Move - pointer.X += entry.AdvanceX * scale; - } - } - } -} - -void Render2D::DrawTextInternal(Font* font, FontFallbackList* fallbacks, const StringView& text, const TextRange& textRange, const Color& color, const TextLayoutOptions& layout, MaterialBase* customMaterial) -{ - DrawTextInternal(font, fallbacks, textRange.Substring(text), color, layout, customMaterial); + DrawText(font, textRange.Substring(text), color, layout, customMaterial); } FORCE_INLINE bool NeedAlphaWithTint(const Color& color) @@ -2181,22 +1875,22 @@ void Render2D::DrawBezier(const Float2& p1, const Float2& p2, const Float2& p3, { RENDER2D_CHECK_RENDERING_STATE; - // Find amount of blocks to use + // Find amount of segments to use const Float2 d1 = p2 - p1; const Float2 d2 = p3 - p2; const Float2 d3 = p4 - p3; const float len = d1.Length() + d2.Length() + d3.Length(); - const int32 blockCount = Math::Clamp(Math::CeilToInt(len * 0.05f), 1, 100); - const float blockCountInv = 1.0f / blockCount; + const int32 segmentCount = Math::Clamp(Math::CeilToInt(len * 0.05f), 1, 100); + const float segmentCountInv = 1.0f / segmentCount; - // Draw blocked curve + // Draw segmented curve Float2 p; AnimationUtils::Bezier(p1, p2, p3, p4, 0, p); Lines2.Clear(); Lines2.Add(p); - for (int32 i = 1; i <= blockCount; i++) + for (int32 i = 1; i <= segmentCount; i++) { - const float t = i * blockCountInv; + const float t = i * segmentCountInv; AnimationUtils::Bezier(p1, p2, p3, p4, t, p); Lines2.Add(p); } diff --git a/Source/Engine/Render2D/Render2D.cs b/Source/Engine/Render2D/Render2D.cs index b36f155df..c4d9e81b4 100644 --- a/Source/Engine/Render2D/Render2D.cs +++ b/Source/Engine/Render2D/Render2D.cs @@ -1,13 +1,11 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. -using FlaxEngine.GUI; using System; namespace FlaxEngine { partial class Render2D { - /// /// Pushes transformation layer. /// @@ -102,7 +100,7 @@ namespace FlaxEngine } /// - /// Draws a text, follows the font fallback settings defined in . + /// Draws a text. /// /// The font to use. /// The text to render. @@ -128,7 +126,7 @@ namespace FlaxEngine } /// - /// Draws a text using a custom material shader. Given material must have GUI domain and a public parameter named Font (texture parameter used for a font atlas sampling). Follows the font fallback settings defined in . + /// Draws a text using a custom material shader. Given material must have GUI domain and a public parameter named Font (texture parameter used for a font atlas sampling). /// /// The font to use. /// Custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture. diff --git a/Source/Engine/Render2D/Render2D.h b/Source/Engine/Render2D/Render2D.h index 6657d8542..5e5e952dc 100644 --- a/Source/Engine/Render2D/Render2D.h +++ b/Source/Engine/Render2D/Render2D.h @@ -15,7 +15,6 @@ struct Matrix3x3; struct Viewport; struct TextRange; class Font; -class FontFallbackList; class GPUPipelineState; class GPUTexture; class GPUTextureView; @@ -34,7 +33,7 @@ API_CLASS(Static) class FLAXENGINE_API Render2D /// /// The rendering features and options flags. /// - API_ENUM(Attributes = "Flags") enum class RenderingFeatures + API_ENUM(Attributes="Flags") enum class RenderingFeatures { /// /// The none. @@ -45,6 +44,11 @@ API_CLASS(Static) class FLAXENGINE_API Render2D /// Enables automatic geometry vertices snapping to integer coordinates in screen space. Reduces aliasing and sampling artifacts. Might be disabled for 3D projection viewport or for complex UI transformations. /// VertexSnapping = 1, + + /// + /// Enables automatic characters usage from fallback fonts. + /// + FallbackFonts = 2, }; struct CustomData @@ -54,7 +58,6 @@ API_CLASS(Static) class FLAXENGINE_API Render2D }; public: - /// /// Checks if interface is during rendering phrase (Draw calls may be performed without failing). /// @@ -70,10 +73,6 @@ public: /// API_FIELD() static RenderingFeatures Features; - API_FIELD() static bool EnableFontFallback; - - API_FIELD() static FontFallbackList* FallbackFonts; - /// /// Called when frame rendering begins by the graphics device. /// @@ -180,17 +179,17 @@ public: public: /// - /// Draws a text, with font fallbacking disabled. + /// Draws a text. /// /// The font to use. /// The text to render. /// The text color. /// The text location. /// The custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture. - API_FUNCTION() static void DrawTextInternal(Font* font, const StringView& text, const Color& color, const Float2& location, MaterialBase* customMaterial = nullptr); + API_FUNCTION() static void DrawText(Font* font, const StringView& text, const Color& color, const Float2& location, MaterialBase* customMaterial = nullptr); /// - /// Draws a text, with font fallbacking disabled. + /// Draws a text. /// /// The font to use. /// The text to render. @@ -198,20 +197,20 @@ public: /// The text color. /// The text location. /// The custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture. - API_FUNCTION() static void DrawTextInternal(Font* font, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Color& color, const Float2& location, MaterialBase* customMaterial = nullptr); + API_FUNCTION() static void DrawText(Font* font, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Color& color, const Float2& location, MaterialBase* customMaterial = nullptr); /// - /// Draws a text with formatting, with font fallbacking disabled. + /// Draws a text with formatting. /// /// The font to use. /// The text to render. /// The text color. /// The text layout properties. /// The custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture. - API_FUNCTION() static void DrawTextInternal(Font* font, const StringView& text, const Color& color, API_PARAM(Ref) const TextLayoutOptions& layout, MaterialBase* customMaterial = nullptr); + API_FUNCTION() static void DrawText(Font* font, const StringView& text, const Color& color, API_PARAM(Ref) const TextLayoutOptions& layout, MaterialBase* customMaterial = nullptr); /// - /// Draws a text with formatting, with font fallbacking disabled. + /// Draws a text with formatting. /// /// The font to use. /// The text to render. @@ -219,120 +218,7 @@ public: /// The text color. /// The text layout properties. /// The custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture. - API_FUNCTION() static void DrawTextInternal(Font* font, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Color& color, API_PARAM(Ref) const TextLayoutOptions& layout, MaterialBase* customMaterial = nullptr); - - /// - /// Draws a text, using custom fallback options. - /// - /// The fonts to use, ordered by priority. - /// The text to render. - /// The input text range (substring range of the input text parameter). - /// The text color. - /// The text location. - /// The custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture. - API_FUNCTION() static void DrawTextInternal(Font* font, FontFallbackList* fallbacks, const StringView& text, const Color& color, const Float2& location, MaterialBase* customMaterial = nullptr); - - /// - /// Draws a text with formatting, using custom fallback options. - /// - /// The fonts to use, ordered by priority. - /// The text to render. - /// The text color. - /// The text layout properties. - /// The custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture. - API_FUNCTION() static void DrawTextInternal(Font* font, FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Color& color, const Float2& location, MaterialBase* customMaterial = nullptr); - - /// - /// Draws a text with formatting, using custom fallback options. - /// - /// The fonts to use, ordered by priority. - /// The text to render. - /// The input text range (substring range of the input text parameter). - /// The text color. - /// The text layout properties. - /// The custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture. - API_FUNCTION() static void DrawTextInternal(Font* font, FontFallbackList* fallbacks, const StringView& text, const Color& color, API_PARAM(Ref) const TextLayoutOptions& layout, MaterialBase* customMaterial = nullptr); - - /// - /// Draws a text with formatting, using custom fallback options. - /// - /// The fonts to use, ordered by priority. - /// The text to render. - /// The input text range (substring range of the input text parameter). - /// The text color. - /// The text layout properties. - /// The custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture. - API_FUNCTION() static void DrawTextInternal(Font* font, FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Color& color, API_PARAM(Ref) const TextLayoutOptions& layout, MaterialBase* customMaterial = nullptr); - - /// - /// Draws a text, follows the fallback settings defined in . - /// - /// The font to use. - /// The text to render. - /// The text color. - /// The text location. - /// The custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture. - API_FUNCTION() FORCE_INLINE static void DrawText(Font* font, const StringView& text, const Color& color, const Float2& location, MaterialBase* customMaterial = nullptr) { - if (EnableFontFallback && FallbackFonts) { - DrawTextInternal(font, FallbackFonts, text, color, location, customMaterial); - } - else { - DrawTextInternal(font, text, color, location, customMaterial); - } - } - - /// - /// Draws a text, follows the fallback settings defined in . - /// - /// The font to use. - /// The text to render. - /// The input text range (substring range of the input text parameter). - /// The text color. - /// The text location. - /// The custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture. - API_FUNCTION() FORCE_INLINE static void DrawText(Font* font, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Color& color, const Float2& location, MaterialBase* customMaterial = nullptr) { - if (EnableFontFallback && FallbackFonts) { - DrawTextInternal(font, FallbackFonts, text, textRange, color, location, customMaterial); - } - else { - DrawTextInternal(font, text, textRange, color, location, customMaterial); - } - } - - /// - /// Draws a text with formatting, follows the fallback settings defined in . - /// - /// The font to use. - /// The text to render. - /// The text color. - /// The text layout properties. - /// The custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture. - API_FUNCTION() FORCE_INLINE static void DrawText(Font* font, const StringView& text, const Color& color, API_PARAM(Ref) const TextLayoutOptions& layout, MaterialBase* customMaterial = nullptr) { - if (EnableFontFallback && FallbackFonts) { - DrawTextInternal(font, FallbackFonts, text, color, layout, customMaterial); - } - else { - DrawTextInternal(font, text, color, layout, customMaterial); - } - } - - /// - /// Draws a text with formatting, follows the fallback settings defined in . - /// - /// The font to use. - /// The text to render. - /// The input text range (substring range of the input text parameter). - /// The text color. - /// The text layout properties. - /// The custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture. - API_FUNCTION() FORCE_INLINE static void DrawText(Font* font, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Color& color, API_PARAM(Ref) const TextLayoutOptions& layout, MaterialBase* customMaterial = nullptr) { - if (EnableFontFallback && FallbackFonts) { - DrawTextInternal(font, FallbackFonts, text, textRange, color, layout, customMaterial); - } - else { - DrawTextInternal(font, text, textRange, color, layout, customMaterial); - } - } + API_FUNCTION() static void DrawText(Font* font, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Color& color, API_PARAM(Ref) const TextLayoutOptions& layout, MaterialBase* customMaterial = nullptr); /// /// Fills a rectangle area. @@ -571,3 +457,5 @@ public: /// The color. API_FUNCTION() static void FillTriangle(const Float2& p0, const Float2& p1, const Float2& p2, const Color& color); }; + +DECLARE_ENUM_OPERATORS(Render2D::RenderingFeatures); diff --git a/Source/Engine/Scripting/Scripting.cs b/Source/Engine/Scripting/Scripting.cs index a4cfd76b1..188333ff1 100644 --- a/Source/Engine/Scripting/Scripting.cs +++ b/Source/Engine/Scripting/Scripting.cs @@ -294,12 +294,14 @@ namespace FlaxEngine style.DragWindow = style.BackgroundSelected * 0.7f; // Use optionally bundled default font (matches Editor) - FontAsset defaultFont = Content.LoadAsyncInternal("Editor/Fonts/Roboto-Regular"); - - style.FontTitle = new FontReference(defaultFont, 18).GetFont(); - style.FontLarge = new FontReference(defaultFont, 14).GetFont(); - style.FontMedium = new FontReference(defaultFont, 9).GetFont(); - style.FontSmall = new FontReference(defaultFont, 9).GetFont(); + var defaultFont = Content.LoadAsyncInternal("Editor/Fonts/Roboto-Regular"); + if (defaultFont) + { + style.FontTitle = defaultFont.CreateFont(18); + style.FontLarge = defaultFont.CreateFont(14); + style.FontMedium = defaultFont.CreateFont(9); + style.FontSmall = defaultFont.CreateFont(9); + } Style.Current = style; } diff --git a/Source/Engine/UI/GUI/Common/Label.cs b/Source/Engine/UI/GUI/Common/Label.cs index 8de54fda3..3c7c04fb2 100644 --- a/Source/Engine/UI/GUI/Common/Label.cs +++ b/Source/Engine/UI/GUI/Common/Label.cs @@ -182,12 +182,6 @@ namespace FlaxEngine.GUI set => _autoFitTextRange = value; } - /// - /// Gets or sets whether to fallback when the primary font cannot render a char. - /// - [EditorOrder(120), DefaultValue(true), Tooltip("Whether to fallback when the font cannot render a char.")] - public bool EnableFontFallback { get; set; } = true; - /// /// Initializes a new instance of the class. /// @@ -239,23 +233,7 @@ namespace FlaxEngine.GUI } } - if (EnableFontFallback) - { - Render2D.DrawText(_font.GetFont(), Material, _text, rect, color, hAlignment, wAlignment, Wrapping, BaseLinesGapScale, scale); - } - else - { - var layout = new TextLayoutOptions - { - Bounds = rect, - HorizontalAlignment = hAlignment, - VerticalAlignment = wAlignment, - TextWrapping = Wrapping, - Scale = scale, - BaseLinesGapScale = BaseLinesGapScale, - }; - Render2D.DrawTextInternal(_font.GetFont(), _text, color, ref layout, Material); - } + Render2D.DrawText(_font.GetFont(), Material, _text, rect, color, hAlignment, wAlignment, Wrapping, BaseLinesGapScale, scale); if (ClipText) Render2D.PopClip(); @@ -276,8 +254,7 @@ namespace FlaxEngine.GUI layout.Bounds.Size.X = Width - Margin.Width; else if (_autoWidth && !_autoHeight) layout.Bounds.Size.Y = Height - Margin.Height; - _textSize = EnableFontFallback ? - font.MeasureText(_text, ref layout) : font.MeasureTextInternal(_text, ref layout); + _textSize = font.MeasureText(_text, ref layout); _textSize.Y *= BaseLinesGapScale; // Check if size is controlled via text diff --git a/Source/Engine/UI/GUI/Common/RichTextBox.Parsing.cs b/Source/Engine/UI/GUI/Common/RichTextBox.Parsing.cs index ab01df12b..2270136ae 100644 --- a/Source/Engine/UI/GUI/Common/RichTextBox.Parsing.cs +++ b/Source/Engine/UI/GUI/Common/RichTextBox.Parsing.cs @@ -1,6 +1,5 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. -using System; using System.Collections.Generic; using System.Runtime.InteropServices; using FlaxEngine.Utilities; diff --git a/Source/Engine/UI/GUI/Common/RichTextBox.Tags.cs b/Source/Engine/UI/GUI/Common/RichTextBox.Tags.cs index df8e0be7c..b4351da75 100644 --- a/Source/Engine/UI/GUI/Common/RichTextBox.Tags.cs +++ b/Source/Engine/UI/GUI/Common/RichTextBox.Tags.cs @@ -213,18 +213,18 @@ namespace FlaxEngine.GUI style.Alignment &= ~TextBlockStyle.Alignments.VerticalMask; switch (valign) { - case "top": - style.Alignment = TextBlockStyle.Alignments.Top; - break; - case "bottom": - style.Alignment = TextBlockStyle.Alignments.Bottom; - break; - case "middle": - style.Alignment = TextBlockStyle.Alignments.Middle; - break; - case "baseline": - style.Alignment = TextBlockStyle.Alignments.Baseline; - break; + case "top": + style.Alignment = TextBlockStyle.Alignments.Top; + break; + case "bottom": + style.Alignment = TextBlockStyle.Alignments.Bottom; + break; + case "middle": + style.Alignment = TextBlockStyle.Alignments.Middle; + break; + case "baseline": + style.Alignment = TextBlockStyle.Alignments.Baseline; + break; } } context.StyleStack.Push(style); @@ -245,15 +245,15 @@ namespace FlaxEngine.GUI style.Alignment &= ~TextBlockStyle.Alignments.VerticalMask; switch (valign) { - case "left": - style.Alignment = TextBlockStyle.Alignments.Left; - break; - case "right": - style.Alignment = TextBlockStyle.Alignments.Right; - break; - case "center": - style.Alignment = TextBlockStyle.Alignments.Center; - break; + case "left": + style.Alignment = TextBlockStyle.Alignments.Left; + break; + case "right": + style.Alignment = TextBlockStyle.Alignments.Right; + break; + case "center": + style.Alignment = TextBlockStyle.Alignments.Center; + break; } } context.StyleStack.Push(style); diff --git a/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs b/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs index 46f0fb1ad..438a7e3d8 100644 --- a/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs +++ b/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs @@ -316,7 +316,6 @@ namespace FlaxEngine.GUI color = textBlock.Style.ShadowColor; if (!enabled) color *= 0.6f; - // We don't need font fallbacks for rich text since the font is user-selected Render2D.DrawText(font, _text, ref textBlock.Range, color, textBlock.Bounds.Location + textBlock.Style.ShadowOffset, textBlock.Style.CustomMaterial); } diff --git a/Source/Engine/UI/GUI/Common/TextBox.cs b/Source/Engine/UI/GUI/Common/TextBox.cs index 53266a1b2..ee4f744a6 100644 --- a/Source/Engine/UI/GUI/Common/TextBox.cs +++ b/Source/Engine/UI/GUI/Common/TextBox.cs @@ -1,8 +1,5 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. - -using System.ComponentModel; - namespace FlaxEngine.GUI { /// @@ -67,12 +64,6 @@ namespace FlaxEngine.GUI [EditorDisplay("Text Style"), EditorOrder(2022), Tooltip("The color of the selection (Transparent if not used).")] public Color SelectionColor { get; set; } - /// - /// Gets or sets whether to fallback when the primary font cannot render a char. - /// - [EditorOrder(120), DefaultValue(true), Tooltip("Whether to fallback when the font cannot render a char.")] - public bool EnableFontFallback { get; set; } = true; - /// /// Initializes a new instance of the class. /// @@ -112,8 +103,7 @@ namespace FlaxEngine.GUI return Float2.Zero; } - return EnableFontFallback ? font.MeasureText(_text, ref _layout) : - font.MeasureTextInternal(_text, ref _layout); + return font.MeasureText(_text, ref _layout); } /// @@ -126,9 +116,8 @@ namespace FlaxEngine.GUI return Float2.Zero; } - height = (EnableFontFallback ? font.GetMaxHeight() : font.Height) / DpiScale; - return EnableFontFallback ? font.GetCharPosition(_text, index, ref _layout) : - font.GetCharPositionInternal(_text, index, ref _layout); + height = font.Height / DpiScale; + return font.GetCharPosition(_text, index, ref _layout); } /// @@ -140,8 +129,7 @@ namespace FlaxEngine.GUI return 0; } - return EnableFontFallback ? font.HitTestText(_text, location, ref _layout) : - font.HitTestTextInternal(_text, location, ref _layout); + return font.HitTestText(_text, location, ref _layout); } /// @@ -180,13 +168,9 @@ namespace FlaxEngine.GUI // Check if sth is selected to draw selection if (HasSelection) { - var leftEdge = EnableFontFallback ? - font.GetCharPosition(_text, SelectionLeft, ref _layout) : - font.GetCharPositionInternal(_text, SelectionLeft, ref _layout); - var rightEdge = EnableFontFallback ? - font.GetCharPosition(_text, SelectionRight, ref _layout) : - font.GetCharPositionInternal(_text, SelectionRight, ref _layout); - float fontHeight = font.GetMaxHeight() / DpiScale; + var leftEdge = font.GetCharPosition(_text, SelectionLeft, ref _layout); + var rightEdge = font.GetCharPosition(_text, SelectionRight, ref _layout); + float fontHeight = font.Height / DpiScale; // Draw selection background float alpha = Mathf.Min(1.0f, Mathf.Cos(_animateTime * BackgroundSelectedFlashSpeed) * 0.5f + 1.3f); @@ -226,19 +210,11 @@ namespace FlaxEngine.GUI var color = TextColor; if (!enabled) color *= 0.6f; - if (EnableFontFallback) - Render2D.DrawText(font, _text, color, ref _layout, TextMaterial); - else - // Draw without fallback - Render2D.DrawTextInternal(font, _text, color, ref _layout, TextMaterial); + Render2D.DrawText(font, _text, color, ref _layout, TextMaterial); } else if (!string.IsNullOrEmpty(_watermarkText) && !IsFocused) { - if (EnableFontFallback) - Render2D.DrawText(font, _watermarkText, WatermarkTextColor, ref _layout, TextMaterial); - else - // Draw without fallback - Render2D.DrawTextInternal(font, _watermarkText, WatermarkTextColor, ref _layout, TextMaterial); + Render2D.DrawText(font, _watermarkText, WatermarkTextColor, ref _layout, TextMaterial); } // Caret diff --git a/Source/Engine/UI/GUI/Common/TextBoxBase.cs b/Source/Engine/UI/GUI/Common/TextBoxBase.cs index 55b58634d..35819da79 100644 --- a/Source/Engine/UI/GUI/Common/TextBoxBase.cs +++ b/Source/Engine/UI/GUI/Common/TextBoxBase.cs @@ -280,13 +280,13 @@ namespace FlaxEngine.GUI /// [EditorDisplay("Border Style"), EditorOrder(2010), Tooltip("Whether to have a border."), ExpandGroups] public bool HasBorder { get; set; } = true; - + /// /// Gets or sets the border thickness. /// [EditorDisplay("Border Style"), EditorOrder(2011), Tooltip("The thickness of the border."), Limit(0)] public float BorderThickness { get; set; } = 1.0f; - + /// /// Gets or sets the color of the border (Transparent if not used). /// diff --git a/Source/Engine/UI/TextRender.cpp b/Source/Engine/UI/TextRender.cpp index aa49dac45..d190f1123 100644 --- a/Source/Engine/UI/TextRender.cpp +++ b/Source/Engine/UI/TextRender.cpp @@ -239,7 +239,7 @@ void TextRender::UpdateLayout() const bool isWhitespace = StringUtils::IsWhitespace(c); if (!isWhitespace && previous.IsValid) { - kerning = font->GetKerning(previous.Character, entry.Character); + kerning = entry.Font->GetKerning(previous.Character, entry.Character); } else { From 9335925b486c02a32c5c2ebb0ec03f15509482a8 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 18 Feb 2024 20:16:20 +0100 Subject: [PATCH 121/139] Simplify code #1949 --- Source/Engine/Level/Actor.cpp | 11 +++-------- Source/Engine/Level/Actor.h | 2 +- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp index 6e8c2e7b4..cd6b156d8 100644 --- a/Source/Engine/Level/Actor.cpp +++ b/Source/Engine/Level/Actor.cpp @@ -1358,16 +1358,11 @@ bool Actor::IsPrefabRoot() const Actor* Actor::GetPrefabRoot() { - if (!this->HasPrefabLink()) - { - return NULL; - } - + if (!HasPrefabLink()) + return nullptr; Actor* result = this; - while (!result == NULL && !result->IsPrefabRoot()) - { + while (result && !result->IsPrefabRoot()) result = result->GetParent(); - } return result; } diff --git a/Source/Engine/Level/Actor.h b/Source/Engine/Level/Actor.h index d34d66e49..feba73997 100644 --- a/Source/Engine/Level/Actor.h +++ b/Source/Engine/Level/Actor.h @@ -794,7 +794,7 @@ public: { return (T*)FindActor(T::GetStaticClass(), name); } - + /// /// Tries to find the actor of the given type and tag in this actor hierarchy (checks this actor and all children hierarchy). /// From a5a16c3192fb10cef629cb975926b8411401682e Mon Sep 17 00:00:00 2001 From: envision3d Date: Sun, 18 Feb 2024 20:32:32 -0600 Subject: [PATCH 122/139] Fix issue with CharacterController initialization --- Source/Engine/Physics/Colliders/CharacterController.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Physics/Colliders/CharacterController.cpp b/Source/Engine/Physics/Colliders/CharacterController.cpp index e9f86ebab..61fc4d471 100644 --- a/Source/Engine/Physics/Colliders/CharacterController.cpp +++ b/Source/Engine/Physics/Colliders/CharacterController.cpp @@ -210,7 +210,7 @@ void CharacterController::CreateController() // Setup PhysicsBackend::SetControllerUpDirection(_controller, _upDirection); - PhysicsBackend::SetShapeLocalPose(_shape, _center, Quaternion::Identity); + PhysicsBackend::SetShapeLocalPose(_shape, Vector3.Zero, Quaternion::Identity); UpdateLayerBits(); UpdateBounds(); } From a061afd840da6b2af921b5f41cdb770644d5b188 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 19 Feb 2024 10:54:29 +0100 Subject: [PATCH 123/139] Cleanup for #1827 --- Source/Editor/Content/Proxy/ScriptProxy.cs | 3 +- .../CustomEditors/Dedicated/ScriptsEditor.cs | 65 +++++++++---------- Source/Editor/GUI/ItemsListContextMenu.cs | 25 +++---- 3 files changed, 45 insertions(+), 48 deletions(-) diff --git a/Source/Editor/Content/Proxy/ScriptProxy.cs b/Source/Editor/Content/Proxy/ScriptProxy.cs index f688f37df..d728dcc38 100644 --- a/Source/Editor/Content/Proxy/ScriptProxy.cs +++ b/Source/Editor/Content/Proxy/ScriptProxy.cs @@ -69,8 +69,7 @@ namespace FlaxEditor.Content /// public override bool IsFileNameValid(string filename) { - // Scripts cannot start with digit. - if (Char.IsDigit(filename[0])) + if (char.IsDigit(filename[0])) return false; if (filename.Equals("Script")) return false; diff --git a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs index 216c205a1..63374361d 100644 --- a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs @@ -21,6 +21,7 @@ namespace FlaxEditor.CustomEditors.Dedicated internal class NewScriptItem : ItemsListContextMenu.Item { private string _scriptName; + public string ScriptName { get => _scriptName; @@ -37,6 +38,7 @@ namespace FlaxEditor.CustomEditors.Dedicated TooltipText = "Create a new script"; } } + /// /// Drag and drop scripts area control. /// @@ -99,18 +101,14 @@ namespace FlaxEditor.CustomEditors.Dedicated { if (!IsValidScriptName(text)) return; - var items = cm.ItemsPanel.Children.Count(x => x.Visible && x is not NewScriptItem); - if (items == 0) + if (!cm.ItemsPanel.Children.Any(x => x.Visible && x is not NewScriptItem)) { - // If there are no visible items, that means the search failed so we can find the create script - // button or create one if it's the first time. - - var createScriptItem = cm.ItemsPanel.Children.FirstOrDefault(x => x is NewScriptItem); - if (createScriptItem != null) + // If there are no visible items, that means the search failed so we can find the create script button or create one if it's the first time + var newScriptItem = (NewScriptItem)cm.ItemsPanel.Children.FirstOrDefault(x => x is NewScriptItem); + if (newScriptItem != null) { - var item = createScriptItem as NewScriptItem; - item.Visible = true; - item.ScriptName = text; + newScriptItem.Visible = true; + newScriptItem.ScriptName = text; } else { @@ -120,9 +118,9 @@ namespace FlaxEditor.CustomEditors.Dedicated else { // Make sure to hide the create script button if there - var createScriptButton = cm.ItemsPanel.Children.FirstOrDefault(x => x is NewScriptItem); - if (createScriptButton != null) - createScriptButton.Visible = false; + var newScriptItem = cm.ItemsPanel.Children.FirstOrDefault(x => x is NewScriptItem); + if (newScriptItem != null) + newScriptItem.Visible = false; } }; cm.ItemClicked += item => @@ -135,7 +133,6 @@ namespace FlaxEditor.CustomEditors.Dedicated { CreateScript(newScriptItem); } - }; cm.SortItems(); cm.Show(this, button.BottomLeft - new Float2((cm.Width - button.Width) / 2, 0)); @@ -174,16 +171,18 @@ namespace FlaxEditor.CustomEditors.Dedicated return scriptItem.ScriptType != ScriptType.Null; return false; } - + private static bool IsValidScriptName(string text) { + if (string.IsNullOrEmpty(text)) + return false; if (text.Contains(' ')) return false; if (char.IsDigit(text[0])) return false; if (text.Any(c => !char.IsLetterOrDigit(c) && c != '_')) return false; - return true; + return Editor.Instance.ContentDatabase.GetProxy("cs").IsFileNameValid(text); } /// @@ -236,6 +235,7 @@ namespace FlaxEditor.CustomEditors.Dedicated if (_dragScripts.HasValidDrag) { result = _dragScripts.Effect; + AddScripts(_dragScripts.Objects); } else if (_dragAssets.HasValidDrag) @@ -252,17 +252,16 @@ namespace FlaxEditor.CustomEditors.Dedicated private void CreateScript(NewScriptItem item) { - ScriptsEditor.NewScriptItem = item; + ScriptsEditor.NewScriptName = item.ScriptName; var paths = Directory.GetFiles(Globals.ProjectSourceFolder, "*.Build.cs"); string moduleName = null; foreach (var p in paths) { var file = File.ReadAllText(p); - // Skip if (!file.Contains("GameProjectTarget")) - continue; - + continue; // Skip + if (file.Contains("Modules.Add(\"Game\")")) { // Assume Game represents the main game module @@ -273,16 +272,14 @@ namespace FlaxEditor.CustomEditors.Dedicated // Ensure the path slashes are correct for the OS var correctedPath = Path.GetFullPath(Globals.ProjectSourceFolder); - if (string.IsNullOrEmpty(moduleName)) { var error = FileSystem.ShowBrowseFolderDialog(Editor.Instance.Windows.MainWindow, correctedPath, "Select a module folder to put the new script in", out moduleName); if (error) return; } - var path = Path.Combine(Globals.ProjectSourceFolder, moduleName, item.ScriptName + ".cs"); - new CSharpScriptProxy().Create(path, null); + Editor.Instance.ContentDatabase.GetProxy("cs").Create(path, null); } /// @@ -603,10 +600,10 @@ namespace FlaxEditor.CustomEditors.Dedicated /// public override IEnumerable UndoObjects => _scripts; - // We need somewhere to store the newly created script name. - // The problem is that the ScriptsEditor gets destroyed after scripts compilation - // so we must make it static to store this information. - internal static NewScriptItem NewScriptItem { get; set; } + /// + /// Cached the newly created script name - used to add script after compilation. + /// + internal static string NewScriptName; private void AddMissingScript(int index, LayoutElementsContainer layout) { @@ -715,17 +712,15 @@ namespace FlaxEditor.CustomEditors.Dedicated // Area for drag&drop scripts var dragArea = layout.CustomContainer(); dragArea.CustomControl.ScriptsEditor = this; - - // If the initialization is triggered by an editor recompilation, check if it - // was due to script generation from DragAreaControl. - if (NewScriptItem != null) + + // If the initialization is triggered by an editor recompilation, check if it was due to script generation from DragAreaControl + if (NewScriptName != null) { - var script = Editor.Instance.CodeEditing.Scripts.Get() - .FirstOrDefault(x => x.Name == NewScriptItem.ScriptName); + var script = Editor.Instance.CodeEditing.Scripts.Get().FirstOrDefault(x => x.Name == NewScriptName); + NewScriptName = null; if (script != null) { dragArea.CustomControl.AddScript(script); - NewScriptItem = null; } else { @@ -770,7 +765,7 @@ namespace FlaxEditor.CustomEditors.Dedicated var values = new ScriptsContainer(elementType, i, Values); var scriptType = TypeUtils.GetObjectType(script); var editor = CustomEditorsUtil.CreateEditor(scriptType, false); - + // Check if actor has all the required scripts bool hasAllRequirements = true; if (scriptType.HasAttribute(typeof(RequireScriptAttribute), false)) diff --git a/Source/Editor/GUI/ItemsListContextMenu.cs b/Source/Editor/GUI/ItemsListContextMenu.cs index a0351bf0f..c8c2a9c22 100644 --- a/Source/Editor/GUI/ItemsListContextMenu.cs +++ b/Source/Editor/GUI/ItemsListContextMenu.cs @@ -189,6 +189,9 @@ namespace FlaxEditor.GUI /// public event Action ItemClicked; + /// + /// Event fired when search text in this popup menu gets changed. + /// public event Action TextChanged; /// @@ -442,6 +445,7 @@ namespace FlaxEditor.GUI Hide(); return true; case KeyboardKeys.ArrowDown: + { if (RootWindow.FocusedControl == null) { // Focus search box if nothing is focused @@ -450,20 +454,19 @@ namespace FlaxEditor.GUI } // Focus the first visible item or then next one + var items = GetVisibleItems(); + var focusedIndex = items.IndexOf(focusedItem); + if (focusedIndex == -1) + focusedIndex = -1; + if (focusedIndex + 1 < items.Count) { - var items = GetVisibleItems(); - var focusedIndex = items.IndexOf(focusedItem); - if (focusedIndex == -1) - focusedIndex = -1; - if (focusedIndex + 1 < items.Count) - { - var item = items[focusedIndex + 1]; - item.Focus(); - _scrollPanel.ScrollViewTo(item); - return true; - } + var item = items[focusedIndex + 1]; + item.Focus(); + _scrollPanel.ScrollViewTo(item); + return true; } break; + } case KeyboardKeys.ArrowUp: if (focusedItem != null) { From 7e10baf5ea46fc160299ea9d9fd706a0e213595b Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 19 Feb 2024 14:53:34 +0100 Subject: [PATCH 124/139] Fixes to code --- Source/Engine/Debug/DebugDraw.cpp | 23 ++++++++++++----------- Source/Engine/Debug/DebugDraw.h | 9 ++++----- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Source/Engine/Debug/DebugDraw.cpp b/Source/Engine/Debug/DebugDraw.cpp index 633f059a7..8f65e609d 100644 --- a/Source/Engine/Debug/DebugDraw.cpp +++ b/Source/Engine/Debug/DebugDraw.cpp @@ -923,16 +923,16 @@ void DebugDraw::DrawActors(Actor** selectedActors, int32 selectedActorsCount, bo } } -void DebugDraw::DrawAxisFromDirection(const Vector3& origin, const Vector3& direction, const Color& color , float Size, float duration, bool depthTest) +void DebugDraw::DrawAxisFromDirection(const Vector3& origin, const Vector3& direction, float size, float duration, bool depthTest) { - auto rot = Quaternion::FromDirection(direction.GetNormalized()); - Vector3 Up = (rot * Vector3::Up ); - Vector3 Forward = (rot * Vector3::Forward); - Vector3 Right = (rot * Vector3::Right ); - - DrawLine(origin, origin + (Up * (Size * 0.5f)) + Up , Color::Green ,duration, depthTest); - DrawLine(origin, origin + (Forward * (Size * 0.5f)) + Forward, Color::Blue ,duration, depthTest); - DrawLine(origin, origin + (Right * (Size * 0.5f)) + Right , Color::Red ,duration, depthTest); + const auto rot = Quaternion::FromDirection(direction.GetNormalized()); + const Vector3 up = (rot * Vector3::Up); + const Vector3 forward = (rot * Vector3::Forward); + const Vector3 right = (rot * Vector3::Right); + const float sizeHalf = size * 0.5f; + DrawLine(origin, origin + up * sizeHalf + up, Color::Green, duration, depthTest); + DrawLine(origin, origin + forward * sizeHalf + forward, Color::Blue, duration, depthTest); + DrawLine(origin, origin + right * sizeHalf + right, Color::Red, duration, depthTest); } void DebugDraw::DrawDirection(const Vector3& origin, const Vector3& direction, const Color& color, float duration, bool depthTest) @@ -942,14 +942,15 @@ void DebugDraw::DrawDirection(const Vector3& origin, const Vector3& direction, c return; DrawLine(origin, origin + direction, color, duration, depthTest); } -void DebugDraw::DrawRay(const Vector3& origin, const Vector3& direction,const Color& color, float length, float duration, bool depthTest) + +void DebugDraw::DrawRay(const Vector3& origin, const Vector3& direction, const Color& color, float length, float duration, bool depthTest) { if (isnan(length) || isinf(length)) return; DrawLine(origin, origin + (direction.GetNormalized() * length), color, duration, depthTest); } -void DebugDraw::DrawRay(const Ray& ray,const Color& color, float length, float duration, bool depthTest) +void DebugDraw::DrawRay(const Ray& ray, const Color& color, float length, float duration, bool depthTest) { if (isnan(length) || isinf(length)) return; diff --git a/Source/Engine/Debug/DebugDraw.h b/Source/Engine/Debug/DebugDraw.h index dd6d5fee3..8d44b81e6 100644 --- a/Source/Engine/Debug/DebugDraw.h +++ b/Source/Engine/Debug/DebugDraw.h @@ -72,11 +72,10 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// /// The origin of the line. /// The direction of the line. - /// The color. - /// The size of the axis. + /// The size of the axis. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawAxisFromDirection(const Vector3& origin, const Vector3& direction, const Color& color = Color::White, float Size = 100.0f, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawAxisFromDirection(const Vector3& origin, const Vector3& direction, float size = 100.0f, float duration = 0.0f, bool depthTest = true); /// /// Draws the line in a direction. @@ -656,7 +655,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw API_FUNCTION() static void DrawText(const StringView& text, const Transform& transform, const Color& color = Color::White, int32 size = 32, float duration = 0.0f); }; -#define DEBUG_DRAW_AXIS_FROM_DIRECTION(origin, direction, color, size, duration, depthTest) DebugDraw::DrawAxisFromDirection(origin, direction, color, size, duration, depthTest); +#define DEBUG_DRAW_AXIS_FROM_DIRECTION(origin, direction, size, duration, depthTest) DebugDraw::DrawAxisFromDirection(origin, direction, size, duration, depthTest); #define DEBUG_DRAW_DIRECTION(origin, direction, color, duration, depthTest) DebugDraw::DrawDirection(origin, direction, color, duration, depthTest); #define DEBUG_DRAW_RAY(origin, direction, color, length, duration, depthTest) DebugDraw::DrawRay(origin, direction, color, length, duration, depthTest); #define DEBUG_DRAW_RAY(ray, color, length, duration, depthTest) DebugDraw::DrawRay(ray, color, length, duration, depthTest); @@ -689,7 +688,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw #else -#define DEBUG_DRAW_AXIS_FROM_DIRECTION(origin, direction, color, size, duration, depthTest) +#define DEBUG_DRAW_AXIS_FROM_DIRECTION(origin, direction, size, duration, depthTest) #define DEBUG_DRAW_DIRECTION(origin, direction,color,duration, depthTest) #define DEBUG_DRAW_RAY(ray, color, length, duration, depthTest) #define DEBUG_DRAW_LINE(start, end, color, duration, depthTest) From 4c082ef17f03486bc19d70d26ebfe018b7e1c795 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 19 Feb 2024 14:59:02 +0100 Subject: [PATCH 125/139] Codestyle fixes --- Flax.sln.DotSettings | 4 ++++ Source/Engine/Level/Level.cs | 6 +++++- Source/Tools/Flax.Build/Build/FileCache.cs | 16 +++++++------- .../Build/NativeCpp/Builder.NativeCpp.cs | 8 +++---- Source/Tools/Flax.Build/Build/Platform.cs | 21 +++++++------------ .../Flax.Build/Deploy/Deployment.Editor.cs | 2 +- .../Flax.Build/Deps/Dependencies/Assimp.cs | 2 +- .../Flax.Build/Deps/Dependencies/NvCloth.cs | 8 +++---- .../Flax.Build/Deps/Dependencies/OpenAL.cs | 2 +- .../Flax.Build/Deps/Dependencies/curl.cs | 8 +++---- .../Flax.Build/Deps/Dependencies/freetype.cs | 2 +- .../Flax.Build/Deps/Dependencies/glslang.cs | 2 +- .../Tools/Flax.Build/Deps/Dependencies/ogg.cs | 2 +- .../Flax.Build/Deps/Dependencies/vorbis.cs | 2 +- Source/Tools/Flax.Build/Deps/Downloader.cs | 3 +-- .../Tools/Flax.Build/Deps/ProgressDisplay.cs | 2 +- .../Platforms/Android/AndroidSdk.cs | 2 +- .../Flax.Build/Platforms/Mac/MacPlatform.cs | 16 +++++++------- .../VisualStudioProjectGenerator.cs | 2 +- .../VisualStudioCodeProjectGenerator.cs | 5 ++--- .../Flax.Build/Utilities/CppNameMangling.cs | 3 +-- Source/Tools/Flax.Build/Utilities/WinAPI.cs | 2 +- 22 files changed, 58 insertions(+), 62 deletions(-) diff --git a/Flax.sln.DotSettings b/Flax.sln.DotSettings index ff396d824..80cf8fa1e 100644 --- a/Flax.sln.DotSettings +++ b/Flax.sln.DotSettings @@ -73,8 +73,12 @@ <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> + AI LO + RPC + SDK VS + <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> diff --git a/Source/Engine/Level/Level.cs b/Source/Engine/Level/Level.cs index b43d9e91a..3e367195e 100644 --- a/Source/Engine/Level/Level.cs +++ b/Source/Engine/Level/Level.cs @@ -109,6 +109,8 @@ namespace FlaxEngine public static T[] GetScripts() where T : Script { var scripts = GetScripts(typeof(T)); + if (scripts.Length == 0) + return Array.Empty(); var result = new T[scripts.Length]; for (int i = 0; i < scripts.Length; i++) result[i] = scripts[i] as T; @@ -119,11 +121,13 @@ namespace FlaxEngine /// Finds all the actors of the given type in all the loaded scenes. /// /// Type of the object. - /// Finds only active actors. + /// Finds only active actors. /// Found actors list. public static T[] GetActors(bool activeOnly = false) where T : Actor { var actors = GetActors(typeof(T), activeOnly); + if (actors.Length == 0) + return Array.Empty(); var result = new T[actors.Length]; for (int i = 0; i < actors.Length; i++) result[i] = actors[i] as T; diff --git a/Source/Tools/Flax.Build/Build/FileCache.cs b/Source/Tools/Flax.Build/Build/FileCache.cs index a11b53415..d21e0848a 100644 --- a/Source/Tools/Flax.Build/Build/FileCache.cs +++ b/Source/Tools/Flax.Build/Build/FileCache.cs @@ -9,32 +9,30 @@ namespace Flax.Build /// public static class FileCache { - private static Dictionary fileInfoCache = new Dictionary(); + private static readonly Dictionary _cache = new(); public static void FileRemoveFromCache(string path) { - //fileInfoCache[path].Refresh(); - fileInfoCache.Remove(path); + _cache.Remove(path); } - + public static bool Exists(string path) { - if (fileInfoCache.TryGetValue(path, out var fileInfo)) + if (_cache.TryGetValue(path, out var fileInfo)) return fileInfo.Exists; fileInfo = new FileInfo(path); - fileInfoCache.Add(path, fileInfo); + _cache.Add(path, fileInfo); return fileInfo.Exists; } public static DateTime GetLastWriteTime(string path) { - - if (fileInfoCache.TryGetValue(path, out var fileInfo)) + if (_cache.TryGetValue(path, out var fileInfo)) return fileInfo.LastWriteTime; fileInfo = new FileInfo(path); - fileInfoCache.Add(path, fileInfo); + _cache.Add(path, fileInfo); return fileInfo.LastWriteTime; } } diff --git a/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs b/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs index 3efe33b38..f6586c910 100644 --- a/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs +++ b/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs @@ -808,11 +808,11 @@ namespace Flax.Build foreach (var moduleName in moduleOptions.PrivateDependencies.Concat(moduleOptions.PublicDependencies)) { var dependencyModule = buildData.Rules.GetModule(moduleName); - if (dependencyModule != null && - !string.IsNullOrEmpty(dependencyModule.BinaryModuleName) && - dependencyModule.BinaryModuleName != binaryModule.Key && + if (dependencyModule != null && + !string.IsNullOrEmpty(dependencyModule.BinaryModuleName) && + dependencyModule.BinaryModuleName != binaryModule.Key && !moduleNamesUsed.Contains(dependencyModule.BinaryModuleName) && - GetModuleProject(dependencyModule, project) != null && + GetModuleProject(dependencyModule, project) != null && buildData.Modules.TryGetValue(dependencyModule, out var dependencyOptions)) { // Import symbols from referenced binary module diff --git a/Source/Tools/Flax.Build/Build/Platform.cs b/Source/Tools/Flax.Build/Build/Platform.cs index f458225fa..6b0d48dba 100644 --- a/Source/Tools/Flax.Build/Build/Platform.cs +++ b/Source/Tools/Flax.Build/Build/Platform.cs @@ -77,14 +77,10 @@ namespace Flax.Build var architectureId = RuntimeInformation.ProcessArchitecture; switch (architectureId) { - case Architecture.X86: - return TargetArchitecture.x86; - case Architecture.X64: - return TargetArchitecture.x64; - case Architecture.Arm: - return TargetArchitecture.ARM; - case Architecture.Arm64: - return TargetArchitecture.ARM64; + case Architecture.X86: return TargetArchitecture.x86; + case Architecture.X64: return TargetArchitecture.x64; + case Architecture.Arm: return TargetArchitecture.ARM; + case Architecture.Arm64: return TargetArchitecture.ARM64; default: throw new NotImplementedException(string.Format("Unsupported build platform {0}.", architectureId)); } } @@ -290,12 +286,9 @@ namespace Flax.Build var subdir = "Binaries/Editor/"; switch (Platform.BuildTargetPlatform) { - case TargetPlatform.Windows: - return subdir + "Win64"; - case TargetPlatform.Linux: - return subdir + "Linux"; - case TargetPlatform.Mac: - return subdir + "Mac"; + case TargetPlatform.Windows: return subdir + "Win64"; + case TargetPlatform.Linux: return subdir + "Linux"; + case TargetPlatform.Mac: return subdir + "Mac"; } throw new NotImplementedException(); } diff --git a/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs b/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs index e1f2ab4e6..75f43fd68 100644 --- a/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs +++ b/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs @@ -58,7 +58,7 @@ namespace Flax.Deploy DeployFile(src, dst, buildToolExe); CodeSign(Path.Combine(dst, buildToolExe)); var buildToolDll = "Flax.Build.dll"; - DeployFile(src, dst,buildToolDll); + DeployFile(src, dst, buildToolDll); CodeSign(Path.Combine(dst, buildToolDll)); DeployFile(src, dst, "Flax.Build.xml", true); DeployFile(src, dst, "Flax.Build.pdb"); diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/Assimp.cs b/Source/Tools/Flax.Build/Deps/Dependencies/Assimp.cs index 528256f69..bc501c3a5 100644 --- a/Source/Tools/Flax.Build/Deps/Dependencies/Assimp.cs +++ b/Source/Tools/Flax.Build/Deps/Dependencies/Assimp.cs @@ -128,7 +128,7 @@ namespace Flax.Deps.Dependencies case TargetPlatform.Mac: { // Build for Mac - foreach (var architecture in new []{ TargetArchitecture.x64, TargetArchitecture.ARM64 }) + foreach (var architecture in new[] { TargetArchitecture.x64, TargetArchitecture.ARM64 }) { RunCmake(root, platform, architecture, " -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF " + globalConfig); Utilities.Run("make", null, null, root, Utilities.RunOptions.ThrowExceptionOnError); diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/NvCloth.cs b/Source/Tools/Flax.Build/Deps/Dependencies/NvCloth.cs index 6e565b94c..bb42fc253 100644 --- a/Source/Tools/Flax.Build/Deps/Dependencies/NvCloth.cs +++ b/Source/Tools/Flax.Build/Deps/Dependencies/NvCloth.cs @@ -49,7 +49,7 @@ namespace Flax.Deps.Dependencies } } } - + /// public override void Build(BuildOptions options) { @@ -185,16 +185,16 @@ namespace Flax.Deps.Dependencies // Print the NvCloth version Log.Info($"Building {File.ReadAllLines(Path.Combine(root, "README.md"))[0].Trim()} to {platform} {architecture}"); - + // Generate project files SetupDirectory(buildFolder, false); Utilities.FileDelete(Path.Combine(cmakeFolder, "CMakeCache.txt")); cmakeArgs += $" -DPX_STATIC_LIBRARIES=1 -DPX_OUTPUT_DLL_DIR=\"{Path.Combine(buildFolder, "bin")}\" -DPX_OUTPUT_LIB_DIR=\"{Path.Combine(buildFolder, "lib")}\" -DPX_OUTPUT_EXE_DIR=\"{Path.Combine(buildFolder, "bin")}\""; RunCmake(cmakeFolder, platform, architecture, " -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF " + cmakeArgs, envVars); - + // Run build Utilities.Run("cmake", "--build . --config Release", null, cmakeFolder, Utilities.RunOptions.ThrowExceptionOnError, envVars); - + // Deploy binaries var libs = new[] { diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/OpenAL.cs b/Source/Tools/Flax.Build/Deps/Dependencies/OpenAL.cs index 1adb73bb3..f9f5d7ed2 100644 --- a/Source/Tools/Flax.Build/Deps/Dependencies/OpenAL.cs +++ b/Source/Tools/Flax.Build/Deps/Dependencies/OpenAL.cs @@ -172,7 +172,7 @@ namespace Flax.Deps.Dependencies var buildDir = Path.Combine(root, "build"); // Build for Mac - foreach (var architecture in new []{ TargetArchitecture.x64, TargetArchitecture.ARM64 }) + foreach (var architecture in new[] { TargetArchitecture.x64, TargetArchitecture.ARM64 }) { SetupDirectory(buildDir, true); RunCmake(buildDir, platform, architecture, ".. -DLIBTYPE=STATIC -DCMAKE_BUILD_TYPE=Release " + config); diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/curl.cs b/Source/Tools/Flax.Build/Deps/Dependencies/curl.cs index 382c5707c..651c849d8 100644 --- a/Source/Tools/Flax.Build/Deps/Dependencies/curl.cs +++ b/Source/Tools/Flax.Build/Deps/Dependencies/curl.cs @@ -95,7 +95,7 @@ namespace Flax.Deps.Dependencies case TargetPlatform.Linux: { // Build for Linux - var settings = new [] + var settings = new[] { "--without-librtmp", "--without-ssl", @@ -126,7 +126,7 @@ namespace Flax.Deps.Dependencies case TargetPlatform.Mac: { // Build for Mac - var settings = new [] + var settings = new[] { "--with-secure-transport", "--without-librtmp", @@ -137,7 +137,7 @@ namespace Flax.Deps.Dependencies "--enable-static", "-disable-ldap --disable-sspi --disable-ftp --disable-file --disable-dict --disable-telnet --disable-tftp --disable-rtsp --disable-pop3 --disable-imap --disable-smtp --disable-gopher --disable-smb", }; - foreach (var architecture in new []{ TargetArchitecture.x64, TargetArchitecture.ARM64 }) + foreach (var architecture in new[] { TargetArchitecture.x64, TargetArchitecture.ARM64 }) { var arch = GetAppleArchName(architecture); var archName = arch + "-apple-darwin19"; @@ -146,7 +146,7 @@ namespace Flax.Deps.Dependencies var compilerFlags = string.Format("-mmacosx-version-min={0} -arch {1}", Configuration.MacOSXMinVer, arch); var envVars = new Dictionary { - { "CC", "clang" }, + { "CC", "clang" }, { "CXX", "clang" }, { "CFLAGS", compilerFlags }, { "CXXFLAGS", compilerFlags }, diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/freetype.cs b/Source/Tools/Flax.Build/Deps/Dependencies/freetype.cs index 165317180..6be0d3397 100644 --- a/Source/Tools/Flax.Build/Deps/Dependencies/freetype.cs +++ b/Source/Tools/Flax.Build/Deps/Dependencies/freetype.cs @@ -247,7 +247,7 @@ namespace Flax.Deps.Dependencies case TargetPlatform.Mac: { // Build for Mac - foreach (var architecture in new []{ TargetArchitecture.x64, TargetArchitecture.ARM64 }) + foreach (var architecture in new[] { TargetArchitecture.x64, TargetArchitecture.ARM64 }) { SetupDirectory(buildDir, true); RunCmake(buildDir, platform, architecture, ".. -DCMAKE_BUILD_TYPE=Release"); diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/glslang.cs b/Source/Tools/Flax.Build/Deps/Dependencies/glslang.cs index f88ded6a1..5d11c4728 100644 --- a/Source/Tools/Flax.Build/Deps/Dependencies/glslang.cs +++ b/Source/Tools/Flax.Build/Deps/Dependencies/glslang.cs @@ -130,7 +130,7 @@ namespace Flax.Deps.Dependencies }; // Build for Mac - foreach (var architecture in new []{ TargetArchitecture.x64, TargetArchitecture.ARM64 }) + foreach (var architecture in new[] { TargetArchitecture.x64, TargetArchitecture.ARM64 }) { RunCmake(root, platform, architecture, cmakeArgs); Utilities.Run("cmake", string.Format("--build . --config {0} --target install", configuration), null, buildDir, Utilities.RunOptions.None); diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/ogg.cs b/Source/Tools/Flax.Build/Deps/Dependencies/ogg.cs index fe6800001..4fefceb09 100644 --- a/Source/Tools/Flax.Build/Deps/Dependencies/ogg.cs +++ b/Source/Tools/Flax.Build/Deps/Dependencies/ogg.cs @@ -217,7 +217,7 @@ namespace Flax.Deps.Dependencies case TargetPlatform.Mac: { // Build for Mac - foreach (var architecture in new []{ TargetArchitecture.x64, TargetArchitecture.ARM64 }) + foreach (var architecture in new[] { TargetArchitecture.x64, TargetArchitecture.ARM64 }) { SetupDirectory(buildDir, true); RunCmake(buildDir, platform, architecture, ".. -DCMAKE_BUILD_TYPE=Release"); diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/vorbis.cs b/Source/Tools/Flax.Build/Deps/Dependencies/vorbis.cs index 75b2810be..847ff8872 100644 --- a/Source/Tools/Flax.Build/Deps/Dependencies/vorbis.cs +++ b/Source/Tools/Flax.Build/Deps/Dependencies/vorbis.cs @@ -376,7 +376,7 @@ namespace Flax.Deps.Dependencies GitCheckout(oggRoot, "master", "4380566a44b8d5e85ad511c9c17eb04197863ec5"); // Build for Mac - foreach (var architecture in new []{ TargetArchitecture.x64, TargetArchitecture.ARM64 }) + foreach (var architecture in new[] { TargetArchitecture.x64, TargetArchitecture.ARM64 }) { SetupDirectory(oggBuildDir, true); RunCmake(oggBuildDir, platform, architecture, ".. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=\"../install\""); diff --git a/Source/Tools/Flax.Build/Deps/Downloader.cs b/Source/Tools/Flax.Build/Deps/Downloader.cs index dcba01780..19135d634 100644 --- a/Source/Tools/Flax.Build/Deps/Downloader.cs +++ b/Source/Tools/Flax.Build/Deps/Downloader.cs @@ -105,8 +105,7 @@ namespace Flax.Deps if (totalBytes.HasValue) progress.Update(totalBytesRead, totalBytes.Value); } - } - while (hasMoreToRead); + } while (hasMoreToRead); } } } diff --git a/Source/Tools/Flax.Build/Deps/ProgressDisplay.cs b/Source/Tools/Flax.Build/Deps/ProgressDisplay.cs index 5ad6e10d3..cce10f2eb 100644 --- a/Source/Tools/Flax.Build/Deps/ProgressDisplay.cs +++ b/Source/Tools/Flax.Build/Deps/ProgressDisplay.cs @@ -96,7 +96,7 @@ namespace Flax.Deps Console.WriteLine(); return false; } - + return true; */ } diff --git a/Source/Tools/Flax.Build/Platforms/Android/AndroidSdk.cs b/Source/Tools/Flax.Build/Platforms/Android/AndroidSdk.cs index a023d33b2..0fd5b6a41 100644 --- a/Source/Tools/Flax.Build/Platforms/Android/AndroidSdk.cs +++ b/Source/Tools/Flax.Build/Platforms/Android/AndroidSdk.cs @@ -18,7 +18,7 @@ namespace Flax.Build.Platforms public static readonly AndroidSdk Instance = new AndroidSdk(); /// - public override TargetPlatform[] Platforms => new [] + public override TargetPlatform[] Platforms => new[] { TargetPlatform.Windows, TargetPlatform.Linux, diff --git a/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs b/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs index 4a8115fcd..4173e160b 100644 --- a/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs +++ b/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs @@ -77,15 +77,15 @@ namespace Flax.Build.Platforms /// Returns true if running an x64 binary an arm64 host machine. /// public unsafe static bool GetProcessIsTranslated() - { - int ret = 0; - ulong size = sizeof(int); - if (sysctlbyname("sysctl.proc_translated", &ret, &size, null, 0) == -1) - return false; - return ret != 0; - } + { + int ret = 0; + ulong size = sizeof(int); + if (sysctlbyname("sysctl.proc_translated", &ret, &size, null, 0) == -1) + return false; + return ret != 0; + } [DllImport("c")] - private static unsafe extern int sysctlbyname(string name, void* oldp, ulong* oldlenp, void* newp, ulong newlen); + private static unsafe extern int sysctlbyname(string name, void* oldp, ulong* oldlenp, void* newp, ulong newlen); } } diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs index 3438f8960..7bd5495b2 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs @@ -702,7 +702,7 @@ namespace Flax.Build.Projects.VisualStudio { // Build command for the build tool var buildToolPath = Path.ChangeExtension(typeof(Builder).Assembly.Location, null); - + var targetsFileContent = new StringBuilder(); targetsFileContent.AppendLine(""); targetsFileContent.AppendLine(" "); diff --git a/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs index 15b236972..7f3b11e15 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs @@ -410,8 +410,7 @@ namespace Flax.Build.Projects.VisualStudioCode json.AddField("stopAtEntry", false); json.AddField("externalConsole", true); break; - case TargetPlatform.Linux: - break; + case TargetPlatform.Linux: break; } } json.EndObject(); @@ -622,7 +621,7 @@ namespace Flax.Build.Projects.VisualStudioCode json.AddField("**/Output", true); json.AddField("**/*.flax", true); json.EndObject(); - + // Extension settings json.AddField("omnisharp.useModernNet", true); diff --git a/Source/Tools/Flax.Build/Utilities/CppNameMangling.cs b/Source/Tools/Flax.Build/Utilities/CppNameMangling.cs index 1d0166a72..ba00db071 100644 --- a/Source/Tools/Flax.Build/Utilities/CppNameMangling.cs +++ b/Source/Tools/Flax.Build/Utilities/CppNameMangling.cs @@ -79,8 +79,7 @@ namespace Flax.Build } } break; - default: - throw new InvalidPlatformException(buildData.Platform.Target); + default: throw new InvalidPlatformException(buildData.Platform.Target); } var result = sb.ToString(); BindingsGenerator.PutStringBuilder(sb); diff --git a/Source/Tools/Flax.Build/Utilities/WinAPI.cs b/Source/Tools/Flax.Build/Utilities/WinAPI.cs index ae0c5a5fb..47939fc1f 100644 --- a/Source/Tools/Flax.Build/Utilities/WinAPI.cs +++ b/Source/Tools/Flax.Build/Utilities/WinAPI.cs @@ -130,7 +130,7 @@ namespace Flax.Build System = 0x00001000, Task = 0x00002000 } - + public enum Icon : uint { Warning = 0x00000030, From cb0969893dc920ff1c27b434c329733fd84dd075 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 19 Feb 2024 15:12:37 +0100 Subject: [PATCH 126/139] Fix build with older C# compiler --- Source/Editor/Options/InterfaceOptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/Options/InterfaceOptions.cs b/Source/Editor/Options/InterfaceOptions.cs index 6180b5a66..c33cd3674 100644 --- a/Source/Editor/Options/InterfaceOptions.cs +++ b/Source/Editor/Options/InterfaceOptions.cs @@ -249,7 +249,7 @@ namespace FlaxEditor.Options /// The list of fallback fonts to use when main text font is missing certain characters. Empty to use fonts from GraphicsSettings. /// [EditorDisplay("Fonts"), EditorOrder(650)] - public FontAsset[] FallbackFonts = [FlaxEngine.Content.LoadAsyncInternal(EditorAssets.FallbackFont)]; + public FontAsset[] FallbackFonts = new FontAsset[1] { FlaxEngine.Content.LoadAsyncInternal(EditorAssets.FallbackFont) }; /// /// Gets or sets the title font for editor UI. From 2de890ca6a24e861c5ca96401e6c86ce7fa267b5 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 19 Feb 2024 17:33:21 +0100 Subject: [PATCH 127/139] Codecleanup for #1482 --- Source/Editor/SceneGraph/Actors/SplineNode.cs | 122 ++++++------------ Source/Editor/Viewport/EditorViewport.cs | 16 +++ Source/Engine/Core/Math/BoundingFrustum.cs | 10 ++ Source/Engine/Graphics/RenderView.cs | 2 +- Source/Engine/UI/UICanvas.cs | 2 +- 5 files changed, 69 insertions(+), 83 deletions(-) diff --git a/Source/Editor/SceneGraph/Actors/SplineNode.cs b/Source/Editor/SceneGraph/Actors/SplineNode.cs index a3ceb66f9..e6ff43e9f 100644 --- a/Source/Editor/SceneGraph/Actors/SplineNode.cs +++ b/Source/Editor/SceneGraph/Actors/SplineNode.cs @@ -294,8 +294,6 @@ namespace FlaxEditor.SceneGraph.Actors private const Real SnapIndicatorSize = 1.7f; private const Real SnapPointIndicatorSize = 2f; - private static Spline _currentEditSpline; - /// public SplineNode(Actor actor) : base(actor) @@ -306,17 +304,19 @@ namespace FlaxEditor.SceneGraph.Actors private void OnUpdate() { - if (Input.Keyboard.GetKey(KeyboardKeys.Shift)) + // If this node's point is selected + var selection = Editor.Instance.SceneEditing.Selection; + if (selection.Count == 1 && selection[0] is SplinePointNode selectedPoint && selectedPoint.ParentNode == this) { - EditSplineWithSnap(); + if (Input.Keyboard.GetKey(KeyboardKeys.Shift)) + EditSplineWithSnap(selectedPoint); + + var canAddSplinePoint = Input.Mouse.PositionDelta == Float2.Zero && Input.Mouse.Position != Float2.Zero; + var requestAddSplinePoint = Input.Keyboard.GetKey(KeyboardKeys.Control) && Input.Mouse.GetButtonDown(MouseButton.Right); + if (requestAddSplinePoint && canAddSplinePoint) + AddSplinePoint(selectedPoint); } - var canAddSplinePoint = Input.Mouse.PositionDelta == Float2.Zero && Input.Mouse.Position != Float2.Zero; - var requestAddSplinePoint = Input.Keyboard.GetKey(KeyboardKeys.Control) && Input.Mouse.GetButtonDown(MouseButton.Right); - - if (requestAddSplinePoint && canAddSplinePoint) - AddSplinePoint(); - SyncSplineKeyframeWithNodes(); } @@ -351,20 +351,15 @@ namespace FlaxEditor.SceneGraph.Actors } } - private unsafe void AddSplinePoint() + private unsafe void AddSplinePoint(SplinePointNode selectedPoint) { - var selectedPoint = GetSelectedSplineNode(); - if (selectedPoint == null) - return; - - // checking mouse hit on scene + // Check mouse hit on scene var spline = (Spline)Actor; var viewport = Editor.Instance.Windows.EditWin.Viewport; var mouseRay = viewport.MouseRay; - var viewRay = new Ray(viewport.ViewPosition, viewport.ViewDirection); + var viewRay = viewport.ViewRay; var flags = RayCastData.FlagTypes.SkipColliders | RayCastData.FlagTypes.SkipEditorPrimitives; var hit = Editor.Instance.Scene.Root.RayCast(ref mouseRay, ref viewRay, out var closest, out var normal, flags); - if (hit == null) return; @@ -373,7 +368,7 @@ namespace FlaxEditor.SceneGraph.Actors var editAction = new EditSplineAction(spline, oldSpline); Root.Undo.AddAction(editAction); - // Getting spline point to duplicate + // Get spline point to duplicate var hitPoint = mouseRay.Position + mouseRay.Direction * closest; var lastPointIndex = selectedPoint.Index; var newPointIndex = lastPointIndex > 0 ? lastPointIndex + 1 : 0; @@ -381,24 +376,22 @@ namespace FlaxEditor.SceneGraph.Actors var isLastPoint = lastPointIndex == spline.SplinePointsCount - 1; var isFirstPoint = lastPointIndex == 0; - // Getting data to create new point - + // Get data to create new point var lastPointTime = spline.GetSplineTime(lastPointIndex); var nextPointTime = isLastPoint ? lastPointTime : spline.GetSplineTime(newPointIndex); var newTime = isLastPoint ? lastPointTime + 1.0f : (lastPointTime + nextPointTime) * 0.5f; var distanceFromLastPoint = Vector3.Distance(hitPoint, spline.GetSplinePoint(lastPointIndex)); var newPointDirection = spline.GetSplineTangent(lastPointIndex, false).Translation - hitPoint; - // set correctly keyframe direction on spawn point + // Set correctly keyframe direction on spawn point if (isFirstPoint) newPointDirection = hitPoint - spline.GetSplineTangent(lastPointIndex, true).Translation; else if (isLastPoint) newPointDirection = spline.GetSplineTangent(lastPointIndex, false).Translation - hitPoint; - var newPointLocalPosition = spline.Transform.WorldToLocal(hitPoint); var newPointLocalOrientation = Quaternion.LookRotation(newPointDirection); - // Adding new point + // Add new point spline.InsertSplinePoint(newPointIndex, newTime, Transform.Identity, false); var newKeyframe = lastKeyframe.DeepClone(); var newKeyframeTransform = newKeyframe.Value; @@ -406,13 +399,13 @@ namespace FlaxEditor.SceneGraph.Actors newKeyframeTransform.Orientation = newPointLocalOrientation; newKeyframe.Value = newKeyframeTransform; - // Setting new point keyframe - var newkeyframeTangentIn = Transform.Identity; - var newkeyframeTangentOut = Transform.Identity; - newkeyframeTangentIn.Translation = (Vector3.Forward * newPointLocalOrientation) * distanceFromLastPoint; - newkeyframeTangentOut.Translation = (Vector3.Backward * newPointLocalOrientation) * distanceFromLastPoint; - newKeyframe.TangentIn = newkeyframeTangentIn; - newKeyframe.TangentOut = newkeyframeTangentOut; + // Set new point keyframe + var newKeyframeTangentIn = Transform.Identity; + var newKeyframeTangentOut = Transform.Identity; + newKeyframeTangentIn.Translation = (Vector3.Forward * newPointLocalOrientation) * distanceFromLastPoint; + newKeyframeTangentOut.Translation = (Vector3.Backward * newPointLocalOrientation) * distanceFromLastPoint; + newKeyframe.TangentIn = newKeyframeTangentIn; + newKeyframe.TangentOut = newKeyframeTangentOut; spline.SetSplineKeyframe(newPointIndex, newKeyframe); for (int i = 1; i < spline.SplinePointsCount; i++) @@ -430,20 +423,13 @@ namespace FlaxEditor.SceneGraph.Actors spline.UpdateSpline(); } - private void EditSplineWithSnap() + private void EditSplineWithSnap(SplinePointNode selectedPoint) { - if (_currentEditSpline == null || _currentEditSpline != Actor) - return; - - var selectedNode = GetSelectedSplineNode(); - if (selectedNode == null) - return; - - var selectedNodeBounds = new BoundingSphere(selectedNode.Transform.Translation, 1f); - var allSplinesInView = GetSplinesOnView(); - allSplinesInView.Remove(_currentEditSpline); - - if (allSplinesInView.Count == 0 || selectedNode == null) + var spline = (Spline)Actor; + var selectedPointBounds = new BoundingSphere(selectedPoint.Transform.Translation, 1f); + var allSplinesInView = GetSplinesInView(); + allSplinesInView.Remove(spline); + if (allSplinesInView.Count == 0) return; var snappedOnSplinePoint = false; @@ -456,9 +442,9 @@ namespace FlaxEditor.SceneGraph.Actors var keyframeBounds = new BoundingSphere(keyframePosition, pointIndicatorSize); DebugDraw.DrawSphere(keyframeBounds, Color.Red, 0, false); - if (keyframeBounds.Intersects(selectedNodeBounds)) + if (keyframeBounds.Intersects(selectedPointBounds)) { - _currentEditSpline.SetSplinePoint(selectedNode.Index, keyframeBounds.Center); + spline.SetSplinePoint(selectedPoint.Index, keyframeBounds.Center); snappedOnSplinePoint = true; break; } @@ -467,15 +453,13 @@ namespace FlaxEditor.SceneGraph.Actors if (!snappedOnSplinePoint) { - var nearSplineSnapPoint = GetNearSplineSnapPosition(selectedNode.Transform.Translation, allSplinesInView); + var nearSplineSnapPoint = GetNearSplineSnapPosition(selectedPoint.Transform.Translation, allSplinesInView); var snapIndicatorSize = NodeSizeByDistance(nearSplineSnapPoint, SnapIndicatorSize); var snapBounds = new BoundingSphere(nearSplineSnapPoint, snapIndicatorSize); - - if (snapBounds.Intersects(selectedNodeBounds)) + if (snapBounds.Intersects(selectedPointBounds)) { - _currentEditSpline.SetSplinePoint(selectedNode.Index, snapBounds.Center); + spline.SetSplinePoint(selectedPoint.Index, snapBounds.Center); } - DebugDraw.DrawSphere(snapBounds, Color.Yellow, 0, true); } } @@ -494,14 +478,12 @@ namespace FlaxEditor.SceneGraph.Actors var spline = (Spline)Actor; spline.AddSplineLocalPoint(Vector3.Zero, false); spline.AddSplineLocalPoint(new Vector3(0, 0, 100.0f)); - spline.SetSplineKeyframe(0, new BezierCurve.Keyframe() { Value = new Transform(Vector3.Zero, Quaternion.Identity, Vector3.One), TangentIn = new Transform(Vector3.Backward * 100, Quaternion.Identity, Vector3.One), TangentOut = new Transform(Vector3.Forward * 100, Quaternion.Identity, Vector3.One), }); - spline.SetSplineKeyframe(1, new BezierCurve.Keyframe() { Value = new Transform(Vector3.Forward * 100, Quaternion.Identity, Vector3.One), @@ -553,7 +535,6 @@ namespace FlaxEditor.SceneGraph.Actors internal static void OnSplineEdited(Spline spline) { - _currentEditSpline = spline; var collider = spline.GetChild(); if (collider && collider.Scene && collider.IsActiveInHierarchy && collider.HasStaticFlag(StaticFlags.Navigation) && !Editor.IsPlayMode) { @@ -565,39 +546,18 @@ namespace FlaxEditor.SceneGraph.Actors } } - private static SplinePointNode GetSelectedSplineNode() + private static List GetSplinesInView() { - var selection = Editor.Instance.SceneEditing.Selection; - if (selection.Count != 1) - return null; - if (selection[0] is not SplineNode.SplinePointNode) - return null; - - return (SplinePointNode)selection[0]; - } - - private static List GetSplinesOnView() - { - var splines = Level.GetActors(); - var splinesOnView = new List(); - - var viewTransform = Editor.Instance.Windows.EditWin.Viewport.ViewTransform; - var viewFov = Editor.Instance.Windows.EditWin.Viewport.FieldOfView; - var viewNear = Editor.Instance.Windows.EditWin.Viewport.NearPlane; - var viewFar = Editor.Instance.Windows.EditWin.Viewport.FarPlane; - var viewAspect = Editor.Instance.Windows.EditWin.Width / Editor.Instance.Windows.EditWin.Height; - var viewBounds = BoundingFrustum.FromCamera(viewTransform.Translation, viewTransform.Forward, viewTransform.Up, viewFov, viewNear, viewFar, viewAspect); - + var splines = Level.GetActors(true); + var result = new List(); + var viewBounds = Editor.Instance.Windows.EditWin.Viewport.ViewFrustum; foreach (var s in splines) { var contains = viewBounds.Contains(s.EditorBox); if (contains == ContainmentType.Contains || contains == ContainmentType.Intersects) - { - splinesOnView.Add(s); - } + result.Add(s); } - - return splinesOnView; + return result; } private static Vector3 GetNearSplineSnapPosition(Vector3 position, List splines) diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index 526a84c45..759c3736a 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -334,6 +334,22 @@ namespace FlaxEditor.Viewport } } + /// + /// Gets the bounding frustum of the current viewport camera. + /// + public BoundingFrustum ViewFrustum + { + get + { + Vector3 viewOrigin = Task.View.Origin; + Float3 position = ViewPosition - viewOrigin; + CreateViewMatrix(position, out var view); + CreateProjectionMatrix(out var projection); + Matrix.Multiply(ref view, ref projection, out var viewProjection); + return new BoundingFrustum(ref viewProjection); + } + } + /// /// Gets or sets the yaw angle (in degrees). /// diff --git a/Source/Engine/Core/Math/BoundingFrustum.cs b/Source/Engine/Core/Math/BoundingFrustum.cs index 970957fbb..25cd0c33b 100644 --- a/Source/Engine/Core/Math/BoundingFrustum.cs +++ b/Source/Engine/Core/Math/BoundingFrustum.cs @@ -104,6 +104,16 @@ namespace FlaxEngine GetPlanesFromMatrix(ref pMatrix, out pNear, out pFar, out pLeft, out pRight, out pTop, out pBottom); } + /// + /// Creates a new instance of BoundingFrustum. + /// + /// Combined matrix that usually takes view × projection matrix. + public BoundingFrustum(ref Matrix matrix) + { + pMatrix = matrix; + GetPlanesFromMatrix(ref pMatrix, out pNear, out pFar, out pLeft, out pRight, out pTop, out pBottom); + } + /// /// Returns a hash code for this instance. /// diff --git a/Source/Engine/Graphics/RenderView.cs b/Source/Engine/Graphics/RenderView.cs index b770037a3..72cfe3577 100644 --- a/Source/Engine/Graphics/RenderView.cs +++ b/Source/Engine/Graphics/RenderView.cs @@ -27,7 +27,7 @@ namespace FlaxEngine Matrix.Invert(ref View, out IV); Matrix.Invert(ref Projection, out IP); Matrix.Multiply(ref View, ref Projection, out var viewProjection); - Frustum = new BoundingFrustum(viewProjection); + Frustum = new BoundingFrustum(ref viewProjection); Matrix.Invert(ref viewProjection, out IVP); CullingFrustum = Frustum; } diff --git a/Source/Engine/UI/UICanvas.cs b/Source/Engine/UI/UICanvas.cs index bc8c360d1..dda941745 100644 --- a/Source/Engine/UI/UICanvas.cs +++ b/Source/Engine/UI/UICanvas.cs @@ -450,7 +450,7 @@ namespace FlaxEngine { camera.GetMatrices(out tmp1, out var tmp3, ref viewport); Matrix.Multiply(ref tmp1, ref tmp3, out tmp2); - var frustum = new BoundingFrustum(tmp2); + var frustum = new BoundingFrustum(ref tmp2); _guiRoot.Size = new Float2(frustum.GetWidthAtDepth(Distance), frustum.GetHeightAtDepth(Distance)); } else From 2fd47f91e263d030e0a83333a379c8e6b27b5d4d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 19 Feb 2024 17:36:37 +0100 Subject: [PATCH 128/139] Fix codestyle #2179 --- Source/Engine/Core/Collections/Array.h | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/Source/Engine/Core/Collections/Array.h b/Source/Engine/Core/Collections/Array.h index 9c50c5ccf..d67aecbc1 100644 --- a/Source/Engine/Core/Collections/Array.h +++ b/Source/Engine/Core/Collections/Array.h @@ -250,6 +250,16 @@ public: return _count == 0; } + /// + /// Determines if given index is valid. + /// + /// The index. + /// true if is valid a index; otherwise, false. + bool IsValidIndex(int32 index) const + { + return index < _count && index >= 0; + } + /// /// Gets the pointer to the first item in the collection (linear allocation). /// @@ -737,18 +747,7 @@ public: ::Swap(other, *this); } } - - /// - /// Determines if is valid index. - /// - /// The index. - /// - /// true if is valid a index; otherwise, false. - /// - bool IsValidIndex(int index) const - { - return index < _count && index >= 0; - } + /// /// Reverses the order of the added items in the collection. /// From 5cada42842f2e30e3d9abe8e5a4bcc1abeac958a Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 19 Feb 2024 17:37:59 +0100 Subject: [PATCH 129/139] Fix missing engine api expose in `Font.h` #2245 --- Source/Engine/Render2D/Font.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Render2D/Font.h b/Source/Engine/Render2D/Font.h index cd92103cb..c55bb58a1 100644 --- a/Source/Engine/Render2D/Font.h +++ b/Source/Engine/Render2D/Font.h @@ -18,7 +18,7 @@ struct FontTextureAtlasSlot; /// /// The text range. /// -API_STRUCT(NoDefault) struct TextRange +API_STRUCT(NoDefault) struct FLAXENGINE_API TextRange { DECLARE_SCRIPTING_TYPE_MINIMAL(TextRange); @@ -88,7 +88,7 @@ struct TIsPODType /// /// The font line info generated during text processing. /// -API_STRUCT(NoDefault) struct FontLineCache +API_STRUCT(NoDefault) struct FLAXENGINE_API FontLineCache { DECLARE_SCRIPTING_TYPE_MINIMAL(FontLineCache); @@ -152,7 +152,7 @@ struct TIsPODType /// /// The cached font character entry (read for rendering and further processing). /// -API_STRUCT(NoDefault) struct FontCharacterEntry +API_STRUCT(NoDefault) struct FLAXENGINE_API FontCharacterEntry { DECLARE_SCRIPTING_TYPE_MINIMAL(FontCharacterEntry); From 056deb58ad095ab6830bade86213000d42387513 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 19 Feb 2024 17:48:18 +0100 Subject: [PATCH 130/139] Fix compilation in #2257 --- Source/Engine/Physics/Colliders/CharacterController.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Physics/Colliders/CharacterController.cpp b/Source/Engine/Physics/Colliders/CharacterController.cpp index 61fc4d471..db15dd46f 100644 --- a/Source/Engine/Physics/Colliders/CharacterController.cpp +++ b/Source/Engine/Physics/Colliders/CharacterController.cpp @@ -210,7 +210,7 @@ void CharacterController::CreateController() // Setup PhysicsBackend::SetControllerUpDirection(_controller, _upDirection); - PhysicsBackend::SetShapeLocalPose(_shape, Vector3.Zero, Quaternion::Identity); + PhysicsBackend::SetShapeLocalPose(_shape, Vector3::Zero, Quaternion::Identity); UpdateLayerBits(); UpdateBounds(); } From 0035c347f433867f29d6f32703d33994f83f4728 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 19 Feb 2024 18:11:57 +0100 Subject: [PATCH 131/139] Add safety check for splatmap sampling --- Source/Engine/Terrain/TerrainPatch.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Engine/Terrain/TerrainPatch.cpp b/Source/Engine/Terrain/TerrainPatch.cpp index c5d1ace05..e24376f1f 100644 --- a/Source/Engine/Terrain/TerrainPatch.cpp +++ b/Source/Engine/Terrain/TerrainPatch.cpp @@ -610,6 +610,7 @@ FORCE_INLINE byte GetPhysicalMaterial(const Color32& raw, const TerrainDataUpdat uint8 layer = 0; uint8 layerWeight = 0; const int32 splatmapTextureIndex = (chunkZ * info.ChunkSize + z) * info.HeightmapSize + chunkX * info.ChunkSize + x; + ASSERT(splatmapTextureIndex < info.HeightmapLength); for (int32 splatIndex = 0; splatIndex < TERRAIN_MAX_SPLATMAPS_COUNT; splatIndex++) { for (int32 channelIndex = 0; channelIndex < 4; channelIndex++) From f8d9817911d182fa09cc5125e49abcb24de24b5a Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 19 Feb 2024 18:23:14 +0100 Subject: [PATCH 132/139] Fix terrain paint crash refression --- Source/Editor/Tools/Terrain/PaintTerrainGizmoMode.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/Editor/Tools/Terrain/PaintTerrainGizmoMode.cs b/Source/Editor/Tools/Terrain/PaintTerrainGizmoMode.cs index 4e7925dd9..7d6af355a 100644 --- a/Source/Editor/Tools/Terrain/PaintTerrainGizmoMode.cs +++ b/Source/Editor/Tools/Terrain/PaintTerrainGizmoMode.cs @@ -48,6 +48,7 @@ namespace FlaxEditor.Tools.Terrain if (DataPtr != IntPtr.Zero) Marshal.FreeHGlobal(DataPtr); DataPtr = Marshal.AllocHGlobal(size); + Utils.MemoryClear(DataPtr, (ulong)size); Size = size; } } @@ -294,7 +295,7 @@ namespace FlaxEditor.Tools.Terrain base.OnDeactivated(); // Free temporary memory buffer - foreach (var splatmapData in _cachedSplatmapData) + foreach (ref var splatmapData in _cachedSplatmapData.AsSpan()) splatmapData.Free(); } From e985d10b6258cee1fc227b8d95cd92bc1aa167de Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 19 Feb 2024 19:03:11 +0100 Subject: [PATCH 133/139] Fix nested prefabs instance building logic --- Source/Engine/Level/SceneObjectsFactory.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Level/SceneObjectsFactory.cpp b/Source/Engine/Level/SceneObjectsFactory.cpp index 006e54aa6..b60e5fc7d 100644 --- a/Source/Engine/Level/SceneObjectsFactory.cpp +++ b/Source/Engine/Level/SceneObjectsFactory.cpp @@ -475,7 +475,7 @@ void SceneObjectsFactory::SetupPrefabInstances(Context& context, const PrefabSyn const ISerializable::DeserializeStream* prefabData; if (prefab->ObjectsDataCache.TryGet(prefabObjectId, prefabData) && JsonTools::GetGuidIfValid(prefabObjectId, *prefabData, "PrefabObjectID")) { - prefabId = JsonTools::GetGuid(stream, "PrefabID"); + prefabId = JsonTools::GetGuid(*prefabData, "PrefabID"); prefab = Content::LoadAsync(prefabId); if (prefab && !prefab->WaitForLoaded()) { From ec0849c9081dd95832e6aaa3691d4a81e6b1c906 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 19 Feb 2024 19:12:53 +0100 Subject: [PATCH 134/139] Add Ctrl for additive selection in rubberband --- Source/Editor/Content/GUI/ContentView.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Source/Editor/Content/GUI/ContentView.cs b/Source/Editor/Content/GUI/ContentView.cs index 83c5377b7..755a3a1ac 100644 --- a/Source/Editor/Content/GUI/ContentView.cs +++ b/Source/Editor/Content/GUI/ContentView.cs @@ -616,6 +616,7 @@ namespace FlaxEditor.Content.GUI { if (base.OnMouseDown(location, button)) return true; + if (button == MouseButton.Left) { _mousePresslocation = location; @@ -634,6 +635,7 @@ namespace FlaxEditor.Content.GUI _rubberBandRectangle.Width = location.X - _mousePresslocation.X; _rubberBandRectangle.Height = location.Y - _mousePresslocation.Y; } + base.OnMouseMove(location); } @@ -655,9 +657,10 @@ namespace FlaxEditor.Content.GUI _rubberBandRectangle.Size = size; } var itemsInRectangle = _items.Where(t => _rubberBandRectangle.Intersects(t.Bounds)).ToList(); - Select(itemsInRectangle, Input.GetKey(KeyboardKeys.Shift)); + Select(itemsInRectangle, Input.GetKey(KeyboardKeys.Shift) || Input.GetKey(KeyboardKeys.Control)); return true; } + return base.OnMouseUp(location, button); } From c1400748b06875e88c4c91916f1527d9f46fc3b4 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 19 Feb 2024 20:36:41 +0100 Subject: [PATCH 135/139] Fix debug draw lists allocation #2259 --- Source/Engine/Debug/DebugDraw.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/Engine/Debug/DebugDraw.cpp b/Source/Engine/Debug/DebugDraw.cpp index 8f65e609d..c54108b1e 100644 --- a/Source/Engine/Debug/DebugDraw.cpp +++ b/Source/Engine/Debug/DebugDraw.cpp @@ -400,10 +400,10 @@ DebugDrawCall WriteList(int32& vertexCounter, const Array& list) drawCall.StartVertex = vertexCounter; drawCall.VertexCount = list.Count() * 2; vertexCounter += drawCall.VertexCount; - Vertex* dst = DebugDrawVB->WriteReserve(drawCall.VertexCount); + Vertex* dst = DebugDrawVB->WriteReserve(list.Count() * 2); for (int32 i = 0, j = 0; i < list.Count(); i++) { - const DebugLine& l = list[i]; + const DebugLine& l = list.Get()[i]; dst[j++] = { l.Start, l.Color }; dst[j++] = { l.End, l.Color }; } @@ -416,10 +416,10 @@ DebugDrawCall WriteList(int32& vertexCounter, const Array& list) drawCall.StartVertex = vertexCounter; drawCall.VertexCount = list.Count() * 3; vertexCounter += drawCall.VertexCount; - Vertex* dst = DebugDrawVB->WriteReserve(drawCall.VertexCount); + Vertex* dst = DebugDrawVB->WriteReserve(list.Count() * 3); for (int32 i = 0, j = 0; i < list.Count(); i++) { - const DebugTriangle& l = list[i]; + const DebugTriangle& l = list.Get()[i]; dst[j++] = { l.V0, l.Color }; dst[j++] = { l.V1, l.Color }; dst[j++] = { l.V2, l.Color }; From 668b03a409fa4819cdfe506e6f12ea2311004180 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 19 Feb 2024 20:40:30 +0100 Subject: [PATCH 136/139] Use #2234 for all platforms by default --- Source/Engine/Platform/Base/WindowBase.cpp | 3 --- Source/Engine/Platform/Base/WindowBase.h | 10 +++++----- Source/Engine/Platform/Linux/LinuxWindow.cpp | 2 -- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/Source/Engine/Platform/Base/WindowBase.cpp b/Source/Engine/Platform/Base/WindowBase.cpp index 49b4cc631..f4b840a5e 100644 --- a/Source/Engine/Platform/Base/WindowBase.cpp +++ b/Source/Engine/Platform/Base/WindowBase.cpp @@ -100,9 +100,6 @@ WindowBase::WindowBase(const CreateWindowSettings& settings) , _dpi(96) , _dpiScale(1.0f) , _trackingMouseOffset(Float2::Zero) - , _isUsingMouseOffset(false) - , _isTrackingMouse(false) - , _isClippingCursor(false) , RenderTask(nullptr) { // Update window location based on start location diff --git a/Source/Engine/Platform/Base/WindowBase.h b/Source/Engine/Platform/Base/WindowBase.h index 812359fb4..4c5082c9c 100644 --- a/Source/Engine/Platform/Base/WindowBase.h +++ b/Source/Engine/Platform/Base/WindowBase.h @@ -284,12 +284,12 @@ protected: float _dpiScale; Float2 _trackingMouseOffset; - bool _isUsingMouseOffset; + bool _isUsingMouseOffset = false; Rectangle _mouseOffsetScreenSize; - bool _isTrackingMouse; - bool _isHorizontalFlippingMouse; - bool _isVerticalFlippingMouse; - bool _isClippingCursor; + bool _isTrackingMouse = false; + bool _isHorizontalFlippingMouse = false; + bool _isVerticalFlippingMouse = false; + bool _isClippingCursor = false; explicit WindowBase(const CreateWindowSettings& settings); virtual ~WindowBase(); diff --git a/Source/Engine/Platform/Linux/LinuxWindow.cpp b/Source/Engine/Platform/Linux/LinuxWindow.cpp index b771bb45f..9004ed84c 100644 --- a/Source/Engine/Platform/Linux/LinuxWindow.cpp +++ b/Source/Engine/Platform/Linux/LinuxWindow.cpp @@ -55,8 +55,6 @@ LinuxWindow::LinuxWindow(const CreateWindowSettings& settings) return; auto screen = XDefaultScreen(display); - // default to false - _isHorizontalFlippingMouse = false; // Cache data int32 width = Math::TruncToInt(settings.Size.X); int32 height = Math::TruncToInt(settings.Size.Y); From 4ad5eb26d6d1420759c61cc291599d0745960800 Mon Sep 17 00:00:00 2001 From: rkrahn Date: Mon, 19 Feb 2024 13:00:38 -0800 Subject: [PATCH 137/139] Fix NextUnitVector2 degree to radian Fixes the use of degrees when radians should be used. --- Source/Engine/Utilities/Extensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Utilities/Extensions.cs b/Source/Engine/Utilities/Extensions.cs index 57eafc4a3..0da0c00c7 100644 --- a/Source/Engine/Utilities/Extensions.cs +++ b/Source/Engine/Utilities/Extensions.cs @@ -298,8 +298,8 @@ namespace FlaxEngine.Utilities public static Vector2 NextUnitVector2(this Random random, float radius = 1.0f) { float magnitude = (float)random.NextDouble() * radius; - double randomDegree = random.NextDouble() * 360; - return new Vector2((float)Math.Cos(randomDegree) * magnitude, (float)Math.Sin(randomDegree) * magnitude); + double randomRadian = random.NextDouble() * Mathf.RevolutionsToRadians; + return new Vector2((float)Math.Cos(randomRadian) * magnitude, (float)Math.Sin(randomRadian) * magnitude); } /// From 8aaf7417cc796f856cc811f9d3e8c2fef2b9f3f2 Mon Sep 17 00:00:00 2001 From: rkrahn Date: Mon, 19 Feb 2024 13:04:26 -0800 Subject: [PATCH 138/139] Fix NextUnitCircleVector2 degree to radian Fixes the use of degrees when radians should be used. --- Source/Engine/Utilities/Extensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Utilities/Extensions.cs b/Source/Engine/Utilities/Extensions.cs index dde5eb5c7..5530673fc 100644 --- a/Source/Engine/Utilities/Extensions.cs +++ b/Source/Engine/Utilities/Extensions.cs @@ -309,8 +309,8 @@ namespace FlaxEngine.Utilities /// A random . public static Vector2 NextUnitCircleVector2(this Random random, float radius = 1.0f) { - double randomDegree = random.NextDouble() * 360; - return new Vector2((float)Math.Cos(randomDegree) * radius, (float)Math.Sin(randomDegree) * radius); + double randomRadian = random.NextDouble() * Mathf.RevolutionsToRadians; + return new Vector2((float)Math.Cos(randomRadian) * radius, (float)Math.Sin(randomRadian) * radius); } /// From db65b8037d8ffd59324a2015d5cba6cff57db26d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 19 Feb 2024 22:14:39 +0100 Subject: [PATCH 139/139] Fix crash due to font changes refactor #2260 --- Source/Engine/Render2D/Font.cpp | 1 + Source/Engine/Render2D/FontManager.cpp | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Render2D/Font.cpp b/Source/Engine/Render2D/Font.cpp index 71eca1f59..973bf21e3 100644 --- a/Source/Engine/Render2D/Font.cpp +++ b/Source/Engine/Render2D/Font.cpp @@ -62,6 +62,7 @@ void Font::GetCharacter(Char c, FontCharacterEntry& result, bool enableFallback) // Create character cache FontManager::AddNewEntry(this, c, result); + ASSERT(result.Font); // Add to the dictionary _characters.Add(c, result); diff --git a/Source/Engine/Render2D/FontManager.cpp b/Source/Engine/Render2D/FontManager.cpp index 2e1ba8c6b..7aef7098e 100644 --- a/Source/Engine/Render2D/FontManager.cpp +++ b/Source/Engine/Render2D/FontManager.cpp @@ -161,6 +161,12 @@ bool FontManager::AddNewEntry(Font* font, Char c, FontCharacterEntry& entry) } #endif + // Init the character data + Platform::MemoryClear(&entry, sizeof(entry)); + entry.Character = c; + entry.Font = font; + entry.IsValid = false; + // Load the glyph const FT_Error error = FT_Load_Glyph(face, glyphIndex, glyphFlags); if (error) @@ -195,8 +201,6 @@ bool FontManager::AddNewEntry(Font* font, Char c, FontCharacterEntry& entry) ASSERT(bitmap && bitmap->pixel_mode == FT_PIXEL_MODE_GRAY); // Fill the character data - Platform::MemoryClear(&entry, sizeof(entry)); - entry.Character = c; entry.AdvanceX = Convert26Dot6ToRoundedPixel(glyph->advance.x); entry.OffsetY = glyph->bitmap_top; entry.OffsetX = glyph->bitmap_left; @@ -289,7 +293,6 @@ bool FontManager::AddNewEntry(Font* font, Char c, FontCharacterEntry& entry) entry.UVSize.X = static_cast(slot->Width - 2 * padding); entry.UVSize.Y = static_cast(slot->Height - 2 * padding); entry.Slot = slot; - entry.Font = font; return false; }