This file is indexed.

/usr/src/castle-game-engine-5.2.0/x3d/castlenormals.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
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
{
  Copyright 2003-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.

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

{ @abstract(Calculating normal vectors for various 3D objects,
  with appropriate smoothing.)

  This is developed for VRML/X3D geometric primitives,
  although some parts are not coupled with VRML/X3D stuff.
  So it can be used in other situations too. }
unit CastleNormals;

interface

uses SysUtils, CastleUtils, CastleVectors, X3DNodes;

{ Calculate normal vectors for indexed faces, smoothing them according
  to CreaseAngleRad.

  CoordIndex are indexes to Vertices. Indexes < 0 are used to separate
  faces. So this works just like VRML/X3D IndexedFaceSet.coordIndex.

  It's smart and ignores incorrect indexes (outside Vertices range),
  and incorrect triangles in the face (see IndexedPolygonNormal).

  Returns a list of normalized vectors. This has the same Count
  as CoordIndex, and should be accessed in the same way.
  This way you (may) have different normal vector values for each
  vertex on each face, so it's most flexible.
  (For negative indexes in CoordIndex, corresponding value in result
  is undefined.)

  Remember it's your responsibility to free result of this function
  at some point.

  @param(FromCCW Specifies whether we should generate normals
    pointing from CCW (counter-clockwise) or CW.)

  @param(CreaseAngleRad Specifies in radians what is the acceptable
    angle for smoothing adjacent faces. More precisely, we calculate
    for each vertex it's neighbor faces normals. Then we divide these
    faces into groups, such that each group has faces that have normals
    within CreaseAngleRad range, and this group results in one smoothed
    normal. For example, it's possible for a vertex shared by 4 faces
    to be smoothed on first two faces and last two faces separately.

    Note that when creaseAngleRad >= Pi, you wil be better off
    using CreateSmoothNormals. This will work faster, and return shorter
    normals array (so it's also more memory-efficient).)

  @param(Convex Set this to @true if you know the faces are convex.
    This makes calculation faster (but may yield incorrect results
    for concave polygons).) }
function CreateNormals(CoordIndex: TLongintList;
  Vertices: TVector3SingleList;
  CreaseAngleRad: Single;
  const FromCCW, Convex: boolean): TVector3SingleList;

{ Calculate flat per-face normals for indexed faces.

  Note that the result is not a compatible replacement for CreateNormals,
  as it's Count is the number of @italic(faces). For each face, a single
  normal is stored, as this is most sensible compact representation.
  Using something larger would be a waste of memory and time. }
function CreateFlatNormals(coordIndex: TLongintList;
  vertices: TVector3SingleList;
  const FromCCW, Convex: boolean): TVector3SingleList;

{ Calculate always smooth normals per-vertex, for VRML/X3D coordinate-based
  node. We use TAbstractGeometryNode.CoordPolygons for this, so the node class
  must implement it.

  Note that the result is not a compatible replacement for CreateNormals,
  as this generates Coordinates.Count normal vectors in result.
  You should access these normal vectors just like Node.Coordinates,
  i.e. they are indexed by Node.CoordIndex if Node.CoordIndex <> nil.

  If Node.Coordinates is @nil (which means that node is coordinate-based,
  but "coord" field is not present), we return @nil. }
function CreateSmoothNormalsCoordinateNode(
  Node: TAbstractGeometryNode;
  State: TX3DGraphTraverseState;
  const FromCCW: boolean): TVector3SingleList;

implementation

uses X3DFields, CastleTriangulate, CastleGenericLists;

type
  TFace = record
    StartIndex: Integer;
    IndicesCount: Integer;
    Normal: TVector3Single;
  end;
  PFace = ^TFace;

  TFaceList = specialize TGenericStructList<TFace>;

function CreateNormals(CoordIndex: TLongintList;
  Vertices: TVector3SingleList;
  CreaseAngleRad: Single;
  const FromCCW, Convex: boolean): TVector3SingleList;
var
  Faces: TFaceList;
  { For each vertex (this array Count is always Vertices.Count),
    to which faces this vertex belongs? Contains indexes to Faces[] list.

    Although vertex may be more than once on the same face (in case
    of incorrect data, or some concave faces), a face is mentioned
    at most once (for given vertex) in this structure. }
  VerticesFaces: array of TIntegerList;
  NormalsResult: TVector3SingleList absolute Result;
  CosCreaseAngle: Single;

  procedure CalculateFacesAndVerticesFaces;
  var
    ThisFace: PFace;
    I, ThisFaceNum: Integer;
  begin
    I := 0;
    while I < CoordIndex.Count do
    begin
      ThisFaceNum := Faces.Count;
      ThisFace := Faces.Add;

      ThisFace^.StartIndex := I;
      while (I < CoordIndex.Count) and (CoordIndex[I] >= 0) do
      begin
        { Check that CoordIndex[I] is valid (within Vertices.Count range).
          Note that we cannot remove here wrong indexes from CoordIndex.
          It's tempting, but:
          - Removing them is not so easy. We would have to modify also other
            xxxIndex fields e.g. IndexedFaceSet.texCoordIndex.
          - Our engine generally preserves VRML/X3D data, never auto-correcting
            it (it's a decision that makes various things safer). }
        if (CoordIndex[I] < Vertices.Count) and
           { Make sure to add only the 1st occurrence of a vertex on this face.
             Valid concave faces may specify the same vertex multiple times. }
           (VerticesFaces[CoordIndex[I]].IndexOf(ThisFaceNum) = -1) then
          VerticesFaces[CoordIndex[I]].Add(ThisFaceNum);
        Inc(I);
      end;

      { calculate ThisFace.IndicesCount.
        We completed one face: indexes StartIndex .. i - 1 }
      ThisFace^.IndicesCount := i-ThisFace^.StartIndex;

      { calculate ThisFace.Normal }
      ThisFace^.Normal := IndexedPolygonNormal(
        Addr(CoordIndex.L[ThisFace^.StartIndex]), ThisFace^.IndicesCount,
        PVector3Single(Vertices.List), Vertices.Count,
        Vector3Single(0, 0, 1), Convex);

      { move to next face (omits the negative index we're standing on) }
      Inc(I);
    end;
  end;

  { For given Face and VertexNum (index to Vertices array),
    set the normal vector in NormalsResult array.
    Vertex must be present at least once on a given face.
    Works OK also in cases when vertex is duplicated (present more than once)
    on a single face. }
  procedure SetNormal(VertexNum: integer; const face: TFace; const Normal: TVector3Single);
  var
    I: Integer;
    Found: boolean;
  begin
    Found := false;
    for I := Face.StartIndex to Face.StartIndex + Face.IndicesCount - 1 do
      if CoordIndex.L[I] = VertexNum then
      begin
        Found := true; { Found := true, but keep looking in case duplicated }
        NormalsResult.L[I] := Normal;
      end;
    Assert(Found, 'CastleNormals.SetNormal failed, vertex not on face');
  end;

  procedure CalculateVertexNormals(VertexNum: Integer);
  var
    { Initialized to VerticesFaces[VertexNum] }
    ThisVertexFaces: TIntegerList;

    { Can face FaceNum1 be smoothed together with face FaceNum2. }
    function FaceCanBeSmoothedWith(const FaceNum1, FaceNum2: integer): boolean;
    begin
      Result :=
        { I want to check that
            AngleRadBetweenNormals(...) < CreaseAngleRad
          so
            ArcCos(CosAngleRadBetweenNormals(...)) < CreaseAngleRad
          so
            CosAngleBetweenNormals(...) > CosCreaseAngle }
        CosAngleBetweenNormals(
          Faces.L[ThisVertexFaces.L[FaceNum1]].Normal,
          Faces.L[ThisVertexFaces.L[FaceNum2]].Normal) >
          CosCreaseAngle;
    end;

  var
    I, J: Integer;
    Normal: TVector3Single;
  begin
    ThisVertexFaces := VerticesFaces[VertexNum];
    for I := 0 to ThisVertexFaces.Count - 1 do
    begin
      Normal := Faces.L[ThisVertexFaces[I]].Normal;
      for J := 0 to ThisVertexFaces.Count - 1 do
        if (I <> J) and FaceCanBeSmoothedWith(I, J) then
          VectorAddTo1st(Normal, Faces.L[ThisVertexFaces[J]].Normal);
      NormalizeTo1st(Normal);
      SetNormal(VertexNum, Faces.L[ThisVertexFaces[I]], Normal);
    end;
  end;

var
  I: Integer;
begin
  CosCreaseAngle := Cos(CreaseAngleRad);

  SetLength(VerticesFaces, vertices.Count);

  Result := nil;
  Faces := nil;

  try
    try
      for I := 0 to vertices.Count - 1 do
        VerticesFaces[I] := TIntegerList.Create;
      Faces := TFaceList.Create;

      { calculate Faces and VerticesFaces contents }
      CalculateFacesAndVerticesFaces;

      Result := TVector3SingleList.Create;
      Result.Count := CoordIndex.Count;

      { for each vertex, calculate all his normals (on all his faces) }
      for I := 0 to Vertices.Count - 1 do CalculateVertexNormals(I);

      if not FromCCW then Result.Negate;
    finally
      for I := 0 to Vertices.Count - 1 do VerticesFaces[I].Free;
      Faces.Free;
    end;
  except FreeAndNil(Result); raise end;
end;

function CreateFlatNormals(CoordIndex: TLongintList;
  Vertices: TVector3SingleList;
  const FromCCW, Convex: boolean): TVector3SingleList;
var
  I, StartIndex: Integer;
  FaceNumber: Integer;
begin
  { CoordIndex.Count is just a maximum Count, we will shrink it later. }
  Result := TVector3SingleList.Create;
  try
    Result.Count := CoordIndex.Count;
    FaceNumber := 0;

    I := 0;
    while I < CoordIndex.Count do
    begin
      StartIndex := I;
      while (I < CoordIndex.Count) and (CoordIndex.L[I] >= 0) do Inc(I);
      Result.L[FaceNumber] := IndexedPolygonNormal(
        Addr(CoordIndex.L[StartIndex]),
        I - StartIndex,
        PVector3Single(Vertices.List), Vertices.Count,
        Vector3Single(0, 0, 0), Convex);
      Inc(FaceNumber);

      Inc(I);
    end;

    Result.Count := FaceNumber;

    if not FromCCW then Result.Negate;
  except FreeAndNil(Result); raise end;
end;

{ CreateSmoothNormalsCoordinateNode ------------------------------------------ }

type
  TCoordinateNormalsCalculator = class
  public
    Normals: TVector3SingleList;
    CoordIndex: TLongIntList;
    Coord: TVector3SingleList;
    Convex: boolean;
    procedure Polygon(const Indexes: array of Cardinal);
  end;

procedure TCoordinateNormalsCalculator.Polygon(
  const Indexes: array of Cardinal);
var
  FaceNormal: TVector3Single;
  { DirectIndexes is LongInt, not Cardinal array, since we cannot
    guarantee that CoordIndex items are >= 0. }
  DirectIndexes: array of LongInt;
  I: Integer;
  Index: LongInt;
begin
  SetLength(DirectIndexes, Length(Indexes));
  if CoordIndex <> nil then
  begin
    for I := 0 to Length(Indexes) - 1 do
      DirectIndexes[I] := CoordIndex.L[Indexes[I]];
  end else
  begin
    for I := 0 to Length(Indexes) - 1 do
      DirectIndexes[I] := Indexes[I];
  end;

  FaceNormal := IndexedPolygonNormal(
    PArray_LongInt(DirectIndexes), Length(DirectIndexes),
    PVector3Single(Coord.List), Coord.Count,
    Vector3Single(0, 0, 0), Convex);

  for I := 0 to Length(Indexes) - 1 do
  begin
    Index := DirectIndexes[I];
    { Normals count is equal to vertexes count.
      So if Index is incorrect, then we have coordIndex pointing
      to a non-existing vertex index. VRML/X3D code will warn about it
      elsewhere, here just make sure we don't crash. }
    if Index < Normals.Count then
      VectorAddTo1st(Normals.L[Index], FaceNormal);
  end;
end;

function CreateSmoothNormalsCoordinateNode(
  Node: TAbstractGeometryNode;
  State: TX3DGraphTraverseState;
  const FromCCW: boolean): TVector3SingleList;
var
  Calculator: TCoordinateNormalsCalculator;
  C: TMFVec3f;
begin
  C := Node.Coordinates(State);

  { Node coordinate-based, but specified with empty coord }
  if C = nil then Exit(nil);

  Result := TVector3SingleList.Create;
  try
    Result.Count := C.Count; { TFPSList initialized everything to 0 }

    Calculator := TCoordinateNormalsCalculator.Create;
    try
      Calculator.Convex := Node.Convex;
      Calculator.Coord := C.Items;
      if Node.CoordIndex <> nil then
        Calculator.CoordIndex := Node.CoordIndex.Items else
        Calculator.CoordIndex := nil;
      Calculator.Normals := Result;
      Node.CoordPolygons(State, @Calculator.Polygon);
    finally FreeAndNil(Calculator) end;

    Result.Normalize;
    if not FromCCW then Result.Negate;

  except FreeAndNil(Result); raise end;
end;

end.