Skip to content

Commit

Permalink
Add ability to disconnect polyline items
Browse files Browse the repository at this point in the history
  • Loading branch information
sk-zk committed Feb 14, 2024
1 parent 6c5407e commit 62df2bd
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 4 deletions.
1 change: 1 addition & 0 deletions TruckLib/ScsMap/INode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public interface INode : IMapObject
void Move(Vector3 newPos);
void Translate(Vector3 translation);
void Merge(INode n2);
INode Split();
void Deserialize(BinaryReader r, uint? version = null);
string ToString();
void UpdateItemReferences(Dictionary<ulong, MapItem> allItems);
Expand Down
57 changes: 57 additions & 0 deletions TruckLib/ScsMap/Node.cs
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,63 @@ public void Merge(INode n2)
}
}

/// <summary>
/// Splits this node into one node holding the backward item and one node
/// holding the forward item.
/// This method is the opposite of <see cref="Merge(INode)">Merge</see>.
/// </summary>
/// <returns>The newly created node. If no action was performed because
/// this node is already only used by one item, the method returns null.</returns>
/// <exception cref="InvalidOperationException">Thrown if the node can''t be split.</exception>
public INode Split()
{
if (ForwardItem is null || BackwardItem is null)
{
return null;
}
else if (ForwardItem is PolylineItem fwPoly && BackwardItem is PolylineItem bwPoly)
{
var newNode = Parent.AddNode(Position);
newNode.ForwardItem = fwPoly;
newNode.IsRed = true;
newNode.Rotation = Rotation;
IsRed = false;
ForwardItem = null;
fwPoly.Node = newNode;
fwPoly.Recalculate();
bwPoly.Recalculate();
return newNode;
}
else if (ForwardItem is PolylineItem && BackwardItem is Prefab)
{
var newNode = Parent.AddNode(Position);
newNode.ForwardItem = ForwardItem;
newNode.IsRed = true;
newNode.Rotation = Rotation;
IsRed = false;
(ForwardItem as PolylineItem).Node = newNode;
(ForwardItem as PolylineItem).Recalculate();
ForwardItem = BackwardItem;
BackwardItem = null;
Rotation = Quaternion.Inverse(Rotation);
return newNode;
}
else if (ForwardItem is Prefab && BackwardItem is PolylineItem)
{
var newNode = Parent.AddNode(Position);
newNode.BackwardItem = BackwardItem;
newNode.Rotation = Rotation;
(BackwardItem as PolylineItem).ForwardNode = newNode;
(BackwardItem as PolylineItem).Recalculate();
BackwardItem = null;
return newNode;
}
else
{
throw new InvalidOperationException("Unable to split");
}
}

/// <summary>
/// Recalculates the items attached to this node.
/// </summary>
Expand Down
2 changes: 2 additions & 0 deletions TruckLib/ScsMap/UnresolvedNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ public UnresolvedNode(ulong uid)
public bool IsOrphaned() => throw new InvalidOperationException();
public void Move(Vector3 newPos) => throw new InvalidOperationException();
public void Merge(INode n2) => throw new NotImplementedException();
public INode Split() => throw new NotImplementedException();

public void Translate(Vector3 translation) => throw new InvalidOperationException();
public void Deserialize(BinaryReader r, uint? version = null) => throw new InvalidOperationException();
public void UpdateItemReferences(Dictionary<ulong, MapItem> allItems) => throw new InvalidOperationException();
Expand Down
75 changes: 73 additions & 2 deletions TruckLibTests/TruckLib/ScsMap/NodeTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
using System.Threading.Tasks;
using TruckLib.ScsMap;
using System.Numerics;
using Newtonsoft.Json.Bson;
using TruckLib.Models.Ppd;

namespace TruckLibTests.TruckLib.ScsMap
{
Expand Down Expand Up @@ -160,5 +158,78 @@ public void MergeRoadBwNodeIntoPrefabOriginThrows()

Assert.Throws<InvalidOperationException>(() => prefab.Nodes[0].Merge(road.Node));
}

[Fact]
public void SplitRoads()
{
var map = new Map("foo");
var road1 = Road.Add(map, new Vector3(10, 0, 10), new Vector3(30, 0, 30), "blkw1");
var road2 = road1.Append(new Vector3(50, 0, 50));

var newNode = road2.Node.Split();

Assert.NotEqual(road1.ForwardNode, road2.Node);
Assert.Equal(newNode, road2.Node);
Assert.Null(road1.ForwardNode.ForwardItem);
Assert.Equal(road2, newNode.ForwardItem);
Assert.Null(newNode.BackwardItem);
Assert.True(newNode.IsRed);
Assert.False(road1.ForwardNode.IsRed);
Assert.Equal(road1.ForwardNode.Position, newNode.Position);
}

