meta data for this page
Vehicles/VehicleScripts/SnowGroomer.lua
SnowGroomer is a vehicle script that is in use. It 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 51 if dataTable.snowGroomer ~= nil then 52 local v = dataTable.snowGroomer; 53 local axis1 = getChild(self.id, v.axis1 or ""); 54 local axis2 = getChild(self.id, v.axis2 or ""); 55 local axis3 = getChild(self.id, v.axis3 or ""); 56 local axis4 = getChild(self.id, v.axis4 or ""); 57 local raycastOrigin = getChild(self.id, v.raycastOrigin or v.groomerCenter or ""); 58 59 self.snowGroomerIK = SnowGroomerIK.new(self.id, axis1, axis2, axis3, axis4, raycastOrigin); 60 SnowGroomerIK.setupGeometry(self.snowGroomerIK, 61 v.axis23Length or 1, 62 v.groundToAxis4Y or 0.5, 63 v.groundToAxis4Z or 0.5, 64 v.axis4To3Y or 0, 65 v.axis4To3Z or 0 66 ); 67 68 self.snowGroomerLifted = { 69 axis1 = v.lifted.axis1 or 0, 70 axis2 = v.lifted.axis2 or 45, 71 axis3 = v.lifted.axis3 or -45, 72 axis4 = v.lifted.axis4 or 0, 73 }; 74 self.snowGroomerLowered = { 75 axis1Limit = v.lowered.axis1Limit or 20, 76 axis2MinLimit = v.lowered.axis2MinLimit or -45, 77 axis2MaxLimit = v.lowered.axis2MaxLimit or 45, 78 axis3MinLimit = v.lowered.axis3MinLimit or -45, 79 axis3MaxLimit = v.lowered.axis3MaxLimit or 45, 80 axis4MinLimit = v.lowered.axis4MinLimit or -10, 81 axis4MaxLimit = v.lowered.axis4MaxLimit or 10, 82 }; 83 self.snowGroomerTimeToLower = math.max(v.timeToLower or 2, 0.1); 84 self.snowGroomerTimeToLift = math.max(v.timeToLift or 2, 0.1); 85 self.snowGroomerTimeToLock = math.max(v.timeToLock or 2, 0.1); 86 87 if v.leftFlap ~= nil then 88 self.snowGroomerLeftFlap = getChild(self.id, v.leftFlap or ""); 89 Animation.stop(self.snowGroomerLeftFlap); 90 Animation.sampleTime(self.snowGroomerLeftFlap, 0); 91 self.leftFlapPosition = 0; 92 self.leftFlapTarget = 1; 93 self.leftFlapDuration = Animation.getLength(self.snowGroomerLeftFlap); 94 end; 95 if v.rightFlap ~= nil then 96 self.snowGroomerRightFlap = getChild(self.id, v.rightFlap or ""); 97 Animation.stop(self.snowGroomerRightFlap); 98 Animation.sampleTime(self.snowGroomerRightFlap, 0); 99 self.rightFlapPosition = 0; 100 self.rightFlapTarget = 1; 101 self.rightFlapDuration = Animation.getLength(self.snowGroomerRightFlap); 102 end; 103 104 if v.rotatingParts ~= nil then 105 self.groomerRotatingParts = {}; 106 107 for k, v in pairs(v.rotatingParts) do 108 table.insert(self.groomerRotatingParts, { 109 id = getChild(self.id, v.index), 110 axis = v.axis or 1, 111 speed = (v.speed or 300)/60 * 360, -- rpm (here converted to degrees per second) 112 }); 113 end; 114 end; 115 116 -- preparation areas 117 self.snowGroomerPrepAreas = {}; 118 if v.preparationAreas ~= nil then 119 for k, n in pairs(v.preparationAreas) do 120 table.insert(self.snowGroomerPrepAreas, { 121 getChild(self.id, n[1] or ""), 122 getChild(self.id, n[2] or ""), 123 getChild(self.id, n[3] or ""), 124 n[4] or SnowGroomer.directionSmoothRadius, 125 }); 126 end; 127 end; 128 129 -- don't lower by default 130 self.snowGroomerIsLowered = false; 131 self.snowGroomerIsLocked = false; 132 self.snowGroomerPos = 0; -- position to interpolate between limits and lifted 133 self.snowGroomerLockedPos = 0; 134 135 self:updateSnowGroomerLimits(); 136 end; 137 end;
SnowGroomer:saveToTable(tbl)
As always, we need to save some variables in our savegame.
141 function SnowGroomer:saveToTable(tbl) 142 if tbl == nil then return end; 143 if self.snowGroomerIK == nil then return end; 144 145 tbl.snowGroomerIsLowered = self.snowGroomerIsLowered; 146 tbl.snowGroomerIsLocked = self.snowGroomerIsLocked; 147 tbl.snowGroomerPos = self.snowGroomerPos; 148 tbl.snowGroomerLockedPos = self.snowGroomerLockedPos; 149 150 tbl.leftFlapTarget = self.leftFlapTarget; 151 tbl.leftFlapPosition = self.leftFlapPosition; 152 tbl.rightFlapTarget = self.rightFlapTarget; 153 tbl.rightFlapPosition = self.rightFlapPosition; 154 end;
SnowGroomer:loadFromTable(tbl)
Restore the variables from the savegame.
158 function SnowGroomer:loadFromTable(tbl) 159 if tbl == nil then return end; 160 if self.snowGroomerIK == nil then return end; 161 162 self.snowGroomerIsLowered = getNoNil(tbl.snowGroomerIsLowered, self.snowGroomerIsLowered); 163 self.snowGroomerIsLocked = getNoNil(tbl.snowGroomerIsLocked, self.snowGroomerIsLocked); 164 self.snowGroomerPos = getNoNil(tbl.snowGroomerPos, self.snowGroomerPos); 165 self.snowGroomerLockedPos = getNoNil(tbl.snowGroomerLockedPos, self.snowGroomerLockedPos); 166 167 self.leftFlapTarget = getNoNil(tbl.leftFlapTarget, self.leftFlapTarget); 168 self.leftFlapPosition = getNoNil(tbl.leftFlapPosition, self.leftFlapPosition); 169 self.rightFlapTarget = getNoNil(tbl.rightFlapTarget, self.rightFlapTarget); 170 self.rightFlapPosition = getNoNil(tbl.rightFlapPosition, self.rightFlapPosition); 171 172 -- always update real flap position in update 173 if self.leftFlapTarget ~= nil then 174 self.leftFlapPosition = 0.5; 175 end; 176 if self.rightFlapTarget ~= nil then 177 self.rightFlapPosition = 0.5; 178 end; 179 180 SnowGroomer.updateGroomerActive(self, 0, true); 181 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
.
186 function SnowGroomer:update(dt) 187 if not self.isActive then return end; 188 if self.snowGroomerIK == nil then return end; 189 190 if self:getIsInputActive() and not self.hydraulicLiftGroomer then 191 if InputMapper:getKeyDown(InputMapper.VehicleSnowcat_LowerGroomer) then 192 self:setIsSnowGroomerIsLowered(not self.snowGroomerIsLowered); 193 end; 194 g_GUI:addKeyHint(InputMapper.VehicleSnowcat_LowerGroomer, l10n.get(self.snowGroomerIsLowered and "Input_VehicleSnowcat_LowerGroomer1" or "Input_VehicleSnowcat_LowerGroomer0")); 195 196 if self.snowGroomerIsLowered then 197 -- only allow to lock/unlock if snow groomer is lowered 198 if InputMapper:getKeyDown(InputMapper.VehicleSnowcat_LockTrail) then 199 self:setIsSnowGroomerIsLocked(not self.snowGroomerIsLocked); 200 end; 201 if InputMapper:getKeyDown(InputMapper.VehicleSnowcat_LeftFlap) then 202 self:setSnowGroomerFlaps(self.leftFlapTarget == 0, nil); 203 end; 204 if InputMapper:getKeyDown(InputMapper.VehicleSnowcat_RightFlap) then 205 self:setSnowGroomerFlaps(nil, self.rightFlapTarget == 0); 206 end; 207 208 g_GUI:addKeyHint(InputMapper.VehicleSnowcat_LockTrail, l10n.get(self.snowGroomerIsLocked and "Input_VehicleSnowcat_LockTrail1" or "Input_VehicleSnowcat_LockTrail0")); 209 end; 210 end; 211 212 SnowGroomer.updateGroomerActive(self, dt); 213 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.
217 function SnowGroomer:updateGroomerActive(dt, forceUpdate) 218 -- update groomer position 219 local target = self.snowGroomerIsLowered and not self.hydraulicLiftGroomer and 1 or 0; 220 local lockTarget = self.snowGroomerIsLocked and 1 or 0; 221 local leftTarget = (target < 1 or self.snowGroomerPos < 0.5) and 0 or self.leftFlapTarget; 222 local rightTarget = (target < 1 or self.snowGroomerPos < 0.5) and 0 or self.rightFlapTarget; 223 224 local update = false; 225 if math.abs(self.snowGroomerPos - target) > 1e-3 or forceUpdate then 226 local duration = self.snowGroomerIsLowered and self.snowGroomerTimeToLower or self.snowGroomerTimeToLift; 227 self.snowGroomerPos = Utils.moveTowards(self.snowGroomerPos, target, dt/duration); 228 update = true; 229 end; 230 231 if math.abs(self.snowGroomerLockedPos - lockTarget) > 1e-3 or forceUpdate then 232 self.snowGroomerLockedPos = Utils.moveTowards(self.snowGroomerLockedPos, lockTarget, dt/self.snowGroomerTimeToLock); 233 update = true; 234 end; 235 236 if self.snowGroomerLeftFlap ~= nil and (math.abs(self.leftFlapPosition - leftTarget) > 1e-3 or forceUpdate) then 237 self.leftFlapPosition = Utils.moveTowards(self.leftFlapPosition, leftTarget, dt/self.leftFlapDuration); 238 Animation.sampleTime(self.snowGroomerLeftFlap, self.leftFlapPosition * self.leftFlapDuration); 239 end; 240 if self.snowGroomerRightFlap ~= nil and (math.abs(self.rightFlapPosition - rightTarget) > 1e-3 or forceUpdate) then 241 self.rightFlapPosition = Utils.moveTowards(self.rightFlapPosition, rightTarget, dt/self.rightFlapDuration); 242 Animation.sampleTime(self.snowGroomerRightFlap, self.rightFlapPosition * self.rightFlapDuration); 243 end; 244 245 if update then 246 self:updateSnowGroomerLimits(); 247 end; 248 249 -- prepare 250 if self.snowGroomerPos > 0.5 then 251 for k, v in pairs(self.snowGroomerPrepAreas) do 252 SnowSystem.groomTriangle(unpack(v)); 253 end; 254 end; 255 256 -- animate rotating parts 257 if self.groomerRotatingParts ~= nil and self.snowGroomerPos > 0.9 and self.isMotorOn then 258 for k, rp in pairs(self.groomerRotatingParts) do 259 if rp.axis == 1 then 260 rotate(rp.id, rp.speed * dt, 0,0); 261 262 elseif rp.axis == 2 then 263 rotate(rp.id, 0, rp.speed * dt, 0); 264 265 else 266 rotate(rp.id, 0,0, rp.speed * dt); 267 end; 268 end; 269 end; 270 end;
SnowGroomer:setIsSnowGroomerIsLowered(isLowered)
Lower/lift the groomer, depending on isLowered
(bool).
274 function SnowGroomer:setIsSnowGroomerIsLowered(isLowered) 275 self.snowGroomerIsLowered = isLowered or false; 276 end;
SnowGroomer:setIsSnowGroomerIsLocked(isLocked)
Lock/release the groomer, depending on isLocked
(bool).
280 function SnowGroomer:setIsSnowGroomerIsLocked(isLocked) 281 self.snowGroomerIsLocked = isLocked or false; 282 end;
SnowGroomer:setSnowGroomerFlaps(left, right)
Fold out the left
and the right
flap (both bool). true
means the flap shall be moved out.
285 function SnowGroomer:setSnowGroomerFlaps(left, right) 286 if left ~= nil then self.leftFlapTarget = left and 1 or 0; end; 287 if right ~= nil then self.rightFlapTarget = right and 1 or 0; end; 288 end;
SnowGroomer:updateSnowGroomerLimits()
Passes on the axis target values and limits to the C# class SnowGroomerIK
, which will then solve the 4D inverse kinematics.
292 function SnowGroomer:updateSnowGroomerLimits() 293 if self.snowGroomerIK == nil then return end; 294 295 local weight = self.snowGroomerPos; 296 local axis1Weight = clamp01(weight - self.snowGroomerLockedPos); 297 298 SnowGroomerIK.setAxis1Limit(self.snowGroomerIK, lerp(self.snowGroomerLifted.axis1, self.snowGroomerLowered.axis1Limit, axis1Weight)); 299 SnowGroomerIK.setAxis2Limit(self.snowGroomerIK, 300 lerp(self.snowGroomerLifted.axis2, self.snowGroomerLowered.axis2MinLimit, weight), 301 lerp(self.snowGroomerLifted.axis2, self.snowGroomerLowered.axis2MaxLimit, weight) 302 ); 303 SnowGroomerIK.setAxis3Limit(self.snowGroomerIK, 304 lerp(self.snowGroomerLifted.axis3, self.snowGroomerLowered.axis3MinLimit, weight), 305 lerp(self.snowGroomerLifted.axis3, self.snowGroomerLowered.axis3MaxLimit, weight) 306 ); 307 SnowGroomerIK.setAxis4Limit(self.snowGroomerIK, 308 lerp(self.snowGroomerLifted.axis4, self.snowGroomerLowered.axis4MinLimit, weight), 309 lerp(self.snowGroomerLifted.axis4, self.snowGroomerLowered.axis4MaxLimit, weight) 310 ); 311 end;
Copyright
All contents of this page may be used for modding use for Winter Resort Simulator only. Any use exceeding this regulation is not permitted.
Copyright (C) HR Innoways, 2020. All Rights Reserved.