365 | function TestAreas:calculateTestAreaDimensions(workArea, testArea) |
366 | testArea.areaSideOffset = testArea.areaSideOffset or 0 |
367 | |
368 | local startX, _, _ = worldToLocal(workArea.testAreaRootNode, getWorldTranslation(testArea.start)) |
369 | local widthX, _, _ = worldToLocal(workArea.testAreaRootNode, getWorldTranslation(testArea.width)) |
370 | testArea.minWidthValue = startX + testArea.areaSideOffset |
371 | testArea.maxWidthValue = widthX - testArea.areaSideOffset |
372 | |
373 | workArea.testAreaMinX = math.min(workArea.testAreaMinX, testArea.minWidthValue, testArea.maxWidthValue) |
374 | workArea.testAreaMaxX = math.max(workArea.testAreaMaxX, testArea.minWidthValue, testArea.maxWidthValue) |
375 | end |
320 | function TestAreas:generateTestAreasForWorkArea(workArea) |
321 | workArea.testAreaParent = createTransformGroup("testAreaParent") |
322 | link(getParent(workArea.testAreaStartNode), workArea.testAreaParent) |
323 | setTranslation(workArea.testAreaParent, getTranslation(workArea.testAreaStartNode)) |
324 | |
325 | local dirX, dirY, dirZ = localToLocal(workArea.testAreaStartNode, workArea.testAreaWidthNode, 0, 0, 0) |
326 | dirX, dirY, dirZ = MathUtil.vector3Normalize(dirX, dirY, dirZ) |
327 | dirX, dirY, dirZ = localDirectionToLocal(workArea.testAreaStartNode, getParent(workArea.testAreaStartNode), dirX, dirY, dirZ) |
328 | |
329 | I3DUtil.setDirection(workArea.testAreaParent, dirX, dirY, dirZ, 0, 1, 0) |
330 | |
331 | local workAreaWidth = calcDistanceFrom(workArea.testAreaStartNode, workArea.testAreaWidthNode) |
332 | local areaWidth = (workAreaWidth * workArea.testAreaScale) / workArea.testAreaNumAreas |
333 | local totalOffset = -workAreaWidth * (1 - workArea.testAreaScale) * 0.5 |
334 | local areaSideOffset = areaWidth * (1 - workArea.testAreaWidthScale) * 0.5 + workArea.testAreaXOffset |
335 | for index=1, workArea.testAreaNumAreas do |
336 | local startNode = createTransformGroup(string.format("testArea%dStart", index)) |
337 | local widthNode = createTransformGroup(string.format("testArea%dWidth", index)) |
338 | local heightNode = createTransformGroup(string.format("testArea%dHeight", index)) |
339 | |
340 | link(workArea.testAreaParent, startNode) |
341 | link(workArea.testAreaParent, widthNode) |
342 | link(workArea.testAreaParent, heightNode) |
343 | |
344 | local startAreaX = -((index-1) * areaWidth) - areaSideOffset + totalOffset |
345 | local endAreaX = -(index * areaWidth) + areaSideOffset + totalOffset |
346 | |
347 | setTranslation(startNode, -(workArea.testAreaZOffset + workArea.testAreaLength), 0, startAreaX) |
348 | setTranslation(widthNode, -(workArea.testAreaZOffset + workArea.testAreaLength), 0, endAreaX) |
349 | setTranslation(heightNode, -workArea.testAreaZOffset, 0, startAreaX) |
350 | |
351 | local testArea = {} |
352 | testArea.start = startNode |
353 | testArea.width = widthNode |
354 | testArea.height = heightNode |
355 | |
356 | testArea.areaSideOffset = areaSideOffset |
357 | |
358 | self:calculateTestAreaDimensions(workArea, testArea) |
359 | self:registerTestAreaForWorkArea(workArea, testArea) |
360 | end |
361 | end |
15 | function TestAreas.initSpecialization() |
16 | local schema = Vehicle.xmlSchema |
17 | schema:setXMLSpecializationType("TestAreas") |
18 | |
19 | schema:register(XMLValueType.BOOL, WorkArea.WORK_AREA_XML_KEY .. ".testAreas#autoGenerate", "Automatically generate test areas", false) |
20 | schema:register(XMLValueType.NODE_INDEX, WorkArea.WORK_AREA_XML_KEY .. ".testAreas#rootNode", "Root node as reference for width") |
21 | schema:register(XMLValueType.NODE_INDEX, WorkArea.WORK_AREA_XML_KEY .. ".testAreas#startNode", "Left node reference for automatic calculation") |
22 | schema:register(XMLValueType.NODE_INDEX, WorkArea.WORK_AREA_XML_KEY .. ".testAreas#widthNode", "Right node reference for automatic calculation") |
23 | schema:register(XMLValueType.FLOAT, WorkArea.WORK_AREA_XML_KEY .. ".testAreas#zOffset", "Offset in Z direction", 0) |
24 | schema:register(XMLValueType.FLOAT, WorkArea.WORK_AREA_XML_KEY .. ".testAreas#xOffset", "Offset for both sides mirrored (negative value will shrink area, positive will increase area on both sides)", 0) |
25 | schema:register(XMLValueType.FLOAT, WorkArea.WORK_AREA_XML_KEY .. ".testAreas#length", "Length of area itself", 0.5) |
26 | schema:register(XMLValueType.INT, WorkArea.WORK_AREA_XML_KEY .. ".testAreas#numAreas", "Number of used areas", 10) |
27 | schema:register(XMLValueType.FLOAT, WorkArea.WORK_AREA_XML_KEY .. ".testAreas#areaWidthScale", "Width percentage of each individual area", 0.9) |
28 | schema:register(XMLValueType.FLOAT, WorkArea.WORK_AREA_XML_KEY .. ".testAreas#scale", "Scale of test areas over width of work area", 1) |
29 | |
30 | schema:register(XMLValueType.NODE_INDEX, WorkArea.WORK_AREA_XML_KEY .. ".testAreas.testArea(?)#startNode", "Start Node") |
31 | schema:register(XMLValueType.NODE_INDEX, WorkArea.WORK_AREA_XML_KEY .. ".testAreas.testArea(?)#widthNode", "Width Node") |
32 | schema:register(XMLValueType.NODE_INDEX, WorkArea.WORK_AREA_XML_KEY .. ".testAreas.testArea(?)#heightNode", "Height Node") |
33 | |
34 | schema:register(XMLValueType.BOOL, WorkArea.WORK_AREA_XML_CONFIG_KEY .. ".testAreas#autoGenerate", "Automatically generate test areas", false) |
35 | schema:register(XMLValueType.NODE_INDEX, WorkArea.WORK_AREA_XML_CONFIG_KEY .. ".testAreas#rootNode", "Root node as reference for width") |
36 | schema:register(XMLValueType.NODE_INDEX, WorkArea.WORK_AREA_XML_CONFIG_KEY .. ".testAreas#startNode", "Left node reference for automatic calculation") |
37 | schema:register(XMLValueType.NODE_INDEX, WorkArea.WORK_AREA_XML_CONFIG_KEY .. ".testAreas#widthNode", "Right node reference for automatic calculation") |
38 | schema:register(XMLValueType.FLOAT, WorkArea.WORK_AREA_XML_CONFIG_KEY .. ".testAreas#zOffset", "Offset in Z direction", 0) |
39 | schema:register(XMLValueType.FLOAT, WorkArea.WORK_AREA_XML_CONFIG_KEY .. ".testAreas#xOffset", "Offset for both sides mirrored (negative value will shrink area, positive will increase area on both sides)", 0) |
40 | schema:register(XMLValueType.FLOAT, WorkArea.WORK_AREA_XML_CONFIG_KEY .. ".testAreas#length", "Length of area itself", 0.5) |
41 | schema:register(XMLValueType.INT, WorkArea.WORK_AREA_XML_CONFIG_KEY .. ".testAreas#numAreas", "Number of used areas", 10) |
42 | schema:register(XMLValueType.FLOAT, WorkArea.WORK_AREA_XML_CONFIG_KEY .. ".testAreas#areaWidthScale", "Width percentage of each individual area", 0.9) |
43 | schema:register(XMLValueType.FLOAT, WorkArea.WORK_AREA_XML_CONFIG_KEY .. ".testAreas#scale", "Scale of test areas over width of work area", 1) |
44 | |
45 | schema:register(XMLValueType.NODE_INDEX, WorkArea.WORK_AREA_XML_CONFIG_KEY .. ".testAreas.testArea(?)#startNode", "Start Node") |
46 | schema:register(XMLValueType.NODE_INDEX, WorkArea.WORK_AREA_XML_CONFIG_KEY .. ".testAreas.testArea(?)#widthNode", "Width Node") |
47 | schema:register(XMLValueType.NODE_INDEX, WorkArea.WORK_AREA_XML_CONFIG_KEY .. ".testAreas.testArea(?)#heightNode", "Height Node") |
48 | |
49 | schema:setXMLSpecializationType() |
50 | end |
259 | function TestAreas:loadWorkAreaFromXML(superFunc, workArea, xmlFile, key) |
260 | if not superFunc(self, workArea, xmlFile, key) then |
261 | return false |
262 | end |
263 | |
264 | workArea.automaticTestAreas = xmlFile:getValue(key .. ".testAreas#autoGenerate", false) |
265 | |
266 | workArea.testAreaRootNode = self.xmlFile:getValue(key .. ".testAreas#rootNode", nil, self.components, self.i3dMappings) |
267 | |
268 | workArea.testAreaStartNode = self.xmlFile:getValue(key .. ".testAreas#startNode", workArea.start, self.components, self.i3dMappings) |
269 | workArea.testAreaWidthNode = self.xmlFile:getValue(key .. ".testAreas#widthNode", workArea.width, self.components, self.i3dMappings) |
270 | |
271 | workArea.testAreaXOffset = self.xmlFile:getValue(key .. ".testAreas#xOffset", 0) |
272 | workArea.testAreaZOffset = self.xmlFile:getValue(key .. ".testAreas#zOffset", 0) |
273 | workArea.testAreaNumAreas = self.xmlFile:getValue(key .. ".testAreas#numAreas", 10) |
274 | workArea.testAreaLength = self.xmlFile:getValue(key .. ".testAreas#length", 0.5) |
275 | workArea.testAreaWidthScale = self.xmlFile:getValue(key .. ".testAreas#areaWidthScale", 0.9) |
276 | workArea.testAreaScale = self.xmlFile:getValue(key .. ".testAreas#scale", 1) |
277 | |
278 | workArea.testAreaMinX = 0 |
279 | workArea.testAreaMaxX = 0 |
280 | |
281 | if workArea.automaticTestAreas then |
282 | if workArea.testAreaRootNode == nil then |
283 | workArea.testAreaRootNode = createTransformGroup("testAreaRootNode") |
284 | link(getParent(workArea.testAreaStartNode), workArea.testAreaRootNode) |
285 | |
286 | local x1, y1, z1 = getWorldTranslation(workArea.testAreaStartNode) |
287 | local x2, y2, z2 = getWorldTranslation(workArea.testAreaWidthNode) |
288 | setWorldTranslation(workArea.testAreaRootNode, (x1 + x2) * 0.5, (y1 + y2) * 0.5, (z1 + z2) * 0.5) |
289 | end |
290 | |
291 | self:generateTestAreasForWorkArea(workArea) |
292 | else |
293 | if workArea.testAreaRootNode == nil then |
294 | workArea.testAreaRootNode = self.components[1].node |
295 | end |
296 | |
297 | xmlFile:iterate(key .. ".testAreas.testArea", function(_, areaKey) |
298 | local testArea = {} |
299 | testArea.start = xmlFile:getValue(areaKey .. "#startNode", nil, self.components, self.i3dMappings) |
300 | testArea.width = xmlFile:getValue(areaKey .. "#widthNode", nil, self.components, self.i3dMappings) |
301 | testArea.height = xmlFile:getValue(areaKey .. "#heightNode", nil, self.components, self.i3dMappings) |
302 | |
303 | if testArea.start ~= nil and testArea.width ~= nil and testArea.height ~= nil then |
304 | self:calculateTestAreaDimensions(workArea, testArea) |
305 | self:registerTestAreaForWorkArea(workArea, testArea) |
306 | end |
307 | end) |
308 | end |
309 | |
310 | workArea.hasTestAreas = workArea.testAreaRootNode ~= nil |
311 | |
312 | workArea.testAreaCurrentWidthMin = -math.huge |
313 | workArea.testAreaCurrentWidthMax = math.huge |
314 | |
315 | return true |
316 | end |
199 | function TestAreas:onUpdateTick(dt, isActiveForInput, isActiveForInputIgnoreSelection, isSelected) |
200 | local spec = self.spec_testAreas |
201 | |
202 | for workArea, testAreas in pairs(spec.testAreasByWorkArea) do |
203 | if self:getIsWorkAreaActive(workArea) then |
204 | workArea.testAreaCurrentWidthMin = -math.huge |
205 | workArea.testAreaCurrentWidthMax = math.huge |
206 | |
207 | local numTestAreas = #testAreas |
208 | local chargedAreas = numTestAreas |
209 | local foundLeft = false |
210 | local foundLeftIndex = -1 |
211 | for i=1, numTestAreas do |
212 | local testArea = testAreas[i] |
213 | if self:processTestArea(testArea) then |
214 | foundLeft = true |
215 | foundLeftIndex = i |
216 | |
217 | workArea.testAreaCurrentWidthMin = testArea.minWidthValue |
218 | workArea.testAreaCurrentWidthMax = testArea.maxWidthValue |
219 | |
220 | break |
221 | else |
222 | chargedAreas = chargedAreas - 1 |
223 | end |
224 | end |
225 | |
226 | if foundLeft then |
227 | local fruitFound = false |
228 | for i=numTestAreas, foundLeftIndex + 1, -1 do |
229 | local testArea = testAreas[i] |
230 | if not fruitFound then |
231 | if self:processTestArea(testArea) then |
232 | workArea.testAreaCurrentWidthMax = testArea.maxWidthValue |
233 | fruitFound = true |
234 | else |
235 | chargedAreas = chargedAreas - 1 |
236 | end |
237 | else |
238 | testArea.hasContact = true |
239 | end |
240 | end |
241 | end |
242 | |
243 | if VehicleDebug.state == VehicleDebug.DEBUG_ATTRIBUTES then |
244 | local x1, y1, z1 = localToWorld(workArea.testAreaRootNode, math.max(workArea.testAreaCurrentWidthMin, workArea.testAreaMinX), 0, 0) |
245 | local x2, y2, z2 = localToWorld(workArea.testAreaRootNode, math.min(workArea.testAreaCurrentWidthMax, workArea.testAreaMaxX), 0, 0) |
246 | |
247 | drawDebugLine(x1, y1, z1, 0, 1, 0, x1, y1 + 2, z1, 0, 1, 0) |
248 | drawDebugLine(x2, y2, z2, 0, 1, 0, x2, y2 + 1, z2, 0, 1, 0) |
249 | end |
250 | else |
251 | workArea.testAreaCurrentWidthMin = -math.huge |
252 | workArea.testAreaCurrentWidthMax = math.huge |
253 | end |
254 | end |
255 | end |
418 | function TestAreas:processTestArea(testArea) |
419 | local spec = self.spec_testAreas |
420 | |
421 | local x, _, z = getWorldTranslation(testArea.start) |
422 | local x1, _, z1 = getWorldTranslation(testArea.width) |
423 | local x2, _, z2 = getWorldTranslation(testArea.height) |
424 | |
425 | if self.isServer and (spec.fruitTypeIndex ~= nil or spec.fillTypeIndex ~= nil) then |
426 | if self:getIsTestAreaActive(testArea) then |
427 | if spec.fruitTypeIndex ~= nil then |
428 | local fruitValue, _, _, _ = FSDensityMapUtil.getFruitArea(spec.fruitTypeIndex, x, z, x1, z1, x2, z2, nil, spec.allowsForageGrowthState) |
429 | testArea.hasContact = fruitValue > 0 |
430 | else |
431 | local fillLevel = DensityMapHeightUtil.getFillLevelAtArea(spec.fillTypeIndex, x, z, x1, z1, x2, z2) |
432 | testArea.hasContact = fillLevel > 0 |
433 | end |
434 | |
435 | if VehicleDebug.state == VehicleDebug.DEBUG_ATTRIBUTES then |
436 | local dx, dz, widthX, widthZ, heightX, heightZ = MathUtil.getXZWidthAndHeight(x, z, x1, z1, x2, z2) |
437 | DebugUtil.drawDebugParallelogram(dx, dz, widthX, widthZ, heightX, heightZ, 0.2, testArea.hasContact and 0 or 1, testArea.hasContact and 1 or 0, 0, 0.5) |
438 | DebugUtil.drawDebugNode(testArea.start, getName(testArea.start), true) |
439 | DebugUtil.drawDebugNode(testArea.width, getName(testArea.width), true) |
440 | DebugUtil.drawDebugNode(testArea.height, getName(testArea.height), true) |
441 | end |
442 | |
443 | if testArea.hasContactSent ~= testArea.hasContact then |
444 | self:raiseDirtyFlags(spec.testAreaDirtyFlag) |
445 | testArea.hasContactSent = testArea.hasContact |
446 | end |
447 | end |
448 | elseif spec.fruitTypeIndex ~= nil or spec.fillTypeIndex ~= nil then |
449 | if VehicleDebug.state == VehicleDebug.DEBUG_ATTRIBUTES then |
450 | local dx, dz, widthX, widthZ, heightX, heightZ = MathUtil.getXZWidthAndHeight(x, z, x1, z1, x2, z2) |
451 | DebugUtil.drawDebugParallelogram(dx, dz, widthX, widthZ, heightX, heightZ, 0.2, testArea.hasContact and 0 or 1, testArea.hasContact and 1 or 0, 0, 0.5) |
452 | end |
453 | end |
454 | |
455 | return testArea.hasContact |
456 | end |
60 | function TestAreas.registerFunctions(vehicleType) |
61 | SpecializationUtil.registerFunction(vehicleType, "readTestAreasStream", TestAreas.readTestAreasStream) |
62 | SpecializationUtil.registerFunction(vehicleType, "writeTestAreasStream", TestAreas.writeTestAreasStream) |
63 | SpecializationUtil.registerFunction(vehicleType, "generateTestAreasForWorkArea", TestAreas.generateTestAreasForWorkArea) |
64 | SpecializationUtil.registerFunction(vehicleType, "calculateTestAreaDimensions", TestAreas.calculateTestAreaDimensions) |
65 | SpecializationUtil.registerFunction(vehicleType, "registerTestAreaForWorkArea", TestAreas.registerTestAreaForWorkArea) |
66 | SpecializationUtil.registerFunction(vehicleType, "setTestAreaRequirements", TestAreas.setTestAreaRequirements) |
67 | SpecializationUtil.registerFunction(vehicleType, "getIsTestAreaActive", TestAreas.getIsTestAreaActive) |
68 | SpecializationUtil.registerFunction(vehicleType, "processTestArea", TestAreas.processTestArea) |
69 | SpecializationUtil.registerFunction(vehicleType, "getTestAreaWidthByWorkAreaIndex", TestAreas.getTestAreaWidthByWorkAreaIndex) |
70 | SpecializationUtil.registerFunction(vehicleType, "getTestAreaChargeByWorkAreaIndex", TestAreas.getTestAreaChargeByWorkAreaIndex) |
71 | end |
173 | function TestAreas:writeTestAreasStream(streamId, connection) |
174 | local spec = self.spec_testAreas |
175 | |
176 | local hadFruitContact = false |
177 | for i=1, #spec.testAreas do |
178 | local testArea = spec.testAreas[i] |
179 | streamWriteBool(streamId, testArea.hasContact) |
180 | if testArea.hasContact then |
181 | hadFruitContact = true |
182 | break |
183 | end |
184 | end |
185 | |
186 | if hadFruitContact then |
187 | for i=#spec.testAreas, 1, -1 do |
188 | local testArea = spec.testAreas[i] |
189 | streamWriteBool(streamId, testArea.hasContact) |
190 | if testArea.hasContact then |
191 | break |
192 | end |
193 | end |
194 | end |
195 | end |