/usr/src/castle-game-engine-6.4/3d/castlecubemaps.pas is in castle-game-engine-src 6.4+dfsg1-2.
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 | {
Copyright 2008-2017 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;
{$I castleconf.inc}
interface
uses Math,
CastleVectors, CastleCompositeImage;
type
TCubeMapSide = CastleCompositeImage.TCubeMapSide;
TCubeMapInfo = record
Dir, Up, Side: TVector3;
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: (Data: ( 1, 0, 0)); Up: (Data: (0, -1, 0)); Side: (Data: ( 0, 0,-1)); ScreenX: 3; ScreenY: 0),
(Dir: (Data: (-1, 0, 0)); Up: (Data: (0, -1, 0)); Side: (Data: ( 0, 0, 1)); ScreenX: 1; ScreenY: 0),
(Dir: (Data: ( 0, 1, 0)); Up: (Data: (0, 0, 1)); Side: (Data: ( 1, 0, 0)); ScreenX: 2; ScreenY: -1),
(Dir: (Data: ( 0, -1, 0)); Up: (Data: (0, 0, -1)); Side: (Data: ( 1, 0, 0)); ScreenX: 2; ScreenY: +1),
(Dir: (Data: ( 0, 0, 1)); Up: (Data: (0, -1, 0)); Side: (Data: ( 1, 0, 0)); ScreenX: 2; ScreenY: 0),
(Dir: (Data: ( 0, 0, -1)); Up: (Data: (0, -1, 0)); Side: (Data: (-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): TVector3;
{ 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: TVector3;
out Side: TCubeMapSide; out Pixel: Cardinal);
type
TCubeMapSide4 = array [0..3] of TCubeMapSide;
{ 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: TVector3;
out Side: TCubeMapSide4;
out Pixel: TVector4Cardinal;
out Ratio: TVector4);
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): TVector3;
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;
Result := Result + 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
);
Result := Result + CubeMapInfo[Side].Up * (-1 + 2 * PixelY/CubeMapSize
+ 1/CubeMapSize);
end;
procedure DirectionToCubeMap(const Dir: TVector3;
out Side: TCubeMapSide; out Pixel: Cardinal);
var
SidePlane: TVector4;
SidePlaneDir: TVector3 absolute SidePlane;
SideCoord: Integer;
SideIntersect: TVector3;
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, TVector3.Zero, Dir) then
raise Exception.CreateFmt('DirectionToCubeMap: direction (%s) doesn''t hit it''s cube map side (%d)',
[Dir.ToRawString, Side]);
{ We're not interested in this coord, this is either 1 or -1.
Having this non-zero would break TVector3.DotProduct (projecting to Side/Up)
in following code. }
SideIntersect[SideCoord] := 0;
PixelX := Round(MapRange(
TVector3.DotProduct(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(
TVector3.DotProduct(SideIntersect, CubeMapInfo[Side].Up),
-1 + 1/CubeMapSize,
1 - 1/CubeMapSize,
0, CubeMapSize - 1));
{ clamp, just to be safe }
ClampVar(PixelX, 0, CubeMapSize - 1);
ClampVar(PixelY, 0, CubeMapSize - 1);
Pixel := PixelY * CubeMapSize + PixelX;
end;
procedure Direction4ToCubeMap(const Dir: TVector3;
out Side: TCubeMapSide4;
out Pixel: TVector4Cardinal;
out Ratio: TVector4);
var
SidePlane: TVector4;
SidePlaneDir: TVector3 absolute SidePlane;
SideCoord: Integer;
SideIntersect: TVector3;
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, TVector3.Zero, Dir) then
raise Exception.CreateFmt('DirectionToCubeMap: direction (%s) doesn''t hit it''s cube map side (%d)',
[Dir.ToRawString, Side[0]]);
{ We're not interested in this coord, this is either 1 or -1.
Having this non-zero would break TVector3.DotProduct (projecting to Side/Up)
in following code. }
SideIntersect[SideCoord] := 0;
PixelFX := MapRange(
TVector3.DotProduct(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(
TVector3.DotProduct(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
ClampVar(PixelX[I], 0, CubeMapSize - 1);
ClampVar(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: TVector3;
DirLength: Single;
begin
Dir := CubeMapDirection(Side, Pixel);
DirLength := Dir.Length;
{ normalize Dir. Since we already have DirLength,
we can just call VectorScale. }
Dir := Dir * (1 / DirLength);
Result := TVector3.DotProduct(Dir, CubeMapInfo[Side].Dir) *
( 4 / Sqr(CubeMapSize) ) /
Sqr(DirLength);
end;
end.
|