File: Read3ds.cpp

package info (click to toggle)
descent3 1.5.0%2Bds-2
  • links: PTS, VCS
  • area: contrib
  • in suites: forky, sid
  • size: 35,256 kB
  • sloc: cpp: 416,147; ansic: 3,233; sh: 10; makefile: 8
file content (797 lines) | stat: -rw-r--r-- 22,396 bytes parent folder | download | duplicates (2)
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
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
/*
* Descent 3
* Copyright (C) 2024 Parallax Software
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.

--- HISTORICAL COMMENTS FOLLOW ---

 * $Logfile: /DescentIII/Main/editor/Read3ds.cpp $
 * $Revision: 1.1.1.1 $
 * $Date: 2003-08-26 03:57:38 $
 * $Author: kevinb $
 *
 * Code to read .p3d files generated in 3DS Max
 *
 * $Log: not supported by cvs2svn $
 *
 * 26    10/15/99 12:26p Matt
 * Added error checking for too many verts & faces when importing a room.
 * Also, now allow more than the max number of faces, as long as there is
 * a valid number after combining faces.
 *
 * 25    4/13/99 11:23a Jason
 * check for degenerate faces on import
 *
 * 24    2/25/99 10:35a Jason
 * added removal of redundant verts
 *
 * 23    2/04/99 4:40p Matt
 * Added some error checking
 *
 * 22    1/21/99 11:15p Jeff
 * pulled out some structs and defines from header files and moved them
 * into seperate header files so that multiplayer dlls don't require major
 * game headers, just those new headers.  Side effect is a shorter build
 * time.  Also cleaned up some header file #includes that weren't needed.
 * This affected polymodel.h, object.h, player.h, vecmat.h, room.h,
 * manage.h and multi.h
 *
 * 21    12/22/98 2:03p Matt
 * Added room names, and made rooms not compress so that room numbers are
 * suitable for persistant uses.
 *
 * 20    5/01/98 5:55p Jason
 * made combine faces much stricter
 *
 * 19    4/02/98 12:23p Jason
 * trimmed some fat from our structures
 *
 * 18    3/31/98 3:49p Jason
 * added memory lib
 *
 * 17    2/02/98 5:14p Matt
 * Check for bad normals when importing room
 *
 * 16    1/22/98 2:56p Brent
 * Define default textures to unassigned faces
 *
 * 15    1/19/98 2:55p Jason
 * added the ability to have the importer keep the textures on the faces
 * if they are already present in memory
 *
 * 14    12/23/97 11:06a Samir
 * Added pserror.h
 *
 * 13    12/10/97 5:20p Jason
 * set alphas to 1 when importing rooms
 *
 * 12    12/10/97 4:26p Jason
 * don't assign default UVs to room
 *
 * 11    8/21/97 5:57p Matt
 * Use new & modified functions from erooms.cpp
 *
 * 10    8/01/97 3:16p Chris
 *
 * 9     7/21/97 12:11p Matt
 * Fixed stupid bug in concavity check
 *
 * 8     7/21/97 11:39a Jason
 * checked in for matt to debug
 *
 * 7     7/17/97 11:13a Jason
 * fixed bug with reading in rooms - somehow it got broken
 *
 * 7     7/17/97 10:51a Jason
 * fixed bug with normals
 *
 * $NoKeywords: $
 */

#include "read3ds.h"
#include "cfile.h"
#include "room.h"
#include "erooms.h"
#include "gametexture.h"
#include "ddio.h"
#include "pserror.h"
#include <string.h>
#include <stdlib.h>
#include "mem.h"
#include "vecmat.h"

// 3ds MAX id's
#define ID_3DS_MODIFIED 0xbeef
#define ID_OBJ_PROPS 0xdead
#define ID_SPLINE_PATH 0x8001
#define ID_3DSM 0xcfd0
#define ID_3DS 0x4d4d
#define ID_OBJECT 0x4000
#define ID_UNKNOWN 0x3d3d
#define ID_TRI_MESH 0x4100
#define ID_VERTLIST 0x4110
#define ID_FACELIST 0x4120
#define ID_MAT_APP 0x4130
#define ID_VERTEX_MAPPING 0x4140
#define ID_SMOOTH 0x4150
#define ID_MATRIX 0x4160
#define ID_MATERIAL 0xafff
#define ID_MAT_NAME 0xa000
#define ID_MAT_DSIDED 0xa081
#define ID_MAT_DIFFUSE_COLOR 0xa020
#define ID_MAT_TEXTURE 0xa200
#define ID_DIRECT_LIGHT 0x4600
#define OBJECT_NODE_TAG 0xB002

