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 (falseto 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.