This file is indexed.

/usr/src/castle-game-engine-5.2.0/x3d/x3dloadinternalspine_slottimelines.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
{
  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 slot timelines. }

{$ifdef read_interface}
  { Node with key field and set_fraction event. Like interpolators,
    but not only (e.g. IntegerSequencer). }
  TKeyedNode = object
    EventSet_Fraction: TSFFloatEvent;
    Key: TMFFloat;
    Node: TX3DNode;
    UsedAsChild: boolean;
  end;

  TSlotTimeline = class
  private
    FMaxTime: Single;
  public
    Slot: TSlot;
    Time: TSingleList;
    Node1, Node2: TKeyedNode;
    HasNode2: boolean;
    property MaxTime: Single read FMaxTime;
    constructor Create;
    destructor Destroy; override;
    procedure Parse(const Json: TJSONArray);
    procedure ParseSingleValue(const Json: TJSONObject); virtual; abstract;
    procedure BuildNodes(const BaseUrl: string;
      const MaxAnimationTime: Single; const Container: TX3DRootNode;
      const SlotsToReset: TSlotsAnimated); virtual;
  end;

  TSlotTimelineAttachment = class(TSlotTimeline)
    Attachments: TStringList;
    constructor Create;
    destructor Destroy; override;
    procedure ParseSingleValue(const Json: TJSONObject); override;
    procedure BuildNodes(const BaseUrl: string;
      const MaxAnimationTime: Single; const Container: TX3DRootNode;
      const SlotsToReset: TSlotsAnimated); override;
  end;

  TSlotTimelineColor = class(TSlotTimeline)
    Colors: TVector4SingleList;
    Curve: boolean;
    CurveControlPoints: TVector4SingleList;
    constructor Create;
    destructor Destroy; override;
    procedure ParseSingleValue(const Json: TJSONObject); override;
    procedure BuildNodes(const BaseUrl: string;
      const MaxAnimationTime: Single; const Container: TX3DRootNode;
      const SlotsToReset: TSlotsAnimated); override;
  end;

  TSlotTimelineList = class(specialize TFPGObjectList<TSlotTimeline>)
  end;
{$endif}

{$ifdef read_implementation}

{ TSlotTimeline -------------------------------------------------------------- }

constructor TSlotTimeline.Create;
begin
  inherited;
  Time := TSingleList.Create;
end;

destructor TSlotTimeline.Destroy;
begin
  FreeAndNil(Time);
  inherited;
end;

procedure TSlotTimeline.Parse(const Json: TJSONArray);
var
  I: Integer;
  O: TJSONObject;
  NextTime: Single;
begin
  for I := 0 to Json.Count - 1 do
    if Json[I] is TJSONObject then
    begin
      O := TJSONObject(Json[I]);

      NextTime := O.Get('time', 0.0);
      if (Time.Count <> 0) and (Time.Last > NextTime) then
        raise ESpineReadError.Create('Timeline must have increasing time values');
      Time.Add(NextTime);
      FMaxTime := NextTime;

      ParseSingleValue(O);
    end;
end;

procedure TSlotTimeline.BuildNodes(const BaseUrl: string;
  const MaxAnimationTime: Single; const Container: TX3DRootNode;
  const SlotsToReset: TSlotsAnimated);

  procedure FillKeys(var Node: TKeyedNode);
  var
    I: Integer;
  begin
    Node.Key.Items.Clear;
    for I := 0 to Time.Count - 1 do
      Node.Key.Items.Add(Time[I] / MaxAnimationTime);
    Node.UsedAsChild := true;
    Container.FdChildren.Add(Node.Node);
  end;

begin
  { We assume that descendant already created Node1/2 in overridden BuildNodes }
  FillKeys(Node1);
  if HasNode2 then
    FillKeys(Node2);
end;

{ TSlotTimelineAttachment ------------------------------------------------------- }

constructor TSlotTimelineAttachment.Create;
begin
  inherited Create;
  Attachments := TStringList.Create;
end;

destructor TSlotTimelineAttachment.Destroy;
begin
  FreeAndNil(Attachments);
  inherited;
end;

procedure TSlotTimelineAttachment.ParseSingleValue(const Json: TJSONObject);
var
  AttachmentName: string;
begin
  AttachmentName := Json.Get('name', '');
  Slot.AttachmentNames.Add(AttachmentName);
  Attachments.Add(AttachmentName);
end;

procedure TSlotTimelineAttachment.BuildNodes(const BaseUrl: string;
  const MaxAnimationTime: Single; const Container: TX3DRootNode;
  const SlotsToReset: TSlotsAnimated);
var
  I: Integer;
  N: TIntegerSequencerNode;
  EventValueChanged: TX3DEvent;
  Route: TX3DRoute;
