This file is indexed.

/usr/src/castle-game-engine-5.2.0/3d/castlerays.pas is in castle-game-engine-src 5.2.0-3.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
{
  Copyright 2003-2014 Michalis Kamburelis.

  This file is part of "Castle Game Engine".

  "Castle Game Engine" is free software; see the file COPYING.txt,
  included in this distribution, for details about the copyright.

  "Castle Game Engine" is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

  ----------------------------------------------------------------------------
}

{ Calculating 3D rays that correspond to the given points on 2D screen.
  This is used by ray-tracing (casting a ray for each image pixel)
  or when picking objects (what 3D object/point is indicated by
  the current mouse position). }
unit CastleRays;

interface

uses CastleVectors;

{ Calculate second viewing angle for perspective projection.
  Given one viewing angle of the camera (FirstViewAngleDeg) and
  aspect ratio of your window sizes (SecondToFirstRatio),
  calculate second viewing angle of the camera.

  The intention is that when projecting camera view (with given view angles)
  on a screen with given aspect ratio), the image will not be distorted
  (squeezed horizontally or vertically).

  For the "Deg" version both angles (given and returned) are in degress,
  for the "Rad" version both angles and in radians.

  @groupBegin }
function AdjustViewAngleDegToAspectRatio(const FirstViewAngleDeg,
  SecondToFirstRatio: Single): Single;
function AdjustViewAngleRadToAspectRatio(const FirstViewAngleRad,
  SecondToFirstRatio: Single): Single;
{ @groupEnd }

type
  TProjectionType = (ptOrthographic, ptPerspective);

  { Projection parameters.
    This is calculated when calling @link(TCastleAbstractViewport.Projection). }
  TProjection = object
    { Perspective / orthogonal projection properties.

      When ProjectionType = ptPerspective, then PerspectiveAngles
      specify angles of view (horizontal and vertical), in degrees.
      When ProjectionType = ptOrthographic, then OrthoDimensions
      specify dimensions of ortho window (in the order: -X, -Y, +X, +Y,
      just like X3D OrthoViewpoint.fieldOfView).

      @groupBegin }
    ProjectionType: TProjectionType;
    PerspectiveAngles: TVector2Single;
    OrthoDimensions: TVector4Single;
    { @groupEnd }

    { Projection near/far values.

      Note that ProjectionFar may be ZFarInfinity, which means that no far
      clipping plane is used. For example, shadow volumes require this.

      If you really need to know "what would be projection far,
      if it could not be infinite" look at ProjectionFarFinite.
      ProjectionFarFinite is calculated just like ProjectionFar
      (looking at scene size, NavigationInfo.visibilityLimit and such),
      except it's never changed to be ZFarInfinity.

      @groupBegin }
    ProjectionNear: Single;
    ProjectionFar : Single;
    ProjectionFarFinite: Single;
    { @groupEnd }
  end;

  { Calculate primary rays for given camera settings and screen size. }
  TRaysWindow = class
  private
    FCamPosition, FCamDirection, FCamUp: TVector3Single;
    CamSide: TVector3Single;
  public
    { Camera vectors. Initialized in the constructor.
      Must be given already normalized.
      Note that CamUp may be changed in constructor, to be always perfectly
      orthogonal to CamDirection.

      @groupBegin }
    property CamPosition: TVector3Single read FCamPosition;
    property CamDirection: TVector3Single read FCamDirection;
    property CamUp: TVector3Single read FCamUp;
    { @groupEnd }

    { Constructor. Calculates some values based on camera settings,
      this way many calls to PrimaryRay for the same camera settings
      are fast (useful for ray-tracers). }
    constructor Create(const ACamPosition, ACamDirection, ACamUp: TVector3Single);

    { Create appropriate TRaysWindow instance.
      Constructs non-abstract descendant (TPerspectiveRaysWindow or
      TOrthographicRaysWindow, depending on Projection.ProjectionType). }
    class function CreateDescendant(
      const ACamPosition, ACamDirection, ACamUp: TVector3Single;
      const Projection: TProjection): TRaysWindow;

    { Calculate position and direction of the primary ray cast from CamPosition,
      going through the pixel X, Y.

      X, Y coordinates start from (0, 0) if bottom left (like in typical 2D
      OpenGL).  When they are integers and in the range of
      X = 0..ScreenWidth-1 (left..right), Y = 0..ScreenHeight-1 (bottom..top)
      it's guaranteed that resulting ray will go exactly through the middle
      of the appropriate pixel (on imaginary "rzutnia" = image positioned
      paraller to view direction). But you can provide non-integer X, Y,
      useful for multisampling (taking many samples within the pixel,
      like (X, Y) = (PixX + Random - 0.5, PixY + Random - 0.5)).

      Resulting RayDirection is guaranteed to be normalized
      (this is in practice not costly to us, and it often helps --- when ray
      direction is normalized, various distances from ray collisions are "real"). }
    procedure PrimaryRay(const x, y: Single;
      const ScreenWidth, ScreenHeight: Integer;
      out RayOrigin, RayDirection: TVector3Single); virtual; abstract;
  end;

  TPerspectiveRaysWindow = class(TRaysWindow)
  private
    WindowWidth, WindowHeight: Single;
    PerspectiveAngles: TVector2Single;
  public
    constructor Create(const ACamPosition, ACamDirection, ACamUp: TVector3Single;
      const APerspectiveAngles: TVector2Single);

    procedure PrimaryRay(const x, y: Single;
      const ScreenWidth, ScreenHeight: Integer;
      out RayOrigin, RayDirection: TVector3Single); override;
  end;

  TOrthographicRaysWindow = class(TRaysWindow)
  private
    OrthoDimensions: TVector4Single;
  public
    constructor Create(const ACamPosition, ACamDirection, ACamUp: TVector3Single;
      const AOrthoDimensions: TVector4Single);

    procedure PrimaryRay(const x, y: Single;
      const ScreenWidth, ScreenHeight: Integer;
      out RayOrigin, RayDirection: TVector3Single); override;
  end;

