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); 29 30 VehicleCamera.RAYCAST_LAYER = 0xFFFFFFFF - (2^2) - (2^22) - (2^26) - (2^30) - (2^31) - (2^15);
VehicleCamera:load(cameraId, rotationNode, zoomNode, isCarrier, parentId, maxCameraAccelerationZoom)
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
(default) when the camera's physics shall be ignored because the camera is mounted on a ropeway carrier (false
to disable this)parentId
(int): the id of the parent, of which all collisions will be ignored for raycasts.
39 function VehicleCamera:load(cameraId, rotationNode, zoomNode, isCarrier, parentId, maxCameraAccelerationZoom) 40 self.cameraId = cameraId; 41 self.trueCameraId = cameraId; -- used if the object the rotation is applied to is not the same as the camera itself 42 self.rotationNode = rotationNode; 43 44 if rotationNode ~= nil then 45 self.zoomLevel = 0.5; 46 else 47 self.zoomLevel = 1; 48 end; 49 50 self.zoomFOV = GameplaySettings.fieldOfView; 51 self.cameraFrozen = false; 52 self.blockInput = false; 53 self.parentId = parentId or 0; 54 55 self.rightMouseDownDuration = 0; 56 57 self.cx = 0; 58 self.cy = 0; 59 60 self.minRotX = -85; 61 self.maxRotX = 85; 62 63 self.accelerationZoom = 0; 64 self.accelerationFOVFactor = 0; 65 66 self.maxCameraAccelerationZoom = maxCameraAccelerationZoom or 0; 67 self.accelerationFOVLerp = 0.5; 68 69 if self.rotationNode == nil then 70 local x,y,z = getRotation(cameraId); 71 self.cx = x; 72 self.cy = y; 73 74 self.enableCameraDynamics = isCarrier == false; 75 76 else 77 self.zoomNode = zoomNode; 78 self.zoomTarget = 6; 79 self.lastZoomPosition = 3; 80 self.raycastIgnoreDistance = 1; 81 82 self.cx = 20; 83 84 -- cx2/cy2 are smoothing rotation one more time and are then applied to the camera 85 self.cx2 = 0; 86 self.cy2 = 0; 87 88 -- world reference is only valid for exterior cameras 89 self.worldReference = isCarrier == false; 90 end; 91 92 if self.worldReference then 93 -- create a new game object which holds the position of our rotation node 94 self.vehicleReferenceId = createGameObject("cameraReference"); 95 96 -- copy its position 97 setParent(self.vehicleReferenceId, getParent(self.rotationNode)); 98 setWorldPosition(self.vehicleReferenceId, getWorldPosition(self.rotationNode)); 99 setRotation(self.vehicleReferenceId, 0,0,0); 100 101 -- create a new game object for the camera 102 self.worldRotationNode = createGameObject("worldCameraRotation"); 103 104 setParent(self.worldRotationNode, 0); 105 106 setWorldPosition(self.worldRotationNode, getWorldPosition(self.vehicleReferenceId)); 107 setWorldRotation(self.worldRotationNode, getWorldRotation(self.vehicleReferenceId)); 108 109 setParent(self.rotationNode, self.worldRotationNode); 110 -- now we free the rotation node so that it is in world space 111 112 self.lastCameraWorldPosition = VectorUtils.getWorldPosition(self.vehicleReferenceId); 113 self.lastReferenceForwardVector = VectorUtils.transformDirection(self.vehicleReferenceId, Vector3.forward); 114 115 if math.abs(self.lastReferenceForwardVector.x) + math.abs(self.lastReferenceForwardVector.z) < 0.1 then 116 self.lastReferenceForwardVector = Vector3.forward:clone(); 117 end; 118 119 elseif self.enableCameraDynamics then 120 121 -- again, create a new game object 122 self.cameraDynamicsRoot = createGameObject("CameraDynamicsRoot"); 123 self.cameraDynamicsRef = createGameObject("CameraDynamicsReference"); 124 125 -- make it a child of the actual camera id 126 setParent(self.cameraDynamicsRoot, getParent(self.cameraId)); 127 setWorldPosition(self.cameraDynamicsRoot, getWorldPosition(self.cameraId)); 128 setRotation(self.cameraDynamicsRoot, 0,0,0); 129 130 setParent(self.cameraDynamicsRef, getParent(self.cameraId)); 131 setWorldPosition(self.cameraDynamicsRef, getWorldPosition(self.cameraId)); 132 setRotation(self.cameraDynamicsRef, 0,0,0); 133 134 setParent(self.cameraDynamicsRoot, self.cameraDynamicsRef); 135 setParent(self.cameraId, self.cameraDynamicsRoot); 136 137 -- store current speed 138 self.lastSpeedVector = Vector3.zero:clone(); 139 self.lastCameraWorldPosition = Vector3.zero:clone(); 140 self.lastCameraDirection = Vector3.zero:clone(); 141 self.lastAcceleration = Vector3:new(0, 0, 0); 142 self.lastCameraPos = Vector3:new(0, 0, 0); 143 end; 144 145 self.enableRotX = true; 146 self.enableRotY = true; 147 self.enableSelector = true; 148 end;
VehicleCamera:destroy()
Destroys the vehicle camera and removes all objects that were created, but not linked to the vehicle.
151 function VehicleCamera:destroy() 152 if self.worldRotationNode ~= nil then 153 destroy(self.worldRotationNode); 154 end; 155 end;
VehicleCamera:activate()
Activates this camera.
159 function VehicleCamera:activate() 160 if not self.dontActivateCamera then 161 GameControl.setCamera(self.trueCameraId); 162 end; 163 self.cameraFrozen = false; 164 self.blockInput = false; 165 166 -- set the global "activeCamera" to this instance 167 -- note that this has to be resetted every time another camera is activated 168 VehicleCamera.activeCamera = self; 169 170 if self.worldReference then 171 setWorldPosition(self.worldRotationNode, getWorldPosition(self.vehicleReferenceId)); 172 self.lastCameraWorldPosition = VectorUtils.getWorldPosition(self.vehicleReferenceId); 173 end; 174 175 self:resetWorldReferenceMovement(); 176 end;
VehicleCamera:saveToTable()
Stores all current camera settings in the savegame.
179 function VehicleCamera:saveToTable() 180 return { 181 zoomLevel = self.zoomLevel, 182 cx = self.cx, 183 cy = self.cy, 184 isActive = VehicleCamera.activeCamera == self, 185 } 186 end;
VehicleCamera:loadFromTable(tbl)
Restores variables from savegame.
189 function VehicleCamera:loadFromTable(tbl) 190 if tbl == nil then return end; 191 192 self.zoomLevel = tbl.zoomLevel; 193 self.cx, self.cy = tbl.cx, tbl.cy; 194 195 -- directly apply rotation 196 if self.rotationNode ~= nil then 197 setRotation(self.rotationNode, self.cx, self.cy, 0); 198 else 199 setRotation(self.cameraId, self.cx, self.cy, 0); 200 end; 201 202 if tbl.isActive then 203 self:activate(); 204 end; 205 end;
VehicleCamera:getRotateFactor()
Returns the active rotate factor, depending on zoom level.
209 function VehicleCamera:getRotateFactor() 210 if self.rotationNode ~= nil then 211 -- if this is an exterior camera, the behaviour has to be inversed 212 return GameplaySettings.mouseSensitivity * 0.5; 213 end; 214 return GameplaySettings.mouseSensitivity * lerp(0.2, 1, self.zoomLevel); 215 end;
VehicleCamera:getCameraFrozen()
Returns whether the camera is currently frozen or not.
219 function VehicleCamera:getCameraFrozen() 220 return self.cameraFrozen or (Input.getMouseButton(0) and self.enableSelector); 221 end;
VehicleCamera:setIsFrozen(isFrozen)
Sets the camera to frozen or unfreezes it, depending on isFrozen
(bool).
224 function VehicleCamera:setIsFrozen(isFrozen) 225 self.cameraFrozen = isFrozen; 226 227 g_GUI:wrapMouse(not self.cameraFrozen); 228 end;
VehicleCamera:setBlockInput(block)
Specifies whether input shall be blocked or not. This is in use if any MovingParts controls are activated.
231 function VehicleCamera:setBlockInput(block) 232 self.blockInput = block; 233 234 if block then 235 -- hide mouse 236 g_GUI:wrapMouse(true); 237 else 238 g_GUI:wrapMouse(not self.cameraFrozen); 239 end; 240 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
245 function VehicleCamera:update(dt) 246 -- vehicle cameras may only be updated when they are actually in use 247 248 if self.worldReference and not GameplaySettings.dynamicExteriorCamera then 249 local wx,wy,wz = getWorldPosition(self.vehicleReferenceId); 250 setWorldPosition(self.worldRotationNode, wx,wy,wz); 251 end; 252 253 if g_GUI:getAnyGuiActive() then 254 return; 255 end; 256 257 if Input.getMouseButton(1) then 258 self.rightMouseDownDuration = self.rightMouseDownDuration + dt; 259 260 -- as long as right mouse button is down, we can return right here 261 return; 262 263 elseif self.rightMouseDownDuration > 0 then 264 265 if self.rightMouseDownDuration < 0.2 and not self.blockInput then 266 self:setIsFrozen(not self.cameraFrozen); 267 end; 268 269 self.rightMouseDownDuration = 0; 270 end; 271 272 if self.blockInput then return end; 273 274 if not g_GUI:getAnyGuiActive() then 275 self.zoomLevel = clamp01(self.zoomLevel - InputMapper:getAxis(InputMapper.Axis_Look_Zoom) * GameplaySettings.zoomSensitivity); 276 277 local rotXLimit = 0; 278 279 -- apply zoom level 280 if self.zoomNode ~= nil then 281 self.zoomTarget = lerp(6, 40, self.zoomLevel); 282 283 -- find the true minimum distance using raycast 284 local dist = self.zoomTarget; 285 286 local limitDistance; 287 limitDistance, rotXLimit = self:getZoomLimits(self.zoomTarget); 288 289 -- smoothen out zooming 290 dist = 0.2 * dist + 0.8 * self.lastZoomPosition; 291 292 if limitDistance ~= nil then 293 dist = math.min(dist, limitDistance); 294 end; 295 296 setPosition(self.zoomNode, 0,0, -dist); 297 298 -- set the camerazoom acording to the vehicles acceleration / speed 299 -- lerp the accelerationFOV, so this value becomes smoothed out 300 self.accelerationFOVLerp = lerp(self.accelerationFOVLerp, self.accelerationFOVFactor, dt); 301 302 -- calculate the FOV level according to the maxCameraAccelerationZoom given in the datatable 303 local accelerationFOV = GameplaySettings.fieldOfView + self.accelerationFOVLerp * self.maxCameraAccelerationZoom; 304 305 -- set the FOV level 306 GameControl.setCameraFOV(self.trueCameraId, accelerationFOV); 307 308 self.lastZoomPosition = dist; 309 310 else 311 local targetFOV = lerp(20, GameplaySettings.fieldOfView, self.zoomLevel); 312 self.zoomFOV = 0.5 * (self.zoomFOV + targetFOV); 313 GameControl.setCameraFOV(self.trueCameraId, self.zoomFOV); 314 end; 315 316 if not self:getCameraFrozen() then 317 local movX = InputMapper:getAxis(InputMapper.Axis_Look_LeftRight); 318 local movY = InputMapper:getAxis(InputMapper.Axis_Look_UpDown); 319 320 local rotateFactor = self:getRotateFactor(); 321 322 if self.enableRotX then 323 324 -- apply rot X limit 325 if rotXLimit > 0 then 326 movY = math.min(movY, 0); 327 elseif rotXLimit < 0 then 328 movY = math.max(movY, 0); 329 end; 330 331 self.cx = clamp(self.cx - rotateFactor * movY, self.minRotX, self.maxRotX); 332 end; 333 if self.enableRotY then 334 self.cy = self.cy + rotateFactor * movX; 335 end; 336 337 if self.rotationNode ~= nil then 338 -- smooth rotation 339 self.cx2 = 0.40 * self.cx2 + 0.60 * self.cx; 340 self.cy2 = 0.40 * self.cy2 + 0.60 * self.cy; 341 342 setRotation(self.rotationNode, self.cx2, self.cy2, 0); 343 else 344 setRotation(self.cameraId, self.cx, self.cy, 0); 345 end; 346 end; 347 end; 348 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.
354 355 local sqrt = function(x) 356 if x < 0 then 357 return -math.sqrt(-x); 358 end; 359 return x; 360 end; 361 362 function VehicleCamera:fixedUpdate(dt) 363 if self.worldReference then 364 365 -- get the camera's world position 366 local wx,wy,wz = getWorldPosition(self.vehicleReferenceId); 367 local nx,ny,nz; 368 369 -- ignore a movement of more than 15 metres 370 if (math.abs(self.lastCameraWorldPosition.x - wx) + math.abs(self.lastCameraWorldPosition.y - wy) + math.abs(self.lastCameraWorldPosition.z - wz)) > 15 then 371 self:resetWorldReferenceMovement(); 372 end; 373 374 375 if not GameplaySettings.dynamicExteriorCamera then 376 -- no camera smoothing 377 nx, ny, nz = wx, wy, wz; 378 379 -- apply the position 380 setWorldPosition(self.worldRotationNode, nx,ny,nz); 381 else 382 nx = 0.5 * self.lastCameraWorldPosition.x + 0.5 * wx; 383 ny = 0.5 * self.lastCameraWorldPosition.y + 0.5 * wy; 384 nz = 0.5 * self.lastCameraWorldPosition.z + 0.5 * wz; 385 386 -- apply the position 387 setWorldPosition(self.worldRotationNode, nx,ny,nz); 388 end; 389 390 self.lastCameraWorldPosition.x = nx; 391 self.lastCameraWorldPosition.y = ny; 392 self.lastCameraWorldPosition.z = nz; 393 394 local dx,dy,dz = Utils.transformDirection(self.vehicleReferenceId, 0,0,1); 395 --local dx,dy,dz = wx-nx, wy-ny, wz-nz; 396 397 if math.abs(dx) + math.abs(dz) > 0.1 then 398 399 local lx, ly, lz = Utils.inverseTransformPoint(self.vehicleReferenceId, nx,ny,nz); 400 401 -- invert the term (wx-nx) for reversing for better camera feeling 402 local revFactor = 0.3; 403 if lz > 0 then 404 revFactor = clamp(5*lz, 0.3, 1); 405 end; 406 407 -- smoothly blend the direction 408 if not GameplaySettings.dynamicExteriorCamera then 409 nx, nz = dx, dz; 410 else 411 nx = 0.9 * self.lastReferenceForwardVector.x + 0.10 * lerp(wx-nx, dx, revFactor); 412 ny = 0.90 * self.lastReferenceForwardVector.y + 0.10 * lerp(wy-ny, dy, revFactor); 413 nz = 0.90 * self.lastReferenceForwardVector.z + 0.10 * lerp(wz-nz, dz, revFactor); 414 end; 415 416 self.lastReferenceForwardVector.x = nx; 417 self.lastReferenceForwardVector.y = ny; 418 self.lastReferenceForwardVector.z = nz; 419 420 self.accelerationZoom = nx; 421 422 -- make sure LookRotation does never receive a null vector 423 if math.abs(nx) + math.abs(nz) > 1e-3 then 424 local rx,ry,rz = Utils.lookRotation(nx, 0 ,nz, 0,1,0); 425 setWorldRotation(self.worldRotationNode, 0 ,ry,0); 426 end; 427 end; 428 429 elseif self.enableCameraDynamics and self.cameraDynamicsRef ~= nil then 430 431 local wx,wy,wz = getWorldPosition(self.cameraDynamicsRef); 432 433 if not GameplaySettings.dynamicInteriorCamera then 434 setPosition(self.cameraDynamicsRoot, 0,0,0); 435 return; 436 end; 437 438 if (math.abs(self.lastCameraWorldPosition.x - wx) + math.abs(self.lastCameraWorldPosition.y - wy) + math.abs(self.lastCameraWorldPosition.z - wz)) > 15 then 439 self:resetWorldReferenceMovement(); 440 end; 441 442 local cameraDirection = VectorUtils.transformDirection(self.cameraDynamicsRef, Vector3.forward); 443 local up = VectorUtils.transformDirection(self.cameraDynamicsRef, Vector3.up); 444 445 -- project lastCameraDirection along the plane between cameraDirection and Y 446 -- this will keep rot Y centered to the middle of the screen 447 self.lastCameraDirection = VectorUtils.projectOnPlane(self.lastCameraDirection, VectorUtils.crossProduct(cameraDirection, up)); 448 449 -- smoothen them 450 cameraDirection = Vector3.lerp(cameraDirection, self.lastCameraDirection, 0.8); 451 452 if not cameraDirection:isZero() then 453 VectorUtils.setWorldRotation(self.cameraDynamicsRoot, VectorUtils.lookRotation(cameraDirection, Vector3.up)); 454 end; 455 456 self.lastCameraDirection = cameraDirection; 457 458 -- determine speed 459 local sx,sy,sz; 460 sx = 0.2 * (wx - self.lastCameraWorldPosition.x) / dt + 0.8 * self.lastSpeedVector.x; 461 sy = 0.2 * (wy - self.lastCameraWorldPosition.y) / dt + 0.8 * self.lastSpeedVector.y; 462 sz = 0.2 * (wz - self.lastCameraWorldPosition.z) / dt + 0.8 * self.lastSpeedVector.z; 463 464 -- determine acceleration 465 local ax,ay,az; 466 ax = 0.02 * (sx - self.lastSpeedVector.x) / dt + 0.98 * self.lastAcceleration.x; 467 ay = 0.02 * (sy - self.lastSpeedVector.y) / dt + 0.98 * self.lastAcceleration.y; 468 az = 0.02 * (sz - self.lastSpeedVector.z) / dt + 0.98 * self.lastAcceleration.z; 469 470 local b = -0.01; 471 local c = -0.15; 472 473 -- apply acceleration (multiplied with coefficients b and c) 474 local x,y,z = sqrt(sx)*b + ax*c, sqrt(sy)*b + ay*c, sqrt(sz)*b + az*c; 475 x, y, z = Utils.inverseTransformDirection(self.cameraDynamicsRef, x,y,z); 476 477 -- one more time, dampen it for smooth results 478 x = 0.8 * self.lastCameraPos.x + 0.2 * x; 479 y = 0.8 * self.lastCameraPos.y + 0.2 * y; 480 z = 0.8 * self.lastCameraPos.z + 0.2 * z; 481 482 self.lastCameraPos.x = x; 483 self.lastCameraPos.y = y; 484 self.lastCameraPos.z = z; 485 486 setPosition(self.cameraDynamicsRoot, x,y,z); 487 488 -- store values for next frame 489 self.lastCameraWorldPosition.x = wx; 490 self.lastCameraWorldPosition.y = wy; 491 self.lastCameraWorldPosition.z = wz; 492 self.lastSpeedVector.x = sx; 493 self.lastSpeedVector.y = sy; 494 self.lastSpeedVector.z = sz; 495 496 end; 497 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.
501 function VehicleCamera:resetWorldReferenceMovement() 502 if self.worldReference then 503 504 -- we need to update the position 505 self.lastCameraWorldPosition.x, self.lastCameraWorldPosition.y, self.lastCameraWorldPosition.z = getWorldPosition(self.vehicleReferenceId); 506 507 -- and the direction 508 local dx,dy,dz = Utils.transformDirection(self.vehicleReferenceId, 0,0,1); 509 if math.abs(dx) + math.abs(dz) > 0.1 then 510 self.lastReferenceForwardVector.x = dx; 511 self.lastReferenceForwardVector.y = dy; 512 self.lastReferenceForwardVector.z = dz; 513 -- otherwise the reference stays unchanged, because we can't come up with a better one 514 end; 515 516 elseif self.enableCameraDynamics then 517 518 self.lastSpeedVector.x = 0; 519 self.lastSpeedVector.y = 0; 520 self.lastSpeedVector.z = 0; 521 self.lastAcceleration.x = 0; 522 self.lastAcceleration.y = 0; 523 self.lastAcceleration.z = 0; 524 self.lastCameraPos.x = 0; 525 self.lastCameraPos.y = 0; 526 self.lastCameraPos.z = 0; 527 528 local wx, wy, wz = getWorldPosition(self.cameraId); 529 530 self.lastCameraWorldPosition.x = wx; 531 self.lastCameraWorldPosition.y = wy; 532 self.lastCameraWorldPosition.z = wz; 533 534 setPosition(self.cameraDynamicsRoot, 0,0,0); 535 536 self.lastCameraDirection = VectorUtils.transformDirection(self.cameraDynamicsRef, Vector3.forward); 537 end; 538 end;
VehicleCamera:getZoomLimits(dist)
Returns the zoom limits for the current camera pose, as determined by some raycasts.
542 function VehicleCamera:getZoomLimits(dist) 543 544 local rayStart = VectorUtils.getWorldPosition(self.rotationNode); 545 local rayDirection = VectorUtils.transformDirection(self.zoomNode, Vector3:new(0,0,-1)); 546 rayStart = rayStart + rayDirection:multiply(self.raycastIgnoreDistance); 547 548 local distOffset = self.raycastIgnoreDistance; 549 550 -- there's a max iteration count 551 for i=1, 32, 1 do 552 553 local hit, normal, raycastDistance, otherId = VectorUtils.raycastGetAll(rayStart, rayDirection, dist + 10 - distOffset, VehicleCamera.RAYCAST_LAYER); 554 555 -- no single hit 556 if not hit then 557 return nil, 0; 558 end; 559 560 -- there's a hit, check if its relevant 561 if self.parentId == 0 or not isChildOf(otherId, self.parentId) then 562 -- we've found what we've been looking for 563 local returnDist = distOffset + raycastDistance; 564 565 -- project the normal onto our raycast vector and reduce the returnDist if the surface is almost perpendicular to it 566 local projectedLen = rayDirection:dotProduct(normal); 567 568 returnDist = returnDist - 1.5 * (1-projectedLen*projectedLen); 569 570 -- convert the normal into local space of our rotation node 571 local localNormal = VectorUtils.inverseTransformDirection(self.rotationNode, normal); 572 573 -- we want to limit rot x, therefore we are interested in the y component 574 if math.abs(localNormal.y) > 0.5 then 575 -- the normal points up/downwards => don't allow the camera to go down/up 576 return returnDist, localNormal.y; 577 end; 578 579 return returnDist, 0; 580 end; 581 582 -- prepare for next iteration 583 distOffset = distOffset + raycastDistance + 0.1; 584 rayStart = rayStart + rayDirection:multiply(raycastDistance + 0.1); 585 586 end; 587 588 -- number of iterations exceeded => nothing found 589 return nil, 0; 590 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.