/usr/src/castle-game-engine-5.2.0/x3d/x3dloadinternalspine_atlas.inc is in castle-game-engine-src 5.2.0-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 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 | {
Copyright 2014-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.
----------------------------------------------------------------------------
}
{ Spine atlas. }
type
TAtlasRegion = class
public
Name: string;
Rotate: boolean;
XY, Size, Orig, Offset: TVector2Integer;
TexCoord: TQuadTexCoord;
{ When Spine atlas creator uses whitespace compression for texture,
the actual 3D points need to be squeezed a little to show smaller
part of the texture. Without compression, is starts at (0,0)
and ends at (1,1), with compression it is a little smaller. }
TexRect: TQuadTexRect;
Index: Integer;
procedure CalculateTexCoord(const ImageWidth, ImageHeight: Integer);
end;
TAtlasRegionList = specialize TFPGObjectList<TAtlasRegion>;
TAtlasPage = class
public
TextureURL: string;
Format: string;
Filter: string; //< a value allowed by TextureProperties.MinificationFilter and MagnificationFilter
IsRepeat: boolean;
Size: TVector2Integer; //< atlas may (but does not have to) contain this
Regions: TAtlasRegionList;
Node: TImageTextureNode;
NodeUsedAsChild: boolean;
constructor Create;
destructor Destroy; override;
procedure BuildNodes(const BaseUrl: string);
end;
TAtlasPageList = specialize TFPGObjectList<TAtlasPage>;
TAtlas = class(TTextureLoader)
Pages: TAtlasPageList;
constructor Create;
destructor Destroy; override;
{ Read .atlas file as produced by Spine, in format of libgdx, see
https://github.com/libgdx/libgdx/wiki/Texture-packer }
procedure Parse(const URL: string);
procedure BuildNodes(const BaseUrl: string);
{ Find atlas page and region corresponding to given region name.
@raises ESpineReadError If bone does not exist. }
procedure Find(const RegionName: string; out Page: TAtlasPage; out Region: TAtlasRegion);
function UseNode(const AttachmentName: string;
out TexCoord: TQuadTexCoord; out TexRect: TQuadTexRect): TImageTextureNode; override;
end;
procedure TAtlasRegion.CalculateTexCoord(const ImageWidth, ImageHeight: Integer);
var
TextureXY, TextureSize: TVector2Single;
I: Integer;
begin
TextureXY := Vector2Single(
XY[0] / ImageWidth,
XY[1] / ImageHeight);
TexRect[0][0] := Offset[0] / Orig[0];
TexRect[0][1] := Offset[1] / Orig[1];
TexRect[1][0] := (Offset[0] + Size[0]) / Orig[0];
TexRect[1][1] := (Offset[1] + Size[1]) / Orig[1];
if Rotate then
begin
TextureSize := Vector2Single(
Size[0] / ImageHeight,
Size[1] / ImageWidth);
TextureXY[1] :=
{ flip top-bottom }
1 - TextureXY[1]
{ move corner to bottom }
- TextureSize[0];
end else
begin
TextureSize := Vector2Single(
Size[0] / ImageWidth,
Size[1] / ImageHeight);
TextureXY[1] :=
{ flip top-bottom }
1 - TextureXY[1]
{ move corner to bottom }
- TextureSize[1];
end;
if Rotate then
begin
TexCoord[0] := Vector2Single(TextureSize[1], 0);
TexCoord[1] := Vector2Single(TextureSize[1], TextureSize[0]);
TexCoord[2] := Vector2Single( 0, TextureSize[0]);
TexCoord[3] := Vector2Single( 0, 0);
end else
begin
TexCoord[0] := Vector2Single( 0, 0);
TexCoord[1] := Vector2Single(TextureSize[0], 0);
TexCoord[2] := Vector2Single(TextureSize[0], TextureSize[1]);
TexCoord[3] := Vector2Single( 0, TextureSize[1]);
end;
for I := 0 to 3 do
TexCoord[I] := TexCoord[I] + TextureXY;
end;
constructor TAtlasPage.Create;
begin
inherited;
Regions := TAtlasRegionList.Create;
end;
destructor TAtlasPage.Destroy;
begin
FreeAndNil(Regions);
if NodeUsedAsChild then
{ in case NodeUsedAsChild, don't even try FreeIfUnusedAndNil,
as the check "is it unused" may already cause access violation
since it may be already freed by freeing parent. }
Node := nil else
FreeIfUnusedAndNil(Node);
inherited;
end;
procedure TAtlasPage.BuildNodes(const BaseUrl: string);
var
I: Integer;
begin
Node := TImageTextureNode.Create('Page_' + ToX3DName(TextureURL), BaseUrl);
Node.FdUrl.Items.Add(TextureURL);
Node.RepeatS := IsRepeat;
Node.RepeatT := IsRepeat;
{ The only way to calculate TextureXY and TextureSize is to actually load the texture.
We use the texture Node for this, this way loaded texture will be reused for
actual model rendering. }
Node.IsTextureLoaded := true;
if Node.IsTextureImage then
begin
for I := 0 to Regions.Count - 1 do
Regions[I].CalculateTexCoord(Node.TextureImage.Width, Node.TextureImage.Height);
end else
OnWarning(wtMajor, 'Spine', SysUtils.Format('Cannot load texture "%s", texture coordinates cannot be correctly calculated based on Spine atlas information',
[TextureURL]));
end;
constructor TAtlas.Create;
begin
inherited;
Pages := TAtlasPageList.Create;
end;
destructor TAtlas.Destroy;
begin
FreeAndNil(Pages);
inherited;
end;
procedure TAtlas.Parse(const URL: string);
{ Split a Line divided by character Separator into two strings.
Assumes that whitespace doesn't matter (so we trim it),
and Name must not be empty. }
function Split(const Line: string; const Separator: char;
out Name, Value: string): boolean;
var
Index: Integer;
begin
Result := false;
Index := Pos(Separator, Line);
if Index <> 0 then
begin
Name := Trim(Copy(Line, 1, Index - 1));
Value := Trim(SEnding(Line, Index + 1));
if Name <> '' then
Result := true;
end;
end;
function IsNameValueString(const Line, Name: string; out Value: string): boolean;
var
N, V: string;
begin
Result := Split(Line, ':', N, V) and (N = Name);
if Result then
Value := V;
end;
function IsNameValueBoolean(const Line, Name: string; out Value: boolean): boolean;
var
ValueStr: string;
begin
Result := IsNameValueString(Line, Name, ValueStr);
if Result then
begin
if ValueStr = 'false' then
Value := false else
if ValueStr = 'true' then
Value := true else
raise ESpineReadError.CreateFmt('Invalid boolean value "%s"', [ValueStr]);
end;
end;
function IsNameValueInteger(const Line, Name: string; out Value: Integer): boolean;
var
ValueStr: string;
begin
Result := IsNameValueString(Line, Name, ValueStr);
if Result then
begin
try
Value := StrToInt(ValueStr);
except
on E: EConvertError do
raise ESpineReadError.CreateFmt('Invalid integer value "%s": %s', [ValueStr, E.Message]);
end;
end;
end;
function IsNameValueVector2Integer(const Line, Name: string; out Vector: TVector2Integer): boolean;
var
ValueStr, ValueStr0, ValueStr1: string;
begin
Result := IsNameValueString(Line, Name, ValueStr);
if Result then
begin
if Split(ValueStr, ',', ValueStr0, ValueStr1) then
try
Vector[0] := StrToInt(ValueStr0);
Vector[1] := StrToInt(ValueStr1);
except
on E: EConvertError do
raise ESpineReadError.CreateFmt('Invalid integer value in vector of 2 integers "%s": %s', [ValueStr, E.Message]);
end else
raise ESpineReadError.CreateFmt('Cannot split a vector of 2 integers "%s" by a comma', [ValueStr]);
end;
end;
function IsNameValueFilter(const Line, Name: string; out Filter: string): boolean;
var
ValueStr: string;
begin
Result := IsNameValueString(Line, Name, ValueStr);
if Result then
begin
if ValueStr = 'Linear,Linear' then
Filter := 'AVG_PIXEL' else
if ValueStr = 'Nearest,Nearest' then
Filter := 'NEAREST_PIXEL' else
raise ESpineReadError.CreateFmt('Unsupported filter mode "%s"', [ValueStr]);
end;
end;
function IsNameValueRepeat(const Line, Name: string; out IsRepeat: boolean): boolean;
var
ValueStr: string;
begin
Result := IsNameValueString(Line, Name, ValueStr);
if Result then
begin
if ValueStr = 'none' then
IsRepeat := false else
{ is there anything else allowed for repeat: field ? }
raise ESpineReadError.CreateFmt('Unsupported repeat mode "%s"', [ValueStr]);
end;
end;
var
Reader: TTextReader;
Page: TAtlasPage;
Region: TAtlasRegion;
Line: string;
begin
Page := nil;
Region := nil;
Reader := TTextReader.Create(URL);
try
while not Reader.Eof do
begin
Line := Reader.Readln;
if Page = nil then
begin
{ start atlas page }
if Trim(Line) <> '' then
begin
Page := TAtlasPage.Create;
Page.TextureURL := Trim(Line);
Pages.Add(Page);
end;
end else
if Trim(Line) = '' then
{ end atlas page }
Page := nil else
if Line[1] <> ' ' then
begin
{ read per-page (but not per-region) info }
Region := nil;
if IsNameValueString(Line, 'format', Page.Format) then else
if IsNameValueFilter(Line, 'filter', Page.Filter) then else
if IsNameValueRepeat(Line, 'repeat', Page.IsRepeat) then else
if IsNameValueVector2Integer(Line, 'size', Page.Size) then else
if Pos(':', Line) <> 0 then
raise ESpineReadError.CreateFmt('Unhandled name:value pair "%s"', [Line]) else
begin
{ new region }
Region := TAtlasRegion.Create;
Region.Name := Line;
Page.Regions.Add(Region);
end;
end else
if Region <> nil then
begin
{ read per-region info }
if IsNameValueBoolean(Line, 'rotate', Region.Rotate) then else
if IsNameValueVector2Integer(Line, 'xy', Region.XY) then else
if IsNameValueVector2Integer(Line, 'size', Region.Size) then else
if IsNameValueVector2Integer(Line, 'orig', Region.Orig) then else
if IsNameValueVector2Integer(Line, 'offset', Region.Offset) then else
if IsNameValueInteger(Line, 'index', Region.Index) then else
raise ESpineReadError.CreateFmt('Unhandled name:value pair "%s"', [Line]);
end else
raise ESpineReadError.Create('Atlas file contains indented line, but no region name specified');
end;
finally FreeAndNil(Reader) end;
WritelnLog('Spine', Format('Atlas parsed, pages: %d', [Pages.Count]));
end;
procedure TAtlas.BuildNodes(const BaseUrl: string);
var
I: Integer;
begin
for I := 0 to Pages.Count - 1 do
Pages[I].BuildNodes(BaseUrl);
end;
procedure TAtlas.Find(const RegionName: string;
out Page: TAtlasPage; out Region: TAtlasRegion);
var
I, J: Integer;
begin
for I := 0 to Pages.Count - 1 do
begin
Page := Pages[I];
for J := 0 to Page.Regions.Count - 1 do
begin
Region := Page.Regions[J];
if Region.Name = RegionName then
Exit;
end;
end;
raise ESpineReadError.CreateFmt('Region name "%s" not found in atlas', [RegionName]);
end;
function TAtlas.UseNode(const AttachmentName: string;
out TexCoord: TQuadTexCoord; out TexRect: TQuadTexRect): TImageTextureNode;
var
AtlasPage: TAtlasPage;
AtlasRegion: TAtlasRegion;
begin
Find(AttachmentName, AtlasPage, AtlasRegion);
Result := AtlasPage.Node;
AtlasPage.NodeUsedAsChild := true;
TexRect := AtlasRegion.TexRect;
TexCoord := AtlasRegion.TexCoord;
end;
|