char Reading_properties[255];

#define MAX_MATERIALS 100

struct material {
  char name[PAGENAME_LEN];
  int texhandle;
};

int Num_materials = 0;
material Materials[MAX_MATERIALS];

struct reading_face {
  uint8_t flags;                          // flags for this face (see above)
  int16_t portal_num;                     // which portal this face is part of, or -1 if none
  uint8_t num_verts;                      // how many vertices in this face
  int16_t face_verts[MAX_VERTS_PER_FACE]; // index into list of vertices for this face
  roomUVL face_uvls[MAX_VERTS_PER_FACE];  // index into list of uvls for this face
  vector normal;                          // the surface normal of this face
  int16_t tmap;                           // texture numbers for this face
};

struct reading_room {
  char name[PAGENAME_LEN];
  int flags;           // various room flags
  int num_faces;       // how many poygons in this room
  int num_portals;     // how many connections in this room
  int num_verts;       // how many verts in the room
  reading_face *faces; // pointer to list of faces
  vector *verts;       // array of vertices for this room
  int objects;         // index of first object in this room
  float static_light;  // the amount of light in this room
};

reading_room Reading_room; // the global that we use to keep a temp copy of the room while
                           // we're reading it in

int CombineFaces(reading_face *, reading_face *, reading_face *);

// Our nest level
int Nest_level = 0;

extern void AssignDefaultUVsToRoom(room *rp);

int DeleteUnusedRoomVerts(room *);
int RemoveDuplicatePoints(room *);
int RemoveDuplicateFacePoints(room *);

#define MAX_READING_ROOM_FACES (MAX_FACES_PER_ROOM * 2)

