diff --git a/FS25_ADSmartPickup/adFields.lua b/FS25_ADSmartPickup/adFields.lua index 2333ab1..96582d4 100644 --- a/FS25_ADSmartPickup/adFields.lua +++ b/FS25_ADSmartPickup/adFields.lua @@ -151,13 +151,63 @@ local function isOnField(x, z) return FSDensityMapUtil.getFieldDataAtWorldPosition(x, y, z) and true or false end -local function findMarker(adEnv, fieldId, x, z) +ADFields.SAMPLE_DIRECTIONS = 16 + +-- Markens omrids som {{x, z}, ...}. FS25: field:getDensityMapPolygon():getVerticesList() (flad liste +-- x1, z1, x2, z2 ... — som Courseplay); ældre: polygonPoints (noder). Tom liste hvis intet findes. +function ADFields.readPolygon(field) + local verts = {} + local okList, list = pcall(function() + return field.getDensityMapPolygon ~= nil and field:getDensityMapPolygon():getVerticesList() or nil + end) + if okList and type(list) == "table" and #list >= 6 then + for index = 1, #list - 1, 2 do + table.insert(verts, {x = list[index], z = list[index + 1]}) + end + return verts + end + for _, node in ipairs(field.polygonPoints or {}) do + if type(node) == "number" then + local x, _, z = getWorldTranslation(node) + table.insert(verts, {x = x, z = z}) + end + end + return verts +end + +-- Hjørnernes gennemsnit (nil uden hjørner). +function ADFields.polygonCenter(verts) + if verts == nil or #verts == 0 then return nil end + local cx, cz = 0, 0 + for _, v in ipairs(verts) do cx, cz = cx + v.x, cz + v.z end + return cx / #verts, cz / #verts +end + +-- Afstand fra (x, z) til omridsets nærmeste kant; nil uden omrids. +function ADFields.distanceToPolygon(verts, x, z) + if verts == nil or #verts == 0 then return nil end + local best = math.huge + for index = 1, #verts do + local a, b = verts[index], verts[index % #verts + 1] + local dx, dz = b.x - a.x, b.z - a.z + local len2 = dx * dx + dz * dz + local t = len2 > 0 and math.max(0, math.min(1, ((x - a.x) * dx + (z - a.z) * dz) / len2)) or 0 + local d = math.sqrt((a.x + dx * t - x) ^ 2 + (a.z + dz * t - z) ^ 2) + if d < best then best = d end + end + return best +end + +-- Markøren "F" nærmest marken: afstand til omridset (markører står ved kanten; midten kan +-- ligge 900 m væk på store kort), ellers til midten. +local function findMarker(adEnv, fieldId, x, z, verts) local best, bestDistance = nil, math.huge for _, marker in pairs(adEnv.ADGraphManager:getMapMarkers()) do if ADFields.parseMarkerName(marker.name) == fieldId then local wayPoint = adEnv.ADGraphManager:getWayPointById(marker.id) if wayPoint ~= nil then - local distance = math.sqrt((wayPoint.x - x) ^ 2 + (wayPoint.z - z) ^ 2) + local distance = ADFields.distanceToPolygon(verts, wayPoint.x, wayPoint.z) + or math.sqrt((wayPoint.x - x) ^ 2 + (wayPoint.z - z) ^ 2) if distance < bestDistance then best, bestDistance = marker, distance end end end @@ -199,24 +249,19 @@ local function newScan(adEnv, entry) scan.info = info -- 1) polygon og kandidatpunkter (centroid + halvvejs mod op til 8 hjørner) push(scan, function() - local verts = {} - for _, node in ipairs(field.polygonPoints or {}) do - if type(node) == "number" then - local x, _, z = getWorldTranslation(node) - table.insert(verts, {x = x, z = z}) - end - end + local verts = ADFields.readPolygon(field) info.verts = verts - local cx, cz = field.posX, field.posZ - if (cx == nil or cz == nil) and #verts > 0 then - cx, cz = 0, 0 - for _, v in ipairs(verts) do cx, cz = cx + v.x, cz + v.z end - cx, cz = cx / #verts, cz / #verts - end + local cx, cz = ADFields.polygonCenter(verts) + if cx == nil then cx, cz = field.posX, field.posZ end info.x, info.z = cx, cz if cx == nil then return end + -- midten, markskiltet og halvvejs mod op til SAMPLE_DIRECTIONS hjørner: store marker (Dakota + -- 16x: 1,7 km) har striber uden afgrøde, så ét punkt kan ramme forkert local candidates = {{x = cx, z = cz}} - local step = math.max(1, math.floor(#verts / 8)) + if field.posX ~= nil and field.posZ ~= nil and (field.posX - cx) ^ 2 + (field.posZ - cz) ^ 2 > 1 then + table.insert(candidates, {x = field.posX, z = field.posZ}) + end + local step = math.max(1, math.floor(#verts / ADFields.SAMPLE_DIRECTIONS)) for index = 1, #verts, step do table.insert(candidates, {x = (cx + verts[index].x) / 2, z = (cz + verts[index].z) / 2}) end @@ -318,7 +363,7 @@ local function finishScan(adEnv, scan) if previous ~= nil and previous.markerCheckedAt ~= nil and ADFields.now - previous.markerCheckedAt < ADFields.MARKER_REFRESH_MS then info.markerId, info.markerName, info.markerDistance, info.markerCheckedAt = previous.markerId, previous.markerName, previous.markerDistance, previous.markerCheckedAt elseif info.x ~= nil then - local marker, distance = findMarker(adEnv, info.id, info.x, info.z) + local marker, distance = findMarker(adEnv, info.id, info.x, info.z, info.verts) info.markerId = marker ~= nil and marker.id or nil info.markerName = marker ~= nil and marker.name or nil info.markerDistance = distance diff --git a/FS25_ADSmartPickup/modDesc.xml b/FS25_ADSmartPickup/modDesc.xml index 7122d80..5194961 100644 --- a/FS25_ADSmartPickup/modDesc.xml +++ b/FS25_ADSmartPickup/modDesc.xml @@ -1,7 +1,7 @@ masterdraco - 1.28.5.0 + 1.28.6.0 <en>AD Smart Pickup</en> diff --git a/tests/test_adFieldsPolygon.lua b/tests/test_adFieldsPolygon.lua new file mode 100644 index 0000000..41db33f --- /dev/null +++ b/tests/test_adFieldsPolygon.lua @@ -0,0 +1,60 @@ +-- Kør: luajit tests/test_adFieldsPolygon.lua (fra repo-roden) +-- FS25-marker har ingen polygonPoints — omridset hentes med field:getDensityMapPolygon():getVerticesList(). +-- In-game 26/9 (Dakota 16x, marker på 1,7 x 0,6 km): uden omrids blev kun markskiltet (posX/posZ) prøvet; +-- F108 med majs viste "bar jord" og "mangler markør" (markøren stod ved kanten, 890 m fra skiltet). +local failures = 0 +local function check(name, actual, expected) + if actual == expected then print("OK " .. name) else + failures = failures + 1 + print(string.format("FAIL %s: forventede %s, fik %s", name, tostring(expected), tostring(actual))) + end +end +Logging = {warning = function() end, info = function() end} + +local MAIZE = 10 +function getWorldTranslation() return 0, 0, 0 end +function getTerrainHeightAtWorldPos() return 0 end +-- markskiltet står i marken, men ved en stribe uden afgrøde (x > 1400) +local function cropAt(x) return x <= 1400 and MAIZE or nil end +FSDensityMapUtil = { + getFieldDataAtWorldPosition = function() return 1, 2 end, + getFruitTypeIndexAtWorldPos = function(x, z) return cropAt(x) end, + getFruitArea = function() return 0, 10 end, +} +FieldDensityMap = {SPRAY_LEVEL = 4, LIME_LEVEL = 5, PLOW_LEVEL = 6} +DensityCoordType = {POINT_VECTOR_VECTOR = 1} +DensityMapModifier = {new = function() return {setParallelogramWorldCoords = function() end, executeGet = function() return 1, 1 end} end} +g_fruitTypeManager = { + getFruitTypeByIndex = function() return {name = "MAIZE", fillType = {index = 60, title = "Majs"}, minHarvestingGrowthState = 7, maxHarvestingGrowthState = 7, cutState = 9} end, + getConverterDataByName = function() return {} end, +} +g_fillTypeManager = {getConverterDataByName = function() return {} end, getFillTypeNameByIndex = function() return "X" end} +g_farmlandManager = {getFarmlandOwner = function() return 1 end} +local polygon = {getVerticesList = function() return {0, 0, 1600, 0, 1600, 600, 0, 600} end} +local field = {getId = function() return 108 end, farmland = {id = 108}, posX = 1500, posZ = 50, + getDensityMapPolygon = function() return polygon end} +g_fieldManager = {getFields = function() return {field} end} +g_currentMission = {terrainRootNode = 1, missionInfo = {}, fieldGroundSystem = { + getDensityMapData = function() return 1, 0, 2 end, getMaxValue = function() return 2 end}} +local adEnv = {ADGraphManager = { + getMapMarkers = function() return {{id = 900, name = "F108"}} end, + -- markøren står 5 m uden for marken ved den modsatte kant: 1505 m fra skiltet + getWayPointById = function() return {x = -5, z = 300} end, +}} + +dofile("FS25_ADSmartPickup/adFields.lua") +local now = 0 +for _ = 1, 1000 do + if ADFields.getInfo(108) ~= nil then break end + ADFields.scanStep(adEnv, 1, now) + now = now + 16 +end +local info = ADFields.getInfo(108) +check("PG1 marken er scannet", info ~= nil, true) +check("PG2 omrids fra getDensityMapPolygon", info and #info.verts, 4) +check("PG3 majsen findes trods skiltet på bar stribe", info and info.fruitName, "MAIZE") +check("PG4 ikke bar mark", info and info.kind, "harvest") +check("PG5 markør ved kanten findes (afstand til omridset)", info and info.markerId, 900) +check("PG6 afstand = til kanten", info and info.markerDistance, 5) +print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL")) +os.exit(failures == 0 and 0 or 1)