begin
  N := TIntegerSequencerNode.Create('SlotTimeline_attachment_' + ToX3DName(Slot.Name));
  N.FdForceContinousValue_Changed.Value := true;
  for I := 0 to Attachments.Count - 1 do
    N.FdKeyValue.Items.Add(Slot.AttachmentNames.IndexOf(Attachments[I]));
  EventValueChanged := N.EventValue_changed;
  Node1.EventSet_Fraction := N.EventSet_Fraction;
  Node1.Key := N.FdKey;
  Node1.Node := N;

  inherited;

  { When there's only 1 keyframe, Spine does something weird: instead of smooth
    interpolation, animation suddenly jumps from setup pose to given keyframe pose
    at given key time (unless animation is looping and it's the last frame,
    then it's visibly ignored). We just ignore (do not route) such weird animations for now. }
  if Time.Count > 1 then
  begin
    Route := TX3DRoute.Create;
    Route.SetSourceDirectly(EventValueChanged);
    Route.SetDestinationDirectly(Slot.SwitchNode.FdWhichChoice.EventIn);
    Container.Routes.Add(Route);

    if SlotsToReset.Attachment.Remove(Slot) = -1 then
      OnWarning(wtMajor, 'Spine', 'Multiple slot timelines affect slot attachment: slot ' + Slot.Name);
  end;
end;

{ TSlotTimelineColor --------------------------------------------------------- }

constructor TSlotTimelineColor.Create;
begin
  inherited Create;
  Colors := TVector4SingleList.Create;
  CurveControlPoints := TVector4SingleList.Create;
end;

destructor TSlotTimelineColor.Destroy;
begin
  FreeAndNil(Colors);
  FreeAndNil(CurveControlPoints);
  inherited;
end;

procedure TSlotTimelineColor.ParseSingleValue(const Json: TJSONObject);
var
  ControlPoints: TVector4Single;
  CurveTypeJson: TJSONData;
begin
  ControlPoints := Vector4Single(0, 0, 1, 1); // default value, in case curve is later changed from linear to curve
  CurveTypeJson := Json.Find('curve');
  if CurveTypeJson <> nil then
  begin
    if CurveTypeJson is TJSONArray then
    begin
      if TJSONArray(CurveTypeJson).Count <> 4 then
        OnWarning(wtMinor, 'Spine', 'Curve type interpolation is an array, but does not have 4 elements (required for Bezier curve array)') else
      begin
        Curve := true;
        ControlPoints[0] := TJSONArray(CurveTypeJson).Floats[0];
        ControlPoints[1] := TJSONArray(CurveTypeJson).Floats[1];
        ControlPoints[2] := TJSONArray(CurveTypeJson).Floats[2];
        ControlPoints[3] := TJSONArray(CurveTypeJson).Floats[3];
      end;
    end;

    { For now, silently ignore warning that we don't handle curve type
      'stepped'. Spine optimizes it's curves by using 'stepped' where
      previous and next values are equal, so this is common, and we would
      flood the warnings console for many models because of this (while in fact
      models are handled Ok since 'stepped' is only for optimization). }
    { else
    if CurveTypeJson.AsString <> 'linear' then
      OnWarning(wtMinor, 'Spine', 'Found "' + CurveTypeJson.AsString + '" interpolation type on slot timeline of slot ' + Slot.Name + ', we do not support this interpolation type');
    }
  end;
  CurveControlPoints.Add(ControlPoints);

  Colors.Add(HexToColor(Json.Get('color', 'ffffffff')));
end;

procedure TSlotTimelineColor.BuildNodes(const BaseUrl: string;
  const MaxAnimationTime: Single; const Container: TX3DRootNode;
  const SlotsToReset: TSlotsAnimated);
var
  I: Integer;
  N: TColorInterpolatorNode;
  NA: TScalarInterpolatorNode;
  EventValueChanged1, EventValueChanged2: TX3DEvent;
  Route: TX3DRoute;
begin
(* TODO: colors on curve:
  if Curve then
  begin
    NC := TCubicBezierColorInterpolatorNode.Create('SlotTimeline_color_' + ToX3DName(Slot.Name));
    for I := 0 to Colors.Count - 1 do
    begin
      NC.FdKeyValue.Items.Add(Colors[I]);
      NC.FdControlPoints.Items.Add(CurveControlPoints[I]);
    end;
    EventValueChanged := NC.EventValue_changed;
    Node := NC;
  end else
  *)
  begin
    N := TColorInterpolatorNode.Create('SlotTimeline_color_' + ToX3DName(Slot.Name));
    for I := 0 to Colors.Count - 1 do
      N.FdKeyValue.Items.Add(Vector3SingleCut(Colors[I]));
    EventValueChanged1 := N.EventValue_changed;
    Node1.EventSet_Fraction := N.EventSet_Fraction;
    Node1.Node := N;
    Node1.Key := N.FdKey;

    NA := TScalarInterpolatorNode.Create('SlotTimeline_alpha_' + ToX3DName(Slot.Name));
    for I := 0 to Colors.Count - 1 do
      NA.FdKeyValue.Items.Add(1 - Colors[I][3]);
    EventValueChanged2 := NA.EventValue_changed;
    Node2.EventSet_Fraction := NA.EventSet_Fraction;
    Node2.Node := NA;
    Node2.Key := NA.FdKey;
    HasNode2 := true;
  end;

  inherited;

  { When there's only 1 keyframe, Spine does something weird: instead of smooth
    interpolation, animation suddenly jumps from setup pose to given keyframe pose
    at given key time (unless animation is looping and it's the last frame,
    then it's visibly ignored). We just ignore (do not route) such weird animations for now. }
  if Time.Count > 1 then
  begin
    for I := 0 to Slot.Materials.Count - 1 do
    begin
      Route := TX3DRoute.Create;
      Route.SetSourceDirectly(EventValueChanged1);
      Route.SetDestinationDirectly(Slot.Materials[I].FdEmissiveColor);
      Container.Routes.Add(Route);

      Route := TX3DRoute.Create;
      Route.SetSourceDirectly(EventValueChanged2);
      Route.SetDestinationDirectly(Slot.Materials[I].FdTransparency);
      Container.Routes.Add(Route);
    end;

    if SlotsToReset.Color.Remove(Slot) = -1 then
      OnWarning(wtMajor, 'Spine', 'Multiple slot timelines affect slot color: slot ' + Slot.Name);
  end;
end;

{$endif}