// Opens and reads a 3dsmax file for our rooms.  Allocs a room to carry the data
// Returns the index into the Rooms[] array if successful
// Return -1 on fail
int Read3DSMaxFile(char *filename) {
  uint16_t id;
  int len;
  CFILE *fp;
  int i;

  fp = (CFILE *)cfopen(filename, "rb");
  if (!fp) {
    mprintf(0, "Couldn't open 3dsmax file %s!\n", filename);
    return -1;
  }

  Nest_level = 0;

  // Alloc space for reading stuff in
  Reading_room.faces = (reading_face *)mem_malloc(MAX_READING_ROOM_FACES * sizeof(reading_face));
  Reading_room.verts = (vector *)mem_malloc(MAX_VERTS_PER_ROOM * sizeof(vector));

  Reading_room.num_faces = 0;
  Reading_room.num_verts = 0;

  id = cf_ReadShort(fp);
  len = cf_ReadInt(fp);

  Num_materials = 0;

  if (id == ID_3DS_MODIFIED)
    Parse3DSMaxChunk(fp, len - 6);
  else {
    mprintf(0, "This file is not a 3ds max file!\n");
    cfclose(fp);
    return -1;
  }
  cfclose(fp);

  if ((Reading_room.num_verts == 0) || (Reading_room.num_faces == 0)) {
    OutrageMessageBox("The imported room has %d verts and %d faces.  Aborting import.", Reading_room.num_faces,
                      Reading_room.num_faces);
    return -1;
  }

  if (Reading_room.num_verts > MAX_VERTS_PER_ROOM) {
    OutrageMessageBox("The imported room has %d verts.  The limit is %d.  Aborting import.", Reading_room.num_verts,
                      MAX_VERTS_PER_ROOM);
    return -1;
  }

  if (Reading_room.num_faces > MAX_READING_ROOM_FACES) {
    OutrageMessageBox("The imported room has %d faces.  The limit is %d.  Aborting import.", Reading_room.num_faces,
                      MAX_READING_ROOM_FACES);
    return -1;
  }

  if (Reading_room.num_faces > MAX_FACES_PER_ROOM) {
    OutrageMessageBox("The imported room has %d faces.  The limit after combining faces is %d.\n\nIf there are too "
                      "many faces after combining, this room will not be imported.",
                      Reading_room.num_faces, MAX_FACES_PER_ROOM);
  }

  // Convert our points to left handed space
  for (i = 0; i < Reading_room.num_verts; i++)
    ConvertHandiness(&Reading_room.verts[i]);

  // calculate normals
  int bad_normals = 0;
  for (i = 0; i < Reading_room.num_faces; i++) {
    reading_room *rp = &Reading_room;
    reading_face *mfp = &rp->faces[i];
    // vm_GetNormal(&Reading_room.faces[i].normal,&rp->verts[mfp->face_verts[0]],&rp->verts[mfp->face_verts[1]],&rp->verts[mfp->face_verts[2]]);
    if (!ComputeNormal(&Reading_room.faces[i].normal, Reading_room.faces[i].num_verts, mfp->face_verts, rp->verts)) {
      mprintf(1, "Warning: Low precision normal for face %d\n", i);
      bad_normals++;
    }
  }
  if (bad_normals) {
    OutrageMessageBox("Warning:  The loaded room has %d faces with bad normals -- see the mono screen for details.\n\n"
                      "Coplanar faces in this room have NOT been combined.\n\n"
                      "It is STRONGLY recommended that you fix this room before using it.",
                      bad_normals);
    goto skip_combine;
  }

  // Now make a copy of the relevant Reading_room data into our destination
  int t;
  reading_face destface;

  mprintf(0, "Combining faces, please wait...\n");

TryAgain:

  for (i = 0; i < Reading_room.num_faces; i++) {
    reading_face *a = &Reading_room.faces[i];

    for (t = 0; t < Reading_room.num_faces; t++) {

      reading_face *b = &Reading_room.faces[t];

      if (a == b)
        continue;

      int retval = CombineFaces(&destface, a, b);
      if (retval) {
        // Copy the new face in the place of face a,
        // remove face b and then start over!

        Reading_room.faces[i] = destface;
        int k;
        for (k = t; k < Reading_room.num_faces - 1; k++)
          Reading_room.faces[k] = Reading_room.faces[k + 1];

        Reading_room.num_faces--;

        goto TryAgain;
      }
    }
  }

skip_combine:;

  mprintf(0, "Total faces=%d\n", Reading_room.num_faces);

  if (Reading_room.num_faces > MAX_FACES_PER_ROOM) {
    OutrageMessageBox("The imported room has %d faces.  The limit is %d.  Aborting import.", Reading_room.num_faces,
                      MAX_FACES_PER_ROOM);
    return -1;
  }

  mprintf(0, "Trying to allocate a room for %d verts, %d faces!\n", Reading_room.num_verts, Reading_room.num_faces);
  //	int n=AllocRoom (Reading_room.num_verts,Reading_room.num_faces);
  room *rp = CreateNewRoom(Reading_room.num_verts, Reading_room.num_faces, 1);

  if (rp != NULL) {
    for (i = 0; i < Reading_room.num_verts; i++)
      rp->verts[i] = Reading_room.verts[i];

    for (i = 0; i < Reading_room.num_faces; i++) {
      InitRoomFace(&rp->faces[i], Reading_room.faces[i].num_verts);

      rp->faces[i].normal = Reading_room.faces[i].normal;
      rp->faces[i].num_verts = Reading_room.faces[i].num_verts;

      rp->faces[i].tmap = Reading_room.faces[i].tmap;

      for (t = 0; t < Reading_room.faces[i].num_verts; t++) {
        rp->faces[i].face_verts[t] = Reading_room.faces[i].face_verts[t];
        rp->faces[i].face_uvls[t] = Reading_room.faces[i].face_uvls[t];
      }
    }

    // Reset UV's

    // AssignDefaultUVsToRoom (rp);

    // Remove redundant verts
    DeleteUnusedRoomVerts(rp);
    RemoveDuplicatePoints(rp);
    RemoveDuplicateFacePoints(rp);

    char name[255];
    char path[255];
    char extension[255];
    char roomname[255];

    // Find a unique name for this room

    ddio_SplitPath(filename, path, name, extension);
    int done = 0;
    int count = 1;

    sprintf(roomname, "%s.ORF", name);

    while (!done) {
      int val = FindRoomName(roomname);
      if (val == -1) {
        done = 1;
        continue;
      }
      count++;
      sprintf(roomname, "%s%d.ORF", name, count);
    }

    ASSERT(rp->name == NULL);
    rp->name = (char *)mem_malloc(strlen(roomname) + 1);
    strcpy(rp->name, roomname);

    // Save it out to disk (locally)
    ddio_MakePath(name, LocalRoomsDir.u8string().c_str(), roomname, NULL);
    SaveRoom(ROOMNUM(rp), name);
  }

  // Free our verts

  if (Reading_room.faces)
    mem_free(Reading_room.faces);
  if (Reading_room.verts)
    mem_free(Reading_room.verts);

  return ROOMNUM(rp);
}

