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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions Test.MMManaged/Test.MMManaged.dotnet.fsproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>net462</TargetFramework>
<AssemblyName>Test.MMManaged</AssemblyName>
<RootNamespace>Test.MMManaged</RootNamespace>

<!-- Match original output layout -->
<OutputPath>..\$(Configuration)\</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>

<DocumentationFile>$(OutputPath)Test.MMManaged.XML</DocumentationFile>

<!-- F# tail-call behavior, matching original per-config settings -->
<Tailcalls Condition="'$(Configuration)' == 'Debug'">false</Tailcalls>
<Tailcalls Condition="'$(Configuration)' == 'Release'">true</Tailcalls>

<!-- Required for `dotnet test` to discover and run NUnit tests -->
<IsPackable>false</IsPackable>
<GenerateProgramFile>false</GenerateProgramFile>
</PropertyGroup>

<PropertyGroup>
<DisableImplicitFSharpCoreReference>true</DisableImplicitFSharpCoreReference>
</PropertyGroup>
<ItemGroup>
<Reference Include="FSharp.Core">
<HintPath>..\FSharp.Core.4.4.3.0\FSharp.Core.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>

<!-- F# compilation order is significant - keep this list in dependency order -->
<ItemGroup>
<Compile Include="Program.fs" />
<Compile Include="Util.fs" />
<Compile Include="TestMeshTransform.fs" />
<Compile Include="TestModDB.fs" />
<Compile Include="TestYaml.fs" />
<Compile Include="TestMesh.fs" />
<Compile Include="TestModDBInterop.fs" />
<Compile Include="TestWriters.fs" />
<Compile Include="TestMeshRelation.fs" />
</ItemGroup>

<ItemGroup>
<None Include="App.config" />
<Content Include="paket.references" />
</ItemGroup>

<!-- Reference assemblies for net462 - required for non-Windows builds -->
<ItemGroup>
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies"
Version="1.0.3" PrivateAssets="All" />
</ItemGroup>

<!-- Test runner integration: lets `dotnet test` discover and run the NUnit tests.
Original tests were authored against NUnit 2 but only use Assert.IsTrue /
Assert.AreEqual, which carry over unchanged to NUnit 3. -->
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.*" />
<PackageReference Include="NUnit" Version="3.*" />
<PackageReference Include="NUnit3TestAdapter" Version="4.*" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\MMManaged\MMManaged.dotnet.fsproj">
<Private>True</Private>
</ProjectReference>
<ProjectReference Include="..\MMManaged.Engine\MMManaged.Engine.dotnet.fsproj">
<Private>True</Private>
</ProjectReference>
</ItemGroup>

<!-- Package dependencies via HintPath into the existing packages folder.
Matches the pattern used by MMManaged.dotnet.fsproj. -->
<ItemGroup>
<Reference Include="MonoGame.Framework">
<HintPath>..\packages\MonoGame.Framework.WindowsDX\lib\net40\MonoGame.Framework.dll</HintPath>
</Reference>
<Reference Include="SharpDX">
<HintPath>..\packages\MonoGame.Framework.WindowsDX\lib\net40\SharpDX.dll</HintPath>
</Reference>
<Reference Include="SharpDX.Direct3D9">
<HintPath>..\packages\MonoGame.Framework.WindowsDX\lib\net40\SharpDX.Direct3D9.dll</HintPath>
</Reference>
<Reference Include="SharpDX.DXGI">
<HintPath>..\packages\MonoGame.Framework.WindowsDX\lib\net40\SharpDX.DXGI.dll</HintPath>
</Reference>
<Reference Include="YamlDotNet">
<HintPath>..\packages\YamlDotNet\lib\net35\YamlDotNet.dll</HintPath>
</Reference>
<!-- NUnit comes from PackageReference above (NUnit 3.x).
FsUnit was listed in paket.references but no test code uses it,
so it's intentionally omitted here. -->
</ItemGroup>

<!-- Paket integration: harmless if file doesn't exist. -->
<Import Project="..\.paket\Paket.Restore.targets"
Condition="Exists('..\.paket\Paket.Restore.targets')" />

</Project>
1 change: 1 addition & 0 deletions Test.MMManaged/Test.MMManaged.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
<Compile Include="TestMesh.fs" />
<Compile Include="TestModDBInterop.fs" />
<Compile Include="TestWriters.fs" />
<Compile Include="TestMeshRelation.fs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MMManaged\MMManaged.fsproj">
Expand Down
137 changes: 137 additions & 0 deletions Test.MMManaged/TestMeshRelation.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
module TestMeshRelation

open NUnit.Framework

open ModelMod
open ModelMod.CoreTypes

// Build a minimal Mesh with the given positions and vertex-group annotations.
// The relation builder only consults Positions, AnnotatedVertexGroups, and Type
// (the last only matters for CPU skinning, which we don't exercise here).
let private mkMesh (modType: ModType) (positions: Vec3F[]) (groups: string list[]) =
{
Mesh.Type = modType
Triangles = [||]
Positions = positions
UVs = [||]
Normals = [||]
BlendIndices = [||]
BlendWeights = [||]
Declaration = None
BinaryVertexData = None
AnnotatedVertexGroups = groups
AppliedPositionTransforms = [||]
AppliedUVTransforms = [||]
Tex0Path = ""
Tex1Path = ""
Tex2Path = ""
Tex3Path = ""
Cached = false
}

let private mkRef (name: string) (mesh: Mesh) : DBReference =
{
Name = name
Mesh = lazy mesh
MeshPath = ""
MeshReadFlags = DefaultReadFlags
PrimCount = 0
VertCount = mesh.Positions.Length
}