[Fact]
public void SplitReturnsNull()
{
var map = new Map("foo");
var model = Model.Add(map, new Vector3(1, 2, 3), "aaa", "bbb", "ccc");

Assert.Null(model.Node.Split());
}

[Fact]
public void SplitRoadAndPrefab()
{
var map = new Map("foo");
var prefab = Prefab.Add(map, new Vector3(50, 0, 50), "dlc_blkw_02", fixture.CrossingPpd);
var expectedPfNodeRot = prefab.Nodes[3].Rotation;
var road = prefab.AppendRoad(3, new Vector3(100, 0, 20), "ger1");

var newNode = road.Node.Split();

Assert.NotEqual(prefab.Nodes[3], road.Node);
Assert.Equal(newNode, road.Node);
Assert.Null(road.Node.BackwardItem);
Assert.Equal(road, newNode.ForwardItem);
Assert.Null(prefab.Nodes[3].BackwardItem);
Assert.Equal(prefab, prefab.Nodes[3].ForwardItem);
Assert.True(newNode.IsRed);
Assert.False(prefab.Nodes[3].IsRed);
Assert.Equal(prefab.Nodes[3].Position, newNode.Position);
AssertEx.Equal(expectedPfNodeRot, prefab.Nodes[3].Rotation, 0.001f);
}

[Fact]
public void SplitRoadAndPrefabAtOrigin()
{
var map = new Map("foo");
var prefab = Prefab.Add(map, new Vector3(50, 0, 50), "dlc_blkw_02", fixture.CrossingPpd);
prefab.ChangeOrigin(3);
var expectedPfNodeRot = prefab.Nodes[0].Rotation;
var road = prefab.AppendRoad(0, new Vector3(100, 0, 20), "ger1");

var newNode = road.ForwardNode.Split();

Assert.NotEqual(prefab.Nodes[0], road.ForwardNode);
Assert.Equal(newNode, road.ForwardNode);
Assert.Null(road.ForwardNode.ForwardItem);
Assert.Equal(road, newNode.BackwardItem);
Assert.Null(prefab.Nodes[0].BackwardItem);
Assert.Equal(prefab, prefab.Nodes[0].ForwardItem);
Assert.False(newNode.IsRed);
Assert.True(prefab.Nodes[0].IsRed);
Assert.Equal(prefab.Nodes[0].Position, newNode.Position);
AssertEx.Equal(expectedPfNodeRot, prefab.Nodes[0].Rotation, 0.001f);
}
}
}
16 changes: 14 additions & 2 deletions docfx/docs/TruckLib.ScsMap/polyline-items.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,20 @@ road1.ForwardNode.Merge(road2.Node);
road2.Node.Merge(road1.ForwardNode);
```

## First and last item
## Disconnecting two polyline items
You can disconnect two polyline items by calling [`Split`](xref:TruckLib.ScsMap.Node.Split*) on the node
which connects them. For example, the following line of code will disconnect `road` from its forward item:

```cs
INode newNode = road.ForwardNode.Split();
```

The method also returns the newly created node.

## Connecting/disconnecting polyline items to/from prefabs
See [Prefabs](~/docs/TruckLib.ScsMap/prefabs.md).

## First and last item
To find the first or last item of a polyline chain given one of its members, call [`FindFirstItem`](xref:TruckLib.ScsMap.PolylineItem.FindFirstItem*)
or [`FindLastItem`](xref:TruckLib.ScsMap.PolylineItem.FindLastItem*) respectively:

Expand All @@ -50,4 +62,4 @@ PolylineItem end = road.FindLastItem();
```

`start` and `end` are now the first and last polyline item of the chain `road` is a part of. Keep in mind that all polyline item types
can attach to each other, so `start` end `end` are not guaranteed to be of type [`Road`](xref:TruckLib.ScsMap.Road).
can attach to each other, so `start` end `end` are not guaranteed to be of the same type as `road`.
10 changes: 10 additions & 0 deletions docfx/docs/TruckLib.ScsMap/prefabs.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,13 @@ Road road = prefab.AppendRoad(1, new Vector3(42, 0, 21), "ger1");

The given position will be the position of the forward node of the road, unless you are appending to the origin node,
in which case it will be the backward node (meaning the road is actually prepended).

## Disconnecting polyline items
You can disconnect a polyline item from a prefab by calling [`Split`](xref:TruckLib.ScsMap.Node.Split*) on the node
which connects them. For example, the following line of code will detach the item attached to node 1:

```cs
INode newNode = prefab.Nodes[1].Split();
```

The method also returns the newly created node.

0 comments on commit 62df2bd

Please sign in to comment.