meta data for this page
Vehicles/VehicleCamera.lua
This is the camera used by VehicleSeat. VehicleCamera can be used both for interior and exterior cameras, depending on which arguments are specified.
26 27 VehicleCamera = VehicleCamera or {}; 28 local VehicleCameraClass = Class(VehicleCamera);
VehicleCamera:load(cameraId, rotationNode, zoomNode, isCarrier, parentId)
Loads a newly created instance using one or three arguments:
cameraId
(int): id of the camera. This is the only mandatory argumentrotationNode
(int): if this is an exterior camera, this argument shall contain the id of the transform which represents the rotation. Typically, this will be the parent of zoomNode and cameraId.zoomNode
(int): if this is an exterior camera, this argument shall contain the id of the transform which is moved while zooming. Typically, this will be either the camera id or its parent.isCarrier
(bool):true
when the camera's physics shall be ignored because the camera is mounted on a ropeway carrierparentId
(int): the id of the parent, of which all collisions will be ignored for raycasts.
37 function VehicleCamera:load(cameraId, rotationNode, zoomNode, isCarrier, parentId) 38 self.cameraId = cameraId; 39 self.trueCameraId = cameraId; -- used if the object the rotation is applied to is not the same as the camera itself 40 self.rotationNode = rotationNode; 41 42 self.zoomLevel = 1; 43 self.zoomFOV = GameplaySettings.fieldOfView; 44 self.cameraFrozen = false; 45 self.blockInput = false; 46 self.parentId = parentId or 0; 47 48 self.rightMouseDownDuration = 0; 49 50 self.cx = 0; 51 self.cy = 0; 52 53 self.minRotX = -85; 54 self.maxRotX = 85; 55 56 if self.rotationNode == nil then 57 local x,y,z = getRotation(cameraId); 58 self.cx = x; 59 self.cy = y; 60 61 self.enableCameraDynamics = not isCarrier; 62 63 else 64 self.zoomNode = zoomNode; 65 self.zoomTarget = 6; 66 self.lastZoomPosition = 6; 67 self.raycastIgnoreDistance = 1; 68 69 self.cx = 20; 70 71 -- cx2/cy2 are smoothing rotation one more time and are then applied to the camera 72 self.cx2 = 0; 73 self.cy2 = 0; 74 75 self.worldReference = not isCarrier; 76 end; 77 78 if self.worldReference then 79 -- create a new game object which holds the position of our rotation node 80 self.vehicleReferenceId = createGameObject("cameraReference"); 81 82 -- copy its position 83 setParent(self.vehicleReferenceId, getParent(self.rotationNode)); 84 setWorldPosition(self.vehicleReferenceId, getWorldPosition(self.rotationNode)); 85 setRotation(self.vehicleReferenceId, 0,0,0); 86 87 -- create a new game object for the camera 88 self.worldRotationNode = createGameObject("worldCameraRotation"); 89 setWorldPosition(self.worldRotationNode, getWorldPosition(self.vehicleReferenceId)); 90 setWorldRotation(self.worldRotationNode, getWorldRotation(self.vehicleReferenceId)); 91 92 setParent(self.worldRotationNode, 0); 93 setParent(self.rotationNode, self.worldRotationNode); 94 -- now we free the rotation node so that it is in world space 95 96 self.lastCameraWorldPosition = VectorUtils.getWorldPosition(self.vehicleReferenceId); 97 self.lastReferenceForwardVector = VectorUtils.transformDirection(self.vehicleReferenceId, Vector3.forward); 98 99 if math.abs(self.lastReferenceForwardVector.x) + math.abs(self.lastReferenceForwardVector.z) < 0.1 then 100 self.lastReferenceForwardVector = Vector3.forward:clone(); 101 end; 102 103 elseif self.enableCameraDynamics then 104 105 -- again, create a new game object 106 self.cameraDynamicsRoot = createGameObject("CameraDynamicsRoot"); 107 self.cameraDynamicsRef = createGameObject("CameraDynamicsReference"); 108 109 -- make it a child of the actual camera id 110 setParent(self.cameraDynamicsRoot, getParent(self.cameraId)); 111 setWorldPosition(self.cameraDynamicsRoot, getWorldPosition(self.cameraId)); 112 setRotation(self.cameraDynamicsRoot, 0,0,0); 113 114 setParent(self.cameraDynamicsRef, getParent(self.cameraId)); 115 setWorldPosition(self.cameraDynamicsRef, getWorldPosition(self.cameraId)); 116 setRotation(self.cameraDynamicsRef, 0,0,0); 117 118 setParent(self.cameraDynamicsRoot, self.cameraDynamicsRef); 119 setParent(self.cameraId, self.cameraDynamicsRoot); 120 121 -- store current speed 122 self.lastSpeedVector = Vector3.zero:clone(); 123 self.lastCameraWorldPosition = Vector3.zero:clone(); 124 self.lastAcceleration = Vector3:new(0, 0, 0); 125 self.lastCameraPos = Vector3:new(0, 0, 0); 126 end; 127 128 self.enableRotX = true; 129 self.enableRotY = true; 130 self.enableSelector = true; 131 end;
VehicleCamera:activate()
Activates this camera.
135 function VehicleCamera:activate() 136 if not self.dontActivateCamera then 137 GameControl.setCamera(self.trueCameraId); 138 end; 139 self.cameraFrozen = false; 140 self.blockInput = false; 141 142 -- set the global "activeCamera" to this instance 143 -- note that this has to be resetted every time another camera is activated 144 VehicleCamera.activeCamera = self; 145 146 self:resetWorldReferenceMovement(); 147 end;
VehicleCamera:saveToTable()
Stores all current camera settings in the savegame.
150 function VehicleCamera:saveToTable() 151 return { 152 zoomLevel = self.zoomLevel, 153 cx = self.cx, 154 cy = self.cy, 155 isActive = VehicleCamera.activeCamera == self, 156 } 157 end;
VehicleCamera:loadFromTable(tbl)
Restores variables from savegame.
160 function VehicleCamera:loadFromTable(tbl) 161 if tbl == nil then return end; 162 163 self.zoomLevel = tbl.zoomLevel; 164 self.cx, self.cy = tbl.cx, tbl.cy; 165 166 -- directly apply rotation 167 if self.rotationNode ~= nil then 168 setRotation(self.rotationNode, self.cx, self.cy, 0); 169 else 170 setRotation(self.cameraId, self.cx, self.cy, 0); 171 end; 172 173 if tbl.isActive then 174 self:activate(); 175 end; 176 end;
VehicleCamera:getRotateFactor()
Returns the active rotate factor, depending on zoom level.
180 function VehicleCamera:getRotateFactor() 181 if self.rotationNode ~= nil then 182 -- if this is an exterior camera, the behaviour has to be inversed 183 return GameplaySettings.mouseSensitivity * 0.5; 184 end; 185 return GameplaySettings.mouseSensitivity * lerp(0.2, 1, self.zoomLevel); 186 end;
VehicleCamera:getCameraFrozen()
Returns whether the camera is currently frozen or not.
190 function VehicleCamera:getCameraFrozen() 191 return self.cameraFrozen or (Input.getMouseButton(0) and self.enableSelector); 192 end;
VehicleCamera:setIsFrozen(isFrozen)
Sets the camera to frozen or unfreezes it, depending on isFrozen
(bool).
195 function VehicleCamera:setIsFrozen(isFrozen) 196 self.cameraFrozen = isFrozen; 197 198 GUI:wrapMouse(not self.cameraFrozen); 199 end;
VehicleCamera:setBlockInput(block)
Specifies whether input shall be blocked or not. This is in use if any MovingParts controls are activated.
202 function VehicleCamera:setBlockInput(block) 203 self.blockInput = block; 204 205 if block then 206 -- hide mouse 207 GUI:wrapMouse(true); 208 else 209 GUI:wrapMouse(not self.cameraFrozen); 210 end; 211 end;
VehicleCamera:update(dt)
Called every frame to update camera inputs (like rotating or zooming).
If exterior camera is enabled, it will shoot a ray cast to check how much it is allowed to zoom out
216 function VehicleCamera:update(dt) 217 -- vehicle cameras may only be updated when they are actually in use 218 219 if Input.getMouseButton(1) then 220 self.rightMouseDownDuration = self.rightMouseDownDuration + dt; 221 222 -- as long as right mouse button is down, we can return right here 223 return; 224 225 elseif self.rightMouseDownDuration > 0 then 226 227 if self.rightMouseDownDuration < 0.2 and not self.blockInput then 228 self:setIsFrozen(not self.cameraFrozen); 229 end; 230 231 self.rightMouseDownDuration = 0; 232 end; 233 234 if self.blockInput then return end; 235 236 if not GUI:getAnyGuiActive() then 237 self.zoomLevel = clamp01(self.zoomLevel - InputMapper:getAxis(InputMapper.Axis_Look_Zoom) * GameplaySettings.zoomSensitivity); 238 239 local rotXLimit = 0; 240 241 -- apply zoom level 242 if self.zoomNode ~= nil then 243 self.zoomTarget = lerp(6, 40, self.zoomLevel); 244 245 -- find the true minimum distance using raycast 246 local dist = self.zoomTarget; 247 248 local limitDistance; 249 limitDistance, rotXLimit = self:getZoomLimits(self.zoomTarget); 250 251 -- smoothen out zooming 252 dist = 0.2 * dist + 0.8 * self.lastZoomPosition; 253 254 if limitDistance ~= nil then 255 dist = math.min(dist, limitDistance); 256 end; 257 258 setPosition(self.zoomNode, 0,0,-dist); 259 GameControl.setCameraFOV(self.trueCameraId, GameplaySettings.fieldOfView); 260 261 self.lastZoomPosition = dist; 262 263 else 264 local targetFOV = lerp(20, GameplaySettings.fieldOfView, self.zoomLevel); 265 self.zoomFOV = 0.5 * (self.zoomFOV + targetFOV); 266 GameControl.setCameraFOV(self.trueCameraId, self.zoomFOV); 267 end; 268 269 if not self:getCameraFrozen() then 270 local movX = InputMapper:getAxis(InputMapper.Axis_Look_LeftRight); 271 local movY = InputMapper:getAxis(InputMapper.Axis_Look_UpDown); 272 273 local rotateFactor = self:getRotateFactor(); 274 275 if self.enableRotX then 276 277 -- apply rot X limit 278 if rotXLimit > 0 then 279 movY = math.min(movY, 0); 280 elseif rotXLimit < 0 then 281 movY = math.max(movY, 0); 282 end; 283 284 self.cx = clamp(self.cx - rotateFactor * movY, self.minRotX, self.maxRotX); 285 end; 286 if self.enableRotY then 287 self.cy = self.cy + rotateFactor * movX; 288 end; 289 290 if self.rotationNode ~= nil then 291 -- smooth rotation 292 self.cx2 = 0.40 * self.cx2 + 0.60 * self.cx; 293 self.cy2 = 0.40 * self.cy2 + 0.60 * self.cy; 294 295 setRotation(self.rotationNode, self.cx2, self.cy2, 0); 296 else 297 setRotation(self.cameraId, self.cx, self.cy, 0); 298 end; 299 end; 300 end; 301 end;
VehicleCamera:fixedUpdate(dt)
fixedUpdate is used for the physics-dependent camera simulation of both exterior cameras (using worldReference
) and interior cameras (using enableCameraDynamics
).
sqrt
is a local helper function which allows a signed use of the math.sqrt
function.
307 308 local sqrt = function(x) 309 if x < 0 then 310 return -math.sqrt(-x); 311 end; 312 return x; 313 end; 314 315 function VehicleCamera:fixedUpdate(dt) 316 if self.worldReference then 317 318 -- get the camera's world position 319 local wx,wy,wz = getWorldPosition(self.vehicleReferenceId); 320 local nx,ny,nz; 321 322 -- ignore a movement of more than 15 metres 323 if (math.abs(self.lastCameraWorldPosition.x - wx) + math.abs(self.lastCameraWorldPosition.y - wy) + math.abs(self.lastCameraWorldPosition.z - wz)) > 15 then 324 self:resetWorldReferenceMovement(); 325 end; 326 327 nx = 0.5 * self.lastCameraWorldPosition.x + 0.5 * wx; 328 ny = 0.5 * self.lastCameraWorldPosition.y + 0.5 * wy; 329 nz = 0.5 * self.lastCameraWorldPosition.z + 0.5 * wz; 330 331 -- apply the position 332 setWorldPosition(self.worldRotationNode, nx,ny,nz); 333 self.lastCameraWorldPosition.x = nx; 334 self.lastCameraWorldPosition.y = ny; 335 self.lastCameraWorldPosition.z = nz; 336 337 local dx,dy,dz = Utils.transformDirection(self.vehicleReferenceId, 0,0,1); 338 --local dx,dy,dz = wx-nx, wy-ny, wz-nz; 339 340 if math.abs(dx) + math.abs(dz) > 0.1 then 341 342 local lx, ly, lz = Utils.inverseTransformPoint(self.vehicleReferenceId, nx,ny,nz); 343 344 -- invert the term (wx-nx) for reversing for better camera feeling 345 local revFactor = 0.3; 346 if lz > 0 then 347 revFactor = clamp(5*lz, 0.3, 1); 348 end; 349 350 -- smoothly blend the direction 351 nx = 0.70 * self.lastReferenceForwardVector.x + 0.30 * lerp(wx-nx, dx, revFactor); 352 nz = 0.70 * self.lastReferenceForwardVector.z + 0.30 * lerp(wz-nz, dz, revFactor); 353 354 self.lastReferenceForwardVector.x = nx; 355 self.lastReferenceForwardVector.z = nz; 356 357 -- make sure LookRotation does never receive a null vector 358 if math.abs(nx) + math.abs(nz) > 1e-3 then 359 local rx,ry,rz = Utils.lookRotation(nx,0,nz, 0,1,0); 360 setWorldRotation(self.worldRotationNode, 0,ry,0); 361 end; 362 end; 363 364 elseif self.enableCameraDynamics then 365 local wx,wy,wz = getWorldPosition(self.cameraDynamicsRef); 366 367 if (math.abs(self.lastCameraWorldPosition.x - wx) + math.abs(self.lastCameraWorldPosition.y - wy) + math.abs(self.lastCameraWorldPosition.z - wz)) > 15 then 368 self:resetWorldReferenceMovement(); 369 end; 370 371 -- determine speed 372 local sx,sy,sz; 373 sx = 0.2 * (wx - self.lastCameraWorldPosition.x) / dt + 0.8 * self.lastSpeedVector.x; 374 sy = 0.2 * (wy - self.lastCameraWorldPosition.y) / dt + 0.8 * self.lastSpeedVector.y; 375 sz = 0.2 * (wz - self.lastCameraWorldPosition.z) / dt + 0.8 * self.lastSpeedVector.z; 376 377 -- determine acceleration 378 local ax,ay,az; 379 ax = 0.02 * (sx - self.lastSpeedVector.x) / dt + 0.98 * self.lastAcceleration.x; 380 ay = 0.02 * (sy - self.lastSpeedVector.y) / dt + 0.98 * self.lastAcceleration.y; 381 az = 0.02 * (sz - self.lastSpeedVector.z) / dt + 0.98 * self.lastAcceleration.z; 382 383 local b = -0.01; 384 local c = -0.15; 385 386 -- apply acceleration (multiplied with coefficients b and c) 387 local x,y,z = sqrt(sx)*b + ax*c, sqrt(sy)*b + ay*c, sqrt(sz)*b + az*c; 388 x, y, z = Utils.inverseTransformDirection(self.cameraDynamicsRef, x,y,z); 389 390 -- one more time, dampen it for smooth results 391 x = 0.8 * self.lastCameraPos.x + 0.2 * x; 392 y = 0.8 * self.lastCameraPos.y + 0.2 * y; 393 z = 0.8 * self.lastCameraPos.z + 0.2 * z; 394 395 self.lastCameraPos.x = x; 396 self.lastCameraPos.y = y; 397 self.lastCameraPos.z = z; 398 399 setPosition(self.cameraDynamicsRoot, x,y,z); 400 401 -- store values for next frame 402 self.lastCameraWorldPosition.x = wx; 403 self.lastCameraWorldPosition.y = wy; 404 self.lastCameraWorldPosition.z = wz; 405 self.lastSpeedVector.x = sx; 406 self.lastSpeedVector.y = sy; 407 self.lastSpeedVector.z = sz; 408 409 end; 410 end;
VehicleCamera:resetWorldReferenceMovement()
This is internally called if the vehicle is teleported, which causes physics-dependent cameras to yield fake results. Therefore, their calculation is reset.
414 function VehicleCamera:resetWorldReferenceMovement() 415 if self.worldReference then 416 417 -- we need to update the position 418 self.lastCameraWorldPosition.x, self.lastCameraWorldPosition.y, self.lastCameraWorldPosition.z = getWorldPosition(self.vehicleReferenceId); 419 420 -- and the direction 421 local dx,dy,dz = Utils.transformDirection(self.vehicleReferenceId, 0,0,1); 422 if math.abs(dx) + math.abs(dz) > 0.1 then 423 self.lastReferenceForwardVector.x = dx; 424 self.lastReferenceForwardVector.y = dy; 425 self.lastReferenceForwardVector.z = dz; 426 -- otherwise the reference stays unchanged, because we can't come up with a better one 427 end; 428 429 elseif self.enableCameraDynamics then 430 431 self.lastSpeedVector.x = 0; 432 self.lastSpeedVector.y = 0; 433 self.lastSpeedVector.z = 0; 434 self.lastAcceleration.x = 0; 435 self.lastAcceleration.y = 0; 436 self.lastAcceleration.z = 0; 437 self.lastCameraPos.x = 0; 438 self.lastCameraPos.y = 0; 439 self.lastCameraPos.z = 0; 440 441 local wx, wy, wz = getWorldPosition(self.cameraId); 442 443 self.lastCameraWorldPosition.x = wx; 444 self.lastCameraWorldPosition.y = wy; 445 self.lastCameraWorldPosition.z = wz; 446 447 setPosition(self.cameraDynamicsRoot, 0,0,0); 448 end; 449 end;
VehicleCamera:getZoomLimits(dist)
Returns the zoom limits for the current camera pose, as determined by some raycasts.
453 function VehicleCamera:getZoomLimits(dist) 454 455 local rayStart = VectorUtils.getWorldPosition(self.rotationNode); 456 local rayDirection = VectorUtils.transformDirection(self.zoomNode, Vector3:new(0,0,-1)); 457 rayStart = rayStart + rayDirection:multiply(self.raycastIgnoreDistance); 458 459 local distOffset = self.raycastIgnoreDistance; 460 461 -- there's a max iteration count 462 for i=1, 32, 1 do 463 464 local hit, normal, raycastDistance, otherId = VectorUtils.raycastGetAll(rayStart, rayDirection, dist + 10 - distOffset); 465 466 -- no single hit 467 if not hit then 468 return nil, 0; 469 end; 470 471 -- there's a hit, check if its relevant 472 if self.parentId == 0 or not isChildOf(otherId, self.parentId) then 473 -- we've found what we've been looking for 474 local returnDist = distOffset + raycastDistance; 475 476 -- project the normal onto our raycast vector and reduce the returnDist if the surface is almost perpendicular to it 477 local projectedLen = rayDirection:dotProduct(normal); 478 479 returnDist = returnDist - 1.5 * (1-projectedLen*projectedLen); 480 481 -- convert the normal into local space of our rotation node 482 local localNormal = VectorUtils.inverseTransformDirection(self.rotationNode, normal); 483 484 -- we want to limit rot x, therefore we are interested in the y component 485 if math.abs(localNormal.y) > 0.5 then 486 -- the normal points up/downwards => don't allow the camera to go down/up 487 return returnDist, localNormal.y; 488 end; 489 490 return returnDist, 0; 491 end; 492 493 -- prepare for next iteration 494 distOffset = distOffset + raycastDistance + 0.1; 495 rayStart = rayStart + rayDirection:multiply(raycastDistance + 0.1); 496 497 end; 498 499 -- number of iterations exceeded => nothing found 500 return nil, 0; 501 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.