From 385eff1fc1db4b2ec5fc61cef2fc072569c23dc9 Mon Sep 17 00:00:00 2001 From: Andreas Habel Date: Tue, 5 Jan 2021 22:39:14 +0100 Subject: [PATCH 1/8] Initial Version with auto curve generation. Not yet production ready. --- FS19_AutoDrive/scripts/Hud.lua | 12 ++- .../scripts/Manager/GraphManager.lua | 80 +++++++++++++++++++ 2 files changed, 89 insertions(+), 3 deletions(-) diff --git a/FS19_AutoDrive/scripts/Hud.lua b/FS19_AutoDrive/scripts/Hud.lua index 320a98f5..680a7cba 100644 --- a/FS19_AutoDrive/scripts/Hud.lua +++ b/FS19_AutoDrive/scripts/Hud.lua @@ -423,9 +423,15 @@ function AutoDriveHud:mouseEvent(vehicle, posX, posY, isDown, isUp, button) if button == 1 and isUp and not AutoDrive.leftALTmodifierKeyPressed and not AutoDrive.leftCTRLmodifierKeyPressed then -- left mouse button to select point / connect to already selected point if vehicle.ad.selectedNodeId ~= nil then - if vehicle.ad.selectedNodeId ~= vehicle.ad.hoveredNodeId then - -- connect selected point with hovered point - ADGraphManager:toggleConnectionBetween(ADGraphManager:getWayPointById(vehicle.ad.selectedNodeId), ADGraphManager:getWayPointById(vehicle.ad.hoveredNodeId), AutoDrive.leftLSHIFTmodifierKeyPressed) + if vehicle.ad.selectedNodeId ~= vehicle.ad.hoveredNodeId then + local success = true + if AutoDrive.isCAPSKeyActive then + -- try a smooth connection between two nodes + success = ADGraphManager:smoothConnectionBetween(ADGraphManager:getWayPointById(vehicle.ad.selectedNodeId), ADGraphManager:getWayPointById(vehicle.ad.hoveredNodeId), AutoDrive.leftLSHIFTmodifierKeyPressed) + else + -- connect selected point with hovered point + ADGraphManager:toggleConnectionBetween(ADGraphManager:getWayPointById(vehicle.ad.selectedNodeId), ADGraphManager:getWayPointById(vehicle.ad.hoveredNodeId), AutoDrive.leftLSHIFTmodifierKeyPressed) + end end -- unselect point vehicle.ad.selectedNodeId = nil diff --git a/FS19_AutoDrive/scripts/Manager/GraphManager.lua b/FS19_AutoDrive/scripts/Manager/GraphManager.lua index c1535c54..05aa418a 100644 --- a/FS19_AutoDrive/scripts/Manager/GraphManager.lua +++ b/FS19_AutoDrive/scripts/Manager/GraphManager.lua @@ -513,6 +513,86 @@ function ADGraphManager:toggleConnectionBetween(startNode, endNode, reverseDirec end end + +function ADGraphManager:smoothConnectionBetween(startNode, endNode, reverseDirection) + if startNode == nil or endNode == nil then + return + end + + if table.contains(startNode.out, endNode.id) or table.contains(endNode.incoming, startNode.id) then + -- nodes are already connected - remove connections + table.removeValue(startNode.out, endNode.id) + table.removeValue(endNode.incoming, startNode.id) + else + if #startNode.incoming == 1 and #endNode.out == 1 then + local p0 = nil + for _, px in pairs(startNode.incoming) do + p0 = ADGraphManager:getWayPointById(px) + break + end + local p3 = nil + for _, px in pairs(endNode.out) do + p3 = ADGraphManager:getWayPointById(px) + break + end + + local prevWP = startNode + for i = 1, 9 do + local px = ADGraphManager:CatmullRomInterpolate(i, p0, startNode, endNode, p3, 10) + ADGraphManager:createWayPoint(px.x, px.y, px.z) + -- this is a hack - we assume that we created the last WP here, ignoring MP and parallel events... :( + local newID = self:getWayPointsCount() + local newWP = self:getWayPointById(newID) + ADGraphManager:toggleConnectionBetween(prevWP, newWP, false) + prevWP = newWP + end + ADGraphManager:toggleConnectionBetween(prevWP, endNode, false) + end + end + self:markChanges() +end + + +function ADGraphManager:CatmullRomInterpolate(index, p0, p1, p2, p3, segments) + local px = {x=nil, y=nil, z=nil} + local x = {p0.x, p1.x, p2.x, p3.x} + local z = {p0.z, p1.z, p2.z, p3.z} + local time = {0, 1, 2, 3} -- linear at start... calculate weights over time + local total = 0.0 + + for i = 2, 4 do + local dx = x[i] - x[i - 1] + local dz = z[i] - z[i - 1] + -- the .9 is giving the wideness and roundness of the curve, + -- lower values (like .25 will be more straight, while high values like .95 will be wider and rounder) + total = total + math.pow(dx * dx + dz * dz, 0.9) + time[i] = total + end + local tstart = time[2] + local tend = time[3] + local t = tstart + (index * (tend - tstart)) / segments + + local L01 = p0.x * (time[2] - t) / (time[2] - time[1]) + p1.x * (t - time[1]) / (time[2] - time[1]) + local L12 = p1.x * (time[3] - t) / (time[3] - time[2]) + p2.x * (t - time[2]) / (time[3] - time[2]) + local L23 = p2.x * (time[4] - t) / (time[4] - time[3]) + p3.x * (t - time[3]) / (time[4] - time[3]) + local L012 = L01 * (time[3] - t) / (time[3] - time[1]) + L12 * (t - time[1]) / (time[3] - time[1]) + local L123 = L12 * (time[4] - t) / (time[4] - time[2]) + L23 * (t - time[2]) / (time[4] - time[2]) + local C12 = L012 * (time[3] - t) / (time[3] - time[2]) + L123 * (t - time[2]) / (time[3] - time[2]) + px.x = C12 + + L01 = p0.z * (time[2] - t) / (time[2] - time[1]) + p1.z * (t - time[1]) / (time[2] - time[1]) + L12 = p1.z * (time[3] - t) / (time[3] - time[2]) + p2.z * (t - time[2]) / (time[3] - time[2]) + L23 = p2.z * (time[4] - t) / (time[4] - time[3]) + p3.z * (t - time[3]) / (time[4] - time[3]) + L012 = L01 * (time[3] - t) / (time[3] - time[1]) + L12 * (t - time[1]) / (time[3] - time[1]) + L123 = L12 * (time[4] - t) / (time[4] - time[2]) + L23 * (t - time[2]) / (time[4] - time[2]) + C12 = L012 * (time[3] - t) / (time[3] - time[2]) + L123 * (t - time[2]) / (time[3] - time[2]) + px.z = C12 + + px.y = (p0.y + p1.y + p2.y + p3.y) / 4 + .3 + return px +end + + function ADGraphManager:createWayPoint(x, y, z, sendEvent) if sendEvent == nil or sendEvent == true then -- Propagating waypoint creation all over the network From c9d29a8ed918ba2919068ee91a397fc9f79b595b Mon Sep 17 00:00:00 2001 From: Andreas Habel Date: Wed, 6 Jan 2021 21:13:30 +0100 Subject: [PATCH 2/8] Much better smoothing and constant results due to normalized tangents. --- .../scripts/Manager/GraphManager.lua | 56 +++++++++++++++---- FS19_AutoDrive/scripts/Utils/UtilFuncs.lua | 55 ++++++++++++++++++ 2 files changed, 100 insertions(+), 11 deletions(-) diff --git a/FS19_AutoDrive/scripts/Manager/GraphManager.lua b/FS19_AutoDrive/scripts/Manager/GraphManager.lua index 05aa418a..a022f564 100644 --- a/FS19_AutoDrive/scripts/Manager/GraphManager.lua +++ b/FS19_AutoDrive/scripts/Manager/GraphManager.lua @@ -513,7 +513,10 @@ function ADGraphManager:toggleConnectionBetween(startNode, endNode, reverseDirec end end - +--- Create a smooth connection between two nodes by applying a RomCutmull smoothing algorithm. +---@param startNode table Node to start the curve with +---@param endNode table Node to end the curve at +---@param reverseDirection boolean If true, make a curve driving reverse function ADGraphManager:smoothConnectionBetween(startNode, endNode, reverseDirection) if startNode == nil or endNode == nil then return @@ -525,6 +528,7 @@ function ADGraphManager:smoothConnectionBetween(startNode, endNode, reverseDirec table.removeValue(endNode.incoming, startNode.id) else if #startNode.incoming == 1 and #endNode.out == 1 then + -- TODO: if we have more then one inbound or outbound connections, get the avg. vector local p0 = nil for _, px in pairs(startNode.incoming) do p0 = ADGraphManager:getWayPointById(px) @@ -536,15 +540,45 @@ function ADGraphManager:smoothConnectionBetween(startNode, endNode, reverseDirec break end + -- distance from start to end, divided by two to give it more roundness... + local dStartEnd = ADVectorUtils.distance2D(startNode, endNode) / 1.7 + + -- we need to normalize the length of p0-start and end-p3, otherwise their length will influence the curve + -- get vector from p0->start + local vp0Start = ADVectorUtils.subtract2D(p0, startNode) + -- calculate unit vector of vp0Start + vp0Start = ADVectorUtils.unitVector2D(vp0Start) + -- scale it like start->end + vp0Start = ADVectorUtils.scaleVector2D(vp0Start, dStartEnd) + -- invert it + vp0Start = ADVectorUtils.invert2D(vp0Start) + -- add it to the start Vector so that we get new p0 + p0 = ADVectorUtils.add2D(startNode, vp0Start) + -- make sure p0 has a y value + p0.y = startNode.y + + -- same for end->p3, except that we do not need to invert it, but just add it to the endNode + local vEndp3 = ADVectorUtils.subtract2D(endNode, p3) + vEndp3 = ADVectorUtils.unitVector2D(vEndp3) + vEndp3 = ADVectorUtils.scaleVector2D(vEndp3, dStartEnd) + p3 = ADVectorUtils.add2D(endNode, vEndp3) + p3.y = endNode.y + local prevWP = startNode - for i = 1, 9 do - local px = ADGraphManager:CatmullRomInterpolate(i, p0, startNode, endNode, p3, 10) - ADGraphManager:createWayPoint(px.x, px.y, px.z) - -- this is a hack - we assume that we created the last WP here, ignoring MP and parallel events... :( - local newID = self:getWayPointsCount() - local newWP = self:getWayPointById(newID) - ADGraphManager:toggleConnectionBetween(prevWP, newWP, false) - prevWP = newWP + -- we're calculting a VERY smooth curve and whenever the new point on the curve has a good distance to the last one create a new waypoint + -- but make sure that the last point also has a good distance to the endNode + for i = 1, 200 do + local px = ADGraphManager:CatmullRomInterpolate(i, p0, startNode, endNode, p3, 200) + if ADVectorUtils.distance2D(prevWP, px) >= 2 and ADVectorUtils.distance2D(px, endNode) >= 2 then + -- TODO: we can reduce the amount of waypoints by also taking the angle into account. Only create new waypoint if angle > whetever.. + + ADGraphManager:createWayPoint(px.x, px.y, px.z) + -- HACK: we assume that we created the last WP here, ignoring MP and parallel events... :( + local newID = self:getWayPointsCount() + local newWP = self:getWayPointById(newID) + ADGraphManager:toggleConnectionBetween(prevWP, newWP, false) + prevWP = newWP + end end ADGraphManager:toggleConnectionBetween(prevWP, endNode, false) end @@ -565,7 +599,7 @@ function ADGraphManager:CatmullRomInterpolate(index, p0, p1, p2, p3, segments) local dz = z[i] - z[i - 1] -- the .9 is giving the wideness and roundness of the curve, -- lower values (like .25 will be more straight, while high values like .95 will be wider and rounder) - total = total + math.pow(dx * dx + dz * dz, 0.9) + total = total + math.pow(dx * dx + dz * dz, 0.99) time[i] = total end local tstart = time[2] @@ -588,7 +622,7 @@ function ADGraphManager:CatmullRomInterpolate(index, p0, p1, p2, p3, segments) C12 = L012 * (time[3] - t) / (time[3] - time[2]) + L123 * (t - time[2]) / (time[3] - time[2]) px.z = C12 - px.y = (p0.y + p1.y + p2.y + p3.y) / 4 + .3 + px.y = (p0.y + p1.y + p2.y + p3.y) / 4 return px end diff --git a/FS19_AutoDrive/scripts/Utils/UtilFuncs.lua b/FS19_AutoDrive/scripts/Utils/UtilFuncs.lua index 6ee59acf..43f41890 100644 --- a/FS19_AutoDrive/scripts/Utils/UtilFuncs.lua +++ b/FS19_AutoDrive/scripts/Utils/UtilFuncs.lua @@ -962,4 +962,59 @@ function AutoDrive.checkWaypointsMultipleSameOut(correctit) end end +ADVectorUtils = {} + +--- Calculates the unit vector on a given vector. +--- @param vector table Table with x and z properties. +--- @return table Unitvector as table with x and z properties. +function ADVectorUtils.unitVector2D(vector) + local x, z = vector.x or 0, vector.z or 0 + local q = math.sqrt( (x * x) + ( z * z ) ) + return {x = x / q, z = z / q} +end + +--- Scales a vector by a given scalar. +--- @param vector table Table with x and z properties. +--- @param scale number Scale +--- @return table Vector +function ADVectorUtils.scaleVector2D(vector, scale) + scale = scale or 1.0 + vector.x = ( vector.x * scale ) or 0 + vector.z = ( vector.z * scale ) or 0 + return vector +end + +--- Returns the distance between zwo vectors using their x and z coordinates. +--- @param vectorA table with x and z property +--- @param vectorB table with x and z property +--- @return number Distance between vectorA and vectorB +function ADVectorUtils.distance2D(vectorA, vectorB) + return MathUtil.vector2Length(vectorA.x - vectorB.x, vectorA.z - vectorB.z) +end + +--- Returns a new vector pointing from vectorA to vectorB by subtracting A from B +--- @param vectorA table with x and z property +--- @param vectorB table with x and z property +--- @return table Vector pointing from A to B +function ADVectorUtils.subtract2D(vectorA, vectorB) + return {x = vectorB.x - vectorA.x, z = vectorB.z - vectorA.z} +end + +--- Inverts a vector with x and z properties. +--- @param vector table with x and z property +--- @return table Vector inverted +function ADVectorUtils.invert2D(vector) + vector.x = vector.x * -1 + vector.z = vector.z * -1 + return vector +end + +--- Adds x and z values of two given vectors and returns a new vector with x and z properties. +--- @param vectorA table with x and z property +--- @param vectorB table with x and z property +--- @return table Vector +function ADVectorUtils.add2D(vectorA, vectorB) + return {x = vectorA.x + vectorB.x, z = vectorA.z + vectorB.z} +end + -- TODO: Maybe we should add a console command that allows to run console commands to server From 471dc9c8708746341ae2114040f6c2b1479ff75d Mon Sep 17 00:00:00 2001 From: Andreas Habel Date: Thu, 7 Jan 2021 23:22:32 +0100 Subject: [PATCH 3/8] Made the roundness dependent based on the angle of the curve. --- .../scripts/Manager/GraphManager.lua | 36 ++++++++++++++----- FS19_AutoDrive/scripts/Utils/UtilFuncs.lua | 18 ++++++++++ 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/FS19_AutoDrive/scripts/Manager/GraphManager.lua b/FS19_AutoDrive/scripts/Manager/GraphManager.lua index a022f564..e770c02c 100644 --- a/FS19_AutoDrive/scripts/Manager/GraphManager.lua +++ b/FS19_AutoDrive/scripts/Manager/GraphManager.lua @@ -514,9 +514,9 @@ function ADGraphManager:toggleConnectionBetween(startNode, endNode, reverseDirec end --- Create a smooth connection between two nodes by applying a RomCutmull smoothing algorithm. ----@param startNode table Node to start the curve with ----@param endNode table Node to end the curve at ----@param reverseDirection boolean If true, make a curve driving reverse +--- @param startNode table Node to start the curve with +--- @param endNode table Node to end the curve at +--- @param reverseDirection boolean If true, make a curve driving reverse function ADGraphManager:smoothConnectionBetween(startNode, endNode, reverseDirection) if startNode == nil or endNode == nil then return @@ -540,8 +540,15 @@ function ADGraphManager:smoothConnectionBetween(startNode, endNode, reverseDirec break end + -- calculate the angle between start tangent and end tangent + local dAngle = math.abs(AutoDrive.angleBetween( + ADVectorUtils.subtract2D(p0, startNode), + ADVectorUtils.subtract2D(endNode, p3) + )) + local roundFac = ADVectorUtils.linterp(0, 180, dAngle, 1.5, 2.5) + -- distance from start to end, divided by two to give it more roundness... - local dStartEnd = ADVectorUtils.distance2D(startNode, endNode) / 1.7 + local dStartEnd = ADVectorUtils.distance2D(startNode, endNode) / roundFac -- we need to normalize the length of p0-start and end-p3, otherwise their length will influence the curve -- get vector from p0->start @@ -565,12 +572,21 @@ function ADGraphManager:smoothConnectionBetween(startNode, endNode, reverseDirec p3.y = endNode.y local prevWP = startNode + local prevV = ADVectorUtils.subtract2D(p0, startNode) -- we're calculting a VERY smooth curve and whenever the new point on the curve has a good distance to the last one create a new waypoint -- but make sure that the last point also has a good distance to the endNode for i = 1, 200 do local px = ADGraphManager:CatmullRomInterpolate(i, p0, startNode, endNode, p3, 200) - if ADVectorUtils.distance2D(prevWP, px) >= 2 and ADVectorUtils.distance2D(px, endNode) >= 2 then - -- TODO: we can reduce the amount of waypoints by also taking the angle into account. Only create new waypoint if angle > whetever.. + local newV = ADVectorUtils.subtract2D(prevWP, px) + local dAngle = math.abs(AutoDrive.angleBetween(prevV, newV)) + + -- only create new WP if distance to last one is > 2m and distance to target > 2m and angle to last one >0 3° + if ADVectorUtils.distance2D(prevWP, px) >= 2 and + ADVectorUtils.distance2D(px, endNode) >= 2 and + dAngle >= 3 then + + -- get height at terrain + px.y = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, px.x, 1, px.z) ADGraphManager:createWayPoint(px.x, px.y, px.z) -- HACK: we assume that we created the last WP here, ignoring MP and parallel events... :( @@ -578,9 +594,14 @@ function ADGraphManager:smoothConnectionBetween(startNode, endNode, reverseDirec local newWP = self:getWayPointById(newID) ADGraphManager:toggleConnectionBetween(prevWP, newWP, false) prevWP = newWP + prevV = newV end end ADGraphManager:toggleConnectionBetween(prevWP, endNode, false) + + else -- fallback to straight line connection behaviour + ADGraphManager:toggleConnectionBetween(startNode, endNode, reverseDirection) + return end end self:markChanges() @@ -599,7 +620,7 @@ function ADGraphManager:CatmullRomInterpolate(index, p0, p1, p2, p3, segments) local dz = z[i] - z[i - 1] -- the .9 is giving the wideness and roundness of the curve, -- lower values (like .25 will be more straight, while high values like .95 will be wider and rounder) - total = total + math.pow(dx * dx + dz * dz, 0.99) + total = total + math.pow(dx * dx + dz * dz, 0.95) time[i] = total end local tstart = time[2] @@ -622,7 +643,6 @@ function ADGraphManager:CatmullRomInterpolate(index, p0, p1, p2, p3, segments) C12 = L012 * (time[3] - t) / (time[3] - time[2]) + L123 * (t - time[2]) / (time[3] - time[2]) px.z = C12 - px.y = (p0.y + p1.y + p2.y + p3.y) / 4 return px end diff --git a/FS19_AutoDrive/scripts/Utils/UtilFuncs.lua b/FS19_AutoDrive/scripts/Utils/UtilFuncs.lua index 43f41890..583c8e77 100644 --- a/FS19_AutoDrive/scripts/Utils/UtilFuncs.lua +++ b/FS19_AutoDrive/scripts/Utils/UtilFuncs.lua @@ -1017,4 +1017,22 @@ function ADVectorUtils.add2D(vectorA, vectorB) return {x = vectorA.x + vectorB.x, z = vectorA.z + vectorB.z} end +--- Does a linear interpolation based on the in* range and value, and returns a new value +--- fitting in the out range. Second return value is the interpolated value between 0..1. +--- @param inMin number Minimum value for input range +--- @param inMax number Maximum number for input range +--- @param inValue number Current value for input range +--- @param outMin number Minimum value vor output range +--- @param outMax number Maximum value for output range +--- @return number, number Interpolated value in output range, value in range 0..1 +function ADVectorUtils.linterp(inMin, inMax, inValue, outMin, outMax) + -- normalize input, make min range boundary = 0, nval is between 0..1 + local imax = inMax - inMin + local nval = math.clamp(0, inValue - inMin, imax) / imax + -- normalize output + local omax = outMax - outMin + local oval = outMin + ( omax * nval ) + return oval, nval +end + -- TODO: Maybe we should add a console command that allows to run console commands to server From d27ecf268ad08908822f4b0cbaff68bce8a87a8e Mon Sep 17 00:00:00 2001 From: Andreas Habel Date: Fri, 8 Jan 2021 16:27:51 +0100 Subject: [PATCH 4/8] Allow for curve preview and edit roundness before creation. --- FS19_AutoDrive/scripts/AutoDrive.lua | 11 +- FS19_AutoDrive/scripts/Hud.lua | 39 ++++-- .../scripts/Manager/GraphManager.lua | 119 ++++++++++++++---- FS19_AutoDrive/scripts/Specialization.lua | 10 ++ 4 files changed, 138 insertions(+), 41 deletions(-) diff --git a/FS19_AutoDrive/scripts/AutoDrive.lua b/FS19_AutoDrive/scripts/AutoDrive.lua index ae974a4f..6888d9cc 100644 --- a/FS19_AutoDrive/scripts/AutoDrive.lua +++ b/FS19_AutoDrive/scripts/AutoDrive.lua @@ -246,7 +246,16 @@ function AutoDrive:keyEvent(unicode, sym, modifier, isDown) AutoDrive.enableSphrere = true else AutoDrive.enableSphrere = AutoDrive.toggleSphrere - end + end + + -- TODO: Make those keys proper actions and bindable settings + if sym == Input.KEY_period and isDown and ADGraphManager.curvePreview ~= nil then + ADGraphManager:recalculatePreview(math.max(ADGraphManager.curvePreview.curvature - .2, 0.5)) + end + if sym == Input.KEY_comma and isDown and ADGraphManager.curvePreview ~= nil then + ADGraphManager:recalculatePreview(math.min(ADGraphManager.curvePreview.curvature + .2, 3.5)) + end + end end diff --git a/FS19_AutoDrive/scripts/Hud.lua b/FS19_AutoDrive/scripts/Hud.lua index 680a7cba..00713604 100644 --- a/FS19_AutoDrive/scripts/Hud.lua +++ b/FS19_AutoDrive/scripts/Hud.lua @@ -409,14 +409,32 @@ function AutoDriveHud:mouseEvent(vehicle, posX, posY, isDown, isUp, button) -- 1st or 2nd Editor Mode enabled -- try to get a waypoint in mouse range for _, point in pairs(vehicle:getWayPointsInRange(0, AutoDrive.drawDistance)) do - if AutoDrive.mouseIsAtPos(point, 0.01) then - vehicle.ad.hoveredNodeId = point.id + if AutoDrive.mouseIsAtPos(point, 0.01) then + if point.id ~= vehicle.ad.hoveredNodeId then + vehicle.ad.hoveredNodeId = point.id + + -- if there is a selected node which is not the hovered node, draw a preview of a potential connection + if vehicle.ad.selectedNodeId ~= nil and vehicle.ad.selectedNodeId ~= vehicle.ad.hoveredNodeId then + ADGraphManager:previewConnectionBetween( + ADGraphManager:getWayPointById(vehicle.ad.selectedNodeId), + ADGraphManager:getWayPointById(vehicle.ad.hoveredNodeId) + ) + end + end break end end if vehicle.ad.nodeToMoveId ~= nil then -- move point at mouse position - AutoDrive.moveNodeToMousePos(vehicle.ad.nodeToMoveId) + AutoDrive.moveNodeToMousePos(vehicle.ad.nodeToMoveId) + + if ADGraphManager.curvePreview ~= nil and ADGraphManager:doesNodeAffectPreview(vehicle.ad.nodeToMoveId) then + -- recalculate curve + ADGraphManager:previewConnectionBetween( + ADGraphManager.curvePreview.startNode, + ADGraphManager.curvePreview.endNode + ) + end end if vehicle.ad.hoveredNodeId ~= nil then -- waypoint at mouse position @@ -424,21 +442,16 @@ function AutoDriveHud:mouseEvent(vehicle, posX, posY, isDown, isUp, button) -- left mouse button to select point / connect to already selected point if vehicle.ad.selectedNodeId ~= nil then if vehicle.ad.selectedNodeId ~= vehicle.ad.hoveredNodeId then - local success = true - if AutoDrive.isCAPSKeyActive then - -- try a smooth connection between two nodes - success = ADGraphManager:smoothConnectionBetween(ADGraphManager:getWayPointById(vehicle.ad.selectedNodeId), ADGraphManager:getWayPointById(vehicle.ad.hoveredNodeId), AutoDrive.leftLSHIFTmodifierKeyPressed) - else - -- connect selected point with hovered point - ADGraphManager:toggleConnectionBetween(ADGraphManager:getWayPointById(vehicle.ad.selectedNodeId), ADGraphManager:getWayPointById(vehicle.ad.hoveredNodeId), AutoDrive.leftLSHIFTmodifierKeyPressed) - end + ADGraphManager:toggleConnectionBetween(ADGraphManager:getWayPointById(vehicle.ad.selectedNodeId), ADGraphManager:getWayPointById(vehicle.ad.hoveredNodeId), AutoDrive.leftLSHIFTmodifierKeyPressed) end -- unselect point - vehicle.ad.selectedNodeId = nil + vehicle.ad.selectedNodeId = nil + ADGraphManager.curvePreview = nil else -- select point -- no selectedNodeId: hoveredNodeId is now selectedNodeId - vehicle.ad.selectedNodeId = vehicle.ad.hoveredNodeId + vehicle.ad.selectedNodeId = vehicle.ad.hoveredNodeId + ADGraphManager.curvePreview = nil end end diff --git a/FS19_AutoDrive/scripts/Manager/GraphManager.lua b/FS19_AutoDrive/scripts/Manager/GraphManager.lua index e770c02c..56ef6c39 100644 --- a/FS19_AutoDrive/scripts/Manager/GraphManager.lua +++ b/FS19_AutoDrive/scripts/Manager/GraphManager.lua @@ -7,6 +7,7 @@ function ADGraphManager:load() self.groups["All"] = 1 self.changes = false self.preparedWayPoints = false + self.curvePreview = nil end function ADGraphManager:markChanges() @@ -503,9 +504,29 @@ function ADGraphManager:toggleConnectionBetween(startNode, endNode, reverseDirec table.removeValue(startNode.out, endNode.id) table.removeValue(endNode.incoming, startNode.id) else - table.insert(startNode.out, endNode.id) - if not reverseDirection then - table.insert(endNode.incoming, startNode.id) + -- if there is an active preview between startNode and endNode use this to create new WP - otherwise just create a straight line + if self.curvePreview ~= nil and startNode == self.curvePreview.startNode and endNode == self.curvePreview.endNode then + + local aWP = startNode + local bWP = nil + + -- ommit start and endnode in the list + for i = 2, #self.curvePreview.waypoints - 1 do + local p = self.curvePreview.waypoints[i] + self:createWayPoint(p.x, p.y, p.z) + -- HACK: we assume that we created the last WP here, ignoring MP and parallel events... :( + bWP = self:getWayPointById(self:getWayPointsCount()) + + ADGraphManager:toggleConnectionBetween(aWP, bWP, false) + aWP, bWP = bWP, nil + end + -- connect last new WP with end node + ADGraphManager:toggleConnectionBetween(aWP, endNode, false) + else + table.insert(startNode.out, endNode.id) + if not reverseDirection then + table.insert(endNode.incoming, startNode.id) + end end end @@ -513,42 +534,90 @@ function ADGraphManager:toggleConnectionBetween(startNode, endNode, reverseDirec end end +--- Checks if a given node is part of the current preview. +--- Will be used to decide if the preview need to be refreshed, e.g. if the node has been moved. +--- @param nodeId integer Id of the node to check +--- @return boolean true if the node is used in preview calculation +function ADGraphManager:doesNodeAffectPreview(nodeId) + if self.curvePreview == nil then + return false + end + if self.curvePreview.startNode.id == nodeId or + self.curvePreview.endNode.id == nodeId or + self.curvePreview.p0.id == nodeId or + self.curvePreview.p3.id == nodeId then + return true + end + return false +end + +function ADGraphManager:recalculatePreview(curvature) + if self.curvePreview == nil then + return + end + self.curvePreview.curvature = curvature or self.curvePreview.curvature + self:previewConnectionBetween(self.curvePreview.startNode, self.curvePreview.endNode, nil, true) +end + --- Create a smooth connection between two nodes by applying a RomCutmull smoothing algorithm. --- @param startNode table Node to start the curve with --- @param endNode table Node to end the curve at --- @param reverseDirection boolean If true, make a curve driving reverse -function ADGraphManager:smoothConnectionBetween(startNode, endNode, reverseDirection) - if startNode == nil or endNode == nil then +function ADGraphManager:previewConnectionBetween(startNode, endNode, reverseDirection, reuseParams) + if startNode == nil or endNode == nil then return end + if self.curvePreview ~= nil and startNode == self.curvePreview.startNode and endNode == self.curvePreview.endNode then + reuseParams = true + end if table.contains(startNode.out, endNode.id) or table.contains(endNode.incoming, startNode.id) then -- nodes are already connected - remove connections - table.removeValue(startNode.out, endNode.id) - table.removeValue(endNode.incoming, startNode.id) + -- table.removeValue(startNode.out, endNode.id) + -- table.removeValue(endNode.incoming, startNode.id) else if #startNode.incoming == 1 and #endNode.out == 1 then + + if reuseParams == nil or reuseParams == false then + -- make sure to get own references - do not rely on incoming pointers... + self.curvePreview = { + startNode = startNode, + p0 = nil, + endNode = endNode, + p3 = nil, + curvature = 1, + waypoints = { startNode } + } + else + self.curvePreview.waypoints = { startNode } + end + -- TODO: if we have more then one inbound or outbound connections, get the avg. vector local p0 = nil for _, px in pairs(startNode.incoming) do p0 = ADGraphManager:getWayPointById(px) + self.curvePreview.p0 = p0 break end local p3 = nil for _, px in pairs(endNode.out) do p3 = ADGraphManager:getWayPointById(px) + self.curvePreview.p3 = p3 break end - -- calculate the angle between start tangent and end tangent - local dAngle = math.abs(AutoDrive.angleBetween( - ADVectorUtils.subtract2D(p0, startNode), - ADVectorUtils.subtract2D(endNode, p3) - )) - local roundFac = ADVectorUtils.linterp(0, 180, dAngle, 1.5, 2.5) + if reuseParams == nil or reuseParams == false then + -- calculate the angle between start tangent and end tangent + local dAngle = math.abs(AutoDrive.angleBetween( + ADVectorUtils.subtract2D(p0, startNode), + ADVectorUtils.subtract2D(endNode, p3) + )) + self.curvePreview.curvature = ADVectorUtils.linterp(0, 180, dAngle, 1.5, 2.5) + end + -- distance from start to end, divided by two to give it more roundness... - local dStartEnd = ADVectorUtils.distance2D(startNode, endNode) / roundFac + local dStartEnd = ADVectorUtils.distance2D(startNode, endNode) / self.curvePreview.curvature -- we need to normalize the length of p0-start and end-p3, otherwise their length will influence the curve -- get vector from p0->start @@ -581,30 +650,26 @@ function ADGraphManager:smoothConnectionBetween(startNode, endNode, reverseDirec local dAngle = math.abs(AutoDrive.angleBetween(prevV, newV)) -- only create new WP if distance to last one is > 2m and distance to target > 2m and angle to last one >0 3° - if ADVectorUtils.distance2D(prevWP, px) >= 2 and - ADVectorUtils.distance2D(px, endNode) >= 2 and - dAngle >= 3 then + if ADVectorUtils.distance2D(prevWP, px) >= 1 and + ADVectorUtils.distance2D(px, endNode) >= 1 and + dAngle >= 2.5 then -- get height at terrain px.y = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, px.x, 1, px.z) - - ADGraphManager:createWayPoint(px.x, px.y, px.z) - -- HACK: we assume that we created the last WP here, ignoring MP and parallel events... :( - local newID = self:getWayPointsCount() - local newWP = self:getWayPointById(newID) - ADGraphManager:toggleConnectionBetween(prevWP, newWP, false) - prevWP = newWP + table.insert(self.curvePreview.waypoints, px) + prevWP = px -- newWP prevV = newV end end - ADGraphManager:toggleConnectionBetween(prevWP, endNode, false) + -- ADGraphManager:toggleConnectionBetween(prevWP, endNode, false) + table.insert(self.curvePreview.waypoints, endNode) else -- fallback to straight line connection behaviour - ADGraphManager:toggleConnectionBetween(startNode, endNode, reverseDirection) + -- ADGraphManager:toggleConnectionBetween(startNode, endNode, reverseDirection) return end end - self:markChanges() + -- self:markChanges() end diff --git a/FS19_AutoDrive/scripts/Specialization.lua b/FS19_AutoDrive/scripts/Specialization.lua index 99c9a242..8d68c4ce 100644 --- a/FS19_AutoDrive/scripts/Specialization.lua +++ b/FS19_AutoDrive/scripts/Specialization.lua @@ -610,6 +610,16 @@ function AutoDrive:onDrawEditorMode() DrawingManager:addCrossTask(x, y, z) end end + + -- draw curve preview if available + if AutoDrive.isInExtendedEditorMode() and ADGraphManager.curvePreview ~= nil then + for i = 1, #ADGraphManager.curvePreview.waypoints - 1, 1 do + local p0 = ADGraphManager.curvePreview.waypoints[i] + local p1 = ADGraphManager.curvePreview.waypoints[i + 1] + DrawingManager:addLineTask(p0.x, p0.y, p0.z, p1.x, p1.y, p1.z, 1, 0.85, 0) + DrawingManager:addSphereTask(p1.x, p1.y, p1.z, 2.0, 1, 0.85, 0, 1) + end + end end function AutoDrive:startAutoDrive() From 9782eef82943362d380e2458762a330c0e7a4698 Mon Sep 17 00:00:00 2001 From: Andreas Habel Date: Sat, 9 Jan 2021 14:14:43 +0100 Subject: [PATCH 5/8] Fixed straight line behaviour and reduced points a bit. --- .../scripts/Manager/GraphManager.lua | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/FS19_AutoDrive/scripts/Manager/GraphManager.lua b/FS19_AutoDrive/scripts/Manager/GraphManager.lua index 56ef6c39..f8175932 100644 --- a/FS19_AutoDrive/scripts/Manager/GraphManager.lua +++ b/FS19_AutoDrive/scripts/Manager/GraphManager.lua @@ -507,6 +507,16 @@ function ADGraphManager:toggleConnectionBetween(startNode, endNode, reverseDirec -- if there is an active preview between startNode and endNode use this to create new WP - otherwise just create a straight line if self.curvePreview ~= nil and startNode == self.curvePreview.startNode and endNode == self.curvePreview.endNode then + -- if there are only 2 WP in the preview - just create a straight line as before + if #self.curvePreview.waypoints == 2 then + table.insert(startNode.out, endNode.id) + if not reverseDirection then + table.insert(endNode.incoming, startNode.id) + end + self:markChanges() + return + end + local aWP = startNode local bWP = nil @@ -572,10 +582,10 @@ function ADGraphManager:previewConnectionBetween(startNode, endNode, reverseDire end if table.contains(startNode.out, endNode.id) or table.contains(endNode.incoming, startNode.id) then - -- nodes are already connected - remove connections - -- table.removeValue(startNode.out, endNode.id) - -- table.removeValue(endNode.incoming, startNode.id) + -- nodes are already connected - do not create preview + return else + -- TODO: if we have more then one inbound or outbound connections, get the avg. vector if #startNode.incoming == 1 and #endNode.out == 1 then if reuseParams == nil or reuseParams == false then @@ -592,7 +602,6 @@ function ADGraphManager:previewConnectionBetween(startNode, endNode, reverseDire self.curvePreview.waypoints = { startNode } end - -- TODO: if we have more then one inbound or outbound connections, get the avg. vector local p0 = nil for _, px in pairs(startNode.incoming) do p0 = ADGraphManager:getWayPointById(px) @@ -650,12 +659,13 @@ function ADGraphManager:previewConnectionBetween(startNode, endNode, reverseDire local dAngle = math.abs(AutoDrive.angleBetween(prevV, newV)) -- only create new WP if distance to last one is > 2m and distance to target > 2m and angle to last one >0 3° - if ADVectorUtils.distance2D(prevWP, px) >= 1 and - ADVectorUtils.distance2D(px, endNode) >= 1 and - dAngle >= 2.5 then + if ADVectorUtils.distance2D(prevWP, px) >= 1.5 and + ADVectorUtils.distance2D(px, endNode) >= 1.5 and + dAngle >= 3 then - -- get height at terrain - px.y = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, px.x, 1, px.z) + -- get height at terrain + -- TODO: sometimes lines go into the ground - no idea why... making sure that they are at least as high as start or end + px.y = math.max(math.min(startNode.y, endNode.y), getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, px.x, 1, px.z)) table.insert(self.curvePreview.waypoints, px) prevWP = px -- newWP prevV = newV From 12e484be4c4f675ac0124c970dd005ad66164bc6 Mon Sep 17 00:00:00 2001 From: Andreas Habel Date: Sat, 9 Jan 2021 14:55:11 +0100 Subject: [PATCH 6/8] Made smooth connections an experimental feature which can be enabled/disabled ingame. --- FS19_AutoDrive/scripts/AutoDrive.lua | 27 ++++++++++++------- FS19_AutoDrive/scripts/Hud.lua | 6 +++-- .../scripts/Manager/GraphManager.lua | 8 ++++-- FS19_AutoDrive/scripts/Specialization.lua | 8 +++--- 4 files changed, 33 insertions(+), 16 deletions(-) diff --git a/FS19_AutoDrive/scripts/AutoDrive.lua b/FS19_AutoDrive/scripts/AutoDrive.lua index 6888d9cc..0fe961e3 100644 --- a/FS19_AutoDrive/scripts/AutoDrive.lua +++ b/FS19_AutoDrive/scripts/AutoDrive.lua @@ -6,9 +6,11 @@ AutoDrive.directory = g_currentModDirectory g_autoDriveUIFilename = AutoDrive.directory .. "textures/GUI_Icons.dds" g_autoDriveDebugUIFilename = AutoDrive.directory .. "textures/gui_debug_Icons.dds" -AutoDrive.experimentalFeatures = {} -AutoDrive.experimentalFeatures.redLinePosition = false -AutoDrive.experimentalFeatures.dynamicChaseDistance = false +AutoDrive.experimentalFeatures = { + redLinePosition = false, + dynamicChaseDistance = false, + smoothWaypointConnection = false +} AutoDrive.smootherDriving = true AutoDrive.developmentControls = false @@ -18,6 +20,11 @@ AutoDrive.mapHotspotsBuffer = {} AutoDrive.drawHeight = 0.3 AutoDrive.drawDistance = getViewDistanceCoeff() * 50 +-- Global set of colors used in rendering +AutoDrive.COLORS = { + EDITOR_LINE_PREVIEW = {r=1, g=0.85, b=0, a=1} +} + AutoDrive.STAT_NAMES = {"driversTraveledDistance", "driversHired"} for _, statName in pairs(AutoDrive.STAT_NAMES) do table.insert(FarmStats.STAT_NAMES, statName) @@ -248,12 +255,14 @@ function AutoDrive:keyEvent(unicode, sym, modifier, isDown) AutoDrive.enableSphrere = AutoDrive.toggleSphrere end - -- TODO: Make those keys proper actions and bindable settings - if sym == Input.KEY_period and isDown and ADGraphManager.curvePreview ~= nil then - ADGraphManager:recalculatePreview(math.max(ADGraphManager.curvePreview.curvature - .2, 0.5)) - end - if sym == Input.KEY_comma and isDown and ADGraphManager.curvePreview ~= nil then - ADGraphManager:recalculatePreview(math.min(ADGraphManager.curvePreview.curvature + .2, 3.5)) + if AutoDrive.experimentalFeatures.smoothWaypointConnection then + -- TODO: Make those keys proper actions and bindable settings + if sym == Input.KEY_period and isDown and ADGraphManager.curvePreview ~= nil then + ADGraphManager:recalculatePreview(math.max(ADGraphManager.curvePreview.curvature - .2, 0.5)) + end + if sym == Input.KEY_comma and isDown and ADGraphManager.curvePreview ~= nil then + ADGraphManager:recalculatePreview(math.min(ADGraphManager.curvePreview.curvature + .2, 3.5)) + end end end diff --git a/FS19_AutoDrive/scripts/Hud.lua b/FS19_AutoDrive/scripts/Hud.lua index 00713604..976f5b49 100644 --- a/FS19_AutoDrive/scripts/Hud.lua +++ b/FS19_AutoDrive/scripts/Hud.lua @@ -414,7 +414,8 @@ function AutoDriveHud:mouseEvent(vehicle, posX, posY, isDown, isUp, button) vehicle.ad.hoveredNodeId = point.id -- if there is a selected node which is not the hovered node, draw a preview of a potential connection - if vehicle.ad.selectedNodeId ~= nil and vehicle.ad.selectedNodeId ~= vehicle.ad.hoveredNodeId then + if AutoDrive.experimentalFeatures.smoothWaypointConnection and + vehicle.ad.selectedNodeId ~= nil and vehicle.ad.selectedNodeId ~= vehicle.ad.hoveredNodeId then ADGraphManager:previewConnectionBetween( ADGraphManager:getWayPointById(vehicle.ad.selectedNodeId), ADGraphManager:getWayPointById(vehicle.ad.hoveredNodeId) @@ -428,7 +429,8 @@ function AutoDriveHud:mouseEvent(vehicle, posX, posY, isDown, isUp, button) -- move point at mouse position AutoDrive.moveNodeToMousePos(vehicle.ad.nodeToMoveId) - if ADGraphManager.curvePreview ~= nil and ADGraphManager:doesNodeAffectPreview(vehicle.ad.nodeToMoveId) then + if AutoDrive.experimentalFeatures.smoothWaypointConnection and + ADGraphManager.curvePreview ~= nil and ADGraphManager:doesNodeAffectPreview(vehicle.ad.nodeToMoveId) then -- recalculate curve ADGraphManager:previewConnectionBetween( ADGraphManager.curvePreview.startNode, diff --git a/FS19_AutoDrive/scripts/Manager/GraphManager.lua b/FS19_AutoDrive/scripts/Manager/GraphManager.lua index f8175932..ce114116 100644 --- a/FS19_AutoDrive/scripts/Manager/GraphManager.lua +++ b/FS19_AutoDrive/scripts/Manager/GraphManager.lua @@ -504,8 +504,9 @@ function ADGraphManager:toggleConnectionBetween(startNode, endNode, reverseDirec table.removeValue(startNode.out, endNode.id) table.removeValue(endNode.incoming, startNode.id) else - -- if there is an active preview between startNode and endNode use this to create new WP - otherwise just create a straight line - if self.curvePreview ~= nil and startNode == self.curvePreview.startNode and endNode == self.curvePreview.endNode then + -- if there is an active preview between startNode and endNode use this to create new WPs - otherwise just create a straight line + if AutoDrive.experimentalFeatures.smoothWaypointConnection and + self.curvePreview ~= nil and startNode == self.curvePreview.startNode and endNode == self.curvePreview.endNode then -- if there are only 2 WP in the preview - just create a straight line as before if #self.curvePreview.waypoints == 2 then @@ -533,6 +534,7 @@ function ADGraphManager:toggleConnectionBetween(startNode, endNode, reverseDirec -- connect last new WP with end node ADGraphManager:toggleConnectionBetween(aWP, endNode, false) else + -- original behaviour - just create a straight line table.insert(startNode.out, endNode.id) if not reverseDirection then table.insert(endNode.incoming, startNode.id) @@ -561,6 +563,8 @@ function ADGraphManager:doesNodeAffectPreview(nodeId) return false end +--- Enforces a recalculation of the smooth preview, e.g. if the roundness has changed. +--- @param curvature number Roundness of the curve. Should be between 0.5 and 3 for useful results. function ADGraphManager:recalculatePreview(curvature) if self.curvePreview == nil then return diff --git a/FS19_AutoDrive/scripts/Specialization.lua b/FS19_AutoDrive/scripts/Specialization.lua index 8d68c4ce..1a5d3bd6 100644 --- a/FS19_AutoDrive/scripts/Specialization.lua +++ b/FS19_AutoDrive/scripts/Specialization.lua @@ -612,12 +612,14 @@ function AutoDrive:onDrawEditorMode() end -- draw curve preview if available - if AutoDrive.isInExtendedEditorMode() and ADGraphManager.curvePreview ~= nil then + if AutoDrive.experimentalFeatures.smoothWaypointConnection and + AutoDrive.isInExtendedEditorMode() and ADGraphManager.curvePreview ~= nil then + local col = AutoDrive.COLORS.EDITOR_LINE_PREVIEW for i = 1, #ADGraphManager.curvePreview.waypoints - 1, 1 do local p0 = ADGraphManager.curvePreview.waypoints[i] local p1 = ADGraphManager.curvePreview.waypoints[i + 1] - DrawingManager:addLineTask(p0.x, p0.y, p0.z, p1.x, p1.y, p1.z, 1, 0.85, 0) - DrawingManager:addSphereTask(p1.x, p1.y, p1.z, 2.0, 1, 0.85, 0, 1) + DrawingManager:addLineTask(p0.x, p0.y, p0.z, p1.x, p1.y, p1.z, col.r, col.g, col.b) + DrawingManager:addSphereTask(p1.x, p1.y, p1.z, 2.0, col.r, col.g, col.b, col.a) end end end From 6ad32b7c727f1d787a2a07088fd4b59006869389 Mon Sep 17 00:00:00 2001 From: Andreas Habel Date: Sat, 9 Jan 2021 22:23:40 +0100 Subject: [PATCH 7/8] Changed height calculation by using raycast to the ground. --- FS19_AutoDrive/scripts/Hud.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FS19_AutoDrive/scripts/Hud.lua b/FS19_AutoDrive/scripts/Hud.lua index 976f5b49..40fc3a32 100644 --- a/FS19_AutoDrive/scripts/Hud.lua +++ b/FS19_AutoDrive/scripts/Hud.lua @@ -429,7 +429,7 @@ function AutoDriveHud:mouseEvent(vehicle, posX, posY, isDown, isUp, button) -- move point at mouse position AutoDrive.moveNodeToMousePos(vehicle.ad.nodeToMoveId) - if AutoDrive.experimentalFeatures.smoothWaypointConnection and + if AutoDrive.experimentalFeatures.smoothWaypointConnection and ADGraphManager.curvePreview ~= nil and ADGraphManager:doesNodeAffectPreview(vehicle.ad.nodeToMoveId) then -- recalculate curve ADGraphManager:previewConnectionBetween( From 4f387148066962800a0bc8cbe20dc75a51f99182 Mon Sep 17 00:00:00 2001 From: Andreas Habel Date: Sat, 16 Jan 2021 14:20:29 +0100 Subject: [PATCH 8/8] Made curvature controllable via mousewheel. --- FS19_AutoDrive/scripts/AutoDrive.lua | 11 - .../scripts/Manager/GraphManager.lua | 192 +++++++++--------- FS19_AutoDrive/scripts/Utils/UtilFuncs.lua | 7 +- 3 files changed, 107 insertions(+), 103 deletions(-) diff --git a/FS19_AutoDrive/scripts/AutoDrive.lua b/FS19_AutoDrive/scripts/AutoDrive.lua index 0fe961e3..e1234446 100644 --- a/FS19_AutoDrive/scripts/AutoDrive.lua +++ b/FS19_AutoDrive/scripts/AutoDrive.lua @@ -254,17 +254,6 @@ function AutoDrive:keyEvent(unicode, sym, modifier, isDown) else AutoDrive.enableSphrere = AutoDrive.toggleSphrere end - - if AutoDrive.experimentalFeatures.smoothWaypointConnection then - -- TODO: Make those keys proper actions and bindable settings - if sym == Input.KEY_period and isDown and ADGraphManager.curvePreview ~= nil then - ADGraphManager:recalculatePreview(math.max(ADGraphManager.curvePreview.curvature - .2, 0.5)) - end - if sym == Input.KEY_comma and isDown and ADGraphManager.curvePreview ~= nil then - ADGraphManager:recalculatePreview(math.min(ADGraphManager.curvePreview.curvature + .2, 3.5)) - end - end - end end diff --git a/FS19_AutoDrive/scripts/Manager/GraphManager.lua b/FS19_AutoDrive/scripts/Manager/GraphManager.lua index ce114116..bfbd4ff2 100644 --- a/FS19_AutoDrive/scripts/Manager/GraphManager.lua +++ b/FS19_AutoDrive/scripts/Manager/GraphManager.lua @@ -563,13 +563,21 @@ function ADGraphManager:doesNodeAffectPreview(nodeId) return false end ---- Enforces a recalculation of the smooth preview, e.g. if the roundness has changed. ---- @param curvature number Roundness of the curve. Should be between 0.5 and 3 for useful results. +--- Enforces a recalculation of the smooth preview, e.g. if the curvature has changed. +--- @param curvature number Roundness of the curve. Delta that is used to increase or decrease the roundness function ADGraphManager:recalculatePreview(curvature) if self.curvePreview == nil then return end - self.curvePreview.curvature = curvature or self.curvePreview.curvature + curvature = curvature or 0 + -- if the curvature is already at it's min value and we still try to decrease is - fallback to a straight line + if curvature < 0 and self.curvePreview.curvature <= self.curvePreview.MIN_CURVATURE then + self.curvePreview.curvature = -1 + else + self.curvePreview.curvature = math.clamp( + self.curvePreview.MIN_CURVATURE, self.curvePreview.curvature + curvature, self.curvePreview.MAX_CURVATURE + ) + end self:previewConnectionBetween(self.curvePreview.startNode, self.curvePreview.endNode, nil, true) end @@ -581,6 +589,7 @@ function ADGraphManager:previewConnectionBetween(startNode, endNode, reverseDire if startNode == nil or endNode == nil then return end + if self.curvePreview ~= nil and startNode == self.curvePreview.startNode and endNode == self.curvePreview.endNode then reuseParams = true end @@ -588,102 +597,103 @@ function ADGraphManager:previewConnectionBetween(startNode, endNode, reverseDire if table.contains(startNode.out, endNode.id) or table.contains(endNode.incoming, startNode.id) then -- nodes are already connected - do not create preview return - else - -- TODO: if we have more then one inbound or outbound connections, get the avg. vector - if #startNode.incoming == 1 and #endNode.out == 1 then - - if reuseParams == nil or reuseParams == false then - -- make sure to get own references - do not rely on incoming pointers... - self.curvePreview = { - startNode = startNode, - p0 = nil, - endNode = endNode, - p3 = nil, - curvature = 1, - waypoints = { startNode } - } - else - self.curvePreview.waypoints = { startNode } - end + end - local p0 = nil - for _, px in pairs(startNode.incoming) do - p0 = ADGraphManager:getWayPointById(px) - self.curvePreview.p0 = p0 - break - end - local p3 = nil - for _, px in pairs(endNode.out) do - p3 = ADGraphManager:getWayPointById(px) - self.curvePreview.p3 = p3 - break + -- TODO: if we have more then one inbound or outbound connections, get the avg. vector + if #startNode.incoming == 1 and #endNode.out == 1 then + + if reuseParams == nil or reuseParams == false then + self.curvePreview = { + MIN_CURVATURE = 0.5, + MAX_CURVATURE = 3.5, + startNode = startNode, + p0 = nil, + endNode = endNode, + p3 = nil, + curvature = 1, + waypoints = { startNode } + } + else + -- fallback to straight line + if self.curvePreview.curvature <= 0 then + self.curvePreview.waypoints = { startNode, endNode } + return end + self.curvePreview.waypoints = { startNode } + end - if reuseParams == nil or reuseParams == false then - -- calculate the angle between start tangent and end tangent - local dAngle = math.abs(AutoDrive.angleBetween( - ADVectorUtils.subtract2D(p0, startNode), - ADVectorUtils.subtract2D(endNode, p3) - )) - self.curvePreview.curvature = ADVectorUtils.linterp(0, 180, dAngle, 1.5, 2.5) - end + local p0 = nil + for _, px in pairs(startNode.incoming) do + p0 = ADGraphManager:getWayPointById(px) + self.curvePreview.p0 = p0 + break + end + local p3 = nil + for _, px in pairs(endNode.out) do + p3 = ADGraphManager:getWayPointById(px) + self.curvePreview.p3 = p3 + break + end + if reuseParams == nil or reuseParams == false then + -- calculate the angle between start tangent and end tangent + local dAngle = math.abs(AutoDrive.angleBetween( + ADVectorUtils.subtract2D(p0, startNode), + ADVectorUtils.subtract2D(endNode, p3) + )) + self.curvePreview.curvature = ADVectorUtils.linterp(0, 180, dAngle, 1.5, 2.5) + end - -- distance from start to end, divided by two to give it more roundness... - local dStartEnd = ADVectorUtils.distance2D(startNode, endNode) / self.curvePreview.curvature - - -- we need to normalize the length of p0-start and end-p3, otherwise their length will influence the curve - -- get vector from p0->start - local vp0Start = ADVectorUtils.subtract2D(p0, startNode) - -- calculate unit vector of vp0Start - vp0Start = ADVectorUtils.unitVector2D(vp0Start) - -- scale it like start->end - vp0Start = ADVectorUtils.scaleVector2D(vp0Start, dStartEnd) - -- invert it - vp0Start = ADVectorUtils.invert2D(vp0Start) - -- add it to the start Vector so that we get new p0 - p0 = ADVectorUtils.add2D(startNode, vp0Start) - -- make sure p0 has a y value - p0.y = startNode.y - - -- same for end->p3, except that we do not need to invert it, but just add it to the endNode - local vEndp3 = ADVectorUtils.subtract2D(endNode, p3) - vEndp3 = ADVectorUtils.unitVector2D(vEndp3) - vEndp3 = ADVectorUtils.scaleVector2D(vEndp3, dStartEnd) - p3 = ADVectorUtils.add2D(endNode, vEndp3) - p3.y = endNode.y - - local prevWP = startNode - local prevV = ADVectorUtils.subtract2D(p0, startNode) - -- we're calculting a VERY smooth curve and whenever the new point on the curve has a good distance to the last one create a new waypoint - -- but make sure that the last point also has a good distance to the endNode - for i = 1, 200 do - local px = ADGraphManager:CatmullRomInterpolate(i, p0, startNode, endNode, p3, 200) - local newV = ADVectorUtils.subtract2D(prevWP, px) - local dAngle = math.abs(AutoDrive.angleBetween(prevV, newV)) - - -- only create new WP if distance to last one is > 2m and distance to target > 2m and angle to last one >0 3° - if ADVectorUtils.distance2D(prevWP, px) >= 1.5 and - ADVectorUtils.distance2D(px, endNode) >= 1.5 and - dAngle >= 3 then - - -- get height at terrain - -- TODO: sometimes lines go into the ground - no idea why... making sure that they are at least as high as start or end - px.y = math.max(math.min(startNode.y, endNode.y), getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, px.x, 1, px.z)) - table.insert(self.curvePreview.waypoints, px) - prevWP = px -- newWP - prevV = newV - end + -- distance from start to end, divided by two to give it more roundness... + local dStartEnd = ADVectorUtils.distance2D(startNode, endNode) / self.curvePreview.curvature + + -- we need to normalize the length of p0-start and end-p3, otherwise their length will influence the curve + -- get vector from p0->start + local vp0Start = ADVectorUtils.subtract2D(p0, startNode) + -- calculate unit vector of vp0Start + vp0Start = ADVectorUtils.unitVector2D(vp0Start) + -- scale it like start->end + vp0Start = ADVectorUtils.scaleVector2D(vp0Start, dStartEnd) + -- invert it + vp0Start = ADVectorUtils.invert2D(vp0Start) + -- add it to the start Vector so that we get new p0 + p0 = ADVectorUtils.add2D(startNode, vp0Start) + -- make sure p0 has a y value + p0.y = startNode.y + + -- same for end->p3, except that we do not need to invert it, but just add it to the endNode + local vEndp3 = ADVectorUtils.subtract2D(endNode, p3) + vEndp3 = ADVectorUtils.unitVector2D(vEndp3) + vEndp3 = ADVectorUtils.scaleVector2D(vEndp3, dStartEnd) + p3 = ADVectorUtils.add2D(endNode, vEndp3) + p3.y = endNode.y + + local prevWP = startNode + local prevV = ADVectorUtils.subtract2D(p0, startNode) + -- we're calculting a VERY smooth curve and whenever the new point on the curve has a good distance to the last one create a new waypoint + -- but make sure that the last point also has a good distance to the endNode + for i = 1, 200 do + local px = ADGraphManager:CatmullRomInterpolate(i, p0, startNode, endNode, p3, 200) + local newV = ADVectorUtils.subtract2D(prevWP, px) + local dAngle = math.abs(AutoDrive.angleBetween(prevV, newV)) + + -- only create new WP if distance to last one is > 1.5m and distance to target > 1.5m and angle to last one > 3° + -- or at least every 4m + local distPrev = ADVectorUtils.distance2D(prevWP, px) + local distEnd = ADVectorUtils.distance2D(px, endNode) + if ( distPrev >= 1.5 and distEnd >= 1.5 and dAngle >= 3 ) or (distPrev >= 4 and distEnd >= 4) then + -- get height at terrain + px.y = AutoDrive:getTerrainHeightAtWorldPos(px.x, px.z) + table.insert(self.curvePreview.waypoints, px) + prevWP = px -- newWP + prevV = newV end - -- ADGraphManager:toggleConnectionBetween(prevWP, endNode, false) - table.insert(self.curvePreview.waypoints, endNode) - - else -- fallback to straight line connection behaviour - -- ADGraphManager:toggleConnectionBetween(startNode, endNode, reverseDirection) - return end + table.insert(self.curvePreview.waypoints, endNode) + + else -- fallback to straight line connection behaviour + return end - -- self:markChanges() end diff --git a/FS19_AutoDrive/scripts/Utils/UtilFuncs.lua b/FS19_AutoDrive/scripts/Utils/UtilFuncs.lua index 583c8e77..5ba4bf23 100644 --- a/FS19_AutoDrive/scripts/Utils/UtilFuncs.lua +++ b/FS19_AutoDrive/scripts/Utils/UtilFuncs.lua @@ -76,7 +76,7 @@ function AutoDrive:getTerrainHeightAtWorldPos(x, z) -- get a starting height with the basic function local y = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x, 1, z) -- do a raycast from a bit above y - raycastClosest(x, y + 9, z, 0, -1, 0, "getTerrainHeightAtWorldPos_Callback", 10, self, 12) + raycastClosest(x, y + 2, z, 0, -1, 0, "getTerrainHeightAtWorldPos_Callback", 3, self, 12) return self.raycastHeight or y end @@ -709,6 +709,11 @@ function AutoDrive:getIsActivatable(superFunc, objectToFill) end function AutoDrive:zoomSmoothly(superFunc, offset) + -- if smooth curve preview is active, use mousewheel to increase and decrease smoothness and prevent zooming + if AutoDrive.experimentalFeatures.smoothWaypointConnection and ADGraphManager.curvePreview and AutoDrive.leftLSHIFTmodifierKeyPressed then + ADGraphManager:recalculatePreview(offset / 3) + return + end if not AutoDrive.mouseWheelActive then -- don't zoom camera when mouse wheel is used to scroll targets (thanks to sperrgebiet) superFunc(self, offset) end