This file is indexed.

/usr/src/castle-game-engine-5.0.0/x3d/x3dnodes_extrusion.inc is in castle-game-engine-src 5.0.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
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
{
  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.

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


type
  { Utility for dealing with Extrusion.

   Calculating vertices (and other properties) of Extrusion
   is non-trivial enough to be separated into this file. We want to reuse
   this by TExtrusionNode.BoundingBox calculation,
   TExtrusionNode.Triangulate etc. }
  TVRMLExtrusion = class
  private
    FHigh: Integer;
    FNode: TExtrusionNode;
    FSpineClosed, FCrossSectionClosed: boolean;
    FCrossSectionOmit: Cardinal;
    FBeginEndCapsMatching: boolean;
    procedure SetNode(Value: TExtrusionNode);
  public
    constructor Create;

    { Node used. Always assign something non-nil, and generally
      use rest of this class only when this is assigned. }
    property Node: TExtrusionNode read FNode write SetNode;

    { You have spines from 0 to High.
      Remember that this can be -1, if no spine points are defined.

      You can safely ask SpineXxxTransform about various values between
      0..High. }
    property High: Integer read FHigh;

    { Same thing as @link(TExtrusionNode.SpineClosed Node.SpineClosed) and
      @link(TExtrusionNode.CrossSectionClosed Node.CrossSectionClosed),
      just calculated once (when setting Node) for speed.

      @groupBegin }
    property SpineClosed: boolean read FSpineClosed;
    property CrossSectionClosed: boolean read FCrossSectionClosed;
    { @groupEnd }

    { If we should omit a first (or last) vertex when rendering caps,
      this is 1, otherwise it is 0.
      When crossSection is closed, we really should do it,
      otherwise our concave triangulation may get confused ---
      see extrusion_concave_cap.wrl testcase. }
    property CrossSectionOmit: Cardinal read FCrossSectionOmit;

    { Are begin and end caps at the same place.

      This is a stronger condition
      than just a SpineClosed: whole SpineTransformTo1st must be
      guaranteed the same at the beginning and end. The Extrusion rules are such
      that closed spine -> always produces the same automatic orientation
      calculated at the beginning and end (X, Y, Z vectors in
      TVRMLExtrusion.SpineTransformTo1st implementation). But we also
      have to compare Orientation and Scale factors --- only when they
      also match, the caps match. }
    property BeginEndCapsMatching: boolean read FBeginEndCapsMatching;

    { If Spine > 0, LastY and LastZ must contain what was set here by calling
      SpineTransformTo1st(Spine - 1, LastY, LastZ). }
    procedure SpineTransformTo1st(Spine: Cardinal;
      var LastY, LastZ: TVector3Single;
      out Transform: TMatrix4Single);

    procedure SpineScaleTo1st(Spine: Cardinal;
      out Scale: TVector2Single);

    procedure SpineOrientationTo1st(Spine: Cardinal;
      out Orientation: TVector4Single);
  end;

constructor TVRMLExtrusion.Create;
begin
  inherited;
end;

procedure TVRMLExtrusion.SetNode(Value: TExtrusionNode);
var
  BeginOrientation, EndOrientation: TVector4Single;
  BeginScale, EndScale: TVector2Single;
begin
  FNode := Value;

  { calculate FHigh }

  if Node.FdSpine.Count = 0 then
  begin
    FHigh := -1;
    { ... and don't even check scale/orientation }
  end else
  if (Node.FdScale.Count = 0) or
     (Node.FdOrientation.Count = 0) then
  begin
    OnWarning(wtMajor, 'VRML/X3D', 'Extrusion has no scale or orientation specified');
    FHigh := -1;
  end else
  begin
    FHigh := Node.FdSpine.Count - 1;

    { We will handle all other scale/orientation counts.
      Excessive scale/orientation values will be ignored.
      If not enough will be available then we'll only use the first one
      (spec says the behavior is undefined then).
      We know that at least one is available, we checked this above. }

    if (Node.FdScale.Count > 1) and
       (Node.FdScale.Count < Node.FdSpine.Count) then
      OnWarning(wtMajor, 'VRML/X3D', 'Extrusion has more scales than 1, but not as much as spines. ' +
        'We''ll use only the first scale.');

    if (Node.FdOrientation.Count > 1) and
       (Node.FdOrientation.Count < Node.FdSpine.Count) then
      OnWarning(wtMajor, 'VRML/X3D', 'Extrusion has more orientations than 1, but not as much as spines. ' +
        'We''ll use only the first orientation.');
  end;

  FSpineClosed := Node.SpineClosed;
  FCrossSectionClosed := Node.CrossSectionClosed;

  if FCrossSectionClosed then
    FCrossSectionOmit := 1 else
    FCrossSectionOmit := 0;

  if SpineClosed then
  begin
    SpineScaleTo1st(0, BeginScale);
    SpineScaleTo1st(High, EndScale);
    SpineOrientationTo1st(0, BeginOrientation);
    SpineOrientationTo1st(High, EndOrientation);
    FBeginEndCapsMatching :=
      VectorsPerfectlyEqual(BeginOrientation, EndOrientation) and
      VectorsPerfectlyEqual(BeginScale, EndScale);
  end else
    FBeginEndCapsMatching := false;
end;

procedure TVRMLExtrusion.SpineScaleTo1st(Spine: Cardinal;
  out Scale: TVector2Single);
begin
  Assert(Between(Spine, 0, High));
  { So High must be >= 0, by the way.
    Also, High checked that we have at least one scale. }

  if Node.FdScale.Count < Node.FdSpine.Count then
    Scale := Node.FdScale.Items.L[0] else
    Scale := Node.FdScale.Items.L[Spine];
end;

procedure TVRMLExtrusion.SpineOrientationTo1st(Spine: Cardinal;
  out Orientation: TVector4Single);
begin
  Assert(Between(Spine, 0, High));
  { So High must be >= 0, by the way.
    Also, High checked that we have at least one orientation. }

  if Node.FdOrientation.Count < Node.FdSpine.Count then
    Orientation := Node.FdOrientation.Items.L[0] else
    Orientation := Node.FdOrientation.Items.L[Spine];
end;

procedure TVRMLExtrusion.SpineTransformTo1st(Spine: Cardinal;
  var LastY, LastZ: TVector3Single;
  out Transform: TMatrix4Single);
var
  SpinePoints: TVector3SingleList;

  { Calculate Z by searching for the first non-colinear three spine
    points. @false if not found. }
  function FindFirstNonColinear(out Z: TVector3Single): boolean;
  var
    I: Integer;
  begin
    Result := false;

    for I := 1 to High - 1 do
    begin
      { Try to find first spine point with Z defined as non-zero.
        This follows X3D / VRML spec ("If the Z-axis of the
        first point is undefined..."). }
      Z := VectorProduct(
        VectorSubtract(SpinePoints.L[I + 1], SpinePoints.L[I]),
        VectorSubtract(SpinePoints.L[I - 1], SpinePoints.L[I]));
      if not ZeroVector(Z) then
      begin
        Result := true;
        break;
      end;
    end;
  end;

  { Calculate Y by searching for the first non-coincident two spine
    points. Sets to (0, 1, 0) if not found.

    Y is always normalized. }
  procedure FindFirstNonCoincident(out Y: TVector3Single);
  var
    I: Integer;
  begin
    for I := 1 to High do
    begin
      Y := VectorSubtract(SpinePoints.L[I], SpinePoints.L[I - 1]);
      if not ZeroVector(Y) then
      begin
        NormalizeTo1st(Y);
        Exit;
      end;
    end;
    Y := UnitVector3Single[1];
  end;

  { Calculate Z based on Y (already normalized) if all spine points
    are colinear. }
  function AllColinear(const Y: TVector3Single): TVector3Single;
  var
    AngleRad: Single;
    Rotation, ForPositiveAngleRad, ForNegativeAngleRad: TVector3Single;
  begin
    { Spec: "If the entire spine is collinear,
      the SCP is computed by finding the rotation of a
      vector along the positive Y-axis (v1) to the vector
      formed by the spine points (v2). The Y=0 plane is then rotated
      by this value."

      v2 is actually just our Y variable. }
    AngleRad := AngleRadBetweenNormals(UnitVector3Single[1], Y);
    Rotation := VectorProduct(UnitVector3Single[1], Y);

    if ZeroVector(Rotation) then
    begin
      { This means that Y is actually just equal (0, 1, 0).
        So Z is just (0, 0, 1). }
      Result := UnitVector3Single[2];
    end else
    begin
      { Is the rotation by AngleRad or -AngleRad?
        Lame way of checking this below, we just check which result
        produces back Y when rotated.

        Note: the first implementation was like

          if not VectorsEqual(ForPositiveAngleRad, Y) then
          begin
            AngleRad := -AngleRad;
            Assert(VectorsEqual(ForNegativeAngleRad, Y));
          end;

        but this is obviously unsafe because of floating point errors,
        there was always a chance that both VectorsEqual fail.
        Fixed below to just choose the best one. }
      ForPositiveAngleRad := RotatePointAroundAxisRad( AngleRad, UnitVector3Single[1], Rotation);
      ForNegativeAngleRad := RotatePointAroundAxisRad(-AngleRad, UnitVector3Single[1], Rotation);

      if PointsDistanceSqr(ForPositiveAngleRad, Y) >
         PointsDistanceSqr(ForNegativeAngleRad, Y) then
        AngleRad := -AngleRad;

      Result := RotatePointAroundAxisRad(AngleRad, UnitVector3Single[2], Rotation);
    end;
  end;

  procedure CalculateYZForClosed(out Y, Z: TVector3Single);
  begin
    { Same for Spine = 0 and High, as this is the same point actually. }
    Y := VectorSubtract(SpinePoints.L[1], SpinePoints.L[High - 1]);

    if not ZeroVector(Y) then
      NormalizeTo1st(Y) else
      FindFirstNonCoincident(Y);

    Z := VectorProduct(
      VectorSubtract(SpinePoints.L[1], SpinePoints.L[0]),
      VectorSubtract(SpinePoints.L[High - 1], SpinePoints.L[0]));

    if not ZeroVector(Z) then
      NormalizeTo1st(Z) else
    if FindFirstNonColinear(Z) then
      NormalizeTo1st(Z) else
      Z := AllColinear(Y);
  end;

var
  X, Y, Z: TVector3Single;
  Scale: TVector2Single;
  Orientation: TVector4Single;
begin
  Assert(Between(Spine, 0, High));

  SpinePoints := Node.FdSpine.Items;

  if High = 0 then
  begin
    Y := UnitVector3Single[1];
    Z := UnitVector3Single[2];
  end else
  if Spine = 0 then
  begin
    if SpineClosed then
      CalculateYZForClosed(Y, Z) else
    begin
      Y := VectorSubtract(SpinePoints.L[1], SpinePoints.L[0]);
      if not ZeroVector(Y) then
        NormalizeTo1st(Y) else
        FindFirstNonCoincident(Y);

      if FindFirstNonColinear(Z) then
        NormalizeTo1st(Z) else
        Z := AllColinear(Y);
    end;
  end else
  if Integer(Spine) = High then
  begin
    if SpineClosed then
      CalculateYZForClosed(Y, Z) else
    begin
      Y := VectorSubtract(SpinePoints.L[High], SpinePoints.L[High - 1]);
      if not ZeroVector(Y) then
        NormalizeTo1st(Y) else
        Y := LastY;

      Z := LastZ;
    end;
  end else
  begin
    { Note that I avoided wasting time on checking SpineClosed
      in this case, and that's good. This is the most common case
      for this routine. }

    Y := VectorSubtract(SpinePoints.L[Spine + 1], SpinePoints.L[Spine - 1]);

    if not ZeroVector(Y) then
      NormalizeTo1st(Y) else
      Y := LastY;

    Z := VectorProduct(
      VectorSubtract(SpinePoints.L[Spine + 1], SpinePoints.L[Spine]),
      VectorSubtract(SpinePoints.L[Spine - 1], SpinePoints.L[Spine]));

    if not ZeroVector(Z) then
      NormalizeTo1st(Z) else
      Z := LastZ;
  end;

  if (Spine > 0) and (VectorDotProduct(LastZ, Z) < 0) then
  begin
    VectorNegateTo1st(Z);
  end;

  LastY := Y;
  LastZ := Z;

  X := VectorProduct(Y, Z);

  SpineScaleTo1st(Spine, Scale);
  SpineOrientationTo1st(Spine, Orientation);

  Transform := MatrixMult(
    TransformToCoordsMatrix(SpinePoints.L[Spine], X, Y, Z),
    MatrixMult(RotationMatrixRad(Orientation[3],
      Orientation[0], Orientation[1], Orientation[2]),
      ScalingMatrix(Vector3Single(Scale[0], 1, Scale[1]))));
end;