This file is indexed.

/usr/src/castle-game-engine-5.2.0/3d/castlecubemaps.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
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
{
  Copyright 2008-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.

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

{ Utilities for cube (environment) maps. }
unit CastleCubeMaps;

interface

uses CastleVectors, Math;

type
  { Cube map faces.

    Order matches order of OpenGL constants
    GL_TEXTURE_CUBE_MAP_POSITIVE/NEGATIVE_X/Y/Z_ARB. }
  TCubeMapSide = (csPositiveX, csNegativeX,
                  csPositiveY, csNegativeY,
                  csPositiveZ, csNegativeZ);
  TCubeMapInfo = record
    Dir, Up, Side: TVector3Single;
    ScreenX, ScreenY: Integer;
  end;

const
  CubeMapSize = 16;

  { Information about cube map faces.

    Names and orientation of faces match precisely OpenGL naming and
    orientation (see http://www.opengl.org/registry/specs/ARB/texture_cube_map.txt),
    so it's straighforward to use this for OpenGL cube maps. }
  CubeMapInfo: array [TCubeMapSide] of TCubeMapInfo =
  ( (Dir: ( 1,  0, 0); Up: (0, -1, 0); Side: ( 0, 0,-1); ScreenX: 3; ScreenY: 0),
    (Dir: (-1,  0, 0); Up: (0, -1, 0); Side: ( 0, 0, 1); ScreenX: 1; ScreenY: 0),

    (Dir: ( 0,  1, 0); Up: (0, 0,  1); Side: ( 1, 0, 0); ScreenX: 2; ScreenY: -1),
    (Dir: ( 0, -1, 0); Up: (0, 0, -1); Side: ( 1, 0, 0); ScreenX: 2; ScreenY: +1),

    (Dir: ( 0, 0,  1); Up: (0, -1, 0); Side: ( 1, 0, 0); ScreenX: 2; ScreenY: 0),
    (Dir: ( 0, 0, -1); Up: (0, -1, 0); Side: (-1, 0, 0); ScreenX: 0; ScreenY: 0)
  );

{ Direction corresponding to given cube map side and pixel number.
  That is, assuming that the middle of cube is placed at (0, 0, 0),
  each pixel of the cube map corresponds to some direction.

  Pixel number must be between 0 and CubeMapSize*2-1.
  It's assumed that pixels
  are in the same order as they are in OpenGL images, that is row-by-row,
  from the lowest row to highest, in each row from left to right.

  Returned vector is @italic(not normalized). }
function CubeMapDirection(const Side: TCubeMapSide;
  const Pixel: Cardinal): TVector3Single;

{ Return cube map side and pixel that is the closest to
  given direction Dir. This is the reverse of CubeMapDirection function.

  Given here Dir need not be normalized, although must not be zero. }
procedure DirectionToCubeMap(const Dir: TVector3Single;
  out Side: TCubeMapSide; out Pixel: Cardinal);

type
  TCubeMapSide4 = array [0..3] of TCubeMapSide;
  TCardinal4 = array [0..3] of Cardinal;

{ Return 4 cube map indexes (side and pixel, along with ratio)
  that are closest to given direction Dir.
  All ratios will sum to 1.

  This is like DirectionToCubeMap, except this returns 4 values.
  It allows you to do bilinear interpolation between cube map items. }
procedure Direction4ToCubeMap(const Dir: TVector3Single;
  out Side: TCubeMapSide4;
  out Pixel: TVector4Cardinal;
  out Ratio: TVector4Single);

type
  { Cube map, with each item being a Float. }
  TCubeMapFloat = array [TCubeMapSide, 0..Sqr(CubeMapSize) - 1] of Float;
  PCubeMapFloat = ^TCubeMapFloat;

  { Cube map, with each item being in 0..1 range, encoded as a Byte.
    This assumes that every item is actually a float in 0..1 range,
    encoded as Byte. }
  TCubeMapByte = array [TCubeMapSide, 0..Sqr(CubeMapSize) - 1] of Byte;
  PCubeMapByte = ^TCubeMapByte;

{ Calculate solid angle of given pixel on the cube map. }
function CubeMapSolidAngle(const Side: TCubeMapSide;
  const Pixel: Cardinal): Single;

implementation

uses SysUtils, CastleUtils;

{ Note: CubeMapSolidAngle assumes that implementation of this actually
  returns the position of the middle of the pixel. That is, it assumes
  you don't normalize the returned direction. }
function CubeMapDirection(const Side: TCubeMapSide;
  const Pixel: Cardinal): TVector3Single;
var
  PixelX, PixelY: Cardinal;
begin
  PixelX := Pixel mod CubeMapSize;
  PixelY := Pixel div CubeMapSize;
  { Result = exactly CubeMapInfo[Side].Dir when
    PixelX/Y = CubeMapSize/2 (pixel is on the middle of the image). }
  Result := CubeMapInfo[Side].Dir;
  VectorAddTo1st(Result,
    VectorScale(CubeMapInfo[Side].Side, -1 + 2 * PixelX/CubeMapSize

    { We want the generated direction to be exactly in the middle of
      cube map pixel.

      Reasons? For perfection, and to be synchronized with captured
      images (from OpenGL, and such), and to be synchronized with
      DirectionToCubeMap, and to cover the cube map most fairly.

      So we want to have results for min PixelX (0) and max PixelX
      (CubeMapSize - 1) have the same distance from middle (absolute value).
      For "-1 + 2 * PixelX/CubeMapSize", we have min = -1,
      max = -1 + 2 * (1 - 1/CubeMapSize) = -1 + 2 - 2/CubeMapSize
      = 1 - 2/CubeMapSize.

      So if we just add 1/CubeMapSize, we'll have
      min = -1 + 1/CubeMapSize,
      max =  1 - 1/CubeMapSize,
      so all will be perfect. }
      + 1/CubeMapSize
    ));
  VectorAddTo1st(Result,
    VectorScale(CubeMapInfo[Side].Up  , -1 + 2 * PixelY/CubeMapSize
      + 1/CubeMapSize));
end;

procedure DirectionToCubeMap(const Dir: TVector3Single;
  out Side: TCubeMapSide; out Pixel: Cardinal);
var
  SidePlane: TVector4Single;
  SidePlaneDir: TVector3Single absolute SidePlane;
  SideCoord: Integer;
  SideIntersect: TVector3Single;
  PixelX, PixelY: Integer;
begin
  SideCoord := MaxAbsVectorCoord(Dir);
  case SideCoord of
    0: if Dir[0] >= 0 then Side := csPositiveX else Side := csNegativeX;
    1: if Dir[1] >= 0 then Side := csPositiveY else Side := csNegativeY;
    2: if Dir[2] >= 0 then Side := csPositiveZ else Side := csNegativeZ;
  end;

  SidePlaneDir := CubeMapInfo[Side].Dir;
  SidePlane[3] := -1;

  if not TryPlaneRayIntersection(SideIntersect,
    SidePlane, ZeroVector3Single, Dir) then
    raise Exception.CreateFmt('DirectionToCubeMap: direction (%s) doesn''t hit it''s cube map side (%d)',
      [VectorToRawStr(Dir), Side]);

  { We're not interested in this coord, this is either 1 or -1.
    Having this non-zero would break VectorDotProduct (projecting to Side/Up)
    in following code. }
  SideIntersect[SideCoord] := 0;

  PixelX := Round(MapRange(
    VectorDotProduct(SideIntersect, CubeMapInfo[Side].Side),
    { 1/CubeMapSize here, to take into account that the perfect ray
      goes exactly through the middle pixel of cube map pixel.
      See CubeMapDirection reasoning. }
    -1 + 1/CubeMapSize,
     1 - 1/CubeMapSize,
    0, CubeMapSize - 1));

  PixelY := Round(MapRange(
    VectorDotProduct(SideIntersect, CubeMapInfo[Side].Up),
    -1 + 1/CubeMapSize,
     1 - 1/CubeMapSize,
    0, CubeMapSize - 1));

  { clamp, just to be safe }
  Clamp(PixelX, 0, CubeMapSize - 1);
  Clamp(PixelY, 0, CubeMapSize - 1);

  Pixel := PixelY * CubeMapSize + PixelX;
end;

procedure Direction4ToCubeMap(const Dir: TVector3Single;
  out Side: TCubeMapSide4;
  out Pixel: TVector4Cardinal;
  out Ratio: TVector4Single);
var
  SidePlane: TVector4Single;
  SidePlaneDir: TVector3Single absolute SidePlane;
  SideCoord: Integer;
  SideIntersect: TVector3Single;
  PixelFX, PixelFY, PixelXFrac, PixelYFrac: Single;
  PixelX, PixelY: array [0..3] of Cardinal;
  I: Cardinal;
  PixelXTrunc, PixelYTrunc: Integer;
begin
  SideCoord := MaxAbsVectorCoord(Dir);
  case SideCoord of
    0: if Dir[0] >= 0 then Side[0] := csPositiveX else Side[0] := csNegativeX;
    1: if Dir[1] >= 0 then Side[0] := csPositiveY else Side[0] := csNegativeY;
    2: if Dir[2] >= 0 then Side[0] := csPositiveZ else Side[0] := csNegativeZ;
  end;

  { TODO: for now, all four sides are always equal.
    This means that bilinear interpolation using this will look
    wrong (have aliasing) on the edges of cube map. }
  Side[1] := Side[0];
  Side[2] := Side[0];
  Side[3] := Side[0];

  SidePlaneDir := CubeMapInfo[Side[0]].Dir;
  SidePlane[3] := -1;

  if not TryPlaneRayIntersection(SideIntersect,
    SidePlane, ZeroVector3Single, Dir) then
    raise Exception.CreateFmt('DirectionToCubeMap: direction (%s) doesn''t hit it''s cube map side (%d)',
      [VectorToRawStr(Dir), Side[0]]);

  { We're not interested in this coord, this is either 1 or -1.
    Having this non-zero would break VectorDotProduct (projecting to Side/Up)
    in following code. }
  SideIntersect[SideCoord] := 0;

  PixelFX := MapRange(
    VectorDotProduct(SideIntersect, CubeMapInfo[Side[0]].Side),
    { 1/CubeMapSize here, to take into account that the perfect ray
      goes exactly through the middle pixel of cube map pixel.
      See CubeMapDirection reasoning. }
    -1 + 1/CubeMapSize,
     1 - 1/CubeMapSize,
    0, CubeMapSize - 1);

  PixelFY := MapRange(
    VectorDotProduct(SideIntersect, CubeMapInfo[Side[0]].Up),
    -1 + 1/CubeMapSize,
     1 - 1/CubeMapSize,
    0, CubeMapSize - 1);

  PixelXTrunc := Trunc(PixelFX);
  PixelYTrunc := Trunc(PixelFY);
  PixelXFrac := Frac(PixelFX);
  PixelYFrac := Frac(PixelFY);

  PixelX[0] := PixelXTrunc;
  PixelY[0] := PixelYTrunc;
  Ratio[0] := (1-PixelXFrac) * (1-PixelYFrac);

  PixelX[1] := PixelXTrunc+1;
  PixelY[1] := PixelYTrunc;
  Ratio[1] := PixelXFrac * (1-PixelYFrac);

  PixelX[2] := PixelXTrunc;
  PixelY[2] := PixelYTrunc+1;
  Ratio[2] := (1-PixelXFrac) * PixelYFrac;

  PixelX[3] := PixelXTrunc+1;
  PixelY[3] := PixelYTrunc+1;
  Ratio[3] := PixelXFrac * PixelYFrac;

  { test: Writeln((Ratio[0] + Ratio[1] + Ratio[2] + Ratio[3]):1:10); }

  { clamp, just to be safe }
  for I := 0 to 3 do
  begin
    Clamp(PixelX[I], 0, CubeMapSize - 1);
    Clamp(PixelY[I], 0, CubeMapSize - 1);
    Pixel[I] := PixelY[I] * CubeMapSize + PixelX[I];
  end;
end;

function CubeMapSolidAngle(const Side: TCubeMapSide;
  const Pixel: Cardinal): Single;

{ An approximation of solid angle valid for small angles is:

  cos(angle between vector from zero through middle of the polygon
            and normal vector of polygon)
  * polygon area
  / Sqr(distance from zero to middle of the polygon)

  "middle of the polygon" = just Dir (we depend here on CubeMapDirection
  implementation --- this is Ok, we're in the same unit).

  The cos(...) = vector dot product between normalized(dir) and normal of
  this side.

  The area is always Sqr(2/CubeMapSize) (since cube map = cube of size 2,
  each side has CubeMapSize * CubeMapSize pixels. }

{ A note on accuracy: I tried to return here Float, and use Float values
  in the middle, but testing with
  castle_game_engine/tests/testcubemap.pas
  shows that it doesn't matter. Single is enough, Double or Extended
  doesn't improve it. }

var
  Dir: TVector3Single;
  DirLength: Single;
begin
  Dir := CubeMapDirection(Side, Pixel);
  DirLength := VectorLen(Dir);

  { normalize Dir. Since we already have DirLength,
    we can just call VectorScale. }
  VectorScaleTo1st(Dir, 1/DirLength);

  Result := VectorDotProduct(Dir, CubeMapInfo[Side].Dir) *
    ( 4 / Sqr(CubeMapSize) ) /
    Sqr(DirLength);
end;

end.