/* * 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 . --- 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 #include #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;ifaces[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 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;iverts[i]=Reading_room.verts[i]; for (i=0;ifaces[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;tfaces[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,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_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 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;iface_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; inum_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;inum_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; startanum_verts; starta++ ) { for (startb=0; startbnum_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; inum_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; inum_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;vnum_verts;v++) { if (dest->face_verts[v] == dest->face_verts[(v+2)%dest->num_verts]) { return 0; } } return 1; } } } return 0; }