diff --git a/scripts/Course.lua b/scripts/Course.lua index 80a666016..69b186328 100644 --- a/scripts/Course.lua +++ b/scripts/Course.lua @@ -1467,9 +1467,31 @@ function Course:getProgress(ix) return self.waypoints[ix].dToHere / self.length, ix, ix == #self.waypoints end -local function saveWaypointsToXml(waypoints, xmlFile, key) - for i, wp in ipairs(waypoints) do - wp:setXmlValue(xmlFile, key, i) +---@param compact boolean skip waypoints between row start and end (as for straight rows, these can be regenerated +--- easily after the course is loaded) +local function saveWaypointsToXml(waypoints, xmlFile, key, compact) + local i = 1 + for _, wp in ipairs(waypoints) do + if not compact or (not wp:getRowNumber() or wp:isRowStart() or wp:isRowEnd()) then + wp:setXmlValue(xmlFile, key, i) + i = i + 1 + end + end +end + +-- The idea is not to store the waypoints of a fieldwork row (as it is just a straight line, unless we use the baseline +-- edge feature of the generator), only the start and the end +-- of the row (turn end and turn start waypoints). We still need those intermediate +-- waypoints though when working so the PPC does not put the targets kilometers away, so after loading a course, these +-- points can be generated by this function +local function addIntermediateWaypoints(d, waypoints, rowStart, rowEnd) + local dx, dz = (rowEnd.x - rowStart.x) / d, (rowEnd.z - rowStart.z) / d + for n = 1, (d / CourseGenerator.cRowWaypointDistance) - 1 do + local newWp = Waypoint({}) + newWp.x = rowStart.x + n * CourseGenerator.cRowWaypointDistance * dx + newWp.z = rowStart.z + n * CourseGenerator.cRowWaypointDistance * dz + newWp:copyRowData(rowStart) + table.insert(waypoints, newWp) end end @@ -1477,21 +1499,24 @@ end local function createWaypointsFromXml(xmlFile, key) local waypoints = {} -- these are only saved for the row start waypoint, here we add them to all waypoints of the row - local rowNumber, leftSideWorked, rightSideWorked + local rowStart xmlFile:iterate(key .. Waypoint.xmlKey, function(ix, wpKey) - table.insert(waypoints, Waypoint.createFromXmlFile(xmlFile, wpKey)) - local last = waypoints[#waypoints].attributes - if last.rowStart then - rowNumber = last.rowNumber - leftSideWorked = last.leftSideWorked - rightSideWorked = last.rightSideWorked - elseif last.rowEnd then - rowNumber, leftSideWorked, rightSideWorked = nil, nil, nil - elseif rowNumber then - last.rowNumber = rowNumber - last.leftSideWorked = leftSideWorked - last.rightSideWorked = rightSideWorked + local wp = Waypoint.createFromXmlFile(xmlFile, wpKey) + if wp:isRowStart() then + rowStart = wp + elseif wp:isRowEnd() then + local d = wp:getDistanceFromOther(waypoints[#waypoints]) + if waypoints[#waypoints]:isRowStart() and d > CourseGenerator.cRowWaypointDistance + 0.1 then + -- there is now intermediate waypoints between the row start and row end and they are further + -- apart than the row waypoint distance, add intermediate waypoints + addIntermediateWaypoints(d, waypoints, waypoints[#waypoints], wp) + end + rowStart = nil + elseif rowStart then + -- normal row waypoint, copy the row data from the row start waypoint + wp:copyRowData(rowStart) end + table.insert(waypoints, wp) end) return waypoints end @@ -1504,9 +1529,12 @@ function Course:saveToXml(courseXml, courseKey) CpUtil.setXmlValue(courseXml, courseKey .. '#headlandClockwise', self.headlandClockwise) CpUtil.setXmlValue(courseXml, courseKey .. '#islandHeadlandClockwise', self.islandHeadlandClockwise) courseXml:setValue(courseKey .. '#wasEdited', self.editedByCourseEditor) - saveWaypointsToXml(self.waypoints, courseXml, courseKey) + courseXml:setValue(courseKey .. '#compacted', self.compacted) if self.nVehicles > 1 then - self.multiVehicleData:setXmlValue(courseXml, courseKey) + self.multiVehicleData:setXmlValue(courseXml, courseKey, self.compacted) + else + -- only write the current waypoints if we are not a multi-vehicle course + saveWaypointsToXml(self.waypoints, courseXml, courseKey, self.compacted) end end @@ -1514,34 +1542,36 @@ end ---@param courseXml XmlFile ---@param courseKey string key to the course in the XML function Course.createFromXml(vehicle, courseXml, courseKey) - local name = courseXml:getValue(courseKey .. '#name') - local workWidth = courseXml:getValue(courseKey .. '#workWidth') - local numberOfHeadlands = courseXml:getValue(courseKey .. '#numHeadlands') - local multiTools = courseXml:getValue(courseKey .. '#multiTools') - local nVehicles = courseXml:getValue(courseKey .. '#nVehicles') - local headlandClockwise = courseXml:getValue(courseKey .. '#headlandClockwise') - local islandHeadlandClockwise = courseXml:getValue(courseKey .. '#islandHeadlandClockwise') - local wasEdited = courseXml:getValue(courseKey .. '#wasEdited', false) - local waypoints = createWaypointsFromXml(courseXml, courseKey) - if #waypoints == 0 then - CpUtil.debugVehicle(CpDebug.DBG_COURSES, vehicle, 'No waypoints loaded, trying old format') - courseXml:iterate(courseKey .. '.waypoints' .. Waypoint.xmlKey, function(ix, key) - local d - d = CpUtil.getXmlVectorValues(courseXml:getString(key)) - table.insert(waypoints, Waypoint.initFromXmlFileLegacyFormat(d, ix)) - end) - end - local course = Course(vehicle, waypoints) - course.name = name - course.workWidth = workWidth - course.numberOfHeadlands = numberOfHeadlands - course.nVehicles = nVehicles or 1 - course.headlandClockwise = headlandClockwise - course.islandHeadlandClockwise = islandHeadlandClockwise - course.editedByCourseEditor = wasEdited - if nVehicles and nVehicles > 1 then + local course = Course(vehicle, {}) + course.name = courseXml:getValue(courseKey .. '#name') + course.workWidth = courseXml:getValue(courseKey .. '#workWidth') + course.numberOfHeadlands = courseXml:getValue(courseKey .. '#numHeadlands') + course.nVehicles = courseXml:getValue(courseKey .. '#nVehicles', 1) + course.headlandClockwise = courseXml:getValue(courseKey .. '#headlandClockwise') + course.islandHeadlandClockwise = courseXml:getValue(courseKey .. '#islandHeadlandClockwise') + course.editedByCourseEditor = courseXml:getValue(courseKey .. '#compacted', false) + course.compacted = courseXml:getValue(courseKey .. '#compacted', false) + course.waypoints = {} + if not course.nVehicles or course.nVehicles == 1 then + -- TODO: not nVehicles for backwards compatibility, remove later + -- for multi-vehicle courses, we load the multi-vehicle data and restore the current course + -- from there, so we don't need to write the same course twice in the savegame + course.waypoints = createWaypointsFromXml(courseXml, courseKey) + if #course.waypoints == 0 then + CpUtil.debugVehicle(CpDebug.DBG_COURSES, vehicle, 'No waypoints loaded, trying old format') + courseXml:iterate(courseKey .. '.waypoints' .. Waypoint.xmlKey, function(ix, key) + local d + d = CpUtil.getXmlVectorValues(courseXml:getString(key)) + table.insert(course.waypoints, Waypoint.initFromXmlFileLegacyFormat(d, ix)) + end) + end + end + if course.nVehicles and course.nVehicles > 1 then course.multiVehicleData = Course.MultiVehicleData.createFromXmlFile(courseXml, courseKey) + course:setPosition(course.multiVehicleData:getPosition()) vehicle:getCpLaneOffsetSetting():setValue(course.multiVehicleData:getPosition()) + else + course:enrichWaypointData() end CpUtil.debugVehicle(CpDebug.DBG_COURSES, vehicle, 'Course with %d waypoints loaded.', #course.waypoints) return course @@ -1566,32 +1596,26 @@ function Course:writeStream(vehicle, streamId, connection) end function Course.createFromStream(vehicle, streamId, connection) - local name = streamReadString(streamId) - local workWidth = streamReadFloat32(streamId) - local numberOfHeadlands = streamReadInt32(streamId) - local nVehicles = streamReadInt32(streamId) - local headlandClockwise = CpUtil.streamReadBool(streamId) - local islandHeadlandClockwise = CpUtil.streamReadBool(streamId) + local course = Course(vehicle, {}) + course.name = streamReadString(streamId) + course.workWidth = streamReadFloat32(streamId) + course.numberOfHeadlands = streamReadInt32(streamId) + course.nVehicles = streamReadInt32(streamId) + course.headlandClockwise = CpUtil.streamReadBool(streamId) + course.islandHeadlandClockwise = CpUtil.streamReadBool(streamId) local numWaypoints = streamReadInt32(streamId) - local wasEdited = streamReadBool(streamId) - local waypoints = {} + course.editedByCourseEditor = streamReadBool(streamId) + course.waypoints = {} for ix = 1, numWaypoints do - table.insert(waypoints, Waypoint.createFromStream(streamId, ix)) + table.insert(course.waypoints, Waypoint.createFromStream(streamId, ix)) end - local course = Course(vehicle, waypoints) - course.name = name - course.workWidth = workWidth - course.numberOfHeadlands = numberOfHeadlands - course.nVehicles = nVehicles - course.headlandClockwise = headlandClockwise - course.islandHeadlandClockwise = islandHeadlandClockwise - course.editedByCourseEditor = wasEdited - if nVehicles > 1 then - course.multiVehicleData = Course.MultiVehicleData.createFromStream(streamId, nVehicles) + if course.nVehicles > 1 then + course.multiVehicleData = Course.MultiVehicleData.createFromStream(streamId, course.nVehicles) vehicle:getCpLaneOffsetSetting():setValue(course.multiVehicleData:getPosition()) end + course:enrichWaypointData() CpUtil.debugVehicle(CpDebug.DBG_MULTIPLAYER, vehicle, 'Course with %d waypoints, %d vehicles loaded.', - #course.waypoints, nVehicles) + #course.waypoints, course.nVehicles) return course end @@ -1604,13 +1628,16 @@ local function createWaypointsFromGeneratedPath(path) return waypoints end +---@param straightRows boolean rows are straight, so are fully defined by the row start and end waypoints, therefore +--- waypoints between them don't have to be saved (for better performance) as they can be restored when loading function Course.createFromGeneratedCourse(vehicle, generatedCourse, workWidth, numberOfHeadlands, nVehicles, - headlandClockwise, islandHeadlandClockwise) + headlandClockwise, islandHeadlandClockwise, straightRows) local waypoints = createWaypointsFromGeneratedPath(generatedCourse:getPath()) local course = Course(vehicle or g_currentMission.controlledVehicle, waypoints) course.workWidth = workWidth course.numberOfHeadlands = numberOfHeadlands course.nVehicles = nVehicles + course.compacted = straightRows course.headlandClockwise = headlandClockwise course.islandHeadlandClockwise = islandHeadlandClockwise if course.nVehicles > 1 then @@ -1705,7 +1732,7 @@ function Course.MultiVehicleData.registerXmlSchema(schema, baseKey) Waypoint.registerXmlSchema(schema, key) end -function Course.MultiVehicleData:setXmlValue(xmlFile, baseKey) +function Course.MultiVehicleData:setXmlValue(xmlFile, baseKey, compacted) local mvdKey = baseKey .. Course.MultiVehicleData.key xmlFile:setValue(mvdKey .. '#selectedPosition', self.position) local i = 0 @@ -1713,7 +1740,7 @@ function Course.MultiVehicleData:setXmlValue(xmlFile, baseKey) for position, waypoints in pairs(self.waypoints) do local posKey = string.format("%s%s(%d)", mvdKey, '.waypoints', i) xmlFile:setValue(posKey .. '#position', position) - saveWaypointsToXml(waypoints, xmlFile, posKey) + saveWaypointsToXml(waypoints, xmlFile, posKey, compacted) i = i + 1 end end diff --git a/scripts/Waypoint.lua b/scripts/Waypoint.lua index 8c668a9ed..9bf7a7363 100644 --- a/scripts/Waypoint.lua +++ b/scripts/Waypoint.lua @@ -179,6 +179,10 @@ function Waypoint:getDistanceFromPoint(x, z) return MathUtil.getPointPointDistance(x, z, self.x, self.z) end +function Waypoint:getDistanceFromOther(other) + return self:getDistanceFromPoint(other.x, other.z) +end + function Waypoint:getDistanceFromVehicle(vehicle) local vx, _, vz = getWorldTranslation(vehicle:getAIDirectionNode() or vehicle.rootNode) return self:getDistanceFromPoint(vx, vz) @@ -282,6 +286,12 @@ function Waypoint:setOnConnectingPath(onConnectingPath) self.attributes:setOnConnectingPath(onConnectingPath) end +function Waypoint:copyRowData(other) + self.attributes.rowNumber = other.attributes.rowNumber + self.attributes.leftSideWorked = other.attributes.leftSideWorked + self.attributes.rightSideWorked = other.attributes.rightSideWorked +end + -- a node related to a waypoint ---@class WaypointNode WaypointNode = CpObject() diff --git a/scripts/courseGenerator/CourseGeneratorInterface.lua b/scripts/courseGenerator/CourseGeneratorInterface.lua index 41ed13517..e702a14ba 100644 --- a/scripts/courseGenerator/CourseGeneratorInterface.lua +++ b/scripts/courseGenerator/CourseGeneratorInterface.lua @@ -109,7 +109,7 @@ function CourseGeneratorInterface.generate(fieldPolygon, local course = Course.createFromGeneratedCourse(vehicle, CourseGeneratorInterface.generatedCourse, settings.workWidth:getValue(), numberOfHeadlands, settings.multiTools:getValue(), - settings.headlandClockwise:getValue(), settings.islandHeadlandClockwise:getValue()) + settings.headlandClockwise:getValue(), settings.islandHeadlandClockwise:getValue(), not settings.useBaseLineEdge:getValue()) course:setFieldPolygon(fieldPolygon) return true, course end @@ -167,7 +167,7 @@ function CourseGeneratorInterface.generateVineCourse( #CourseGeneratorInterface.generatedCourse:getCenterPath()) local course = Course.createFromGeneratedCourse(nil, CourseGeneratorInterface.generatedCourse, - workWidth, 0, multiTools) + workWidth, 0, multiTools, true, true, true) course:setFieldPolygon(fieldPolygon) return true, course end diff --git a/scripts/specializations/CpCourseManager.lua b/scripts/specializations/CpCourseManager.lua index 8c155f956..5a5c38568 100644 --- a/scripts/specializations/CpCourseManager.lua +++ b/scripts/specializations/CpCourseManager.lua @@ -30,6 +30,7 @@ function CpCourseManager.registerXmlSchemaValues(schema,baseKey) schema:register(XMLValueType.BOOL, baseKey .. "#headlandClockwise", "Headlands are clockwise.") schema:register(XMLValueType.BOOL, baseKey .. "#islandHeadlandClockwise", "Headlands around islands are clockwise.") schema:register(XMLValueType.BOOL, baseKey .. "#wasEdited", "Was the course edited by the course editor.") + schema:register(XMLValueType.BOOL, baseKey .. "#compacted", "Rows are compacted, only start and end is saved.") schema:register(XMLValueType.STRING, baseKey .. ".waypoints", "Course serialized waypoints") -- old save format Waypoint.registerXmlSchema(schema, baseKey) Course.MultiVehicleData.registerXmlSchema(schema, baseKey)