let private mkMod (name: string) (refr: DBReference) (mesh: Mesh) : DBMod =
{
RefName = Some refr.Name
Type = mesh.Type
Ref = Some refr
Name = name
Mesh = Some (lazy mesh)
MeshPath = ""
MeshReadFlags = DefaultReadFlags
WeightMode = WeightMode.Ref
PixelShader = ""
Attributes = EmptyModAttributes
ParentModName = None
UpdateTangentSpace = None
Profile = None
VBChecksum = None
}

[<Test>]
let ``MeshRelation: Include and Exclude vertex group tracking``() =
// Reference mesh: 8 verts, indices 0..3 in "Hat.Top", indices 4..7 in "Hat.Bottom".
// Top and bottom positions are deliberately interleaved very close together so that
// a naive nearest-neighbor match without group filtering would pick the wrong group;
// this proves the include/exclude logic is doing the work, not coincidence.
let refPositions = [|
// Hat.Top (indices 0..3) at x = 0.00, 1.00, 2.00, 3.00
Vec3F(0.0f, 0.f, 0.f)
Vec3F(1.0f, 0.f, 0.f)
Vec3F(2.0f, 0.f, 0.f)
Vec3F(3.0f, 0.f, 0.f)
// Hat.Bottom (indices 4..7) at x = 0.05, 1.05, 2.05, 3.05
Vec3F(0.05f, 0.f, 0.f)
Vec3F(1.05f, 0.f, 0.f)
Vec3F(2.05f, 0.f, 0.f)
Vec3F(3.05f, 0.f, 0.f)
|]
let refGroups : string list [] = [|
["Hat.Top"]; ["Hat.Top"]; ["Hat.Top"]; ["Hat.Top"]
["Hat.Bottom"]; ["Hat.Bottom"]; ["Hat.Bottom"]; ["Hat.Bottom"]
|]
let refMesh = mkMesh Reference refPositions refGroups

// Mod mesh: 8 verts.
// verts 0..3 use "Include.Hat.Top": positioned at x = 0.04 etc., physically
// closer to Hat.Bottom (0.05) than Hat.Top (0.00). The include filter
// must force them to map to Hat.Top (indices 0..3).
// verts 4..7 use "Exclude.Hat.Top": positioned at x = 0.01 etc., physically
// closer to Hat.Top (0.00) than Hat.Bottom (0.05). The exclude filter
// must force them to map to Hat.Bottom (indices 4..7).
let modPositions = [|
Vec3F(0.04f, 0.f, 0.f)
Vec3F(1.04f, 0.f, 0.f)
Vec3F(2.04f, 0.f, 0.f)
Vec3F(3.04f, 0.f, 0.f)
Vec3F(0.01f, 0.f, 0.f)
Vec3F(1.01f, 0.f, 0.f)
Vec3F(2.01f, 0.f, 0.f)
Vec3F(3.01f, 0.f, 0.f)
|]
let modGroups : string list [] = [|
["Include.Hat.Top"]; ["Include.Hat.Top"]; ["Include.Hat.Top"]; ["Include.Hat.Top"]
["Exclude.Hat.Top"]; ["Exclude.Hat.Top"]; ["Exclude.Hat.Top"]; ["Exclude.Hat.Top"]
|]
let modMesh = mkMesh GPUReplacement modPositions modGroups

let refr = mkRef "TestRef" refMesh
let dbmod = mkMod "TestMod" refr modMesh

// empty bin cache dir disables disk caching for this test
let mr = MeshRelation.MeshRelation(dbmod, refr, "")
let vrs = mr.Build()

Assert.AreEqual(8, vrs.Length, sprintf "wrong vert rel count: %d" vrs.Length)

// First 4 mod verts (Include.Hat.Top) must map into the Hat.Top ref range 0..3.
for i in 0..3 do
let idx = vrs.[i].RefPointIdx
Assert.IsTrue(
idx >= 0 && idx <= 3,
sprintf "mod vert %d (Include.Hat.Top) matched ref idx %d; expected one of 0..3" i idx)

// Last 4 mod verts (Exclude.Hat.Top) must map into the Hat.Bottom ref range 4..7.
for i in 4..7 do
let idx = vrs.[i].RefPointIdx
Assert.IsTrue(
idx >= 4 && idx <= 7,
sprintf "mod vert %d (Exclude.Hat.Top) matched ref idx %d; expected one of 4..7" i idx)

// Check that all 4 Hat.Top ref verts get used by exactly one mod vert
// (4 Include.Hat.Top mod verts mapped to 4 distinct Hat.Top ref verts), and
// similarly for Hat.Bottom. This catches a regression where the filter
// works but every mod vert collapses onto a single ref vert.
let topMatches = [for i in 0..3 -> vrs.[i].RefPointIdx] |> Set.ofList
let botMatches = [for i in 4..7 -> vrs.[i].RefPointIdx] |> Set.ofList
Assert.AreEqual(4, topMatches.Count, sprintf "Include.Hat.Top mod verts collapsed: %A" topMatches)
Assert.AreEqual(4, botMatches.Count, sprintf "Exclude.Hat.Top mod verts collapsed: %A" botMatches)
1 change: 1 addition & 0 deletions build.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ Target "BuildFS" (fun _ ->

Target "BuildTest" (fun _ ->
!! "**/Test.*.fsproj"
-- "**/*dotnet.fsproj"
-- "**/Test.ManagedLaunch.fsproj"
|> MSBuildRelease testDir "Build"
|> Log "BuildTest-Output: "
Expand Down
Loading