-
-
Notifications
You must be signed in to change notification settings - Fork 79
Expand file tree
/
Copy pathFieldBoundaryDetector.lua
More file actions
135 lines (126 loc) · 6.45 KB
/
Copy pathFieldBoundaryDetector.lua
File metadata and controls
135 lines (126 loc) · 6.45 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
--- Wrapper around the Giants field boundary detection
---@class FieldBoundaryDetector
FieldBoundaryDetector = CpObject()
--- Create a FieldBoundaryDetector instance and start the detection immediately. The detection is an asynchronous
--- process and FieldBoundaryDetector:update() must be called until it returns false to get the result.
---
--- If the user prefers custom fields, the detection will first check if there is a custom field at the given position,
--- and if so, use that as the field boundary. If not, the Giants field boundary detection is used.
--- If the Giants detection fails, the custom field is used as a fallback if exists at the position.
---
--- The result is available through getFieldPolygon(), which is a polygon representing the field boundary around
--- x, z, and through getIslandPolygons() which is an array of polygons representing the islands inside the field.
---
---@param x number world X coordinate to start the detection at
---@param z number world Z coordinate to start the detection at
---@param vehicle table vehicle, this is used to generate the field course settings the Giants detection needs.
function FieldBoundaryDetector:init(x, z, vehicle)
self.logger = Logger('FieldBoundaryDetector', Logger.level.debug, CpDebug.DBG_COURSES)
self.vehicle = vehicle
self.x = x
self.z = z
self.updates = 0
local customField = g_customFieldManager:getCustomField(x, z)
if customField and g_Courseplay.globalSettings.preferCustomFields:getValue() then
self.logger:info( 'Foun d custom field %s at %.1f %.1f and custom fields are preferred',
customField:getName(), x, z)
self:_useCustomField(customField)
return
end
self.logger:info( 'Detecting field boundary at %.1f %.1f using the Giants function', x, z)
local fieldCourseSettings, implementData = FieldCourseSettings.generate(vehicle)
self.courseField = FieldCourseField.generateAtPosition(x, z, fieldCourseSettings, function(courseField, success)
if success then
self.done = true
self.logger:info('Field boundary detection successful after %d updates, %d boundary points and %d islands',
self.updates, #courseField.fieldRootBoundary.boundaryLine, #courseField.islands)
self.fieldPolygon = self:_getAsVertices(courseField.fieldRootBoundary.boundaryLine)
self:_tryLowResolutionBoundaryFallback()
self.islandPolygons = {}
for i, island in ipairs(courseField.islands) do
local islandBoundary = self:_getAsVertices(island.rootBoundary.boundaryLine)
table.insert(self.islandPolygons, islandBoundary)
end
else
if customField then
self.logger:info('Field boundary detection failed after %d updates, but found custom field %s at %.1f %.1f',
self.updates, customField:getName(), x, z)
self:_useCustomField(customField)
return
else
self.logger:info('Field boundary detection failed after %d updates and no custom field found at %.1f %.1f',
self.updates, x, z)
self:_tryGiantsFailedBoundaryFallback()
return
end
end
end)
end
---@return boolean true if still in progress, false when done
function FieldBoundaryDetector:update(dt)
-- when we use the custom field, we are done immediately
-- FieldCourseField:update() returns true until it's state is FieldCourseDetectionState.FINISHED. Problem
-- is, it may never go to the FINISHED state, and then our indication of done is that the callback is called with
-- success == true, therefore use the self.done to indicate it.
if not self.useCustomField and not self.done and self.courseField:update(dt, 0.00025) then
self.updates = self.updates + 1
return true
else
return false
end
end
---@return table|nil [{x, y, z}] field polygon with game vertices
function FieldBoundaryDetector:getFieldPolygon()
return self.fieldPolygon
end
---@return table|nil [[{x, y, z}]] array of island polygons with game vertices (x, y, z)
function FieldBoundaryDetector:getIslandPolygons()
return self.islandPolygons
end
---@param boundaryLine table [[x, z]] array of arrays as the Giants functions return the field boundary
---@return table [{x, z}] array of vertices as the course generator needs it
function FieldBoundaryDetector:_getAsVertices(boundaryLine)
local vertices = {}
-- Giants seem to have the first vertex of the polygon repeated as the last, so skip the last one.
for i = 1, #boundaryLine - 1 do
local point = boundaryLine[i]
local x, z = point[1], point[2]
table.insert(vertices, { x = x, y = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x, 1, z), z = z })
end
return vertices
end
function FieldBoundaryDetector:_useCustomField(customField)
self.fieldPolygon = customField:getVertices()
self.useCustomField = true
end
function FieldBoundaryDetector:_tryLowResolutionBoundaryFallback()
local minimumGiantsBoundaryPoints = 60
if not self.fieldPolygon or #self.fieldPolygon >= minimumGiantsBoundaryPoints then
return
end
if not g_fieldScanner or not g_fieldScanner.findContour then
return
end
local valid, scannerPolygon = g_fieldScanner:findContour(self.x, self.z)
if valid and scannerPolygon and #scannerPolygon > #self.fieldPolygon * 2 then
self.logger:info('Giants boundary only has %d points, using Courseplay field scanner result with %d points instead',
#self.fieldPolygon, #scannerPolygon)
self.fieldPolygon = scannerPolygon
else
self.logger:info('Giants boundary only has %d points, but Courseplay field scanner fallback did not improve it',
#self.fieldPolygon)
end
end
function FieldBoundaryDetector:_tryGiantsFailedBoundaryFallback()
if not g_fieldScanner or not g_fieldScanner.findContour then
return
end
local valid, scannerPolygon = g_fieldScanner:findContour(self.x, self.z)
if valid and scannerPolygon and #scannerPolygon > 0 then
self.logger:info('Giants boundary detection failed, using Courseplay field scanner result with %d points instead',
#scannerPolygon)
self.fieldPolygon = scannerPolygon
else
self.logger:info('Giants boundary detection failed, and Courseplay field scanner fallback did not find a boundary')
end
end