{ Calculate position and direction of the primary ray cast from CamPosition,
  going through the pixel X, Y.
  Takes into account camera 3D settings and screen sizes.
  RayDirection will always be normalized, just like from TRaysWindow.PrimaryRay.

  If you want to call this many times for the same camera settings,
  it may be more optimal to create TRaysWindow instance first
  and call it's TRaysWindow.PrimaryRay method. }
procedure PrimaryRay(const x, y: Single; const ScreenWidth, ScreenHeight: Integer;
  const CamPosition, CamDirection, CamUp: TVector3Single;
  const Projection: TProjection;
  out RayOrigin, RayDirection: TVector3Single);

implementation

uses Math, CastleUtils;

{ AdjustViewAngle*ToAspectRatio ---------------------------------------- }

function AdjustViewAngleRadToAspectRatio(const FirstViewAngleRad, SecondToFirstRatio: Single): Single;
begin
  { Ratio on window sizes is the ratio of angle tangeses.
    For two angles (a, b), and window sizes (x, y) we have
      Tan(a/2) = (x/2)/d
      Tan(b/2) = (y/2)/d
    where D is the distance to projection plane. So
      Tan(a/2) / Tan(b/2) = x/y }

  Result := ArcTan( Tan(FirstViewAngleRad/2) * SecondToFirstRatio) * 2;
end;

function AdjustViewAngleDegToAspectRatio(const FirstViewAngleDeg, SecondToFirstRatio: Single): Single;
begin
  Result := RadToDeg( AdjustViewAngleRadToAspectRatio( DegToRad(FirstViewAngleDeg),
    SecondToFirstRatio));
end;

{ TRaysWindow ------------------------------------------------------------ }

constructor TRaysWindow.Create(
  const ACamPosition, ACamDirection, ACamUp: TVector3Single);
begin
  inherited Create;

  FCamPosition := ACamPosition;
  FCamDirection := ACamDirection;
  FCamUp := ACamUp;

  { fix CamUp }
  MakeVectorsOrthoOnTheirPlane(FCamUp, FCamDirection);

  { CamSide will be normalized, if CamDirection and CamUp are normalized too. }
  CamSide := CamDirection >< CamUp;

  Assert(FloatsEqual(VectorLenSqr(CamDirection), 1.0, 0.01));
  Assert(FloatsEqual(VectorLenSqr(CamUp), 1.0, 0.01));
  Assert(FloatsEqual(VectorLenSqr(CamSide), 1.0, 0.01));
end;

class function TRaysWindow.CreateDescendant(
  const ACamPosition, ACamDirection, ACamUp: TVector3Single;
  const Projection: TProjection): TRaysWindow;
begin
  case Projection.ProjectionType of
    ptPerspective:
      Result := TPerspectiveRaysWindow.Create(
        ACamPosition, ACamDirection, ACamUp, Projection.PerspectiveAngles);
    ptOrthographic:
      Result := TOrthographicRaysWindow.Create(
        ACamPosition, ACamDirection, ACamUp, Projection.OrthoDimensions);
    else raise EInternalError.Create('TRaysWindow.CreateDescendant:ProjectionType?');
  end;
end;

{ TPerspectiveRaysWindow ----------------------------------------------------- }

constructor TPerspectiveRaysWindow.Create(
  const ACamPosition, ACamDirection, ACamUp: TVector3Single;
  const APerspectiveAngles: TVector2Single);
begin
  inherited Create(ACamPosition, ACamDirection, ACamUp);

  PerspectiveAngles := APerspectiveAngles;

  { calculate window parameters, ignoring camera settings.
    We assume distance to projection plane is 1 (this simplifies some calculations,
    and we can choose this distance arbitrarily --- it doesn't matter for user
    of this class).
    We know that WindowWidth / 2 = Tan(ViewAngleX / 2).
    From this, equations below follow. }
  WindowWidth  := Tan(DegToRad(PerspectiveAngles[0]) / 2) * 2;
  WindowHeight := Tan(DegToRad(PerspectiveAngles[1]) / 2) * 2;
end;

procedure TPerspectiveRaysWindow.PrimaryRay(const x, y: Single;
  const ScreenWidth, ScreenHeight: Integer;
  out RayOrigin, RayDirection: TVector3Single);
begin
  RayOrigin := CamPosition;

  { Direction of ray, ignoring current camera settings
    (assume camera position = zero, direction = -Z, up = +Y).
    Integer X, Y values should result in a ray that goes
    right through the middle of the pixel area. }
  RayDirection[0] := MapRange(x+0.5, 0, ScreenWidth , -WindowWidth /2, WindowWidth /2);
  RayDirection[1] := MapRange(y+0.5, 0, ScreenHeight, -WindowHeight/2, WindowHeight/2);
  RayDirection[2] := -1;

  { Transform ray to take camera settings into acount. }
  RayDirection := TransformToCoords(RayDirection, CamSide, CamUp, -CamDirection);

  NormalizeTo1st(RayDirection);
end;

{ TOrthographicRaysWindow ---------------------------------------------------- }

constructor TOrthographicRaysWindow.Create(
  const ACamPosition, ACamDirection, ACamUp: TVector3Single;
  const AOrthoDimensions: TVector4Single);
begin
  inherited Create(ACamPosition, ACamDirection, ACamUp);
  OrthoDimensions := AOrthoDimensions;
end;

procedure TOrthographicRaysWindow.PrimaryRay(const x, y: Single;
  const ScreenWidth, ScreenHeight: Integer;
  out RayOrigin, RayDirection: TVector3Single);
begin
  RayOrigin := CamPosition;
  RayOrigin += VectorScale(CamSide, MapRange(X + 0.5, 0, ScreenWidth,
    OrthoDimensions[0], OrthoDimensions[2]));
  RayOrigin += VectorScale(CamUp, MapRange(Y + 0.5, 0, ScreenHeight,
    OrthoDimensions[1], OrthoDimensions[3]));

  { CamDirection must already be normalized, so RayDirection is normalized too }
  RayDirection := CamDirection;
end;

{ global functions ----------------------------------------------------------- }

procedure PrimaryRay(const x, y: Single; const ScreenWidth, ScreenHeight: Integer;
  const CamPosition, CamDirection, CamUp: TVector3Single;
  const Projection: TProjection;
  out RayOrigin, RayDirection: TVector3Single);
var
  RaysWindow: TRaysWindow;
begin
  RaysWindow := TRaysWindow.CreateDescendant(CamPosition, CamDirection, CamUp,
    Projection);
  try
    RaysWindow.PrimaryRay(x, y, ScreenWidth, ScreenHeight, RayOrigin, RayDirection);
  finally RaysWindow.Free end;
end;

end.