// Converts the 3dsmax coordinate space into our left-handed coordinate space
void ConvertHandiness(vector *v) {
  vector v1 = *v;
  v->x = -1.0f * v1.x;
  v->y = v1.z;
  v->z = -1.0f * v1.y;
}

#define skip(f, n) cfseek(f, n, SEEK_CUR)
// Parses a chunk of a 3dsmax file - this function calls itself
void Parse3DSMaxChunk(CFILE *fp, int size) {
  uint16_t id;
  int len;
  int level = Nest_level;
  int i;
  float scale_factor = 0.0254f; // 0.0254 inches/meter

  Nest_level++;

  while (size && !cfeof(fp)) {

    id = cf_ReadShort(fp);
    len = cf_ReadInt(fp);

    if (size < 0) {
      mprintf(0, "%d:chunk error\n", level);
      exit(1);
    }

    switch (id) {
    case ID_UNKNOWN:
      Parse3DSMaxChunk(fp, len - 6);
      break;

    case ID_MAT_NAME: {
      char material_name[PAGENAME_LEN];

      cf_ReadString(material_name, PAGENAME_LEN, fp);

      strcpy(Materials[Num_materials].name, material_name);
      Num_materials++;

      ASSERT(Num_materials < MAX_MATERIALS);
      break;
    }

    case ID_MATERIAL: {
      Parse3DSMaxChunk(fp, len - 6);
      break;
    }

    case ID_MAT_TEXTURE: {
      int i;
      char texture_name[PAGENAME_LEN];

      // Read in Unknown field
      for (i = 0; i < 6; i++)
        cf_ReadByte(fp);

      cf_ReadShort(fp);

      // Read in Unknown field
      for (i = 0; i < 6; i++)
        cf_ReadByte(fp);

      cf_ReadString(texture_name, PAGENAME_LEN, fp);

      // Find the texture that has this bitmap as a name

      int texlen = strlen(texture_name);

      texture_name[texlen - 4] = 0;
      strcat(texture_name, ".OGF");

      int ret = FindTextureBitmapName(texture_name);
      if (ret == -1) {
        mprintf(0, "Couldn't find bitmap %s!\n", texture_name);
        ret = GetNextTexture(0);
      }

      Materials[Num_materials - 1].texhandle = ret;

      skip(fp, len - 6 - 6 - 2 - 6 - (strlen(texture_name) + 1));

      break;
    }
    case ID_MAT_APP: {
      char material_name[PAGENAME_LEN];
      int16_t n_faces, face;
      int done = 0;
      int texnum;

      cf_ReadString(material_name, PAGENAME_LEN, fp);

      n_faces = cf_ReadShort(fp);

      for (int i = 0; i < Num_materials && !done; i++) {
        if (!stricmp(Materials[i].name, material_name)) {
          texnum = Materials[i].texhandle;
          done = 1;
        }
      }

      if (!done) {
        mprintf(0, "Couldn't find material named %s!\n", material_name);
        texnum = GetNextTexture(0);
      }

      while (n_faces--) {
        face = cf_ReadShort(fp);

        Reading_room.faces[face].tmap = texnum;
      }

      break;
    }

    case ID_OBJECT: {
      cf_ReadString(Reading_room.name, PAGENAME_LEN, fp);
      Parse3DSMaxChunk(fp, len - 6 - (strlen(Reading_room.name) + 1));
      break;
    }
    // Special properties for this object/room
    case ID_OBJ_PROPS: {
      cf_ReadString(Reading_properties, 255, fp);
      // Skip next two ints
      cf_ReadInt(fp);
      cf_ReadInt(fp);

      Parse3DSMaxChunk(fp, len - 6 - 4 - 4 - (strlen(Reading_properties) + 1));
      break;
    }

    case ID_TRI_MESH:
      mprintf(0, "Found 3dsmax TRI_MESH chunk!\n");

      Parse3DSMaxChunk(fp, len - 6);
      break;

    // Vertex list
    case ID_VERTLIST: {
      uint16_t num_verts = cf_ReadShort(fp);
      int i;

      if (num_verts > MAX_VERTS_PER_ROOM)
        return;

      mprintf(0, "Reading in %d verts from room!\n", num_verts);

      // Make room for these verts
      Reading_room.num_verts = num_verts;

      for (i = 0; i < num_verts; i++) {
        Reading_room.verts[i].x = cf_ReadFloat(fp);
        Reading_room.verts[i].y = cf_ReadFloat(fp);
        Reading_room.verts[i].z = cf_ReadFloat(fp);

        Reading_room.verts[i] *= scale_factor;
      }

      break;
    }

    case ID_FACELIST: {
      uint16_t num_faces = cf_ReadShort(fp);
      uint16_t a, b, c, flags;
      int i, t, j, this_size;

      if (num_faces > MAX_READING_ROOM_FACES)
        return;

      mprintf(0, "Reading in %d faces!\n", num_faces);

      Reading_room.num_faces = num_faces;
      ASSERT(Reading_room.faces != NULL);

      for (i = 0; i < num_faces; i++) {
        // a,b, and c are indices into the list of vertices for this room
        Reading_room.faces[i].num_verts = 3;

        a = cf_ReadShort(fp);
        b = cf_ReadShort(fp);
        c = cf_ReadShort(fp);

        flags = cf_ReadShort(fp);

        // Set our pointers accordingly
        // We must reverse the ordering of the verts because
        // 3ds has a right handed coordinate system
        Reading_room.faces[i].face_verts[0] = c;
        Reading_room.faces[i].face_verts[1] = b;
        Reading_room.faces[i].face_verts[2] = a;

        Reading_room.faces[i].tmap = GetNextTexture(0);

        Reading_room.faces[i].flags = flags;

        // Read UVs

        float u[3], v[3];
        for (t = 0; t < 3; t++) {
          u[t] = cf_ReadFloat(fp);
          v[t] = cf_ReadFloat(fp);
        }

        for (j = 0; j < 3; j++) {
          Reading_room.faces[i].face_uvls[j].u = u[2 - j];
          Reading_room.faces[i].face_uvls[j].v = -v[2 - j];
          Reading_room.faces[i].face_uvls[j].alpha = 255;
        }
      }

      // get whatever is left in this segment
      this_size = num_faces * ((4 * sizeof(int16_t)) + (6 * sizeof(float)));
      if (len - 6 - this_size)
        Parse3DSMaxChunk(fp, len - 6 - 2 - this_size);

      break;
    }

    case ID_MATRIX: {
      float room_matrix[12];
      int16_t i;
      for (i = 0; i < 12; i++)
        room_matrix[i] = cf_ReadFloat(fp);
      break;
    }

    case OBJECT_NODE_TAG: {
      // printf( "=================== OBJECT_NODE_TAG! =======================\n" );

      Parse3DSMaxChunk(fp, len - 6);
      break;
    }

    default:
      // Skip this stuff
      for (i = 0; i < len - 6; i++)
        cf_ReadByte(fp);
      break;
    }
    size -= len;
  }

  Nest_level--;
}

