Skip to content
This repository has been archived by the owner on Mar 27, 2024. It is now read-only.

Commit

Permalink
1.0.3 - add initial fence mode.
Browse files Browse the repository at this point in the history
  • Loading branch information
algernon-A committed Dec 2, 2023
1 parent a34f9b0 commit 5653e02
Show file tree
Hide file tree
Showing 12 changed files with 180 additions and 33 deletions.
2 changes: 1 addition & 1 deletion BepInEx/manifest.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "Line_Tool_Lite",
"version_number": "1.0.2",
"version_number": "1.0.3",
"website_url": "https://github.com/algernon-A/LineToolLite",
"description": "Place objects in lines, curves, or circles",
"dependencies": [
Expand Down
3 changes: 3 additions & 0 deletions Changelog.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
1.0.3
- Add initial fence mode.

1.0.2
- Rework UI JavaScript to survive UI regeneration and enable compatibility with HookUI (thanks to Captain of Coit).
- Remove JavaScript globals.
Expand Down
7 changes: 5 additions & 2 deletions Code/LineModes/Circle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace LineTool
{
using Colossal.Mathematics;
using Game.Simulation;
using Unity.Collections;
using Unity.Mathematics;
Expand All @@ -26,11 +27,13 @@ public Circle(LineBase mode)
/// Calculates the points to use based on this mode.
/// </summary>
/// <param name="currentPos">Selection current position.</param>
/// <param name="fenceMode">Set to <c>true</c> if fence mode is active.</param>
/// <param name="spacing">Spacing setting.</param>
/// <param name="rotation">Rotation setting.</param>
/// <param name="zBounds">Prefab zBounds.</param>
/// <param name="pointList">List of points to populate.</param>
/// <param name="heightData">Terrain height data reference.</param>
public override void CalculatePoints(float3 currentPos, float spacing, int rotation, NativeList<PointData> pointList, ref TerrainHeightData heightData)
public override void CalculatePoints(float3 currentPos, bool fenceMode, float spacing, int rotation, Bounds1 zBounds, NativeList<PointData> pointList, ref TerrainHeightData heightData)
{
// Don't do anything if we don't have valid start.
if (!m_validStart)
Expand Down Expand Up @@ -59,7 +62,7 @@ public override void CalculatePoints(float3 currentPos, float spacing, int rotat
thisPoint.y = TerrainUtils.SampleHeight(ref heightData, thisPoint);

// Add point to list.
pointList.Add(new PointData { Position = thisPoint, Rotation = quaternion.Euler(0f, math.radians(i + rotation), 0f), });
pointList.Add(new PointData { Position = thisPoint, Rotation = quaternion.Euler(0f, math.radians(rotation) - i, 0f), });
}
}

Expand Down
21 changes: 15 additions & 6 deletions Code/LineModes/LineBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,13 @@ public virtual void ItemsPlaced(float3 position)
/// Calculates the points to use based on this mode.
/// </summary>
/// <param name="currentPos">Selection current position.</param>
/// <param name="fenceMode">Set to <c>true</c> if fence mode is active.</param>
/// <param name="spacing">Spacing setting.</param>
/// <param name="rotation">Rotation setting.</param>
/// <param name="zBounds">Prefab zBounds.</param>
/// <param name="pointList">List of points to populate.</param>
/// <param name="heightData">Terrain height data reference.</param>
public virtual void CalculatePoints(float3 currentPos, float spacing, int rotation, NativeList<PointData> pointList, ref TerrainHeightData heightData)
public virtual void CalculatePoints(float3 currentPos, bool fenceMode, float spacing, int rotation, Bounds1 zBounds, NativeList<PointData> pointList, ref TerrainHeightData heightData)
{
// Don't do anything if we don't have a valid start point.
if (!m_validStart)
Expand All @@ -107,17 +109,24 @@ public virtual void CalculatePoints(float3 currentPos, float spacing, int rotati
// Calculate length.
float length = math.length(currentPos - m_startPos);

// Calculate applied rotation (in radians).
float appliedRotation = math.radians(rotation);
if (fenceMode)
{
float3 difference = currentPos - m_startPos;
appliedRotation = math.atan2(difference.x, difference.z);
}

// Rotation quaternion.
quaternion rotationQuaternion = quaternion.Euler(0f, math.radians(rotation), 0f);
quaternion rotationQuaternion = quaternion.Euler(0f, appliedRotation, 0f);

// Create points.
float currentDistance = 0f;
while (currentDistance < length)
float currentDistance = fenceMode ? -zBounds.min : 0f;
float endLength = fenceMode ? length - zBounds.max : length;
while (currentDistance < endLength)
{
// Calculate interpolated point.
float3 thisPoint = math.lerp(m_startPos, currentPos, currentDistance / length);

// Calculate terrain height.
thisPoint.y = TerrainUtils.SampleHeight(ref heightData, thisPoint);

// Add point to list.
Expand Down
24 changes: 16 additions & 8 deletions Code/LineModes/SimpleCurve.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,13 @@ public override void ItemsPlaced(float3 position)
/// Calculates the points to use based on this mode.
/// </summary>
/// <param name="currentPos">Selection current position.</param>
/// <param name="fenceMode">Set to <c>true</c> if fence mode is active.</param>
/// <param name="spacing">Spacing setting.</param>
/// <param name="rotation">Rotation setting.</param>
/// <param name="zBounds">Prefab zBounds.</param>
/// <param name="pointList">List of points to populate.</param>
/// <param name="heightData">Terrain height data reference.</param>
public override void CalculatePoints(float3 currentPos, float spacing, int rotation, NativeList<PointData> pointList, ref TerrainHeightData heightData)
public override void CalculatePoints(float3 currentPos, bool fenceMode, float spacing, int rotation, Bounds1 zBounds, NativeList<PointData> pointList, ref TerrainHeightData heightData)
{
// Don't do anything if we don't have valid start.
if (!m_validStart)
Expand All @@ -96,31 +98,37 @@ public override void CalculatePoints(float3 currentPos, float spacing, int rotat
// If we have a valid start but no valid elbow, just draw a straight line.
if (!m_validElbow)
{
base.CalculatePoints(currentPos, spacing, rotation, pointList, ref heightData);
base.CalculatePoints(currentPos, fenceMode, spacing, rotation, zBounds, pointList, ref heightData);
return;
}

// Calculate Bezier.
_thisBezier = NetUtils.FitCurve(new Line3.Segment(m_startPos, m_elbowPoint), new Line3.Segment(currentPos, m_elbowPoint));

// Rotation quaternion.
// Default rotation quaternion.
quaternion qRotation = quaternion.Euler(0f, math.radians(rotation), 0f);

// Create points.
float tFactor = 0f;
while (tFactor < 1.0f)
{
// Calculate point.
Vector3 thisPoint = MathUtils.Position(_thisBezier, tFactor);
float3 thisPoint = MathUtils.Position(_thisBezier, tFactor);

// Get next t factor.
tFactor = BezierStep(tFactor, spacing);

// Calculate applied rotation for fence mode.
if (fenceMode)
{
float3 difference = MathUtils.Position(_thisBezier, tFactor) - thisPoint;
qRotation = quaternion.Euler(0f, math.atan2(difference.x, difference.z), 0f);
}

// Calculate terrain height.
thisPoint.y = TerrainUtils.SampleHeight(ref heightData, thisPoint);

// Add point to list.
pointList.Add(new PointData { Position = thisPoint, Rotation = qRotation, });

// Get next t factor.
tFactor = BezierStep(tFactor, spacing);
}
}

Expand Down
52 changes: 46 additions & 6 deletions Code/Systems/LineToolSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ namespace LineTool
using System.Reflection;
using Colossal.Entities;
using Colossal.Logging;
using Colossal.Mathematics;
using Game.Common;
using Game.Input;
using Game.Objects;
Expand Down Expand Up @@ -35,6 +36,7 @@ public sealed partial class LineToolSystem : ObjectToolBaseSystem

// Line calculations.
private readonly NativeList<PointData> _points = new (Allocator.Persistent);
private bool _fenceMode = false;
private bool _fixedPreview = false;
private float3 _fixedPos;
private Random _random = new ();
Expand All @@ -45,9 +47,10 @@ public sealed partial class LineToolSystem : ObjectToolBaseSystem
private Entity _cursorEntity = Entity.Null;

// Prefab selection.
private ObjectPrefab _selectedPrefab;
private ObjectGeometryPrefab _selectedPrefab;
private Entity _selectedEntity = Entity.Null;
private int _originalXP;
private Bounds1 _zBounds;

// References.
private ILog _log;
Expand Down Expand Up @@ -81,11 +84,21 @@ public sealed partial class LineToolSystem : ObjectToolBaseSystem
public override string toolID => "Line Tool";

/// <summary>
/// Gets or sets the line spacing.
/// Gets or sets the effective line spacing.
/// </summary>
internal float Spacing
{
get => _spacing;
get
{
// Use calculated spacing for fence mode.
if (_fenceMode)
{
return _zBounds.max - _zBounds.min;
}

// Not fence mode - use manual spacing.
return _spacing;
}

set
{
Expand All @@ -94,6 +107,19 @@ internal float Spacing
}
}

/// <summary>
/// Gets or sets a value indicating whether fence mode is active.
/// </summary>
internal bool FenceMode
{
get => _fenceMode;
set
{
_fenceMode = value;
_dirty = true;
}
}

/// <summary>
/// Gets or sets a value indicating whether random rotation is active.
/// </summary>
Expand Down Expand Up @@ -171,18 +197,32 @@ private PrefabBase SelectedPrefab
{
set
{
_selectedPrefab = value as ObjectPrefab;
_selectedPrefab = value as ObjectGeometryPrefab;

// Update selected entity;
// Update selected entity.
if (_selectedPrefab is null)
{
// No valid entity selected.
_selectedEntity = Entity.Null;
}
else
{
// Get selected entity.
_selectedEntity = m_PrefabSystem.GetEntity(_selectedPrefab);

// Check bounds.
_zBounds.min = 0;
_zBounds.max = 0;
foreach (ObjectMeshInfo mesh in _selectedPrefab.m_Meshes)
{
if (mesh.m_Mesh is RenderPrefab renderPrefab)
{
// Update bounds if either of the z extents of this mesh exceed the previous extent.
_zBounds.min = math.min(_zBounds.min, renderPrefab.bounds.z.min);
_zBounds.max = math.max(_zBounds.max, renderPrefab.bounds.z.max);
}
}

// Reduce any XP to zero while we're using the tool.
if (EntityManager.TryGetComponent(_selectedEntity, out PlaceableObjectData placeableData))
{
Expand Down Expand Up @@ -440,7 +480,7 @@ protected override JobHandle OnUpdate(JobHandle inputDeps)

// If we got here we're (re)calculating points.
_points.Clear();
_mode.CalculatePoints(position, Spacing, _rotation, _points, ref _terrainHeightData);
_mode.CalculatePoints(position, _fenceMode, Spacing, _rotation, _zBounds, _points, ref _terrainHeightData);

// Step along length and place objects.
int count = 0;
Expand Down
20 changes: 18 additions & 2 deletions Code/Systems/LineToolUISystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,17 @@ protected override void OnUpdate()
{
ExecuteScript(_uiView, $"document.getElementById(\"line-tool-rotation-random\").classList.add(\"selected\");");

// Hide rotation button.
ExecuteScript(_uiView, "lineToolSetRotationVisibility(false);");
// Hide rotation buttons.
ExecuteScript(_uiView, "lineTool.setRotationVisibility(false);");
}

// Select fence mode button if needed.
if (_lineToolSystem.FenceMode)
{
// Hide rotation and spacing buttons.
ExecuteScript(_uiView, $"document.getElementById(\"line-tool-fence\").classList.add(\"selected\");");
ExecuteScript(_uiView, "let randomRotationButton = document.getElementById(\"line-tool-rotation-random\"); lineTool.setButtonVisibility(randomRotationButton, false);");
ExecuteScript(_uiView, "lineTool.setRotationVisibility(false); lineTool.setSpacingVisibility(false);");
}

// Show tree control menu if tree control is active.
Expand All @@ -117,6 +126,7 @@ protected override void OnUpdate()

// Register event callbacks.
_eventHandles.Add(_uiView.RegisterForEvent("SetLineToolSpacing", (Action<float>)SetSpacing));
_eventHandles.Add(_uiView.RegisterForEvent("SetLineToolFenceMode", (Action<bool>)SetFenceMode));
_eventHandles.Add(_uiView.RegisterForEvent("SetLineToolRandomRotation", (Action<bool>)SetRandomRotation));
_eventHandles.Add(_uiView.RegisterForEvent("SetLineToolRotation", (Action<int>)SetRotation));
_eventHandles.Add(_uiView.RegisterForEvent("SetStraightMode", (Action)SetStraightMode));
Expand Down Expand Up @@ -316,6 +326,12 @@ private string EscapeToJavaScript(string sourceString)
/// <param name="spacing">Value to set.</param>
private void SetSpacing(float spacing) => _lineToolSystem.Spacing = spacing;

/// <summary>
/// Event callback to set fence mode.
/// </summary>
/// <param name="isActive">Value to set.</param>
private void SetFenceMode(bool isActive) => _lineToolSystem.FenceMode = isActive;

/// <summary>
/// Event callback to set the random rotation override.
/// </summary>
Expand Down
16 changes: 16 additions & 0 deletions Icons/Fence.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion LineToolLite.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<Authors>algernon</Authors>
<Copyright>Copyright © 2023 algernon (github.com/algernon-A). All rights reserved.</Copyright>
<Product>$(Title)</Product>
<Version>1.0.2</Version>
<Version>1.0.3</Version>
<LangVersion>9.0</LangVersion>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup>
Expand Down
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Cities Skylines 2 : Line Tool Lite
- **Place objects in lines** - straight lines, curves, or circles.
- **Fence mode** - automatically align objects end-to-end.
- **Accurate placement** - no worrying about imprecision.
- Easily adjust **spacing** and **rotation** using the in-game tool UI.
- Live **previewing** included, so you can provisionally place a line and adjust spacing and/or rotation to see how it looks in real-time before making the final placement (or cancelling).
Expand All @@ -19,6 +20,7 @@ Works on all types of objects - trees, shrubs, props. You can even place simple
- **Control-click** at the end will leave the line in preview mode; it's not fully placed yet so you can go and adjust the settings and see the results in real time. When finished, **left-click** to place or **right-click** to cancel.

### Use the tool UI to:
- Toggle **fence mode**.
- Toggle between **straight line**, **curved**, and **circle** modes.
- Adjust **distances** using the arrow buttons - plain click for 1m increments, **shift-click** for 10m, **control-click** for 0.1m.
- Select **random rotation** to have each object in the line have a different randomly-chosen rotation, or otherwise **manually adjust the rotation** for all items using the arrow buttons - plain click for 10-degree increments, **shift-click** for 90 degrees, **control-click** for 1 degree.
Expand All @@ -30,9 +32,6 @@ Works on all types of objects - trees, shrubs, props. You can even place simple
## Requirements
- BepInEx 5

## Conflicts
- HookUI

## Installation
1. Make sure that BepInEx 5 is installed.
1. Make sure that you're **NOT** using HookUI or mods that use it (such as Unemployment Monitor, Extended Tooltip, City Monitor, or Vehicle Counter).
Expand Down
3 changes: 3 additions & 0 deletions UI/ui.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
<div class="item-content_nNz">
<div class="label_RZX">Line mode</div>
<div class="content_ZIz">
<button id="line-tool-fence" class="button_KVN">
<img class="icon_Ysc" src="coui://linetool/Fence.svg">
</button>
<button id="line-tool-straight" class="button_KVN">
<img class="icon_Ysc" src="Media/Tools/Net Tool/Straight.svg">
</button>
Expand Down
Loading

0 comments on commit 5653e02

Please sign in to comment.