Skip to content

Commit

Permalink
#5336: Add two more unit tests covering CSG merge
Browse files Browse the repository at this point in the history
  • Loading branch information
codereader committed Sep 27, 2020
1 parent 4e06dd6 commit 62303e9
Show file tree
Hide file tree
Showing 6 changed files with 188 additions and 8 deletions.
78 changes: 77 additions & 1 deletion test/CSG.cpp
Expand Up @@ -5,13 +5,89 @@
#include "entitylib.h"
#include "algorithm/Scene.h"

TEST_F(RadiantTest, CSGMergeTwoRegularWorldspawnBrushes)
{
loadMap("csg_merge.map");

// Locate the first worldspawn brush
auto worldspawn = GlobalMapModule().getWorldspawn();

// Try to merge the two brushes with the "1" and "2" materials
auto firstBrush = test::algorithm::findFirstBrushWithMaterial(worldspawn, "1");
auto secondBrush = test::algorithm::findFirstBrushWithMaterial(worldspawn, "2");

ASSERT_TRUE(Node_getIBrush(firstBrush)->getNumFaces() == 5);
ASSERT_TRUE(Node_getIBrush(secondBrush)->getNumFaces() == 5);

// Select the brushes and merge them
GlobalSelectionSystem().setSelectedAll(false);
Node_setSelected(firstBrush, true);
Node_setSelected(secondBrush, true);

// CSG merge
GlobalCommandSystem().executeCommand("CSGMerge");

// The two brushes should be gone, replaced by a new one
ASSERT_TRUE(firstBrush->getParent() == nullptr);
ASSERT_TRUE(secondBrush->getParent() == nullptr);

// The merged brush will carry both materials
auto brushWithMaterial1 = test::algorithm::findFirstBrushWithMaterial(worldspawn, "1");
auto brushWithMaterial2 = test::algorithm::findFirstBrushWithMaterial(worldspawn, "2");

ASSERT_TRUE(brushWithMaterial1 == brushWithMaterial2);
ASSERT_TRUE(Node_getIBrush(brushWithMaterial1)->getNumFaces() == 6);
}

TEST_F(RadiantTest, CSGMergeFourRegularWorldspawnBrushes)
{
loadMap("csg_merge.map");

// Locate the first worldspawn brush
auto worldspawn = GlobalMapModule().getWorldspawn();

// Try to merge the two brushes with the "1" and "2" materials
std::vector<scene::INodePtr> brushes = {
test::algorithm::findFirstBrushWithMaterial(worldspawn, "1"),
test::algorithm::findFirstBrushWithMaterial(worldspawn, "2"),
test::algorithm::findFirstBrushWithMaterial(worldspawn, "3"),
test::algorithm::findFirstBrushWithMaterial(worldspawn, "4")
};

// Check the correct setup
for (const auto& brush : brushes)
{
ASSERT_TRUE(Node_getIBrush(brush)->getNumFaces() == 5);
}

// Select the brushes and merge them
GlobalSelectionSystem().setSelectedAll(false);
for (const auto& brush : brushes)
{
Node_setSelected(brush, true);
}

// CSG merge
GlobalCommandSystem().executeCommand("CSGMerge");

// All brushes should be gone, replaced by a new one
for (const auto& brush : brushes)
{
ASSERT_TRUE(brush->getParent() == nullptr);
}

// The combined brush should be a 6-sided cuboid
auto brushWithMaterial1 = test::algorithm::findFirstBrushWithMaterial(worldspawn, "1");
ASSERT_TRUE(Node_getIBrush(brushWithMaterial1)->getNumFaces() == 6);
}