int next_vertex(reading_face *f, int v) { return f->face_verts[(v + 1) % f->num_verts]; }

int this_vertex(reading_face *f, int v) { return f->face_verts[v]; }

int compute_faces_mapping(reading_face *f, vector *out_norm, float *out_d) {
  int i;
  float ut, vt, len;
  vector point, u, v;
  vector normal;

  vm_MakeZero(&point);
  vm_MakeZero(&normal);

  ut = vt = 0.0f;

  for (i = 0; i < f->num_verts; i++) {
    u.x = f->face_uvls[i].u;
    u.y = f->face_uvls[i].v;
    u.z = 1.0f;

    v.x = f->face_uvls[(i + 1) % f->num_verts].u;
    v.y = f->face_uvls[(i + 1) % f->num_verts].v;
    v.z = 1.0f;

    ut += (float)fabs(u.x - v.x);
    vt += (float)fabs(u.y - v.y);

    normal.x += (u.y - v.y) * (u.z + v.z);
    normal.y += (u.z - v.z) * (u.x + v.x);
    normal.z += (u.x - v.x) * (u.y + v.y);
    point += u;
  }

  len = vm_GetMagnitude(&normal);

  out_norm->x = normal.x / len;
  out_norm->y = normal.y / len;
  out_norm->z = normal.z / len;
  len *= f->num_verts;
  *out_d = (vm_DotProduct(&point, &normal) / len);
  point.x /= f->num_verts;
  point.y /= f->num_verts;
  point.z /= f->num_verts;

  return 0;
}

