diff --git a/BepInEx/Plugin.cs b/BepInEx/Plugin.cs index c8bb430..b146e5c 100644 --- a/BepInEx/Plugin.cs +++ b/BepInEx/Plugin.cs @@ -14,7 +14,7 @@ namespace LineTool /// /// BepInEx plugin to substitute for IMod support. /// - [BepInPlugin(GUID, "Line Tool Lite", "1.0")] + [BepInPlugin(GUID, "Line Tool Lite", "1.0.6")] [HarmonyPatch] public class Plugin : BaseUnityPlugin { diff --git a/BepInEx/manifest.json b/BepInEx/manifest.json index 45937db..106c689 100644 --- a/BepInEx/manifest.json +++ b/BepInEx/manifest.json @@ -1,6 +1,6 @@ { "name": "Line_Tool_Lite", - "version_number": "1.0.5", + "version_number": "1.0.6", "website_url": "https://github.com/algernon-A/LineToolLite", "description": "Place objects in lines, curves, or circles", "dependencies": [ diff --git a/Changelog.txt b/Changelog.txt index f4545b3..876898a 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -1,3 +1,6 @@ +1.0.6 +- Add optional random spacing and lateral offset functions. + 1.0.5 - Enforce minimum spacing distance to prevent overlapping of multiple invisible items. diff --git a/Code/LineModes/Circle.cs b/Code/LineModes/Circle.cs index 90db761..2426223 100644 --- a/Code/LineModes/Circle.cs +++ b/Code/LineModes/Circle.cs @@ -29,11 +29,13 @@ public Circle(LineBase mode) /// Selection current position. /// Set to true if fence mode is active. /// Spacing setting. + /// Random spacing offset maximum. + /// Random lateral offset maximum. /// Rotation setting. /// Prefab zBounds. /// List of points to populate. /// Terrain height data reference. - public override void CalculatePoints(float3 currentPos, bool fenceMode, float spacing, int rotation, Bounds1 zBounds, NativeList pointList, ref TerrainHeightData heightData) + public override void CalculatePoints(float3 currentPos, bool fenceMode, float spacing, float randomSpacing, float randomOffset, int rotation, Bounds1 zBounds, NativeList pointList, ref TerrainHeightData heightData) { // Don't do anything if we don't have valid start. if (!m_validStart) @@ -50,14 +52,30 @@ public override void CalculatePoints(float3 currentPos, bool fenceMode, float sp float numPoints = math.floor(circumference / spacing); float increment = (math.PI * 2f) / numPoints; float startAngle = math.atan2(difference.z, difference.x); + System.Random random = new ((int)circumference * 1000); // Create points. for (float i = startAngle; i < startAngle + (math.PI * 2f); i += increment) { - float xPos = radius * math.cos(i); - float yPos = radius * math.sin(i); + // Apply spacing adjustment. + float adjustedAngle = i; + if (randomSpacing > 0f && !fenceMode) + { + float distanceAdjustment = (float)(random.NextDouble() * randomSpacing * 2f) - randomSpacing; + adjustedAngle += (distanceAdjustment * math.PI * 2f) / circumference; + } + + // Calculate point. + float xPos = radius * math.cos(adjustedAngle); + float yPos = radius * math.sin(adjustedAngle); float3 thisPoint = new (m_startPos.x + xPos, m_startPos.y, m_startPos.z + yPos); + // Apply offset adjustment. + if (randomOffset > 0f && !fenceMode) + { + thisPoint += math.normalize(thisPoint - m_startPos) * ((float)(randomOffset * random.NextDouble() * 2f) - randomOffset); + } + // Calculate terrain height. thisPoint.y = TerrainUtils.SampleHeight(ref heightData, thisPoint); diff --git a/Code/LineModes/LineBase.cs b/Code/LineModes/LineBase.cs index e0bb398..807c8d2 100644 --- a/Code/LineModes/LineBase.cs +++ b/Code/LineModes/LineBase.cs @@ -94,11 +94,13 @@ public virtual void ItemsPlaced(float3 position) /// Selection current position. /// Set to true if fence mode is active. /// Spacing setting. + /// Random spacing offset maximum. + /// Random lateral offset maximum. /// Rotation setting. /// Prefab zBounds. /// List of points to populate. /// Terrain height data reference. - public virtual void CalculatePoints(float3 currentPos, bool fenceMode, float spacing, int rotation, Bounds1 zBounds, NativeList pointList, ref TerrainHeightData heightData) + public virtual void CalculatePoints(float3 currentPos, bool fenceMode, float spacing, float randomSpacing, float randomOffset, int rotation, Bounds1 zBounds, NativeList pointList, ref TerrainHeightData heightData) { // Don't do anything if we don't have a valid start point. if (!m_validStart) @@ -107,13 +109,14 @@ public virtual void CalculatePoints(float3 currentPos, bool fenceMode, float spa } // Calculate length. - float length = math.length(currentPos - m_startPos); + float3 difference = currentPos - m_startPos; + float length = math.length(difference); + System.Random random = new ((int)length * 1000); // Calculate applied rotation (in radians). float appliedRotation = math.radians(rotation); if (fenceMode) { - float3 difference = currentPos - m_startPos; appliedRotation = math.atan2(difference.x, difference.z); } @@ -126,7 +129,21 @@ public virtual void CalculatePoints(float3 currentPos, bool fenceMode, float spa while (currentDistance < endLength) { // Calculate interpolated point. - float3 thisPoint = math.lerp(m_startPos, currentPos, currentDistance / length); + float spacingAdjustment = 0f; + if (randomSpacing > 0f && !fenceMode) + { + spacingAdjustment = (float)(random.NextDouble() * randomSpacing * 2f) - randomSpacing; + } + + float3 thisPoint = math.lerp(m_startPos, currentPos, (currentDistance + spacingAdjustment) / length); + + // Apply offset adjustment. + if (randomOffset > 0f && !fenceMode) + { + float3 left = math.normalize(new float3(-difference.z, 0f, difference.x)); + thisPoint += left * ((float)(randomOffset * random.NextDouble() * 2f) - randomOffset); + } + thisPoint.y = TerrainUtils.SampleHeight(ref heightData, thisPoint); // Add point to list. diff --git a/Code/LineModes/SimpleCurve.cs b/Code/LineModes/SimpleCurve.cs index fd4f6fb..2434a8c 100644 --- a/Code/LineModes/SimpleCurve.cs +++ b/Code/LineModes/SimpleCurve.cs @@ -83,11 +83,13 @@ public override void ItemsPlaced(float3 position) /// Selection current position. /// Set to true if fence mode is active. /// Spacing setting. + /// Random spacing offset maximum. + /// Random lateral offset maximum. /// Rotation setting. /// Prefab zBounds. /// List of points to populate. /// Terrain height data reference. - public override void CalculatePoints(float3 currentPos, bool fenceMode, float spacing, int rotation, Bounds1 zBounds, NativeList pointList, ref TerrainHeightData heightData) + public override void CalculatePoints(float3 currentPos, bool fenceMode, float spacing, float randomSpacing, float randomOffset, int rotation, Bounds1 zBounds, NativeList pointList, ref TerrainHeightData heightData) { // Don't do anything if we don't have valid start. if (!m_validStart) @@ -98,7 +100,7 @@ public override void CalculatePoints(float3 currentPos, bool fenceMode, float sp // If we have a valid start but no valid elbow, just draw a straight line. if (!m_validElbow) { - base.CalculatePoints(currentPos, fenceMode, spacing, rotation, zBounds, pointList, ref heightData); + base.CalculatePoints(currentPos, fenceMode, spacing, randomSpacing, randomOffset, rotation, zBounds, pointList, ref heightData); return; } @@ -108,11 +110,29 @@ public override void CalculatePoints(float3 currentPos, bool fenceMode, float sp // Default rotation quaternion. quaternion qRotation = quaternion.Euler(0f, math.radians(rotation), 0f); + System.Random random = new ((int)(currentPos.x + currentPos.z) * 1000); + float tFactor = 0f; while (tFactor < 1.0f) { + // Apply spacing randomization. + float adjustedT = tFactor; + if (randomSpacing > 0f && !fenceMode) + { + float spacingAdjustment = (float)(random.NextDouble() * randomSpacing * 2f) - randomSpacing; + adjustedT = spacingAdjustment < 0f ? BezierStepReverse(tFactor, spacingAdjustment) : BezierStep(tFactor, spacingAdjustment); + } + // Calculate point. - float3 thisPoint = MathUtils.Position(_thisBezier, tFactor); + float3 thisPoint = MathUtils.Position(_thisBezier, adjustedT); + + // Apply offset randomization. + if (randomOffset > 0f && !fenceMode) + { + float3 tangent = MathUtils.Tangent(_thisBezier, adjustedT); + float3 left = math.normalize(new float3(-tangent.z, 0f, tangent.x)); + thisPoint += left * ((float)(randomOffset * random.NextDouble() * 2f) - randomOffset); + } // Get next t factor. tFactor = BezierStep(tFactor, spacing); @@ -213,18 +233,19 @@ private float BezierStep(float tStart, float distance) } /// - /// Steps along a Bezier BACKWARDS from the end point, calculating the target t factor for the given spacing distance. + /// Steps along a Bezier BACKWARDS from the given t factor, calculating the target t factor for the given spacing distance. /// Code based on Alterran's PropLineTool (StepDistanceCurve, Utilities/PLTMath.cs). /// + /// Starting t factor. /// Distance to travel. /// Target t factor. - private float BezierStepReverse(float distance) + private float BezierStepReverse(float tStart, float distance) { const float Tolerance = 0.001f; const float ToleranceSquared = Tolerance * Tolerance; - float tEnd = Travel(1, -distance); - float usedDistance = CubicBezierArcLengthXZGauss04(tEnd, 1.0f); + float tEnd = Travel(tStart, -distance); + float usedDistance = CubicBezierArcLengthXZGauss04(tEnd, tStart); // Twelve iteration maximum for performance and to prevent infinite loops. for (int i = 0; i < 12; ++i) @@ -236,7 +257,7 @@ private float BezierStepReverse(float distance) break; } - usedDistance = CubicBezierArcLengthXZGauss04(tEnd, 1.0f); + usedDistance = CubicBezierArcLengthXZGauss04(tEnd, tStart); tEnd -= (distance - usedDistance) / CubicSpeedXZ(tEnd); } diff --git a/Code/Mod.cs b/Code/Mod.cs index 00a2695..eb87bfb 100644 --- a/Code/Mod.cs +++ b/Code/Mod.cs @@ -41,8 +41,10 @@ public void OnLoad() // Initialize logger. Log = LogManager.GetLogger(ModName); +#if DEBUG Log.Info("setting logging level to Debug"); Log.effectivenessLevel = Level.Debug; +#endif Log.Info("loading"); } diff --git a/Code/Systems/LineToolSystem.cs b/Code/Systems/LineToolSystem.cs index edc9c7b..21444d4 100644 --- a/Code/Systems/LineToolSystem.cs +++ b/Code/Systems/LineToolSystem.cs @@ -73,6 +73,8 @@ public sealed partial class LineToolSystem : ObjectToolBaseSystem private float _spacing = 20f; private bool _randomRotation = false; private int _rotation = 0; + private float _randomSpacing = 0f; + private float _randomOffset = 0f; private bool _dirty = false; // Tree Controller integration. @@ -141,6 +143,32 @@ internal bool RandomRotation } } + /// + /// Gets or sets the random spacing offset maximum. + /// + internal float RandomSpacing + { + get => _randomSpacing; + set + { + _randomSpacing = value; + _dirty = true; + } + } + + /// + /// Gets or sets the random lateral offset maximum. + /// + internal float RandomOffset + { + get => _randomOffset; + set + { + _randomOffset = value; + _dirty = true; + } + } + /// /// Gets or sets the rotation setting. /// @@ -488,7 +516,7 @@ protected override JobHandle OnUpdate(JobHandle inputDeps) // If we got here we're (re)calculating points. _points.Clear(); - _mode.CalculatePoints(position, _fenceMode, Spacing, _rotation, _zBounds, _points, ref _terrainHeightData); + _mode.CalculatePoints(position, _fenceMode, Spacing, RandomSpacing, RandomOffset, _rotation, _zBounds, _points, ref _terrainHeightData); // Step along length and place objects. int count = 0; diff --git a/Code/Systems/LineToolUISystem.cs b/Code/Systems/LineToolUISystem.cs index c42290c..8f1f41f 100644 --- a/Code/Systems/LineToolUISystem.cs +++ b/Code/Systems/LineToolUISystem.cs @@ -45,7 +45,7 @@ public sealed partial class LineToolUISystem : UISystemBase internal void UpdateSpacing() { // Multiply spacing by 10 for accuracy conversion) - ExecuteScript(_uiView, $"if (lineTool != null) {{ lineTool.spacing = {_lineToolSystem.RawSpacing * 10}; if (lineTool.refreshSpacing != null) lineTool.refreshSpacing();}}"); + ExecuteScript(_uiView, $"if (lineTool) {{ lineTool.spacing = {_lineToolSystem.RawSpacing * 10}; if (lineTool.refreshSpacing) lineTool.refreshSpacing();}}"); } /// @@ -87,8 +87,8 @@ protected override void OnUpdate() // Tool is now active but previously wasn't; ensure namespace. ExecuteScript(_uiView, "if (lineTool == null) var lineTool = {};"); - // Set initial rotation variable in UI (multiply spacing by 10 for accuracy conversion). - ExecuteScript(_uiView, $"lineTool.rotation = {_lineToolSystem.Rotation};"); + // Set initial rotation and offset variables in UI (multiply distances by 10 for accuracy conversion). + ExecuteScript(_uiView, $"lineTool.rotation = {_lineToolSystem.Rotation}; lineTool.randomSpacing = {_lineToolSystem.RandomSpacing * 10}; lineTool.randomOffset = {_lineToolSystem.RandomOffset * 10};"); // Attach our custom controls. // Inject scripts. @@ -117,13 +117,10 @@ protected override void OnUpdate() ExecuteScript(_uiView, "lineTool.setRotationVisibility(false);"); } - // Select fence mode button if needed. + // Select fence mode button if needed and update visibility states. if (_lineToolSystem.FenceMode) { - // Hide rotation and spacing buttons. - ExecuteScript(_uiView, $"document.getElementById(\"line-tool-fence\").classList.add(\"selected\");"); - ExecuteScript(_uiView, "lineTool.randomRotationButton = document.getElementById(\"line-tool-rotation-random\"); lineTool.setButtonVisibility(lineTool.randomRotationButton, false);"); - ExecuteScript(_uiView, "lineTool.setRotationVisibility(false); lineTool.setSpacingVisibility(false);"); + ExecuteScript(_uiView, $"document.getElementById(\"line-tool-fence\").classList.add(\"selected\"); lineTool.setFenceVisibility(false);"); } // Show tree control menu if tree control is active. @@ -136,14 +133,17 @@ protected override void OnUpdate() UpdateSpacing(); // Register event callbacks. - _eventHandles.Add(_uiView.RegisterForEvent("SetLineToolSpacing", (Action)SetSpacing)); _eventHandles.Add(_uiView.RegisterForEvent("SetLineToolFenceMode", (Action)SetFenceMode)); - _eventHandles.Add(_uiView.RegisterForEvent("SetLineToolRandomRotation", (Action)SetRandomRotation)); - _eventHandles.Add(_uiView.RegisterForEvent("SetLineToolRotation", (Action)SetRotation)); _eventHandles.Add(_uiView.RegisterForEvent("SetStraightMode", (Action)SetStraightMode)); _eventHandles.Add(_uiView.RegisterForEvent("SetSimpleCurveMode", (Action)SetSimpleCurveMode)); _eventHandles.Add(_uiView.RegisterForEvent("SetCircleMode", (Action)SetCircleMode)); + _eventHandles.Add(_uiView.RegisterForEvent("SetLineToolSpacing", (Action)SetSpacing)); + _eventHandles.Add(_uiView.RegisterForEvent("SetLineToolRandomRotation", (Action)SetRandomRotation)); + _eventHandles.Add(_uiView.RegisterForEvent("SetLineToolRotation", (Action)SetRotation)); + _eventHandles.Add(_uiView.RegisterForEvent("SetLineToolRandomSpacing", (Action)SetRandomSpacing)); + _eventHandles.Add(_uiView.RegisterForEvent("SetLineToolRandomOffset", (Action)SetRandomOffset)); _eventHandles.Add(_uiView.RegisterForEvent("LineToolTreeControlUpdated", (Action)TreeControlUpdated)); + _eventHandles.Add(_uiView.RegisterForEvent("SetLineToolSpacing", (Action)SetSpacing)); // Record current tool state. _toolIsActive = true; @@ -331,18 +331,33 @@ private string EscapeToJavaScript(string sourceString) return stringBuilder.ToString(); } - /// - /// Event callback to set current spacing. - /// - /// Value to set. - private void SetSpacing(float spacing) => _lineToolSystem.Spacing = spacing; - /// /// Event callback to set fence mode. /// /// Value to set. private void SetFenceMode(bool isActive) => _lineToolSystem.FenceMode = isActive; + /// + /// Event callback to set straight line mode. + /// + private void SetStraightMode() => _lineToolSystem.Mode = LineMode.Straight; + + /// + /// Event callback to set simple curve mode. + /// + private void SetSimpleCurveMode() => _lineToolSystem.Mode = LineMode.SimpleCurve; + + /// + /// Event callback to set circle mode. + /// + private void SetCircleMode() => _lineToolSystem.Mode = LineMode.Circle; + + /// + /// Event callback to set current spacing. + /// + /// Value to set. + private void SetSpacing(float spacing) => _lineToolSystem.Spacing = spacing; + /// /// Event callback to set the random rotation override. /// @@ -356,19 +371,16 @@ private string EscapeToJavaScript(string sourceString) private void SetRotation(int rotation) => _lineToolSystem.Rotation = rotation; /// - /// Event callback to set straight line mode. - /// - private void SetStraightMode() => _lineToolSystem.Mode = LineMode.Straight; - - /// - /// Event callback to set simple curve mode. + /// Event callback to set the random spacing offset maximum. /// - private void SetSimpleCurveMode() => _lineToolSystem.Mode = LineMode.SimpleCurve; + /// Value to set. + private void SetRandomSpacing(float randomSpacing) => _lineToolSystem.RandomSpacing = randomSpacing; /// - /// Event callback to set circle mode. + /// Event callback to set the random lateral offset maximum. /// - private void SetCircleMode() => _lineToolSystem.Mode = LineMode.Circle; + /// Value to set. + private void SetRandomOffset(float randomOffset) => _lineToolSystem.RandomOffset = randomOffset; /// /// Event callback to update Tree Control settings. diff --git a/Icons/Dice.svg b/Icons/Dice.svg new file mode 100644 index 0000000..47d3b2d --- /dev/null +++ b/Icons/Dice.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/Icons/RotateRandomLeft.svg b/Icons/RotateRandomLeft.svg deleted file mode 100644 index c022b43..0000000 --- a/Icons/RotateRandomLeft.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/LineToolLite.csproj b/LineToolLite.csproj index 124dd13..941669b 100644 --- a/LineToolLite.csproj +++ b/LineToolLite.csproj @@ -6,7 +6,7 @@ algernon Copyright © 2023 algernon (github.com/algernon-A). All rights reserved. $(Title) - 1.0.5 + 1.0.6 9.0 True @@ -37,8 +37,4 @@ - - - - diff --git a/README.md b/README.md index 56bb598..1593444 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ - **Fence mode** - automatically align and place objects end-to-end. - **Accurate placement** - no worrying about imprecision. - Easily adjust **spacing** and **rotation** using the in-game tool UI (including random rotation for that natural look). +- Optional **random position variation** can provide some natural irregularity. - 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). - Displays **distances** and **angles** for fine-tuning. @@ -24,6 +25,10 @@ Works on all types of objects - trees, shrubs, props. You can even place simple - 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. +- Set **variable spacing** to more than zero to have a random (length-ways) offset applied to each item's spacing, up to the maximum distance specified - plain click for 1m increments, **shift-click** for 10m, **control-click** for 0.1m. +- Set **variable offset** to more than zero to have a random sideways offset applied to each item, up to the maximum distance specified - plain click for 1m increments, **shift-click** for 10m, **control-click** for 0.1m. + +To remove variable spacing and/or offset, simply set the field(s) back to zero. **Shift-clicking** (10m increments) can make this faster. ### To exit the tool: - Press **Escape**, or diff --git a/UI/ui.html b/UI/ui.html index 5508b02..ef2de2f 100644 --- a/UI/ui.html +++ b/UI/ui.html @@ -1,4 +1,7 @@ +
+
Line Tool
+
Line mode
@@ -19,7 +22,7 @@
-
+
Spacing
-
+
Rotation
+
+
+
+
Variable spacing
+
+ +
0 m
+ +
+
+
+
+
+
Variable offset
+
+ +
0 m
+ +
+
+
+
diff --git a/UI/ui.js b/UI/ui.js index b9aa61d..14f3c1d 100644 --- a/UI/ui.js +++ b/UI/ui.js @@ -2,25 +2,70 @@ // Copyright (c) algernon (K. Algernon A. Sheppard). All rights reserved. // -// Function to adjust spacing. -if (typeof lineTool.adjustSpacing !== 'function') { - lineTool.adjustSpacing = function(event, adjustment) { - // Adjust for modifier keys - multiplying adjustment by 10 for FP rounding. - var finalAdjustment = adjustment; + +// Function to apply modifiers to distance adjustments. +if (typeof lineTool.adjustDistance != 'function') { + lineTool.adjustDistance = function (event, adjustment) { + + // Adjust for modifier keys. + let finalAdjustment = adjustment; if (event) { if (event.shiftKey) - finalAdjustment *= 100; + finalAdjustment *= 90; else if (!event.ctrlKey) finalAdjustment *= 10; } + return finalAdjustment; + } +} + +// Function to implement fence mode selection. +if (typeof lineTool.fenceMode !== 'function') { + lineTool.fenceMode = function () { + let fenceModeButton = document.getElementById("line-tool-fence"); + let activating = !fenceModeButton.classList.contains("selected"); + if (activating) { + fenceModeButton.classList.add("selected"); + + // Deselect random rotation. + document.getElementById("line-tool-rotation-random").classList.remove("selected"); + engine.trigger('SetLineToolRandomRotation', false); + lineTool.setRotationVisibility(true); + } + else { + fenceModeButton.classList.remove("selected"); + } + + // Update control visibility. + lineTool.setFenceVisibility(!activating); + engine.trigger('SetLineToolFenceMode', activating); + } +} + +// Function to toggle visibility of controls based on fence mode state. +if (typeof lineTool.setFenceVisibility !== 'function') { + lineTool.setFenceVisibility = function (isVisible) { + lineTool.setDivVisiblity(isVisible, "line-tool-spacing"); + lineTool.setDivVisiblity(isVisible, "line-tool-rotation"); + lineTool.setDivVisiblity(isVisible, "line-tool-rotation-field"); + lineTool.setDivVisiblity(isVisible, "line-tool-offsets"); + } +} + +// Function to adjust spacing. +if (typeof lineTool.adjustSpacing !== 'function') { + lineTool.adjustSpacing = function (event, adjustment) { + // Adjust for modifiers. + let finalAdjustment = lineTool.adjustDistance(event, adjustment); + // Don't apply if adjutment will bring us below zero. newSpacing = lineTool.spacing + finalAdjustment; if (newSpacing < 1) return; // Apply spacing. lineTool.spacing = newSpacing; - var roundedSpacing = newSpacing / 10; + let roundedSpacing = newSpacing / 10; engine.trigger('SetLineToolSpacing', roundedSpacing); document.getElementById("line-tool-spacing-field").innerHTML = roundedSpacing + " m"; } @@ -33,13 +78,34 @@ if (typeof lineTool.refreshSpacing !== 'function') { return; } - var spacingField = document.getElementById("line-tool-spacing-field"); + let spacingField = document.getElementById("line-tool-spacing-field"); if (spacingField != null) { document.getElementById("line-tool-spacing-field").innerHTML = (lineTool.spacing / 10) + " m"; } } } +// Function to implement random rotation selection. +if (typeof lineTool.randomRotation !== 'function') { + lineTool.randomRotation = function () { + let randomRotationButton = document.getElementById("line-tool-rotation-random"); + if (randomRotationButton.classList.contains("selected")) { + randomRotationButton.classList.remove("selected"); + engine.trigger('SetLineToolRandomRotation', false); + + // Show rotation tools. + lineTool.setRotationVisibility(true); + } + else { + randomRotationButton.classList.add("selected"); + engine.trigger('SetLineToolRandomRotation', true); + + // Hide rotation tools. + lineTool.setRotationVisibility(false); + } + } +} + // Function to adjust rotation. if (typeof lineTool.adjustRotation !== 'function') { lineTool.adjustRotation = function(event, adjustment) { @@ -67,58 +133,46 @@ if (typeof lineTool.adjustRotation !== 'function') { } } -// Function to implement fence mode selection. -if (typeof lineTool.fenceMode !== 'function') { - lineTool.fenceMode = function () { - var fenceModeButton = document.getElementById("line-tool-fence"); - var randomRotationButton = document.getElementById("line-tool-rotation-random"); - if (fenceModeButton.classList.contains("selected")) { - fenceModeButton.classList.remove("selected"); - engine.trigger('SetLineToolFenceMode', false); - - // Show spacing and random rotation button. - lineTool.setSpacingVisibility(true); - lineTool.setButtonVisibility(randomRotationButton, true); +// Function to adjust random spacing offset. +if (typeof lineTool.adjustRandomSpacing !== 'function') { + lineTool.adjustRandomSpacing = function (event, adjustment) { + // Adjust for modifiers. + let finalAdjustment = lineTool.adjustDistance(event, adjustment); - // Show rotation, but only if random rotation is not set. - if (!randomRotationButton.classList.contains("selected")) { - lineTool.setRotationVisibility(true); - } + // Bounds check. + lineTool.randomSpacing += finalAdjustment; + let maxSpacing = Math.round((lineTool.spacing / 3) - 1); + if (lineTool.randomSpacing > maxSpacing) { + lineTool.randomSpacing = maxSpacing; } - else { - fenceModeButton.classList.add("selected"); - engine.trigger('SetLineToolFenceMode', true); - - // Disable random rotation and hide button. - randomRotationButton.classList.remove("selected"); - engine.trigger('SetLineToolRandomRotation', false); - lineTool.setButtonVisibility(randomRotationButton, false); - - // Hide rotation tools. - lineTool.setSpacingVisibility(false); - lineTool.setRotationVisibility(false); + if (lineTool.randomSpacing < 0) { + lineTool.randomSpacing = 0; } + + // Apply spacing offset. + engine.trigger('SetLineToolRandomSpacing', lineTool.randomSpacing / 10); + document.getElementById("line-tool-xOffset-field").innerHTML = (lineTool.randomSpacing / 10) + " m"; } } -// Function to implement random rotation selection. -if (typeof lineTool.randomRotation !== 'function') { - lineTool.randomRotation = function() { - var randomRotationButton = document.getElementById("line-tool-rotation-random"); - if (randomRotationButton.classList.contains("selected")) { - randomRotationButton.classList.remove("selected"); - engine.trigger('SetLineToolRandomRotation', false); +// Function to adjust random lateral offset. +if (typeof lineTool.adjustRandomOffset !== 'function') { + lineTool.adjustRandomOffset = function (event, adjustment) { + // Adjust for modifiers. + let finalAdjustment = lineTool.adjustDistance(event, adjustment); - // Show rotation tools. - lineTool.setRotationVisibility(true); + // Bounds check. + lineTool.randomOffset += finalAdjustment; + if (lineTool.randomOffset > 1000) { + lineTool.randomOffset = 1000; } - else { - randomRotationButton.classList.add("selected"); - engine.trigger('SetLineToolRandomRotation', true); - - // Hide rotation tools. - lineTool.setRotationVisibility(false); + if (lineTool.randomOffset < 0) { + lineTool.randomOffset = 0; } + + // Apply spacing offset. + engine.trigger('SetLineToolRandomOffset', lineTool.randomOffset / 10); + document.getElementById("line-tool-zOffset-field").innerHTML = (lineTool.randomOffset / 10) + " m"; } } @@ -126,7 +180,7 @@ if (typeof lineTool.randomRotation !== 'function') { if (typeof lineTool.addTreeControl !== 'function') { lineTool.addTreeControl = function(event, adjustment) { if (typeof buildTreeAgeItem == 'function') { - var modeLine = document.getElementById("line-tool-mode"); + let modeLine = document.getElementById("line-tool-mode"); buildTreeAgeItem(modeLine, "afterend"); document.getElementById("YYTC-change-age-buttons-panel").onclick = function () { engine.trigger('LineToolTreeControlUpdated') }; } @@ -163,16 +217,14 @@ if (typeof lineTool.handleCircleMode !== 'function') { } } -// Function to set spacing selection control visibility -if (typeof lineTool.setSpacingVisibility !== 'function') { - lineTool.setSpacingVisibility = function (isVisible) { - lineTool.setButtonVisibility(document.getElementById("line-tool-spacing-up"), isVisible); - lineTool.setButtonVisibility(document.getElementById("line-tool-spacing-down"), isVisible); +// Function to set div visibility +if (typeof lineTool.setDivVisiblity !== 'function') { + lineTool.setDivVisiblity = function (isVisible, divId) { if (isVisible) { - document.getElementById("line-tool-spacing-field").style.visibility = "visible"; + document.getElementById(divId).style.visibility = "visible"; } else { - document.getElementById("line-tool-spacing-field").style.visibility = "hidden"; + document.getElementById(divId).style.visibility = "hidden"; } } } @@ -208,13 +260,18 @@ if (typeof lineTool.setButtonVisibility !== 'function') { } } - // Set initial figures. lineTool.adjustSpacing(null, 0); lineTool.adjustRotation(null, 0); +lineTool.adjustRandomOffset(null, 0); +lineTool.adjustRandomSpacing(null, 0); // Add button event handlers. document.getElementById("line-tool-fence").onmousedown = () => { lineTool.fenceMode(); } +document.getElementById("line-tool-straight").onclick = lineTool.handleStraightMode; +document.getElementById("line-tool-simplecurve").onclick = lineTool.handleSimpleCurveMode; +document.getElementById("line-tool-circle").onclick = lineTool.handleCircleMode; + document.getElementById("line-tool-spacing-down").onmousedown = (event) => { lineTool.adjustSpacing(event, -1); } document.getElementById("line-tool-spacing-up").onmousedown = (event) => { lineTool.adjustSpacing(event, 1); } @@ -222,6 +279,7 @@ document.getElementById("line-tool-rotation-random").onmousedown = () => { lineT document.getElementById("line-tool-rotation-up").onmousedown = (event) => { lineTool.adjustRotation(event, 1); } document.getElementById("line-tool-rotation-down").onmousedown = (event) => { lineTool.adjustRotation(event, -1); } -document.getElementById("line-tool-straight").onclick = lineTool.handleStraightMode; -document.getElementById("line-tool-simplecurve").onclick = lineTool.handleSimpleCurveMode; -document.getElementById("line-tool-circle").onclick = lineTool.handleCircleMode; \ No newline at end of file +document.getElementById("line-tool-xOffset-down").onmousedown = (event) => { lineTool.adjustRandomSpacing(event, -1); } +document.getElementById("line-tool-xOffset-up").onmousedown = (event) => { lineTool.adjustRandomSpacing(event, 1); } +document.getElementById("line-tool-zOffset-down").onmousedown = (event) => { lineTool.adjustRandomOffset(event, -1); } +document.getElementById("line-tool-zOffset-up").onmousedown = (event) => { lineTool.adjustRandomOffset(event, 1); } \ No newline at end of file