meta data for this page
Vehicles/VehicleScripts/SnowGroomer.lua
SnowGroomer is a vehicle script that is responsible for the control of the groomer.
The lua script is based on the C# class SnowGroomerIK
, which was specifically designed to solve 4-dimensional inverse kinematics problems. This leads to the best possible visual results at almost no performance impact.
However, the script's configuration requires some parameters that have to be provided in the dataTable
.
28 SnowGroomer = SnowGroomer or {}; 29 SnowGroomer.directionSmoothRadius = 0.8;
SnowGroomer:load(dataTable)
First, we set up the groomer-specific functions.
Afterwards, we read in the configuration data from the dataTable
.
Some remarks:
axis1
toaxis4
are the indexes of the objects that are rotated for each axis. We need 4 degrees of freedom, because the groomer shall be allowed to rotate freely within some limits (i.e. three degrees of freedom in rotation), but it should also stay parallel to the ground (i.e. it needs another rotation axis to allow for a translation movement).- axis 1 rotates around Y (left/right, required for trail)
- axis 2 rotates around X (up/down)
- axis 3 again rotates around X (required for the 4th degree of freedom)
- axis 4 rotates around Z (tilt)
v.lifted.axis1
tov.lifted.axis4
specify the rotation targets if the groomer is lifted.- If the groomer is lowered, the individual axis limits (
axis1Limit
,axis2MinLimit
,axis2MaxLimit
etc.) are activated and the groomer can move freely between the limits. - The script supports flaps and also a rotating milling shaft
- As soon as the groomer is lowered, the milling shaft starts rotating, and the
snowGroomerPrepAreas
are activated. Only then, the slope can actually be groomed.
45 function SnowGroomer:load(dataTable) 46 self.updateSnowGroomerLimits = VehicleManager:newFunction("updateSnowGroomerLimits"); 47 self.setIsSnowGroomerIsLowered = VehicleManager:newFunction("setIsSnowGroomerIsLowered"); 48 self.setIsSnowGroomerIsLocked = VehicleManager:newFunction("setIsSnowGroomerIsLocked"); 49 self.setSnowGroomerFlaps = VehicleManager:newFunction("setSnowGroomerFlaps"); 50 self.setSnowGroomerRotation = VehicleManager:newFunction("setSnowGroomerRotation"); 51 52 53 if dataTable.snowGroomer ~= nil then 54 local v = dataTable.snowGroomer; 55 local axis1 = getChild(self.id, v.axis1 or ""); 56 local axis2 = getChild(self.id, v.axis2 or ""); 57 local axis3 = getChild(self.id, v.axis3 or ""); 58 local axis4 = getChild(self.id, v.axis4 or ""); 59 60 self.useRectangularPrepAreas = v.useRectangularPrepAreas or false; 61 if not self.useRectangularPrepAreas then 62 print("Warning: Your vehicle '" .. tostring(self.vehicleType) .. "' is still not using the rectangularPrepAreas. Please add 'useRectangularPrepAreas' to your groomer and implement the new areas accordingly to avoid low-FPS issues."); 63 end; 64 65 local raycastOrigin = getChild(self.id, v.raycastOrigin or v.groomerCenter or ""); 66 if raycastOrigin == 0 then 67 print("Error: You are missing the 'raycastOrigin' in your groomer in vehicle type '" .. tostring(self.vehicleType) .. "'. Please add a raycastOrigin in the dataTable."); 68 end; 69 70 local raycastOrigin_left = ifelse(v.raycastOrigin_left ~= nil, getChild(self.id, v.raycastOrigin_left), 0); 71 local raycastOrigin_right = ifelse(v.raycastOrigin_right ~= nil, getChild(self.id, v.raycastOrigin_right), 0); 72 73 -- if raycastOrigin_left == 0 or raycastOrigin_right == 0 then 74 -- print("Note: Vehicle type '" .. tostring(self.vehicleType) .. "' does not have a 'raycastOrigin_left' and 'raycastOrigin_right' defined.") 75 -- end; 76 77 self.snowGroomerIK = SnowGroomerIK.new(self.id, axis1, axis2, axis3, axis4, raycastOrigin, raycastOrigin_left, raycastOrigin_right); 78 SnowGroomerIK.setupGeometry(self.snowGroomerIK, 79 v.axis23Length or 1, 80 v.groundToAxis4Y or 0.5, 81 v.groundToAxis4Z or 0.5, 82 v.axis4To3Y or 0, 83 v.axis4To3Z or 0, 84 v.groomerMoveLimit or 0 85 ); 86 87 self.snowGroomerLifted = { 88 axis1 = v.lifted.axis1 or 0, 89 axis2 = v.lifted.axis2 or 45, 90 axis3 = v.lifted.axis3 or -45, 91 axis4 = v.lifted.axis4 or 0, 92 }; 93 self.snowGroomerLowered = { 94 axis1Limit = v.lowered.axis1Limit or 20, 95 axis2MinLimit = v.lowered.axis2MinLimit or -45, 96 axis2MaxLimit = v.lowered.axis2MaxLimit or 45, 97 axis3MinLimit = v.lowered.axis3MinLimit or -45, 98 axis3MaxLimit = v.lowered.axis3MaxLimit or 45, 99 axis4MinLimit = v.lowered.axis4MinLimit or -10, 100 axis4MaxLimit = v.lowered.axis4MaxLimit or 10, 101 }; 102 self.snowGroomerTimeToLower = math.max(v.timeToLower or 2, 0.1); 103 self.snowGroomerTimeToLift = math.max(v.timeToLift or 2, 0.1); 104 self.snowGroomerTimeToLock = math.max(v.timeToLock or 2, 0.1); 105 106 if v.leftFlap ~= nil then 107 self.snowGroomerLeftFlap = getChild(self.id, v.leftFlap or ""); 108 self.leftFlapPosition = 0; 109 self.leftFlapTarget = 1; 110 self.leftFlapDuration = Animation.getLength(self.snowGroomerLeftFlap); 111 Animation.stop(self.snowGroomerLeftFlap); 112 Animation.sampleTime(self.snowGroomerLeftFlap, 0); 113 end; 114 if v.rightFlap ~= nil then 115 self.snowGroomerRightFlap = getChild(self.id, v.rightFlap or ""); 116 self.rightFlapPosition = 0; 117 self.rightFlapTarget = 1; 118 self.rightFlapDuration = Animation.getLength(self.snowGroomerRightFlap); 119 Animation.stop(self.snowGroomerRightFlap); 120 Animation.sampleTime(self.snowGroomerRightFlap, 0); 121 end; 122 123 self.enableTurnGroomer = getNoNil(v.isTurnable, false); 124 125 if v.turningPointSnowcat ~= nil then 126 self.turningPointSnowcat = getChild(self.id, v.turningPointSnowcat); 127 end; 128 if v.turningPointGroomer ~= nil then 129 self.turningPointGroomer = getChild(self.id, v.turningPointGroomer); 130 end; 131 132 self.groomerRotation = 0; 133 self.maxTurnAngle = v.maxTurnAngle or 15; 134 135 if v.rotatingParts ~= nil then 136 self.groomerRotatingParts = {}; 137 138 for k2, v2 in pairs(v.rotatingParts) do 139 table.insert(self.groomerRotatingParts, { 140 id = getChild(self.id, v2.index), 141 axis = v2.axis or 1, 142 speed = (v2.speed or 300)/60 * 360, -- rpm (here converted to degrees per second) 143 }); 144 end; 145 end; 146 147 -- preparation areas 148 self.snowGroomerPrepAreas = {}; 149 if self.useRectangularPrepAreas then 150 self.snowGroomerPrepAreasLastPositions = {}; 151 152 if v.preparationAreas ~= nil then 153 for k, n in pairs(v.preparationAreas) do 154 if n.backLeft == nil then 155 print("Error: You set 'useRectangularPrepAreas' in your groomer in vehicle type '" .. tostring(self.vehicleType) .. "' to true but it seems you are still using the triangle preparation areas."); 156 end; 157 local area = { 158 backLeft = getChild(self.id, n.backLeft or ""), 159 backRight = getChild(self.id, n.backRight or ""), 160 frontLeft = getChild(self.id, n.frontLeft or ""), 161 frontRight = getChild(self.id, n.frontRight or ""), 162 smoothRadius = n[5] or SnowGroomer.directionSmoothRadius, 163 quality = 100, -- 100% quality if driving straight forward 164 --[[slipQualityLossCoeff= 45, 165 slipTolerance = 1, 166 167 lastPositionCenter = Vector3.zero:clone(), 168 }; 169 local lastPositions = { 170 lastBackLeft = VectorUtils.getWorldPosition(area.backLeft), 171 lastBackRight = VectorUtils.getWorldPosition(area.backRight), 172 lastFrontLeft = VectorUtils.getWorldPosition(area.frontLeft), 173 lastFrontRight = VectorUtils.getWorldPosition(area.frontRight), 174 } 175 area.slipReferenceId = area.backLeft; 176 177 table.insert(self.snowGroomerPrepAreas, area); 178 table.insert(self.snowGroomerPrepAreasLastPositions, lastPositions); 179 end; 180 end; 181 elseif v.preparationAreas ~= nil then 182 for k, n in pairs(v.preparationAreas) do 183 if n.backLeft ~= nil then 184 print("Error: You set 'useRectangularPrepAreas' in your groomer in vehicle type '" .. tostring(self.vehicleType) .. "' to false but it seems you are using the rectangular preparation areas."); 185 end; 186 local area = { 187 id1 = getChild(self.id, n[1] or ""), 188 id2 = getChild(self.id, n[2] or ""), 189 id3 = getChild(self.id, n[3] or ""), 190 smoothRadius = n[4] or SnowGroomer.directionSmoothRadius, 191 quality = 100, -- 100% quality if driving straight forward 192 --[[slipQualityLossCoeff= 45, 193 slipTolerance = 1, 194 195 lastPositionCenter = Vector3.zero:clone(), 196 }; 197 area.slipReferenceId = area.id1; 198 199 table.insert(self.snowGroomerPrepAreas, area); 200 end; 201 end; 202 203 -- don't lower by default 204 self.snowGroomerIsLowered = false; 205 self.snowGroomerIsLocked = false; 206 self.snowGroomerPos = 0; -- position to interpolate between limits and lifted 207 self.snowGroomerLockedPos = 0; 208 self.shouldStayLocked = false; 209 210 self:updateSnowGroomerLimits(); 211 end; 212 end;
SnowGroomer:saveToTable(tbl)
As always, we need to save some variables in our savegame.
216 function SnowGroomer:saveToTable(tbl) 217 if tbl == nil then return end; 218 if self.snowGroomerIK == nil then return end; 219 220 tbl.snowGroomerIsLowered = self.snowGroomerIsLowered; 221 tbl.snowGroomerIsLocked = self.snowGroomerIsLocked; 222 tbl.snowGroomerPos = self.snowGroomerPos; 223 tbl.snowGroomerLockedPos = self.snowGroomerLockedPos; 224 225 tbl.leftFlapTarget = self.leftFlapTarget; 226 tbl.leftFlapPosition = self.leftFlapPosition; 227 tbl.rightFlapTarget = self.rightFlapTarget; 228 tbl.rightFlapPosition = self.rightFlapPosition; 229 end;
SnowGroomer:loadFromTable(tbl)
Restore the variables from the savegame.
233 function SnowGroomer:loadFromTable(tbl) 234 if tbl == nil then return end; 235 if self.snowGroomerIK == nil then return end; 236 237 self.snowGroomerIsLowered = getNoNil(tbl.snowGroomerIsLowered, self.snowGroomerIsLowered); 238 self.snowGroomerIsLocked = getNoNil(tbl.snowGroomerIsLocked, self.snowGroomerIsLocked); 239 self.snowGroomerPos = getNoNil(tbl.snowGroomerPos, self.snowGroomerPos); 240 self.snowGroomerLockedPos = getNoNil(tbl.snowGroomerLockedPos, self.snowGroomerLockedPos); 241 242 self.leftFlapTarget = getNoNil(tbl.leftFlapTarget, self.leftFlapTarget); 243 self.leftFlapPosition = getNoNil(tbl.leftFlapPosition, self.leftFlapPosition); 244 self.rightFlapTarget = getNoNil(tbl.rightFlapTarget, self.rightFlapTarget); 245 self.rightFlapPosition = getNoNil(tbl.rightFlapPosition, self.rightFlapPosition); 246 247 -- always update real flap position in update 248 if self.leftFlapTarget ~= nil then 249 self.leftFlapPosition = 0.5; 250 end; 251 if self.rightFlapTarget ~= nil then 252 self.rightFlapPosition = 0.5; 253 end; 254 255 SnowGroomer.updateGroomerActive(self, 0, true); 256 end;
SnowGroomer:update(dt)
Update the controls each frame (i.e. check whether the player wants to lower/lift the groomer, lock the trail or move some flaps).
The actual groomer update is done in SnowGroomer:updateGroomerActive()
.
261 function SnowGroomer:update(dt) 262 if not self.isActive then return end; 263 if self.snowGroomerIK == nil then return end; 264 265 if self:getIsInputActive() and not self.hydraulicLiftGroomer then 266 if InputMapper:getKeyDown(InputMapper.VehicleSnowcat_LowerGroomer) then 267 self:setIsSnowGroomerIsLowered(not self.snowGroomerIsLowered); 268 end; 269 g_GUI:addKeyHint(InputMapper.VehicleSnowcat_LowerGroomer, l10n.get(self.snowGroomerIsLowered and "Input_VehicleSnowcat_LowerGroomer1" or "Input_VehicleSnowcat_LowerGroomer0")); 270 271 if self.snowGroomerIsLowered then 272 -- only allow to lock/unlock if snow groomer is lowered 273 if InputMapper:getKeyDown(InputMapper.VehicleSnowcat_LockTrail) then 274 self:setIsSnowGroomerIsLocked(not self.snowGroomerIsLocked); 275 end; 276 if not self.snowGroomerIsLocked then 277 if InputMapper:getKeyDown(InputMapper.VehicleSnowcat_TurnGroomerLeft) or InputMapper:getKeyDown(InputMapper.VehicleSnowcat_TurnGroomerRight) then 278 self:setIsSnowGroomerIsLocked(not self.snowGroomerIsLocked); 279 end; 280 end; 281 282 -- left/right flap controls 283 if InputMapper:getKeyDown(InputMapper.VehicleSnowcat_LeftFlap) then 284 self:setSnowGroomerFlaps(self.leftFlapTarget == 0, nil); 285 end; 286 if InputMapper:getKeyDown(InputMapper.VehicleSnowcat_RightFlap) then 287 self:setSnowGroomerFlaps(nil, self.rightFlapTarget == 0); 288 end; 289 290 g_GUI:addKeyHint(InputMapper.VehicleSnowcat_LockTrail, l10n.get(self.snowGroomerIsLocked and "Input_VehicleSnowcat_LockTrail1" or "Input_VehicleSnowcat_LockTrail0")); 291 292 if self.enableTurnGroomer then 293 g_GUI:addKeyHint(InputMapper.VehicleSnowcat_TurnGroomerLeft, l10n.get("Input_VehicleSnowcat_TurnGroomerLeft1")); 294 g_GUI:addKeyHint(InputMapper.VehicleSnowcat_TurnGroomerRight, l10n.get("Input_VehicleSnowcat_TurnGroomerRight1")); 295 end; 296 297 if self.snowGroomerIsLocked then 298 if InputMapper:getKey(InputMapper.VehicleSnowcat_TurnGroomerLeft) then 299 if self.groomerRotation > -self.maxTurnAngle then 300 self.groomerRotation = self.groomerRotation - 0.5; 301 self:setSnowGroomerRotation(self.groomerRotation); 302 end; 303 end; 304 --if InputMapper:getKeyUp(InputMapper.VehicleSnowcat_TurnGroomerLeft) then 305 -- if self.groomerRotation > -self.maxTurnAngle-2 then 306 -- self.groomerRotation = self.groomerRotation - 1; 307 -- self:setSnowGroomerRotation(self.groomerRotation); 308 -- end; 309 --end; 310 311 if InputMapper:getKey(InputMapper.VehicleSnowcat_TurnGroomerRight) then 312 if self.groomerRotation < self.maxTurnAngle then 313 self.groomerRotation = self.groomerRotation + 0.5; 314 self:setSnowGroomerRotation(self.groomerRotation); 315 end; 316 end; 317 --if InputMapper:getKeyUp(InputMapper.VehicleSnowcat_TurnGroomerRight) then 318 -- if self.groomerRotation < self.maxTurnAngle+2 then 319 -- self.groomerRotation = self.groomerRotation + 1; 320 -- self:setSnowGroomerRotation(self.groomerRotation); 321 -- end; 322 --end; 323 324 end; 325 end; 326 end; 327 328 SnowGroomer.updateGroomerActive(self, dt); 329 end;
SnowGroomer:updateGroomerActive(dt, forceUpdate)
This is called in every frame if the groomer is active. It determines current animation positions and grooms the preparation areas.
333 function SnowGroomer:updateGroomerActive(dt, forceUpdate) 334 -- update groomer position 335 local target = self.snowGroomerIsLowered and not self.hydraulicLiftGroomer and 1 or 0; 336 local lockTarget = self.snowGroomerIsLocked and 1 or 0; 337 local leftTarget = (target < 1 or self.snowGroomerPos < 0.5) and 0 or self.leftFlapTarget; 338 local rightTarget = (target < 1 or self.snowGroomerPos < 0.5) and 0 or self.rightFlapTarget; 339 340 local update = false; 341 if math.abs(self.snowGroomerPos - target) > 1e-3 or forceUpdate then 342 local duration = self.snowGroomerIsLowered and self.snowGroomerTimeToLower or self.snowGroomerTimeToLift; 343 self.snowGroomerPos = Utils.moveTowards(self.snowGroomerPos, target, dt/duration); 344 update = true; 345 end; 346 347 if math.abs(self.snowGroomerLockedPos - lockTarget) > 1e-3 or forceUpdate then 348 self.snowGroomerLockedPos = Utils.moveTowards(self.snowGroomerLockedPos, lockTarget, dt/self.snowGroomerTimeToLock); 349 update = true; 350 end; 351 352 if self.snowGroomerIsLocked and self.groomerRotation ~= nil and self.turningPointGroomer ~= nil and self.turningPointSnowcat ~= nil then 353 setRotationY(self.turningPointGroomer, self.groomerRotation); 354 setRotationY(self.turningPointSnowcat, -self.groomerRotation); 355 356 elseif not self.snowGroomerIsLocked and self.groomerRotation ~= nil and self.turningPointGroomer ~= nil and self.turningPointSnowcat ~= nil then 357 if math.abs(self.groomerRotation) > 2 then 358 if self.groomerRotation > 0 then 359 self.groomerRotation = self.groomerRotation - 0.5; 360 else 361 self.groomerRotation = self.groomerRotation + 0.5; 362 end; 363 end; 364 365 setRotationY(self.turningPointGroomer, self.groomerRotation); 366 setRotationY(self.turningPointSnowcat, -self.groomerRotation); 367 end; 368 369 if not self.snowGroomerIsLowered then 370 if self.groomerRotation ~= nil and self.turningPointGroomer ~= nil and self.turningPointSnowcat ~= nil then 371 if math.abs(self.groomerRotation) > 2 then 372 if self.groomerRotation > 0 then 373 self.groomerRotation = self.groomerRotation - 0.5; 374 else 375 self.groomerRotation = self.groomerRotation + 0.5; 376 end; 377 end; 378 setRotationY(self.turningPointGroomer, self.groomerRotation); 379 setRotationY(self.turningPointSnowcat, -self.groomerRotation); 380 end; 381 end; 382 383 if self.snowGroomerLeftFlap ~= nil and (math.abs(self.leftFlapPosition - leftTarget) > 1e-3 or forceUpdate) then 384 self.leftFlapPosition = Utils.moveTowards(self.leftFlapPosition, leftTarget, dt/self.leftFlapDuration); 385 Animation.sampleTime(self.snowGroomerLeftFlap, self.leftFlapPosition * self.leftFlapDuration); 386 end; 387 if self.snowGroomerRightFlap ~= nil and (math.abs(self.rightFlapPosition - rightTarget) > 1e-3 or forceUpdate) then 388 self.rightFlapPosition = Utils.moveTowards(self.rightFlapPosition, rightTarget, dt/self.rightFlapDuration); 389 Animation.sampleTime(self.snowGroomerRightFlap, self.rightFlapPosition * self.rightFlapDuration); 390 end; 391 392 if update then 393 self:updateSnowGroomerLimits(); 394 end; 395 396 -- prepare 397 local maxBridgingDistanceSquare = 9; 398 if g_isMaster and self.snowGroomerPos > 0.5 then 399 local totalPosCenter; 400 if self.useRectangularPrepAreas then 401 for k, v in pairs(self.snowGroomerPrepAreas) do 402 local quality = v.quality; 403 404 local bl = VectorUtils.getWorldPosition(v.backLeft); 405 local br = VectorUtils.getWorldPosition(v.backRight); 406 local fl = VectorUtils.getWorldPosition(v.frontLeft); 407 local fr = VectorUtils.getWorldPosition(v.frontRight); 408 409 local lastBl = self.snowGroomerPrepAreasLastPositions[k].lastBackLeft; 410 local lastBr = self.snowGroomerPrepAreasLastPositions[k].lastBackRight; 411 412 if Vector3.sqrMagnitude(lastBl - bl) < maxBridgingDistanceSquare then 413 local posCenter = (lastBl + lastBr + fl):multiply(0.33333); 414 if totalPosCenter == nil then 415 totalPosCenter = posCenter; 416 end; 417 418 -- groom every 7 cm 419 local delta = v.lastPositionCenter - posCenter; 420 local maxSqrMag = 0.07*0.07; 421 422 if delta:sqrMagnitude() > maxSqrMag then 423 424 local forward; 425 426 if not Vector3.isEqual(v.lastPositionCenter, Vector3.zero) then 427 -- angle depends on direction of last movement 428 forward = delta; 429 else 430 forward = VectorUtils.transformDirection(v.backLeft, Vector3.forward); 431 end; 432 433 v.lastPositionCenter = posCenter; 434 435 local angle = (math.deg(math.atan2(-forward.z, forward.x)) - 90) % 360; 436 SnowSystem.groomTriangleByPosition(lastBl, lastBr, fl, angle, v.smoothRadius, quality); 437 SnowSystem.groomTriangleByPosition(fl, fr, lastBr, angle, v.smoothRadius, quality); 438 439 -- send an event in MP 440 if g_isServer then 441 EventGroomTriangle:send(quality, v.smoothRadius, angle, lastBl, lastBr, fl); 442 EventGroomTriangle:send(quality, v.smoothRadius, angle, fl, fr, lastBr); 443 end; 444 445 end; 446 end; 447 448 self.snowGroomerPrepAreasLastPositions[k].lastBackLeft = bl; 449 self.snowGroomerPrepAreasLastPositions[k].lastBackRight = br; 450 self.snowGroomerPrepAreasLastPositions[k].lastFrontLeft = fl; 451 self.snowGroomerPrepAreasLastPositions[k].lastFrontRight = fr; 452 end; 453 else 454 -- legacy snow groomer areas from Season 1 (with low-FPS issues) 455 for k, v in pairs(self.snowGroomerPrepAreas) do 456 local quality = v.quality; 457 458 -- slip is currently unused due to irritating rendering results 459 --[[if v.slipReferenceId ~= nil then 460 -- coordinates of last x/y/z position relative to current position 461 local lx,ly,lz = Rigidbody.getVelocityAtPoint(self.mainId, getWorldPosition(v.slipReferenceId)); 462 lx, ly, lz = Utils.inverseTransformDirection(self.groomerRaycastOrigin or v.slipReferenceId, lx,ly,lz); 463 local slip = math.abs(lx) - math.abs(lz); 464 --printf("slip %.2f %.2f %.2f => %.2f", lx,ly,lz, slip); 465 466 if slip > 0.7 then 467 quality = clamp(quality - 12 * slip, 0, 100); 468 printf("slip %.2f quality %.2f", slip, quality); 469 end; 470 471 --v.lastPosition = VectorUtils.getWorldPosition(v.slipReferenceId); 472 end; 473 474 local pos1 = VectorUtils.getWorldPosition(v.id1); 475 local pos2 = VectorUtils.getWorldPosition(v.id2); 476 local pos3 = VectorUtils.getWorldPosition(v.id3); 477 local posCenter = (pos1 + pos2 + pos3):multiply(0.3333); 478 if totalPosCenter == nil then 479 totalPosCenter = posCenter; 480 end; 481 482 -- groom every 7 cm 483 local delta = v.lastPositionCenter - posCenter; 484 local maxSqrMag = 0.07*0.07; 485 486 if delta:sqrMagnitude() > maxSqrMag then 487 local forward; 488 489 if not Vector3.isEqual(v.lastPositionCenter, Vector3.zero) then 490 -- angle depends on direction of last movement 491 forward = delta; 492 else 493 forward = VectorUtils.transformDirection(v.id1, Vector3.forward); 494 end; 495 496 v.lastPositionCenter = posCenter; 497 498 local angle = (math.deg(math.atan2(-forward.z, forward.x)) - 90) % 360; 499 SnowSystem.groomTriangleByPosition(pos1, pos2, pos3, angle, v.smoothRadius, quality); 500 501 -- send an event in MP 502 if g_isServer then 503 EventGroomTriangle:send(quality, v.smoothRadius, angle, pos1, pos2, pos3); 504 end; 505 end; 506 end; 507 end; 508 509 -- check whether the slope has been bought (only on servers) 510 local slopeNotOwned = false; 511 local slope; 512 if g_scenario ~= nil and g_scenario.skiResort ~= nil then 513 local snowfall, hardness, unused2, layer = SnowSystem.getInfoAtPosition(VectorUtils.getWorldPosition(self.mainId)); 514 515 slopeNotOwned, slope = g_scenario.skiResort:getIsSlopeOwnedByLayer(layer); 516 end; 517 518 if self:getIsInputActive() and slopeNotOwned == false and BottomWarningHUD.showSlopeNotBoughtWarning() then 519 if slope ~= nil and self.bottomWarningHUD == nil then 520 self.bottomWarningHUD = g_GUI:showHUD(BottomWarningHUD, l10n.format("msg_slopeNotBought", slope.slope)); 521 end; 522 523 elseif self.bottomWarningHUD ~= nil then 524 local hud = self.bottomWarningHUD; 525 self.bottomWarningHUD = nil; 526 hud:close(); 527 end; 528 end; 529 530 -- animate rotating parts 531 if self.groomerRotatingParts ~= nil and self.snowGroomerPos > 0.9 and self.isMotorOn then 532 for k, rp in pairs(self.groomerRotatingParts) do 533 if rp.axis == 1 then 534 rotate(rp.id, rp.speed * dt, 0,0); 535 536 elseif rp.axis == 2 then 537 rotate(rp.id, 0, rp.speed * dt, 0); 538 539 else 540 rotate(rp.id, 0,0, rp.speed * dt); 541 end; 542 end; 543 end; 544 end;
SnowGroomer:setIsSnowGroomerIsLowered(isLowered, noEvent)
Lower/lift the groomer, depending on isLowered
(bool). noEvent
(bool) specifies whether the multiplayer event shall be suppressed.
547 function SnowGroomer:setIsSnowGroomerIsLowered(isLowered, noEvent) 548 self.snowGroomerIsLowered = isLowered or false; 549 550 if not noEvent then 551 EventSetSnowGroomerPosition:send(self, self.snowGroomerIsLowered); 552 end; 553 end;
SnowGroomer:setIsSnowGroomerIsLocked(isLocked, noEvent)
Lock/release the groomer, depending on isLocked
(bool). noEvent
(bool) specifies whether the multiplayer event shall be suppressed.
556 function SnowGroomer:setIsSnowGroomerIsLocked(isLocked, noEvent) 557 self.snowGroomerIsLocked = isLocked or false; 558 559 if not noEvent then 560 EventSetSnowGroomerLocked:send(self, self.snowGroomerIsLocked); 561 end; 562 end;
SnowGroomer:setSnowGroomerFlaps(left, right, noEvent)
Fold out the left
and the right
flap (both bool). true
means the flap shall be moved out. noEvent
(bool) specifies whether the multiplayer event shall be suppressed.
565 function SnowGroomer:setSnowGroomerFlaps(left, right, noEvent) 566 if left ~= nil then self.leftFlapTarget = left and 1 or 0; end; 567 if right ~= nil then self.rightFlapTarget = right and 1 or 0; end; 568 569 if not noEvent then 570 EventSetSnowGroomerFlaps:send(self, self.leftFlapTarget == 1, self.rightFlapTarget == 1); 571 end; 572 end;
SnowGroomer:setSnowGroomerRotation(rotation, noEvent)
Adjusts the groomer rotation (left/right) to the specified target value rotation
(number). noEvent
(bool) specifies whether the multiplayer event shall be suppressed.
575 function SnowGroomer:setSnowGroomerRotation(rotation, noEvent) 576 577 if rotation ~= nil then 578 self.groomerRotation = rotation; 579 end; 580 581 if not noEvent then 582 EventSetSnowGroomerRotation:send(self, rotation); 583 end; 584 end;
SnowGroomer:updateSnowGroomerLimits()
Passes on the axis target values and limits to the C# class SnowGroomerIK
, which will then solve the 4D inverse kinematics.
588 function SnowGroomer:updateSnowGroomerLimits() 589 if self.snowGroomerIK == nil then return end; 590 591 local weight = self.snowGroomerPos; 592 local axis1Weight = clamp01(weight - self.snowGroomerLockedPos); 593 594 SnowGroomerIK.setAxis1Limit(self.snowGroomerIK, lerp(self.snowGroomerLifted.axis1, self.snowGroomerLowered.axis1Limit, axis1Weight)); 595 SnowGroomerIK.setAxis2Limit(self.snowGroomerIK, 596 lerp(self.snowGroomerLifted.axis2, self.snowGroomerLowered.axis2MinLimit, weight), 597 lerp(self.snowGroomerLifted.axis2, self.snowGroomerLowered.axis2MaxLimit, weight) 598 ); 599 SnowGroomerIK.setAxis3Limit(self.snowGroomerIK, 600 lerp(self.snowGroomerLifted.axis3, self.snowGroomerLowered.axis3MinLimit, weight), 601 lerp(self.snowGroomerLifted.axis3, self.snowGroomerLowered.axis3MaxLimit, weight) 602 ); 603 SnowGroomerIK.setAxis4Limit(self.snowGroomerIK, 604 lerp(self.snowGroomerLifted.axis4, self.snowGroomerLowered.axis4MinLimit, weight), 605 lerp(self.snowGroomerLifted.axis4, self.snowGroomerLowered.axis4MaxLimit, weight) 606 ); 607 end;
SnowGroomer:onLeave(player, isLocalPlayer)
Disables the warning if a slope has not yet been bought whenever the player exits the vehicle.
611 function SnowGroomer:onLeave(player, isLocalPlayer) 612 if isLocalPlayer and self.bottomWarningHUD ~= nil then 613 local hud = self.bottomWarningHUD; 614 self.bottomWarningHUD = nil; 615 hud:close(); 616 end; 617 end;
SnowGroomer:writeResync()
Resynchronizes all variables when a new player joins the game. The data sent by writeResync
will be received by readResync
.
621 function SnowGroomer:writeResync() 622 streamWriteBool(self.snowGroomerIsLowered); 623 streamWriteBool(self.snowGroomerIsLocked); 624 if self.leftFlapTarget ~= nil or self.rightFlapTarget ~= nil then 625 streamWriteBool(self.leftFlapTarget == 0); 626 streamWriteBool(self.rightFlapTarget == 0); 627 end; 628 end;
SnowGroomer:readResync()
Resynchronizes all variables when a new player joins the game. The data sent by writeResync
will be received by readResync
.
632 function SnowGroomer:readResync() 633 local isLowered = streamReadBool(); 634 self:setIsSnowGroomerIsLowered(isLowered, true); 635 636 local isLocked = streamReadBool() 637 self:setIsSnowGroomerIsLocked(isLocked, true); 638 639 local leftFlap = streamReadBool(); 640 local rightFlap = streamReadBool(); 641 self:setSnowGroomerFlaps(leftFlap, rightFlap, true); 642 end;
Copyright
All contents of this page may be used for modding use for Winter Resort Simulator - Season 2 only. Any use exceeding this regulation is not permitted.
Copyright (C) HR Innoways, 2021. All Rights Reserved.