250 | function PlaceableLeveling:addDeformationArea(deformationObject, area, terrainBrushId, writeBlockedAreaMap) |
251 | local worldStartX, worldStartY, worldStartZ = getWorldTranslation(area.start) |
252 | local worldSide1X, worldSide1Y, worldSide1Z = getWorldTranslation(area.width) |
253 | local worldSide2X, worldSide2Y, worldSide2Z = getWorldTranslation(area.height) |
254 | |
255 | local side1X, side1Y, side1Z = worldSide1X - worldStartX, worldSide1Y - worldStartY, worldSide1Z - worldStartZ |
256 | local side2X, side2Y, side2Z = worldSide2X - worldStartX, worldSide2Y - worldStartY, worldSide2Z - worldStartZ |
257 | |
258 | deformationObject:addArea( |
259 | worldStartX, worldStartY, worldStartZ, |
260 | side1X, side1Y, side1Z, |
261 | side2X, side2Y, side2Z, |
262 | terrainBrushId, |
263 | writeBlockedAreaMap |
264 | ) |
265 | end |
275 | function PlaceableLeveling:applyDeformation(isPreview, callback) |
276 | local deformationObjects = self:getDeformationObjects(g_currentMission.terrainRootNode) |
277 | |
278 | if #deformationObjects == 0 then |
279 | callback(TerrainDeformation.STATE_SUCCESS, 0, nil) |
280 | return |
281 | end |
282 | |
283 | local recursiveCallback = {} |
284 | recursiveCallback.index = 1 |
285 | recursiveCallback.volume = 0 |
286 | recursiveCallback.deformationObjects = deformationObjects |
287 | recursiveCallback.finishCallback = callback |
288 | recursiveCallback.callback = function(target, errorCode, displacedVolume, blockedObjectName) |
289 | if errorCode ~= TerrainDeformation.STATE_SUCCESS then |
290 | -- Finished: delete all objects |
291 | local objects = {} |
292 | for _, object in ipairs(target.deformationObjects) do |
293 | table.insert(objects, object) |
294 | end |
295 | |
296 | -- do the delete in the next frame |
297 | g_asyncTaskManager:addTask(function() |
298 | for _, object in ipairs(objects) do |
299 | object:delete() |
300 | end |
301 | end) |
302 | |
303 | target.finishCallback(errorCode, target.volume, blockedObjectName) |
304 | return |
305 | end |
306 | |
307 | target.volume = target.volume + displacedVolume |
308 | target.index = target.index + 1 |
309 | |
310 | local nextDeformationObject = target.deformationObjects[target.index] |
311 | if nextDeformationObject ~= nil then |
312 | g_terrainDeformationQueue:queueJob(nextDeformationObject, isPreview, "callback", target) |
313 | else |
314 | -- Finished: delete all objects |
315 | local objects = {} |
316 | for _, object in ipairs(target.deformationObjects) do |
317 | table.insert(objects, object) |
318 | end |
319 | |
320 | -- do the delete in the next frame |
321 | g_asyncTaskManager:addTask(function() |
322 | for _, object in ipairs(objects) do |
323 | object:delete() |
324 | end |
325 | end) |
326 | |
327 | target.finishCallback(TerrainDeformation.STATE_SUCCESS, target.volume, nil) |
328 | end |
329 | end |
330 | |
331 | g_terrainDeformationQueue:queueJob(deformationObjects[1], isPreview, "callback", recursiveCallback) |
332 | end |
190 | function PlaceableLeveling:getDeformationObjects(terrainRootNode, forBlockingOnly, isBlocking) |
191 | local spec = self.spec_leveling |
192 | local deformationObjects = {} |
193 | |
194 | if not forBlockingOnly then |
195 | isBlocking = false |
196 | end |
197 | |
198 | if terrainRootNode ~= nil and terrainRootNode ~= 0 then |
199 | if #spec.levelAreas > 0 then |
200 | local deformationObject = TerrainDeformation.new(terrainRootNode) |
201 | |
202 | if g_densityMapHeightManager.placementCollisionMap ~= nil then |
203 | deformationObject:setBlockedAreaMap(g_densityMapHeightManager.placementCollisionMap, 0) |
204 | end |
205 | |
206 | for _, levelArea in pairs(spec.levelAreas) do |
207 | local layer = -1 |
208 | if levelArea.groundType ~= nil then |
209 | layer = g_groundTypeManager:getTerrainLayerByType(levelArea.groundType) |
210 | end |
211 | self:addDeformationArea(deformationObject, levelArea, layer, true) |
212 | end |
213 | |
214 | if spec.smoothingGroundType ~= nil then |
215 | deformationObject:setOutsideAreaBrush(g_groundTypeManager:getTerrainLayerByType(spec.smoothingGroundType)) |
216 | end |
217 | |
218 | deformationObject:setOutsideAreaConstraints(spec.maxSmoothDistance, spec.maxSlope, spec.maxEdgeAngle) |
219 | |
220 | deformationObject:setBlockedAreaMaxDisplacement(0.1) |
221 | deformationObject:setDynamicObjectCollisionMask(CollisionMask.LEVELING) |
222 | deformationObject:setDynamicObjectMaxDisplacement(0.3) |
223 | table.insert(deformationObjects, deformationObject) |
224 | end |
225 | end |
226 | |
227 | if not forBlockingOnly then |
228 | if #spec.paintAreas > 0 then |
229 | local paintingObject = TerrainDeformation.new(terrainRootNode) |
230 | for _, paintArea in pairs(spec.paintAreas) do |
231 | local layer = -1 |
232 | if paintArea.groundType ~= nil then |
233 | layer = g_groundTypeManager:getTerrainLayerByType(paintArea.groundType) |
234 | end |
235 | self:addDeformationArea(paintingObject, paintArea, layer, true) |
236 | end |
237 | paintingObject:enablePaintingMode() |
238 | table.insert(deformationObjects, paintingObject) |
239 | end |
240 | end |
241 | |
242 | return deformationObjects |
243 | end |
129 | function PlaceableLeveling:loadLevelArea(xmlFile, key, area) |
130 | local start = xmlFile:getValue(key .. "#startNode", nil, self.components, self.i3dMappings) |
131 | if start == nil then |
132 | Logging.xmlWarning(xmlFile, "Leveling area start node not defined for '%s'", key) |
133 | return false |
134 | end |
135 | |
136 | local width = xmlFile:getValue(key .. "#widthNode", nil, self.components, self.i3dMappings) |
137 | if width == nil then |
138 | Logging.xmlWarning(xmlFile, "Leveling area width node not defined for '%s'", key) |
139 | return false |
140 | end |
141 | |
142 | local height = xmlFile:getValue(key .. "#heightNode", nil, self.components, self.i3dMappings) |
143 | if height == nil then |
144 | Logging.xmlWarning(xmlFile, "Leveling area height node not defined for '%s'", key) |
145 | return false |
146 | end |
147 | |
148 | area.start = start |
149 | area.width = width |
150 | area.height = height |
151 | area.groundType = xmlFile:getValue(key .. "#groundType") |
152 | |
153 | return true |
154 | end |
158 | function PlaceableLeveling:loadPaintArea(xmlFile, key, area) |
159 | local start = xmlFile:getValue(key .. "#startNode", nil, self.components, self.i3dMappings) |
160 | if start == nil then |
161 | Logging.xmlWarning(xmlFile, "Paint area start node not defined for '%s'", key) |
162 | return false |
163 | end |
164 | |
165 | local width = xmlFile:getValue(key .. "#widthNode", nil, self.components, self.i3dMappings) |
166 | if width == nil then |
167 | Logging.xmlWarning(xmlFile, "Paint area width node not defined for '%s'", key) |
168 | return false |
169 | end |
170 | |
171 | local height = xmlFile:getValue(key .. "#heightNode", nil, self.components, self.i3dMappings) |
172 | if height == nil then |
173 | Logging.xmlWarning(xmlFile, "Paint area height node not defined for '%s'", key) |
174 | return false |
175 | end |
176 | |
177 | area.start = start |
178 | area.width = width |
179 | area.height = height |
180 | area.groundType = xmlFile:getValue(key .. "#groundType") |
181 | |
182 | return true |
183 | end |
102 | function PlaceableLeveling:onDelete() |
103 | local spec = self.spec_leveling |
104 | if spec.writtenBlockedAreas then |
105 | -- unblock areas in the blocked area map |
106 | local deformationObjects = self:getDeformationObjects(g_currentMission.terrainRootNode, true, false) |
107 | for _, deformationObject in ipairs(deformationObjects) do |
108 | deformationObject:unblockAreas() |
109 | deformationObject:delete() |
110 | end |
111 | end |
112 | end |
64 | function PlaceableLeveling:onLoad(savegame) |
65 | local spec = self.spec_leveling |
66 | local xmlFile = self.xmlFile |
67 | |
68 | spec.writtenBlockedAreas = false |
69 | |
70 | -- load leveling properties |
71 | spec.requiresLeveling = xmlFile:getValue("placeable.leveling#requireLeveling", false) |
72 | spec.maxSmoothDistance = xmlFile:getValue("placeable.leveling#maxSmoothDistance", 3) |
73 | spec.maxSlope = xmlFile:getValue("placeable.leveling#maxSlope", 45) |
74 | spec.maxEdgeAngle = xmlFile:getValue("placeable.leveling#maxEdgeAngle", 45) |
75 | spec.smoothingGroundType = xmlFile:getValue("placeable.leveling#smoothingGroundType") |
76 | |
77 | if not self.xmlFile:hasProperty("placeable.leveling") then |
78 | Logging.xmlWarning(self.xmlFile, "Missing leveling areas") |
79 | end |
80 | |
81 | spec.levelAreas = {} |
82 | xmlFile:iterate("placeable.leveling.levelAreas.levelArea", function(_, key) |
83 | local levelArea = {} |
84 | if self:loadLevelArea(xmlFile, key, levelArea) then |
85 | table.insert(spec.levelAreas, levelArea) |
86 | end |
87 | end) |
88 | |
89 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "placeable.leveling.rampAreas.rampArea", "placeable.leveling.levelAreas.levelArea") -- FS19 to FS22 |
90 | |
91 | spec.paintAreas = {} |
92 | xmlFile:iterate("placeable.leveling.paintAreas.paintArea", function(_, key) |
93 | local paintArea = {} |
94 | if self:loadPaintArea(xmlFile, key, paintArea) then |
95 | table.insert(spec.paintAreas, paintArea) |
96 | end |
97 | end) |
98 | end |
116 | function PlaceableLeveling:onPreFinalizePlacement() |
117 | local spec = self.spec_leveling |
118 | -- block areas in the blocked area map to prevent placeable overlaps |
119 | local deformationObjects = self:getDeformationObjects(g_currentMission.terrainRootNode, true, false) |
120 | for _, deformationObject in ipairs(deformationObjects) do |
121 | deformationObject:blockAreas() |
122 | deformationObject:delete() |
123 | end |
124 | spec.writtenBlockedAreas = true |
125 | end |
24 | function PlaceableLeveling.registerFunctions(placeableType) |
25 | SpecializationUtil.registerFunction(placeableType, "loadLevelArea", PlaceableLeveling.loadLevelArea) |
26 | SpecializationUtil.registerFunction(placeableType, "loadPaintArea", PlaceableLeveling.loadPaintArea) |
27 | SpecializationUtil.registerFunction(placeableType, "addDeformationArea", PlaceableLeveling.addDeformationArea) |
28 | SpecializationUtil.registerFunction(placeableType, "applyDeformation", PlaceableLeveling.applyDeformation) |
29 | SpecializationUtil.registerFunction(placeableType, "getDeformationObjects", PlaceableLeveling.getDeformationObjects) |
30 | SpecializationUtil.registerFunction(placeableType, "getRequiresLeveling", PlaceableLeveling.getRequiresLeveling) |
31 | end |
43 | function PlaceableLeveling.registerXMLPaths(schema, basePath) |
44 | schema:setXMLSpecializationType("Leveling") |
45 | schema:register(XMLValueType.BOOL, basePath .. ".leveling#requireLeveling", "If true, the ground around the placeable is leveled and all other leveling properties are used", false) |
46 | schema:register(XMLValueType.FLOAT, basePath .. ".leveling#maxSmoothDistance", "Radius around leveling areas where terrain will be smoothed towards the placeable", 3) |
47 | schema:register(XMLValueType.ANGLE, basePath .. ".leveling#maxSlope", "Maximum slope of terrain created by outside smoothing expressed as an angle in degrees", 45) |
48 | schema:register(XMLValueType.ANGLE, basePath .. ".leveling#maxEdgeAngle", "Maximum angle between polygons in smoothed areas expressed as an angle in degrees", 45) |
49 | schema:register(XMLValueType.STRING, basePath .. ".leveling#smoothingGroundType", "Ground type used to paint the smoothed ground from leveling areas up to the radius of 'maxSmoothDistance' (one of the ground types defined in groundTypes.xml)") |
50 | schema:register(XMLValueType.NODE_INDEX, basePath .. ".leveling.levelAreas.levelArea(?)#startNode", "Start node") |
51 | schema:register(XMLValueType.NODE_INDEX, basePath .. ".leveling.levelAreas.levelArea(?)#widthNode", "Width node") |
52 | schema:register(XMLValueType.NODE_INDEX, basePath .. ".leveling.levelAreas.levelArea(?)#heightNode", "Height node") |
53 | schema:register(XMLValueType.STRING, basePath .. ".leveling.levelAreas.levelArea(?)#groundType", "Ground type name (one of the ground types defined in groundTypes.xml)") |
54 | schema:register(XMLValueType.NODE_INDEX, basePath .. ".leveling.paintAreas.paintArea(?)#startNode", "Start node") |
55 | schema:register(XMLValueType.NODE_INDEX, basePath .. ".leveling.paintAreas.paintArea(?)#widthNode", "Width node") |
56 | schema:register(XMLValueType.NODE_INDEX, basePath .. ".leveling.paintAreas.paintArea(?)#heightNode", "Height node") |
57 | schema:register(XMLValueType.STRING, basePath .. ".leveling.paintAreas.paintArea(?)#groundType", "Ground type name (one of the ground types defined in groundTypes.xml)") |
58 | schema:setXMLSpecializationType() |
59 | end |