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 argument
  • rotationNode (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;

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.