diff --git a/editor/src/panels/ViewportPanelContent.h b/editor/src/panels/ViewportPanelContent.h index 24ea337f..21af2da9 100644 --- a/editor/src/panels/ViewportPanelContent.h +++ b/editor/src/panels/ViewportPanelContent.h @@ -78,9 +78,9 @@ inline void RenderViewportInteractionSurface( result.itemMin = ImGui::GetItemRectMin(); result.itemMax = ImGui::GetItemRectMax(); result.hovered = ImGui::IsItemHovered(); - result.clickedLeft = ImGui::IsItemClicked(ImGuiMouseButton_Left); - result.clickedRight = ImGui::IsItemClicked(ImGuiMouseButton_Right); - result.clickedMiddle = ImGui::IsItemClicked(ImGuiMouseButton_Middle); + result.clickedLeft = result.hovered && ImGui::IsMouseClicked(ImGuiMouseButton_Left, false); + result.clickedRight = result.hovered && ImGui::IsMouseClicked(ImGuiMouseButton_Right, false); + result.clickedMiddle = result.hovered && ImGui::IsMouseClicked(ImGuiMouseButton_Middle, false); } inline ViewportPanelContentResult RenderViewportPanelContent( diff --git a/engine/src/Resources/BuiltinResources.cpp b/engine/src/Resources/BuiltinResources.cpp index 24e092d2..4afc558a 100644 --- a/engine/src/Resources/BuiltinResources.cpp +++ b/engine/src/Resources/BuiltinResources.cpp @@ -865,7 +865,10 @@ LoadResult CreateBuiltinMeshResource(const Containers::String& path) { return LoadResult(Containers::String("Unsupported builtin mesh: ") + path); } - FlipTriangleWinding(buffers); + // The UV sphere generator already emits triangles in the runtime's front-face convention. + if (primitiveType != BuiltinPrimitiveType::Sphere) { + FlipTriangleWinding(buffers); + } Mesh* mesh = BuildMeshResource(path, GetBuiltinPrimitiveDisplayName(primitiveType), std::move(buffers)); if (mesh == nullptr) { diff --git a/tests/Resources/Mesh/test_mesh_loader.cpp b/tests/Resources/Mesh/test_mesh_loader.cpp index 8b953170..f2ae3e70 100644 --- a/tests/Resources/Mesh/test_mesh_loader.cpp +++ b/tests/Resources/Mesh/test_mesh_loader.cpp @@ -1,6 +1,8 @@ #include #include #include +#include +#include #include #include #include @@ -23,6 +25,16 @@ std::string GetMeshFixturePath(const char* fileName) { return (std::filesystem::path(XCENGINE_TEST_FIXTURES_DIR) / "Resources" / "Mesh" / fileName).string(); } +XCEngine::Core::uint32 ReadMeshIndex(const Mesh& mesh, XCEngine::Core::uint32 index) { + if (mesh.IsUse32BitIndex()) { + const auto* indices = static_cast(mesh.GetIndexData()); + return indices[index]; + } + + const auto* indices = static_cast(mesh.GetIndexData()); + return static_cast(indices[index]); +} + XCEngine::Core::uint32 GetFirstSectionMaterialIndex(const Mesh& mesh) { if (mesh.GetSections().Empty()) { return 0; @@ -142,6 +154,41 @@ TEST(MeshLoader, LoadValidObjMesh) { delete mesh; } +TEST(MeshLoader, BuiltinSphereUsesFrontFacingWindingForOutwardNormals) { + LoadResult result = CreateBuiltinMeshResource(GetBuiltinPrimitiveMeshPath(BuiltinPrimitiveType::Sphere)); + ASSERT_TRUE(result); + ASSERT_NE(result.resource, nullptr); + + auto* mesh = static_cast(result.resource); + const auto* vertices = static_cast(mesh->GetVertexData()); + ASSERT_NE(vertices, nullptr); + ASSERT_GE(mesh->GetIndexCount(), 3u); + + bool foundNonDegenerateTriangle = false; + for (XCEngine::Core::uint32 index = 0; index + 2 < mesh->GetIndexCount(); index += 3) { + const XCEngine::Core::uint32 i0 = ReadMeshIndex(*mesh, index + 0); + const XCEngine::Core::uint32 i1 = ReadMeshIndex(*mesh, index + 1); + const XCEngine::Core::uint32 i2 = ReadMeshIndex(*mesh, index + 2); + + const XCEngine::Math::Vector3 edge01 = vertices[i1].position - vertices[i0].position; + const XCEngine::Math::Vector3 edge02 = vertices[i2].position - vertices[i0].position; + const XCEngine::Math::Vector3 geometricNormal = + XCEngine::Math::Vector3::Cross(edge01, edge02); + if (geometricNormal.SqrMagnitude() <= XCEngine::Math::EPSILON) { + continue; + } + + const XCEngine::Math::Vector3 averagedVertexNormal = + (vertices[i0].normal + vertices[i1].normal + vertices[i2].normal).Normalized(); + EXPECT_LT(XCEngine::Math::Vector3::Dot(geometricNormal, averagedVertexNormal), 0.0f); + foundNonDegenerateTriangle = true; + break; + } + + EXPECT_TRUE(foundNonDegenerateTriangle); + delete mesh; +} + TEST(MeshLoader, GeneratesNormalsAndTangentsWhenRequested) { MeshLoader loader; MeshImportSettings settings; diff --git a/tests/editor/test_scene_viewport_navigation.cpp b/tests/editor/test_scene_viewport_navigation.cpp index 8aaddf50..ea5db61f 100644 --- a/tests/editor/test_scene_viewport_navigation.cpp +++ b/tests/editor/test_scene_viewport_navigation.cpp @@ -13,6 +13,8 @@ using XCEngine::Editor::SceneViewportNavigationState; using XCEngine::Editor::SceneViewportToolMode; using XCEngine::Editor::SceneViewportToolShortcutRequest; using XCEngine::Editor::UpdateSceneViewportNavigationState; +using XCEngine::UI::UIPoint; +using XCEngine::UI::UISize; TEST(SceneViewportNavigationTest, ToolShortcutActionIgnoresShortcutsDuringTextInputOrDrag) { SceneViewportToolShortcutRequest request = {}; @@ -73,6 +75,19 @@ TEST(SceneViewportNavigationTest, NavigationUpdateBeginsLookAndPanDrags) { EXPECT_TRUE(panUpdate.beginLeftPanDrag); EXPECT_TRUE(panUpdate.state.panDragging); EXPECT_EQ(panUpdate.state.panDragButton, ImGuiMouseButton_Left); + + SceneViewportNavigationRequest middlePanRequest = {}; + middlePanRequest.hasInteractiveViewport = true; + middlePanRequest.viewportHovered = true; + middlePanRequest.clickedMiddle = true; + middlePanRequest.middleMouseDown = true; + const auto middlePanUpdate = UpdateSceneViewportNavigationState(middlePanRequest); + EXPECT_FALSE(middlePanUpdate.beginLookDrag); + EXPECT_TRUE(middlePanUpdate.beginPanDrag); + EXPECT_FALSE(middlePanUpdate.beginLeftPanDrag); + EXPECT_TRUE(middlePanUpdate.beginMiddlePanDrag); + EXPECT_TRUE(middlePanUpdate.state.panDragging); + EXPECT_EQ(middlePanUpdate.state.panDragButton, ImGuiMouseButton_Middle); } TEST(SceneViewportNavigationTest, NavigationUpdateEndsDragsWhenButtonsRelease) { @@ -105,7 +120,7 @@ TEST(SceneViewportNavigationTest, CaptureFlagsTrackNavigationAndActiveGizmos) { TEST(SceneViewportNavigationTest, BuildInputRoutesWheelFocusMovementAndMouseDelta) { SceneViewportInputBuildRequest request = {}; - request.viewportSize = ImVec2(640.0f, 360.0f); + request.viewportSize = UISize(640.0f, 360.0f); request.viewportHovered = true; request.viewportFocused = true; request.mouseWheel = 2.0f; @@ -117,10 +132,10 @@ TEST(SceneViewportNavigationTest, BuildInputRoutesWheelFocusMovementAndMouseDelt request = {}; request.state.lookDragging = true; - request.viewportSize = ImVec2(640.0f, 360.0f); + request.viewportSize = UISize(640.0f, 360.0f); request.viewportHovered = true; request.mouseWheel = 1.5f; - request.mouseDelta = ImVec2(5.0f, -3.0f); + request.mouseDelta = UIPoint(5.0f, -3.0f); request.fastMove = true; request.focusSelectionKeyPressed = true; request.moveForwardKeyDown = true;