// Issue #5336: Crash when using CSG Merge on brushes that are entities
TEST_F(RadiantTest, CSGMergeWithFuncStatic)
{
loadMap("csg_merge_with_func_static.map");

// Locate the first worldspawn brush
scene::INodePtr firstBrush = test::algorithm::getNthChild(GlobalMapModule().getWorldspawn(), 0);
auto firstBrush = test::algorithm::getNthChild(GlobalMapModule().getWorldspawn(), 0);
ASSERT_TRUE(firstBrush);

// Locate the func_static in the map
Expand Down
32 changes: 32 additions & 0 deletions test/algorithm/Scene.h
Expand Up @@ -3,6 +3,7 @@
#include <cstddef>
#include "inode.h"
#include "ientity.h"
#include "ibrush.h"

namespace test::algorithm
{
Expand All @@ -28,4 +29,35 @@ inline scene::INodePtr getNthChild(const scene::INodePtr& parent, std::size_t in
return candidate;
}

// Finds the first matching child brush of the given parent node matching the given predicate
inline scene::INodePtr findFirstBrush(const scene::INodePtr& parent,
const std::function<bool(const IBrushNodePtr&)>& predicate)
{
scene::INodePtr candidate;

parent->foreachNode([&](const scene::INodePtr& node)
{
auto brushNode = std::dynamic_pointer_cast<IBrushNode>(node);

if (brushNode && predicate(brushNode))
{
candidate = node;
return false;
}

return true;
});

return candidate;
}

// Finds the first matching child brush of the given parent node, with any of the brush's faces matching the given material
inline scene::INodePtr findFirstBrushWithMaterial(const scene::INodePtr& parent, const std::string& material)
{
return findFirstBrush(parent, [&](const IBrushNodePtr& brush)
{
return brush->getIBrush().hasShader(material);
});
}

}
79 changes: 79 additions & 0 deletions test/resources/tdm/maps/csg_merge.map
@@ -0,0 +1,79 @@
Version 2
// entity 0
{
"classname" "worldspawn"
"description" "Brushes in this map have numbers as their material names, such that the unit test algorithm can find them"
// primitive 0
{
brushDef3
{
( 0 0 1 -64 ) ( ( 0.03125 0 0 ) ( 0 0.03125 0 ) ) "1" 0 0 0
( 0 0 -1 -64 ) ( ( 0.03125 0 0 ) ( 0 0.03125 0 ) ) "1" 0 0 0
( 0 -1 0 0 ) ( ( 0.03125 0 0 ) ( 0 0.03125 0 ) ) "1" 0 0 0
( -1 0 0 64 ) ( ( 0.03125 0 0 ) ( 0 0.03125 0 ) ) "1" 0 0 0
( 0.7071067932881648 0.7071067932881648 0 -90.5096695408851 ) ( ( 0.03125 0 0 ) ( 0 0.03125 0 ) ) "1" 0 0 0
}
}
// primitive 1
{
brushDef3
{
( 0 0 1 -64 ) ( ( 0.03125 0 0 ) ( 0 0.03125 0 ) ) "2" 0 0 0
( 0 1 0 -64 ) ( ( 0.03125 0 0 ) ( 0 0.03125 0 ) ) "2" 0 0 0
( 1 0 0 -128 ) ( ( 0.03125 0 0 ) ( 0 0.03125 0 ) ) "2" 0 0 0
( 0 0 -1 -64 ) ( ( 0.03125 0 0 ) ( 0 0.03125 0 ) ) "2" 0 0 0
( -0.7071067932881648 -0.7071067932881648 0 90.5096695408851 ) ( ( 0.03125 0 0 ) ( 0 0.03125 0 ) ) "2" 0 0 0
}
}
// primitive 2
{
brushDef3
{
( 0 0 1 -64 ) ( ( 0.03125 0 0 ) ( 0 0.03125 62 ) ) "4" 0 0 0
( 0 1 0 -64 ) ( ( 0.03125 0 2 ) ( 0 0.03125 0 ) ) "4" 0 0 0
( 1 0 0 -192 ) ( ( 0.03125 0 0 ) ( 0 0.03125 0 ) ) "4" 0 0 0
( 0 0 -1 -64 ) ( ( 0.03125 0 0 ) ( 0 0.03125 2 ) ) "4" 0 0 0
( -0.7071067932881648 -0.7071067932881648 0 135.7645043113276 ) ( ( 0.03125 0 126.5857849121094 ) ( 0 0.03125 0 ) ) "4" 0 0 0
}
}
// primitive 3
{
brushDef3
{
( 0 0 1 -64 ) ( ( 0.03125 0 0 ) ( 0 0.03125 62 ) ) "3" 0 0 0
( 0 0 -1 -64 ) ( ( 0.03125 0 0 ) ( 0 0.03125 2 ) ) "3" 0 0 0
( 0 -1 0 0 ) ( ( 0.03125 0 62 ) ( 0 0.03125 0 ) ) "3" 0 0 0
( -1 0 0 128 ) ( ( 0.03125 0 0 ) ( 0 0.03125 0 ) ) "3" 0 0 0
( 0.7071067932881648 0.7071067932881648 0 -135.7645043113276 ) ( ( 0.03125 0 1.41421365737915 ) ( 0 0.03125 0 ) ) "3" 0 0 0
}
}
}
// entity 1
{
"classname" "func_static"
"name" "func_static_1"
"model" "func_static_1"
"origin" "96 -32 0"
// primitive 0
{
brushDef3
{
( 0 0 1 -64 ) ( ( 0.03125 0 2 ) ( 0 0.03125 0 ) ) "1" 0 0 0
( 0 0 -1 -64 ) ( ( 0.03125 0 2 ) ( 0 0.03125 0 ) ) "1" 0 0 0
( 0 -1 0 -32 ) ( ( 0.03125 0 0 ) ( 0 0.03125 0 ) ) "1" 0 0 0
( -1 0 0 -32 ) ( ( 0.03125 0 62 ) ( 0 0.03125 0 ) ) "1" 0 0 0
( 0.7071067932881648 0.7071067932881648 0 -7.105427357601002e-15 ) ( ( 0.03125 0 2 ) ( 0 0.03125 0 ) ) "1" 0 0 0
}
}
// primitive 1
{
brushDef3
{
( 0 0 1 -64 ) ( ( 0.03125 0 2 ) ( 0 0.03125 0 ) ) "2" 0 0 0
( 0 1 0 -32 ) ( ( 0.03125 0 0 ) ( 0 0.03125 0 ) ) "2" 0 0 0
( 1 0 0 -32 ) ( ( 0.03125 0 2 ) ( 0 0.03125 0 ) ) "2" 0 0 0
( 0 0 -1 -64 ) ( ( 0.03125 0 2 ) ( 0 0.03125 0 ) ) "2" 0 0 0
( -0.7071067932881648 -0.7071067932881648 0 7.105427357601002e-15 ) ( ( 0.03125 0 2 ) ( 0 0.03125 0 ) ) "2" 0 0 0
}
}
}
5 changes: 0 additions & 5 deletions test/test.cpp

This file was deleted.

1 change: 0 additions & 1 deletion tools/msvc/Tests/Tests.vcxproj
Expand Up @@ -63,7 +63,6 @@
<ItemGroup>
<ClCompile Include="..\..\..\test\CSG.cpp" />
<ClCompile Include="..\..\..\test\HeadlessOpenGLContext.cpp" />
<ClCompile Include="..\..\..\test\test.cpp" />
</ItemGroup>
<ItemDefinitionGroup />
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
Expand Down
1 change: 0 additions & 1 deletion tools/msvc/Tests/Tests.vcxproj.filters
Expand Up @@ -3,7 +3,6 @@
<ItemGroup>
<ClCompile Include="..\..\..\test\CSG.cpp" />
<ClCompile Include="..\..\..\test\HeadlessOpenGLContext.cpp" />
<ClCompile Include="..\..\..\test\test.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\..\test\HeadlessOpenGLContext.h" />
Expand Down

0 comments on commit 62303e9

Please sign in to comment.