// Returns 1 if the uv's match
int uvs_match(reading_face *a, int va, reading_face *b, int vb) {
  int f1, f2, flag;
  vector n1, n2;
  float d1, d2;
  float cosTheta;
  float u_err, v_err;

  u_err = (float)fabs((a->face_uvls[va].u - b->face_uvls[vb].u) / ((a->face_uvls[va].u + b->face_uvls[vb].u) / 2.0f));
  v_err = (float)fabs((a->face_uvls[va].v - b->face_uvls[vb].v) / ((a->face_uvls[va].v + b->face_uvls[vb].v) / 2.0f));

  if (u_err + v_err > 0.00001f)
    return 0;

  f1 = compute_faces_mapping(a, &n1, &d1);
  f2 = compute_faces_mapping(b, &n2, &d2);

  if (f1 || f2)
    return 0;

  cosTheta = vm_DotProduct(&n1, &n2);

  if ((cosTheta < 0.99f) || (fabs(d2 - d1) > 0.0001f)) {
    flag = 0;
  } else {
    flag = 1;
  }

  return flag;
}

#define MAX_POINT_DISTANCE_FROM_PLANE .1

int CombineFaces(reading_face *dest, reading_face *a, reading_face *b) {
  int starta, startb, i;
  int va;

  if (!NormalsAreSame(&b->normal, &a->normal))
    return 0;

  if (a->tmap != b->tmap)
    return 0;

  ASSERT(a->num_verts > 2);
  ASSERT(b->num_verts > 2);

  // Compare points to plane
  vector vec = Reading_room.verts[a->face_verts[0]];
  vector norm = a->normal;
  float plane_dist = -(vec.x * norm.x + vec.y * norm.y + vec.z * norm.z);

  for (i = 0; i < b->num_verts; i++) {
    vec = Reading_room.verts[b->face_verts[i]];
    float dist = vec.x * norm.x + vec.y * norm.y + vec.z * norm.z + plane_dist;
    if (fabs(dist) > MAX_POINT_DISTANCE_FROM_PLANE)
      return 0;
  }

  // Go through each vertex and get a match

  for (starta = 0; starta < a->num_verts; starta++) {
    for (startb = 0; startb < b->num_verts; startb++) {
      if ((this_vertex(a, starta) == next_vertex(b, startb)) && (next_vertex(a, starta) == this_vertex(b, startb)) &&
          uvs_match(a, starta, b, (startb + 1) % b->num_verts) &&
          uvs_match(a, (starta + 1) % a->num_verts, b, startb)) {
        // MATCH!!!!!!!!

        dest->num_verts = 0;
        dest->flags = a->flags;
        dest->normal = a->normal; // normal of this face
        dest->tmap = a->tmap;

        for (i = 1; i < a->num_verts; i++) {
          ASSERT(dest->num_verts < MAX_VERTS_PER_FACE);
          dest->face_verts[dest->num_verts] = a->face_verts[(starta + i) % a->num_verts];
          dest->face_uvls[dest->num_verts] = a->face_uvls[(starta + i) % a->num_verts];
          va = dest->face_verts[dest->num_verts];
          dest->num_verts++;
        }

        if ((va == b->face_verts[(startb + 2) % b->num_verts]))
          mprintf(0, "WARNING!!! Faces were combined that caused the loss of a vertex!\n");

        for (i = 1; i < b->num_verts; i++) {
          ASSERT(dest->num_verts < MAX_VERTS_PER_FACE);
          if ((i == 1) && (va == b->face_verts[(startb + i + 1) % b->num_verts]))
            continue;
          else if ((i == 2) && (va == b->face_verts[(startb + i) % b->num_verts]))
            continue;
          else {
            dest->face_verts[dest->num_verts] = b->face_verts[(startb + i) % b->num_verts];
            dest->face_uvls[dest->num_verts] = b->face_uvls[(startb + i) % b->num_verts];
            dest->num_verts++;
          }
        }
        ASSERT(dest->num_verts > 2);

        if ((CheckFaceConcavity(dest->num_verts, dest->face_verts, &dest->normal, Reading_room.verts)) >= 0)
          return 0;

        // Now check for degenerate face
        for (int v = 0; v < dest->num_verts; v++) {
          if (dest->face_verts[v] == dest->face_verts[(v + 2) % dest->num_verts]) {
            return 0;
          }
        }

        return 1;
      }
    }
  }
  return 0;
}