/* * 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/HRoom.cpp $ * $Revision: 1.1.1.1 $ * $Date: 2003-08-26 03:57:38 $ * $Author: kevinb $ * * Code to implement room functions * * $Log: not supported by cvs2svn $ * * 106 9/15/99 1:56p Matt * Added the option to allow rooms or groups placed on the terrain to * either align with the terrain or with gravity. * * 105 9/07/99 12:10p Matt * Added a function to propagate a texture to all adjacent coplanar faces * in a room. * * 104 8/30/99 1:00p Gwar * use the "true" center of faces for determining attach points in room * connecting functions * * 103 8/19/99 6:57p Gwar * call ned_FindSharedEdge in CombineFaces to see if we can't combine * because of a flipped normal * * 102 8/17/99 7:11p Gwar * * 101 8/17/99 12:08p Gwar * don't set World_changed in functions that can be called from palette * rooms * * 100 8/12/99 12:08a Gwar * NEWEDITOR: texture, door and object memory management; and call SetPrim * when changing Curroomp, which now force unmarks everything in current * room view * * 99 7/04/99 4:55p Gwar * changes for texture management in NEWEDITOR * * 98 5/08/99 6:39p Matt * Added a function to delete a face and all faces connected to it. * * 97 5/04/99 12:32p Matt * Fixed stupid bug and stupider bug. * * 96 4/30/99 6:52p Matt * Added a function to merge an object's geometry into a room. * * 95 4/28/99 12:51a Matt * Added some funtions to edit face geometry. * * 94 4/25/99 2:38p Gwar * brought in HRoom.cpp and RoomUVs.cpp, and several misc game functions * were added to globals.cpp to make it possible * * 93 4/19/99 12:10a Matt * Added a menu item to delete a vertex from a face. * * 92 4/18/99 11:26p Matt * Restored point-to-plane check to FixCracks(). This check is necessary * because the point-to-edge check assumes that the points are all in the * same plane, and will not work if they are not. * * 91 4/07/99 4:39p Matt * In link to new external room, copy the textures from the old faces to * the new faces. * * 90 4/07/99 1:50p Matt * Took point-in-plane check out of fix level cracks, because it prevented * tjoints from being fixed for some non-planar faces. * * 89 4/06/99 3:48p Matt * Allow combining of rooms with rendered portals. * * 88 4/06/99 10:23a Matt * * 87 4/01/99 2:55p Matt * Fixed triangulate face code to deal with colinear points * * 86 4/01/99 1:25p Matt * Fixed bug in MatchPortalFaces when checking for match and a colinear * point was found. * * 85 3/31/99 12:58p Matt * Added snap-point-to-face * * 84 3/30/99 8:03p Matt * Deleted commented-out code * * 83 3/30/99 8:01p Matt * Took out normals-are-same check when combining adjacent faces. It was * the wrong way to check the faces (the right way (in the D3 editor, that * is) is to check for point in the plane) and we were only using it as a * first-pass check anyway. * * 82 3/30/99 6:25p Matt * Use new error system for combine faces function. * * 81 3/29/99 6:46p Matt * When combining faces, use FaceIsPlanar() so the face will pass mine * verification. * * 80 3/27/99 5:01p Matt * Fixed stupid bug in build bridge code, that assigned UVs to all faces * instead of one face, causing an Int3 sometimes for faces that hadn't * yet had normals computed. * * 79 3/27/99 4:45p Matt * Fixed a surprising fundamental flaw in the polygon clip code. * * 78 3/23/99 5:12p Matt * Added function to combine rooms. * * 77 2/27/99 3:47p Mark * Place room on terrain was improperly clearing out the placed door. * (MattT on Mark's machine) * * 76 1/29/99 12:48p Matt * Rewrote the doorway system * * 75 1/21/99 11:34a Matt * Got rid of portal triggers. Since we don't have multi-face portals, a * face trigger works fine for a portal. Also fixed a few editor/trigger * bugs. * * 74 12/23/98 6:32p Matt * Changed, slightly, the process of creating a new face * * 73 12/23/98 10:53a Matt * Added functions to create a face * * 72 10/07/98 12:48p Matt * Added some extra error checking to prevent doubly-formed portals. * * 71 10/03/98 8:31p Matt * Added Join Rooms Exact function. * * 70 9/25/98 5:25p Matt * Don't try to fix cracks in unused rooms. * * 69 9/24/98 5:00p Matt * Improved error checking for running out of rooms. * * 68 9/16/98 10:02a Matt * Fixed serval bugs, in JoinAllOverlappingFaces() and MatchPortalFaces(). * * 67 9/11/98 4:46p Matt * Don't allow combining of portal faces, pending code to do the combine * correctly. * * 66 9/10/98 6:38p Matt * Deal with error joining faces after smooth bridge. * * 65 9/09/98 4:07p Matt * Added Smooth Bridge function * * 64 9/08/98 3:25p Matt * Changed an error message. * * 63 9/08/98 12:05p Jason * moved doorway.h out of room.h * * 62 9/07/98 10:58p Matt * Added snap point to point. * * 61 9/07/98 8:14p Matt * Don't delete colinear points (& thus form t-joints) when combining * faces, and deal properly with combining faces that share multiple * edges. * * 60 9/04/98 3:34p Matt * Added groovy vertex snap code * * 59 9/04/98 12:29p Matt * Added marked edge & vertex in the editor * * 58 9/04/98 11:34a Matt * Fixed small bug in portal matching code, that caused one point on the * portals not to be set exactly the same. * * 57 9/03/98 5:29p Matt * Added code to fix cracks in levels. * * 56 9/02/98 5:57p Matt * Added code to make faces match exactly when forming a portal. This * should get rid of all remaining cracking problems. * * 55 9/02/98 4:07p Matt * Fixed one kind of cracking: after clip, make sure old points that * more-or-less line up are exactly the same. * * 54 9/02/98 2:29p Matt * Added code to prevent t-joints from being formed when joining rooms * * 53 9/01/98 12:04p Matt * Ripped out multi-face portal code * * 52 8/28/98 6:21p Matt * Added a function to flip a face. * * 51 8/24/98 3:02p Matt * Gave the user the option, when joining rooms, of extruding straight out * or toward the center of the base face. * * 50 7/01/98 7:53p Matt * Added code to allow forcing of join even when the two rooms stick * through each other. * * 49 6/25/98 7:15p Matt * Added a function to delete a pair of portals. * * 48 6/23/98 2:45p Matt * Added option when propagating to all faces in a room to only do so to * ones with the same texture. * * 47 6/08/98 12:28p Matt * Added a function to triangulate a face * * 46 5/22/98 4:47p Matt * Added menu item to propagate a texture to all the faces in a room. * * 45 4/27/98 6:41p Matt * Added code to join all adjacent faces between two rooms * * 44 4/17/98 6:25p Matt * In CheckFaceToPlane(), store the distance in a variable so it can be * looked at in the debugger. * * 43 4/17/98 6:23p Matt * Added code to make sure that the two faces of a portal have the same * number of vertices, but commented it out for now. See the note in the * code. * * 42 4/16/98 11:50a Matt * Made snap code work for placed groups. * * 41 4/07/98 4:19p Matt * Fixed (I hope) a problem joining faces that matched perfectly. * * 40 4/02/98 12:23p Jason * trimmed some fat from our structures * * 39 4/01/98 5:55p Matt * Partially re-wrote JoinRooms() to allow creation of adjacent portals. * * 38 2/18/98 2:26a Matt * Fixed typo * * 37 2/16/98 2:53p Matt * Deal with small floating-point precision problem. * * 36 2/16/98 1:27p Matt * Added function to snap the placed room to a vertex/edge on the base * room/face * * 35 2/11/98 12:38p Matt * Added epsilon to point-to-plane check * * 34 2/08/98 6:02p Matt * Use groovy new vm_MatrixMulTMatrix() function * * 33 2/04/98 6:23p Matt * Changed object room number to indicate a terrain cell via a flag. Got * rid of the object flag which used to indicate terrain. * * 32 1/30/98 1:27p Matt * Orthogonalize after computing rotation matrix for placed rooms (fixed * probem of bad matrix making rooms bigger). * * 31 1/21/98 12:40p Matt * When attaching a room to the ground, delete the room's attach face * * 30 1/21/98 12:32p Matt * Revamped viewer system * * 29 1/20/98 4:43p Matt * Fixed up the bridge and join functions * * 28 1/15/98 7:34p Matt * Revamped error checking when computing face normals * * 27 11/17/97 7:39p Sean * Changed join epsilon, and took out a return from a bogus error message * (Matt on Sean's machine) * * 26 11/06/97 6:47p Sean * FROM JASON: Changed #include "HRoom.h" #ifndef NEWEDITOR #include "d3edit.h" #else #include "..\neweditor\globals.h" #include "..\neweditor\ned_geometry.h" #endif #include "erooms.h" #include "RoomUVs.h" #include "HView.h" #include "SelectedRoom.h" #include "group.h" #include "door.h" #include "doorway.h" #include "terrain.h" #include "HTexture.h" #include "trigger.h" // Make the Marked room/face the current room/face void SetMarkedRoom() { Markedroomp = Curroomp; Markedface = Curface; Markededge = Curedge; Markedvert = Curvert; State_changed = 1; EditorStatus("Marked room:face set to %d:%d", ROOMNUM(Markedroomp), Markedface); } // Select next face on current room void SelectNextFace() { if (++Curface >= Curroomp->num_faces) Curface = 0; State_changed = 1; } // Select previous face on current room void SelectPrevFace() { if (--Curface < 0) Curface = Curroomp->num_faces - 1; State_changed = 1; } #define DEFAULT_ROOM_LENGTH 20.0 // Adds a room at the current room/face. The room is created by extuding out from the current face void AddRoom() { room *rp; face *cfp; int cnv, nfaces; vector room_delta; int i; // Get values from current room & face cfp = &Curroomp->faces[Curface]; // pointer to current face cnv = cfp->num_verts; // number of verts in current face // Check for portal already here if (cfp->portal_num != -1) { OutrageMessageBox("Can't add room: There is already a connection at the current room:face."); return; } // Get values for new room nfaces = cnv + 2; // Get a pointer to our room rp = CreateNewRoom(cnv * 2, nfaces, 0); if (rp == NULL) { OutrageMessageBox("Cannot add room: No free rooms."); return; } // Compute delta vector room_delta = cfp->normal * -DEFAULT_ROOM_LENGTH; // Set the vertices for the room for (i = 0; i < cnv; i++) { rp->verts[i] = Curroomp->verts[cfp->face_verts[cnv - 1 - i]]; rp->verts[cnv + i] = rp->verts[i] + room_delta; } // Set the faces for the room InitRoomFace(&rp->faces[0], cnv); for (i = 0; i < cnv; i++) rp->faces[0].face_verts[i] = i; InitRoomFace(&rp->faces[1], cnv); for (i = 0; i < cnv; i++) rp->faces[1].face_verts[i] = cnv * 2 - 1 - i; for (i = 0; i < nfaces - 2; i++) { InitRoomFace(&rp->faces[i + 2], 4); rp->faces[i + 2].face_verts[0] = i; rp->faces[i + 2].face_verts[1] = i + cnv; rp->faces[i + 2].face_verts[2] = ((i + 1) % cnv) + cnv; rp->faces[i + 2].face_verts[3] = (i + 1) % cnv; } #ifndef NEWEDITOR // Set normals & textures for each face for (i = 0; i < nfaces; i++) { if (!ComputeFaceNormal(rp, i)) Int3(); // Bad! Get Matt! // Assign arbitrary texture map rp->faces[i].tmap = i + 1; } #else int texnum = Editor_state.GetCurrentTexture(); // Set normals & textures for each face for (i = 0; i < nfaces; i++) { if (!ComputeFaceNormal(rp, i)) Int3(); // Bad! Get Matt! // Apply texture HTextureApplyToRoomFace(rp, i, texnum); } #endif // Set UVs for all the faces AssignDefaultUVsToRoom(rp); // Form the portals between the rooms LinkRoomsSimple(Rooms, ROOMNUM(Curroomp), Curface, ROOMNUM(rp), 0); // Make the new room the current room #ifndef NEWEDITOR Curroomp = rp; Curface = 1; Curedge = Curvert = 0; Curportal = -1; #else theApp.m_pLevelWnd->SetPrim(rp, 1, -1, 0, 0); #endif // Set the flag World_changed = 1; } // Computes the orientation matrix for the placed room void ComputePlacedRoomMatrix() { room *placedroomp; int placedface; matrix srcmat; vector t; if (Placed_room != -1) { placedroomp = &Rooms[Placed_room]; placedface = Placed_room_face; } else { ASSERT(Placed_group != NULL); placedroomp = &Placed_group->rooms[Placed_group->attachroom]; placedface = Placed_group->attachface; } // Compute source and destination matrices t = -placedroomp->faces[placedface].normal; vm_VectorToMatrix(&srcmat, &t, NULL, NULL); vm_VectorAngleToMatrix(&Placed_room_orient, &Placed_room_orient.fvec, Placed_room_angle); mprintf(0, "srcmat: %f %f %f\n", vm_GetMagnitude(&srcmat.fvec), vm_GetMagnitude(&srcmat.rvec), vm_GetMagnitude(&srcmat.uvec)); mprintf(0, "orient: %f %f %f\n", vm_GetMagnitude(&Placed_room_orient.fvec), vm_GetMagnitude(&Placed_room_orient.rvec), vm_GetMagnitude(&Placed_room_orient.uvec)); // Make sure the matrices are ok vm_Orthogonalize(&srcmat); vm_Orthogonalize(&Placed_room_orient); // Compute matrix to rotate src -> dest vm_MatrixMulTMatrix(&Placed_room_rotmat, &srcmat, &Placed_room_orient); mprintf(0, "rotmat: %f %f %f\n", vm_GetMagnitude(&Placed_room_rotmat.fvec), vm_GetMagnitude(&Placed_room_rotmat.rvec), vm_GetMagnitude(&Placed_room_rotmat.uvec)); // Make sure the matrix is ok vm_Orthogonalize(&Placed_room_rotmat); } // Place a room for orientation before attachment // Parameters: baseroomp - pointer to the room in the mine to which the new room will be attached // baseface - the face on baseroomp to attach to // placed_room - the number of the room to be attached // placed_room_face the face on placed_room that's attached void PlaceRoom(room *baseroomp, int baseface, int placed_room, int placed_room_face, int placed_room_door) { // Clear the placed group if one exists Placed_group = NULL; ASSERT(baseroomp->faces[baseface].portal_num == -1); room *placedroomp = &Rooms[placed_room]; // Set globals Placed_room = placed_room; Placed_room_face = placed_room_face; Placed_room_orient.fvec = baseroomp->faces[baseface].normal; Placed_room_angle = 0; Placed_baseroomp = baseroomp; Placed_baseface = baseface; Placed_door = placed_room_door; // Compute attach points on each face #ifndef NEWEDITOR ComputeCenterPointOnFace(&Placed_room_attachpoint, baseroomp, baseface); ComputeCenterPointOnFace(&Placed_room_origin, placedroomp, placed_room_face); #else ComputeFaceBoundingCircle(&Placed_room_attachpoint, baseroomp, baseface); ComputeFaceBoundingCircle(&Placed_room_origin, placedroomp, placed_room_face); #endif // Compute initial orientation matrix ComputePlacedRoomMatrix(); // Set the flag State_changed = 1; } // Lined up a placed room. Moves the placed room so the closest vert to basevert lines up exactly, // and the edge from basevert to basevert+1 lines up with the corresponding edge on the placed room. void SnapRoom(int basevert) { vector basecenter, attcenter; room *baseroomp, *attroomp; face *bfp, *afp; vector *bvp; int baseface, attface; vector baseedge, attedge; // Get values for the attach room if (Placed_room != -1) { attroomp = &Rooms[Placed_room]; attface = Placed_room_face; } else { ASSERT(Placed_group != NULL); attroomp = &Placed_group->rooms[Placed_group->attachroom]; attface = Placed_group->attachface; } // Set some vars baseroomp = Placed_baseroomp; baseface = Placed_baseface; attcenter = Placed_room_origin; basecenter = Placed_room_attachpoint; bfp = &baseroomp->faces[baseface]; afp = &attroomp->faces[attface]; bvp = &baseroomp->verts[bfp->face_verts[basevert]]; // Find matching vert in attach face float closest_dist = FLT_MAX; int closest_v; for (int v = 0; v < afp->num_verts; v++) { vector rotvert; float dist; rotvert = ((attroomp->verts[afp->face_verts[v]] - attcenter) * Placed_room_rotmat) + basecenter; dist = vm_VectorDistance(bvp, &rotvert); if (dist < closest_dist) { closest_dist = dist; closest_v = v; } } // Compute edge vector in base room vm_GetNormalizedDir(&baseedge, &baseroomp->verts[bfp->face_verts[(basevert + 1) % bfp->num_verts]], bvp); // Compute edge vector in attach room vector v0 = ((attroomp->verts[afp->face_verts[closest_v]] - attcenter) * Placed_room_rotmat) + basecenter; vector v1 = ((attroomp->verts[afp->face_verts[(closest_v + afp->num_verts - 1) % afp->num_verts]] - attcenter) * Placed_room_rotmat) + basecenter; vm_GetNormalizedDir(&attedge, &v1, &v0); // Get angle between two edge vectors double dot = baseedge * attedge; if (dot > 1.0) dot = 1.0; double ac = acos(dot); float delta_ang = 32768.0 * ac / PI; // Get sign of angle vector checkv; vm_CrossProduct(&checkv, &baseedge, &attedge); if ((bfp->normal * checkv) > 0) delta_ang = -delta_ang; // Update placed room angle Placed_room_angle += delta_ang; while (Placed_room_angle < 0) Placed_room_angle += 65536; // Regenerate matrix ComputePlacedRoomMatrix(); // Move the room to line up the points v0 = ((attroomp->verts[afp->face_verts[closest_v]] - attcenter) * Placed_room_rotmat) + basecenter; Placed_room_attachpoint += *bvp - v0; // Update view State_changed = 1; } // Attach an already-placed room void AttachRoom() { vector basecenter, attcenter; room *baseroomp, *attroomp, *newroomp; int baseface, attface; ASSERT(Placed_room != -1); // Set some vars baseroomp = Placed_baseroomp; baseface = Placed_baseface; attroomp = &Rooms[Placed_room]; attface = Placed_room_face; attcenter = Placed_room_origin; basecenter = Placed_room_attachpoint; // Get the new room newroomp = CreateNewRoom(attroomp->num_verts, attroomp->num_faces, 0); if (newroomp == NULL) { OutrageMessageBox("Cannot attach room: No free rooms."); return; } // Rotate verts, copying into new room for (int i = 0; i < attroomp->num_verts; i++) newroomp->verts[i] = ((attroomp->verts[i] - attcenter) * Placed_room_rotmat) + basecenter; // Copy faces to new room for (int i = 0; i < attroomp->num_faces; i++) { CopyFace(&newroomp->faces[i], &attroomp->faces[i]); #ifdef NEWEDITOR LevelTexIncrementTexture(attroomp->faces[i].tmap); #endif } // Recompute normals for the faces if (!ResetRoomFaceNormals(newroomp)) Int3(); // Get Matt // Copy other values for this room newroomp->flags = attroomp->flags; newroomp->num_portals = 0; // Check for terrain or mine room if (baseroomp == NULL) { // a terrain room // Flag this as an external room newroomp->flags |= RF_EXTERNAL; // Delete the attach face, which should now be facing the ground DeleteRoomFace(newroomp, attface); } else { // a mine room // Clip the connecting faces against each other if (!ClipFacePair(newroomp, attface, baseroomp, baseface)) { OutrageMessageBox("Error making portal -- faces probably don't overlap."); FreeRoom(newroomp); return; } // Make the two faces match exactly MatchPortalFaces(baseroomp, baseface, newroomp, attface); // Create the portals between the rooms LinkRoomsSimple(Rooms, ROOMNUM(baseroomp), baseface, ROOMNUM(newroomp), attface); // If there is a door, place it! if (Placed_door != -1) { vector room_center; matrix orient = ~Placed_room_rotmat; vector doorcenter = {0, 0, 0}; FreeRoom(&Rooms[Placed_room]); room_center = ((doorcenter - attcenter) * Placed_room_rotmat) + basecenter; ObjCreate(OBJ_DOOR, Placed_door, newroomp - Rooms, &room_center, &orient); doorway *dp = DoorwayAdd(newroomp, Placed_door); #ifdef NEWEDITOR LevelDoorIncrementDoorway(Placed_door); #endif Placed_door = -1; } } // Un-place the now-attached room Placed_room = -1; // Flag the world as changed World_changed = 1; } // structure for keeping track of vertices inserted in edges (from clipping) struct edge_insert { int v0, v1; // the edge that got the new vert int new_v; // the new vertex }; // List of vertices inserted in edges. Used only during clipping. edge_insert Edge_inserts[MAX_VERTS_PER_FACE]; int Num_edge_inserts; // Add an insert to the edge list void AddEdgeInsert(int v0, int v1, int new_v) { Edge_inserts[Num_edge_inserts].v0 = v0; Edge_inserts[Num_edge_inserts].v1 = v1; Edge_inserts[Num_edge_inserts].new_v = new_v; Num_edge_inserts++; } // Clip a polygon against one edge of another polygon // Fills inbuf and maybe outbuf with new polygons, and writes any new verts to the vertices array // Parameters: nv - the number of verts in the polygon to be clipped // vertnums - pointer to list of vertex numbers in the polygon // vertices - list of vertices referred to in vertnums // v0,v1 - the edge we're clipping against // normal - the surface normal of the polygon // inbuf - the clipped polygon is written to this buffer // inv - the number of verts in inbuf is written here // outbuf - the new polygon created by the part of the input polygon that was //clipped away onv - the number of verys in outbuf num_vertices - pointer to the number of verts in the vertices array void ClipAgainstEdge(int nv, int16_t *vertnums, vertex *vertices, int *num_vertices, vector *v0, vector *v1, vector *normal, int16_t *inbuf, int *inv, int16_t *outbuf, int *onv) { int i, prev, next, check; int16_t *ip = inbuf, *op = outbuf; vertex *curv, *prevv, *nextv; int inside_points = 0, outside_points = 0; // real inside/outside points, distinct from edge points for (i = 0, prev = nv - 1, next = 1; i < nv; i++) { curv = &vertices[vertnums[i]]; // Find out where point lies check = CheckPointAgainstEdge(&curv->vec, v0, v1, normal); if (check == 0) { // Current vertex is on edge // Add to both inside & outside lists *op++ = vertnums[i]; *ip++ = vertnums[i]; } else if (check == -1) { // Current vertex is outside int check2; prevv = &vertices[vertnums[prev]]; nextv = &vertices[vertnums[next]]; // Clip edge w/ previous vertex check2 = CheckPointAgainstEdge(&prevv->vec, v0, v1, normal); if (check2 == 1) { // prev inside, so clip ClipEdge(normal, prevv, curv, v0, v1, &vertices[*num_vertices]); AddEdgeInsert(vertnums[prev], vertnums[i], *num_vertices); *op++ = *ip++ = (*num_vertices)++; } // Add current vertex to outside polygon *op++ = vertnums[i]; outside_points++; // Clip edge w/ next vertex check2 = CheckPointAgainstEdge(&nextv->vec, v0, v1, normal); if (check2 == 1) { // next inside, so clip ClipEdge(normal, curv, nextv, v0, v1, &vertices[*num_vertices]); AddEdgeInsert(vertnums[i], vertnums[next], *num_vertices); *op++ = *ip++ = (*num_vertices)++; } } else { // Current vertex is inside ASSERT(check == 1); // Add current vertex to inside polygon *ip++ = vertnums[i]; inside_points++; } prev = i; if (++next == nv) next = 0; } // Set number of verts for return. If no real inside or outside points, then don't count edge points *inv = inside_points ? (ip - inbuf) : 0; *onv = outside_points ? (op - outbuf) : 0; } // Adds a new vertex to all instances of a given edge void AddVertToAllEdges(room *rp, int v0, int v1, int new_v) { face *fp; int f, v; for (f = 0, fp = rp->faces; f < rp->num_faces; f++, fp++) { for (v = 0; v < fp->num_verts; v++) { if (((fp->face_verts[v] == v0) && (fp->face_verts[(v + 1) % fp->num_verts] == v1)) || ((fp->face_verts[v] == v1) && (fp->face_verts[(v + 1) % fp->num_verts] == v0))) AddVertToFace(rp, f, new_v, v); } } } // Adds a new point to all instances of a given edge // First adds the point to the room (or finds it there), then calls AddVertToAllEdges() void AddPointToAllEdges(room *rp, int v0, int v1, vector *new_v) { int newvertnum = RoomAddVertices(rp, 1); rp->verts[newvertnum] = *new_v; AddVertToAllEdges(rp, v0, v1, newvertnum); } // Clips a face against another. Produces one polygon that is the intersection of the two input // faces, and zero or more extra polygons, which are parts of the input face outside the clipping face. // The input face is replaced by the clipped face, and new faces (formed by the parts of the input // face outside the clip-against face) are added to the end of the room's facelist. // This routine assumes that part or all of the being-clipped face is inside the clip-against face // Parameters: arp - the room with the face that is being changed // afacenum - the face being changed // brp - the room with the face we're clipping against // bfacenum - the face we're clipping against // Returns: true if the clip was ok, false if there was an error bool ClipFace(room *arp, int afacenum, room *brp, int bfacenum) { face *afp = &arp->faces[afacenum]; face *bfp = &brp->faces[bfacenum]; int edgenum; int16_t vbuf0[MAX_VERTS_PER_FACE], vbuf1[MAX_VERTS_PER_FACE]; int16_t newface_verts[MAX_VERTS_PER_FACE][MAX_VERTS_PER_FACE]; int newface_nvs[MAX_VERTS_PER_FACE]; vertex newverts[MAX_VERTS_PER_FACE]; int newvertnums[MAX_VERTS_PER_FACE]; int num_newverts; int num_newfaces = 0; int16_t *src, *dest; int nv; int i; // Init some stuff nv = afp->num_verts; src = vbuf0; dest = vbuf1; Num_edge_inserts = 0; // copy our vertices into one buffer for (i = 0; i < nv; i++) { newverts[i].vec = arp->verts[afp->face_verts[i]]; newverts[i].uvl = afp->face_uvls[i]; newvertnums[i] = afp->face_verts[i]; src[i] = i; } num_newverts = nv; // Clip our polygon against each edge for (edgenum = 0; edgenum < bfp->num_verts; edgenum++) { vector *v0, *v1; int16_t *outbuf = newface_verts[num_newfaces]; int *onv = &newface_nvs[num_newfaces]; v0 = &brp->verts[bfp->face_verts[(bfp->num_verts - edgenum) % bfp->num_verts]]; v1 = &brp->verts[bfp->face_verts[bfp->num_verts - edgenum - 1]]; ClipAgainstEdge(nv, src, newverts, &num_newverts, v0, v1, &afp->normal, dest, &nv, outbuf, onv); if (nv <= 2) // no new face -- faces must not overlap return 0; src = dest; dest = (src == vbuf0) ? vbuf1 : vbuf0; if (newface_nvs[num_newfaces]) // is there a new face? num_newfaces++; //..yes, increment counter } // Now we have the clipped face and the other new faces // Replace the old face, and add the new faces int first_new_vert, first_new_face; face *fp; // Allocate space for the new verts first_new_vert = RoomAddVertices(arp, num_newverts - afp->num_verts); // Copy new vertices into room & get real vert numbers for (i = 0; i < num_newverts - afp->num_verts; i++) { arp->verts[first_new_vert + i] = newverts[afp->num_verts + i].vec; newvertnums[afp->num_verts + i] = first_new_vert + i; } // Replace the input face with the clipped face ReInitRoomFace(afp, nv); for (i = 0; i < nv; i++) { afp->face_verts[i] = newvertnums[src[i]]; afp->face_uvls[i] = newverts[src[i]].uvl; } if (!ComputeFaceNormal(arp, afacenum)) Int3(); // Get Matt // Allocate space for the new faces first_new_face = RoomAddFaces(arp, num_newfaces); // Copy data for new faces (the outside faces) for (i = 0; i < num_newfaces; i++) { fp = &arp->faces[first_new_face + i]; InitRoomFace(fp, newface_nvs[i]); for (int j = 0; j < newface_nvs[i]; j++) { fp->face_verts[j] = newvertnums[newface_verts[i][j]]; fp->face_uvls[j] = newverts[newface_verts[i][j]].uvl; } if (!ComputeFaceNormal(arp, first_new_face + i)) Int3(); // Get Matt #ifndef NEWEDITOR fp->tmap = arp->faces[afacenum].tmap; #else // Apply texture HTextureApplyToRoomFace(arp, first_new_face + i, arp->faces[afacenum].tmap); #endif CopyFaceFlags(fp, &arp->faces[afacenum]); } // Add new verts to edges for (i = 0; i < Num_edge_inserts; i++) { int v0, v1, new_v; v0 = newvertnums[Edge_inserts[i].v0]; v1 = newvertnums[Edge_inserts[i].v1]; new_v = newvertnums[Edge_inserts[i].new_v]; AddVertToAllEdges(arp, v0, v1, new_v); } // Done! return 1; } // Adds a point to a face // Parameters: rp,facenum - the face we're adding the point to // vertindex - the position in the face's vertex list at which to add the point // vp - the point to add void AddPointToFace(room *rp, int facenum, int vertindex, vector *vp) { face *fp = &rp->faces[facenum]; int old_verts[MAX_VERTS_PER_FACE]; roomUVL old_uvls[MAX_VERTS_PER_FACE]; int t; // Add the new point to the room int newvertnum = RoomAddVertices(rp, 1); rp->verts[newvertnum] = *vp; // Make copy of old verts for (t = 0; t < fp->num_verts; t++) { old_verts[t] = fp->face_verts[t]; old_uvls[t] = fp->face_uvls[t]; } // Allocate for new verts ReInitRoomFace(fp, fp->num_verts + 1); // Copy old verts for (t = 0; t < vertindex; t++) { fp->face_verts[t] = old_verts[t]; fp->face_uvls[t] = old_uvls[t]; } fp->face_verts[t++] = newvertnum; // fp0->face_uvls[t] = ?? for (; t < fp->num_verts; t++) { fp->face_verts[t] = old_verts[t - 1]; fp->face_uvls[t] = old_uvls[t - 1]; } } // Deletes a point from a face // Parameters: rp,facenum - the face we're deleting the point from // vertindex - the vertex to delete void DeletePointFromFace(room *rp, int facenum, int vertindex) { face *fp = &rp->faces[facenum]; int old_verts[MAX_VERTS_PER_FACE]; roomUVL old_uvls[MAX_VERTS_PER_FACE]; int t; // Make copy of old verts for (t = 0; t < fp->num_verts; t++) { old_verts[t] = fp->face_verts[t]; old_uvls[t] = fp->face_uvls[t]; } // Allocate for new verts ReInitRoomFace(fp, fp->num_verts - 1); // Copy old verts for (t = 0; t < vertindex; t++) { fp->face_verts[t] = old_verts[t]; fp->face_uvls[t] = old_uvls[t]; } for (; t < fp->num_verts; t++) { fp->face_verts[t] = old_verts[t + 1]; fp->face_uvls[t] = old_uvls[t + 1]; } #ifndef NEWEDITOR World_changed = 1; #endif } // Takes two faces which are going to be made into a portal and makes them match exactly // Alternatively, checks if the two faces can be matched // After this function the two faces will have exactly the same vertices // Parameters: rp0,facenum0 - one of the faces // rp1,facenum1 - the other face // check_only - if set, doesn't change anything; just checks the faces // Returns the number of points added to the faces // If just_checking set, returns true if faces match, else false int MatchPortalFaces(room *rp0, int facenum0, room *rp1, int facenum1, bool check_only) { face *fp0 = &rp0->faces[facenum0]; face *fp1 = &rp1->faces[facenum1]; vector *v0, *v1, *prev_v0, *prev_v1; int n0, n1, i, j, prev_vn0, prev_vn1, max_nv; int points_added = 0; check_faces:; // First, find one point in common for (i = 0; i < fp0->num_verts; i++) { for (j = 0; j < fp1->num_verts; j++) if (PointsAreSame(&rp0->verts[fp0->face_verts[i]], &rp1->verts[fp1->face_verts[j]])) break; if (j < fp1->num_verts) break; } if (i >= fp0->num_verts) { if (!check_only) Int3(); // Counldn't find common point! This is very bad. Get Matt. return 0; // no match } prev_vn0 = fp0->face_verts[i]; prev_vn1 = fp1->face_verts[j]; prev_v0 = &rp0->verts[prev_vn0]; prev_v1 = &rp1->verts[prev_vn1]; // Make starting points identical if (!check_only) *prev_v0 = *prev_v1; // Use the larger number of vert max_nv = __max(fp0->num_verts, fp1->num_verts); // Trace through faces, adding points where needed for (n0 = n1 = 1; n0 < max_nv && n1 < max_nv; n0++, n1++) { int vn0, vn1; recheck:; vn0 = fp0->face_verts[(i + n0) % fp0->num_verts]; vn1 = fp1->face_verts[(j - n1 + fp1->num_verts) % fp1->num_verts]; v0 = &rp0->verts[vn0]; v1 = &rp1->verts[vn1]; if (PointsAreSame(v0, v1)) { // Points are at least very close. if (!check_only) *v0 = *v1; // Make the points identical } else { // The points are not the same, so we check for an extra (colinear) point float d0, d1; // One of these points should lie along the edge of the other. Find which is which d0 = vm_VectorDistance(v0, prev_v0); d1 = vm_VectorDistance(v1, prev_v1); if (d0 > d1) { // Point 1 is presumably on the edge prev_v0 -> v0 // Make sure the point is actually on the edge if (CheckPointAgainstEdge(v1, prev_v0, v0, &fp0->normal)) { if (check_only) return 0; Int3(); // point isn't on edge! Bad! Get Matt! } else { if (!check_only) { // Add the point AddPointToAllEdges(rp0, prev_vn0, vn0, v1); points_added++; } else { n1++; // skip edge point & resume checking goto recheck; } } } else { // Point 0 is presumably on the edge prev_v1 -> v1 // Make sure the point is actually on the edge if (CheckPointAgainstEdge(v0, prev_v1, v1, &fp1->normal)) { if (check_only) return 0; Int3(); // point isn't on edge! Bad! Get Matt! } else { if (!check_only) { // Add the point AddPointToAllEdges(rp1, prev_vn1, vn1, v0); points_added++; } else { n0++; // skip edge point & resume checking goto recheck; } } } // Start check again if (!check_only) goto check_faces; } prev_vn0 = vn0; prev_vn1 = vn1; prev_v0 = v0; prev_v1 = v1; } if (check_only) return 1; // no errors found, so faces ok ASSERT(fp0->num_verts == fp1->num_verts); // Get Matt! return points_added; } // Clips a pair of faces against each other. // Produces one polygon in each face that is the intersection of the two input faces, // and zero or more extra polygons, which are parts of the input faces outside the clipping faces. // The input faces are replaced by the clipped faces, and new faces (formed by the parts of the input // face outside the clip-against face) are added to the end of the room's facelist. // This routine assumes that part or all of the being-clipped face is inside the clip-against face // Parameters: rp0,face0 - the first room:face // rp1,face1 - the second room:face // Returns: true if the clip was ok, false if there was an error bool ClipFacePair(room *rp0, int face0, room *rp1, int face1) { // Clip each face aginst the other return (ClipFace(rp0, face0, rp1, face1) && ClipFace(rp1, face1, rp0, face0)); } // Creates a new bridge room connecting two rooms // Parameters: attroomp,attface - one end of the new room // baseroom,baseface - the other end of the new room // The new room is created by extruding from attroom/attface void BuildBridge(room *attroomp, int attface, room *baseroomp, int baseface) { room *newroomp; face *afp, *bfp; int nv, nfaces; vector delta_vec, bc, ac; vector *basevert; // a vertex on the base face int i; float cos, dist; // Check that the two faces don't already have portals if (attroomp->faces[attface].portal_num != -1) { OutrageMessageBox("Cannot build bridge: There is already a portal at %d:%d", ROOMNUM(attroomp), attface); return; } if (baseroomp->faces[baseface].portal_num != -1) { OutrageMessageBox("Cannot build bridge: There is already a portal at %d:%d", ROOMNUM(baseroomp), baseface); return; } // Get values from current room & face afp = &attroomp->faces[attface]; // pointer to attach face bfp = &baseroomp->faces[baseface]; // pointer to base face nv = afp->num_verts; // number of verts in attach face nfaces = nv + 2; // number of faces in new room // Make sure all points on each face are on the front of the other face vector tnorm = -afp->normal; int t = CheckFaceToPlane(baseroomp, baseface, &attroomp->verts[afp->face_verts[0]], &tnorm); if (t != -1) { OutrageMessageBox("Can't build bridge: Vertex %d on %d:%d is behind %d:%d", t, ROOMNUM(baseroomp), baseface, ROOMNUM(attroomp), attface); return; } tnorm = -bfp->normal; t = CheckFaceToPlane(attroomp, attface, &baseroomp->verts[bfp->face_verts[0]], &tnorm); if (t != -1) { OutrageMessageBox("Can't build bridge: Vertex %d on %d:%d is behind %d:%d", t, ROOMNUM(attroomp), attface, ROOMNUM(baseroomp), baseface); return; } // Compute normalized vector from attach face to base face #ifndef NEWEDITOR ComputeCenterPointOnFace(&bc, baseroomp, baseface); ComputeCenterPointOnFace(&ac, attroomp, attface); #else ComputeFaceBoundingCircle(&bc, baseroomp, baseface); ComputeFaceBoundingCircle(&ac, attroomp, attface); #endif dist = vm_GetNormalizedDir(&delta_vec, &bc, &ac); // Compute the cosine of the angle between our vector and the normal of the base face cos = delta_vec * bfp->normal; // Get a pointer to a vertex on the base face basevert = &baseroomp->verts[bfp->face_verts[0]]; // Get a pointer to our room newroomp = CreateNewRoom(nv * 2, nfaces, 0); if (newroomp == NULL) { OutrageMessageBox("Cannot build bridge: No free rooms."); return; } // Create new vertices in the plane of the base face for (i = 0; i < nv; i++) { float dist; // Copy from att face to new room newroomp->verts[i] = attroomp->verts[afp->face_verts[nv - 1 - i]]; // Get distance from base face to this vertex dist = ((*basevert - newroomp->verts[i]) * bfp->normal) / cos; // Get the new vert newroomp->verts[nv + i] = newroomp->verts[i] + (delta_vec * dist); } // Set the faces for the room InitRoomFace(&newroomp->faces[0], nv); for (i = 0; i < nv; i++) newroomp->faces[0].face_verts[i] = i; InitRoomFace(&newroomp->faces[1], nv); for (i = 0; i < nv; i++) newroomp->faces[1].face_verts[i] = nv * 2 - 1 - i; for (i = 0; i < nfaces - 2; i++) { InitRoomFace(&newroomp->faces[i + 2], 4); newroomp->faces[i + 2].face_verts[0] = i; newroomp->faces[i + 2].face_verts[1] = i + nv; newroomp->faces[i + 2].face_verts[2] = ((i + 1) % nv) + nv; newroomp->faces[i + 2].face_verts[3] = (i + 1) % nv; } // Set normals if (!ResetRoomFaceNormals(newroomp)) Int3(); // Get Matt // Set textures #ifndef NEWEDITOR for (i = 0; i < nfaces; i++) newroomp->faces[i].tmap = i + 1; #else int texnum = Editor_state.GetCurrentTexture(); // Apply texture for (i = 0; i < nfaces; i++) HTextureApplyToRoomFace(newroomp, i, texnum); #endif // Assign UVs AssignDefaultUVsToRoom(newroomp); // Connect the new room to the attach room LinkRoomsSimple(Rooms, ROOMNUM(attroomp), attface, ROOMNUM(newroomp), 0); // Clip the connecting faces against each other t = ClipFacePair(newroomp, 1, baseroomp, baseface); ASSERT(t != 0); // Make the two faces match exactly MatchPortalFaces(baseroomp, baseface, newroomp, 1); // Connect the new room to the base room LinkRoomsSimple(Rooms, ROOMNUM(baseroomp), baseface, ROOMNUM(newroomp), 1); // Set Changed flag World_changed = 1; } #define JOIN_EPSILON 0.01 // Connects two rooms by changing the shape of one room to attach to the other // Pretty similar to BuildBridge(), but doesn't create a new room // Parameters: attroomp,attface - one end of the new room // baseroom,baseface - the other end of the new room void JoinRooms(room *attroomp, int attface, room *baseroomp, int baseface) { face *afp, *bfp; int nv, nfaces, t; vector delta_vec; vector *basevert; // a vertex on the base face int i; float cos; vector save_verts[MAX_VERTS_PER_FACE]; // save verts for face in case we bail // Check that the two faces don't already have portals if (attroomp->faces[attface].portal_num != -1) { OutrageMessageBox("Cannot join: There is already a portal at %d:%d", ROOMNUM(attroomp), attface); return; } if (baseroomp->faces[baseface].portal_num != -1) { OutrageMessageBox("Cannot join: There is already a portal at %d:%d", ROOMNUM(baseroomp), baseface); return; } // Get values from current room & face afp = &attroomp->faces[attface]; // pointer to attach face bfp = &baseroomp->faces[baseface]; // pointer to base face nv = afp->num_verts; // number of verts in attach face nfaces = nv + 2; // number of faces in new room // Get a pointer to a vertex on the base face basevert = &baseroomp->verts[bfp->face_verts[0]]; // Make sure all points on the attach face are on the front of the base face // We could probably take this check out and catch the problem when we're moving the points vector tnorm = -bfp->normal; t = CheckFaceToPlane(attroomp, attface, basevert, &tnorm); if (t != -1) { if (OutrageMessageBox( MBOX_YESNO, "One or more points on the attach face are behind the base face.\nDo you still want to join?") != IDYES) { EditorStatus("Join aborted."); return; } } // Make a list of all verts in the attach room that are in a portal int portal_list[MAX_VERTS_PER_ROOM]; int num_portal_verts; num_portal_verts = BuildListOfPortalVerts(attroomp, portal_list); // Save old verts for (i = 0; i < nv; i++) save_verts[i] = attroomp->verts[afp->face_verts[i]]; int answer = OutrageMessageBox(MBOX_YESNO, "Do you want to connect to the center of the base face?\n\n" "Answer NO to extrude straight from the current face."); // Compute normalized vector from attach face to base face. // This is the direction in which we will move the attach face if (answer == IDYES) { // extrude toward the center of the base face vector bc, ac; #ifndef NEWEDITOR ComputeCenterPointOnFace(&bc, baseroomp, baseface); ComputeCenterPointOnFace(&ac, attroomp, attface); #else ComputeFaceBoundingCircle(&bc, baseroomp, baseface); ComputeFaceBoundingCircle(&ac, attroomp, attface); #endif vm_GetNormalizedDir(&delta_vec, &bc, &ac); } else { // extrude straight from attach face delta_vec = afp->normal; } // Compute the cosine of the angle between our vector and the normal of the base face cos = delta_vec * bfp->normal; // If cos is zero, two center points are the same, so no move if (fabs(cos) < JOIN_EPSILON) goto no_move_points; // Move the verts on the attach face to lie in the plane as the base face for (i = 0; i < nv; i++) { vector *vp = &attroomp->verts[afp->face_verts[i]]; float dist; // Get distance from base face to this vertex dist = ((*basevert - *vp) * bfp->normal) / cos; // If we're moving the point, make sure it's not part of a portal if (fabs(dist) > POINT_TO_PLANE_EPSILON) { // Look through list of portal verts for this vert for (t = 0; t < num_portal_verts; t++) if (portal_list[t] == afp->face_verts[i]) { // Vert is in a portal. Abort! OutrageMessageBox("Can't form joint because vertex %d on %d:%d is shared with a portal.\n\nTry doing the " "join the other way, or using the Bridge function.", i, ROOMNUM(attroomp), attface); goto abort_join; } // Move the vert *vp += (delta_vec * dist); } } // Recompute the normals in the room if (!ResetRoomFaceNormals(attroomp)) Int3(); // Get Matt // What about the uvs for the faces that changed? no_move_points:; // Clip the connecting faces against each other if (!ClipFacePair(attroomp, attface, baseroomp, baseface)) { OutrageMessageBox("Error making portal -- faces probably don't overlap."); abort_join:; // Restore old verts for (i = 0; i < nv; i++) attroomp->verts[afp->face_verts[i]] = save_verts[i]; // Recompute the normals in the room if (!ResetRoomFaceNormals(attroomp)) Int3(); // Get Matt return; } // Make the two faces match exactly MatchPortalFaces(baseroomp, baseface, attroomp, attface); // Create the portals between the rooms LinkRoomsSimple(Rooms, ROOMNUM(baseroomp), baseface, ROOMNUM(attroomp), attface); // Set Changed flag World_changed = 1; } // Connects two rooms if they already match up exactly // Parameters: attroomp,attface - one end of the new room // baseroom,baseface - the other end of the new room void JoinRoomsExact(room *attroomp, int attface, room *baseroomp, int baseface) { // Check that the two faces don't already have portals if (attroomp->faces[attface].portal_num != -1) { OutrageMessageBox("Cannot join: There is already a portal at %d:%d", ROOMNUM(attroomp), attface); return; } if (baseroomp->faces[baseface].portal_num != -1) { OutrageMessageBox("Cannot join: There is already a portal at %d:%d", ROOMNUM(baseroomp), baseface); return; } // Check if we can do an exact match if (!MatchPortalFaces(baseroomp, baseface, attroomp, attface, 1)) { OutrageMessageBox("Cannot do exact join: These faces do not match"); return; } // Make the two faces match exactly MatchPortalFaces(baseroomp, baseface, attroomp, attface); // Create the portals between the rooms LinkRoomsSimple(Rooms, ROOMNUM(baseroomp), baseface, ROOMNUM(attroomp), attface); // Set Changed flag World_changed = 1; } bool FaceIsPlanar(int nv, int16_t *face_verts, vector *normal, vector *verts); // Combine two faces, if they can be combined // Parameters: rp - the room the faces are in // face0,face1 - the two faces // Returns: true if the faces were combined, else false // Note: The UV coordinates of the new face are derrived from face0 bool CombineFaces(room *rp, int face0, int face1) { face *fp0 = &rp->faces[face0], *fp1 = &rp->faces[face1]; int nv0 = fp0->num_verts, nv1 = fp1->num_verts; int v0, v1; int16_t vertlist[MAX_VERTS_PER_FACE]; roomUVL uvllist[MAX_VERTS_PER_FACE]; int i, nv; int first0, first1, n0, n1; ASSERT(face0 != face1); // Check for portals if ((fp0->portal_num != -1) || (fp1->portal_num != -1)) { #ifndef NEWEDITOR SetErrorMessage( "You cannot combine portal faces.\n\nMattT will add this functionality if you need it. Bug him about it."); #else SetErrorMessage("You cannot combine portal faces."); #endif return 0; } // Make sure faces share an edge if (!FindSharedEdge(fp0, fp1, &v0, &v1)) { #ifdef NEWEDITOR if (ned_FindSharedEdge(fp0, fp1, &v0, &v1)) SetErrorMessage("The face normals point in opposite directions."); else #endif SetErrorMessage("The faces do not share an edge."); return 0; } // Get indices for first & last points in each face. One shared point is copied with each face. first0 = v0 + 1; n0 = nv0 - 1; first1 = v1 + 1; n1 = nv1 - 1; // See if faces share additional edges while (1) { // check forward on face0, backward on face1 int check0 = (first0 + 1) % nv0, check1 = (first1 + n1 - 1) % nv1; if (fp0->face_verts[check0] != fp1->face_verts[check1]) break; first0++; n0--; n1--; } while (1) { // check forward on face1, backward on face0 int check1 = (first1 + 1) % nv1, check0 = (first0 + n0 - 1) % nv0; if (fp0->face_verts[check0] != fp1->face_verts[check1]) break; first1++; n1--; n0--; } // Build list of verts for new face nv = 0; for (i = 0; i < n0; i++) { // Add points from first face vertlist[nv] = fp0->face_verts[(first0 + i) % nv0]; uvllist[nv] = fp0->face_uvls[(first0 + i) % nv0]; nv++; } for (i = 0; i < n1; i++) { // Add points from second face vertlist[nv] = fp1->face_verts[(first1 + i) % nv1]; uvllist[nv] = fp1->face_uvls[(first1 + i) % nv1]; nv++; } // Calculate new normal vector new_normal; ComputeNormal(&new_normal, nv, vertlist, rp->verts); // Check if new face is planar if (!FaceIsPlanar(nv, vertlist, &new_normal, rp->verts)) { SetErrorMessage("The new face would not be planar."); return 0; } // Check if new face is convex int t; if ((t = CheckFaceConcavity(nv, vertlist, &new_normal, rp->verts)) != -1) { SetErrorMessage("The new face would be concave (at vertex %d).", vertlist[t]); return 0; } // Reset face0 for new verts ReInitRoomFace(fp0, nv); // Copy into new face for (i = 0; i < nv; i++) { fp0->face_verts[i] = vertlist[i]; fp0->face_uvls[i] = uvllist[i]; } // Set new normal fp0->normal = new_normal; // Reassign uvls in face AssignUVsToFace(rp, face0, &fp0->face_uvls[0], &fp0->face_uvls[1], 0, 1); // Delete the old face DeleteRoomFace(rp, face1); // Set flag World_changed = 1; // Everything's ok return 1; } // Deletes the given room from the mine void DeleteRoomFromMine(room *rp) { int i, p; // See if this room is linked to the terrain for (p = 0; p < rp->num_portals; p++) { portal *pp = &rp->portals[p]; if (pp->croom == -1) { OutrageMessageBox("You can't delete a room that attaches to the terrain. Detach the room before deleting."); return; } } // Check if player or viewer object in this room for (i = rp->objects; i != -1; i = Objects[i].next) { // Is this the player object? if (i == OBJNUM(Player_object)) { OutrageMessageBox("You can't delete the room with the player in it."); return; } #ifndef NEWEDITOR // Is this the viewer object? if (i == OBJNUM(Viewer_object)) { // Try to find another viewer int objnum = FindNextViewerObject(Viewer_object->id + 1, -1); if (objnum == OBJNUM(Viewer_object)) { OutrageMessageBox("You can't delete the room with the only viewer in it."); return; } else SelectNextViewer(); } #endif } // count the number of rooms used. if only one, don't delete it. for (i = 0; i <= Highest_room_index; i++) if (Rooms[i].used && (i != ROOMNUM(rp))) break; if (i > Highest_room_index) { OutrageMessageBox("You can't delete the only room in the mine."); return; } // If this is current room, set new current room if (rp == Curroomp) { room *newroomp = NULL; // Look for connected room for (p = 0; p < rp->num_portals; p++) if (rp->portals[p].croom != -1) { newroomp = &Rooms[rp->portals[0].croom]; break; } // If didn't find connected room, look for any room if (newroomp == NULL) { for (i = 0; i <= Highest_room_index; i++) { if (Rooms[i].used && (i != ROOMNUM(rp))) { newroomp = &Rooms[i]; break; } } } ASSERT(newroomp != NULL); #ifndef NEWEDITOR Curroomp = newroomp; Curface = Curedge = Curvert = 0; Curportal = -1; #else theApp.m_pLevelWnd->SetPrim(newroomp, 0, -1, 0, 0); #endif EditorStatus("Current room set to %d.", ROOMNUM(Curroomp)); } // Delete connections while (rp->num_portals) { portal *pp = &rp->portals[0]; ASSERT(pp->croom != -1); DeleteRoomPortal(&Rooms[pp->croom], pp->cportal); DeleteRoomPortal(rp, 0); } // If this is marked room, clear marked room if (Markedroomp == rp) Markedroomp = NULL; // Free the objects in this room for (i = rp->objects; i != -1; i = Objects[i].next) { #ifdef NEWEDITOR if (IS_GENERIC(Objects[i].type)) LevelObjDecrementObject(Objects[i].id); else if (Objects[i].type == OBJ_DOOR) { ASSERT(rp->flags & RF_DOOR); LevelDoorDecrementDoorway(Objects[i].id); } #endif ObjDelete(i); } #ifdef NEWEDITOR // Unmark the textures in this room for (i = 0; i < rp->num_faces; i++) LevelTexDecrementTexture(rp->faces[i].tmap); #endif // Delete any triggers in this room face *fp; for (i = 0, fp = rp->faces; i < rp->num_faces; fp++, i++) if (fp->flags & FF_HAS_TRIGGER) DeleteTrigger(ROOMNUM(rp), i); // Remove this room from the selected list (if there) RemoveRoomFromSelectedList(ROOMNUM(rp)); // Free up the room FreeRoom(rp); } #define DROP_ROOM_OFFSET 20.0 // Places a room a short distance from the specified room & face // The new room is not attached to anything // Parameters: baseroomp,baseface - the new room is dropped off of this face // droproom_num - the room to be dropped void DropRoom(room *baseroomp, int baseface, int droproom_num) { int roomnum; room *newroomp; vector oldcenter, facecenter, newcenter, offset; float rad; // Create a new room roomnum = GetFreeRoom(0); if (roomnum == -1) { OutrageMessageBox("Cannot drop room: No free rooms."); return; } newroomp = &Rooms[roomnum]; // Copy data to new room CopyRoom(newroomp, &Rooms[droproom_num]); // Compute offset rad = ComputeRoomBoundingSphere(&oldcenter, newroomp); #ifndef NEWEDITOR ComputeCenterPointOnFace(&facecenter, baseroomp, baseface); #else ComputeFaceBoundingCircle(&facecenter, baseroomp, baseface); #endif newcenter = facecenter - (baseroomp->faces[baseface].normal * (rad + DROP_ROOM_OFFSET)); offset = newcenter - oldcenter; // Add in offset to all the points for (int i = 0; i < newroomp->num_verts; i++) newroomp->verts[i] += offset; // Set current room to new room #ifndef NEWEDITOR Curroomp = newroomp; Curface = Curedge = Curvert = 0; Curportal = -1; #else theApp.m_pLevelWnd->SetPrim(newroomp, 0, -1, 0, 0); #endif // We're done World_changed = 1; } // Place a room on the terrain for orientation before attachment // Parameters: cellnum - the cell where the room is being placed // placed_room - the number of the room to be attached // placed_room_face the face on placed_room that's attached void PlaceExternalRoom(int cellnum, int placed_room, int placed_room_face, bool align_to_terrain) { room *placedroomp = &Rooms[placed_room]; // Clear the placed group if one exists Placed_group = NULL; // Clear the placed door if one exists if (Placed_door != -1) { ASSERT(Placed_room != -1); FreeRoom(&Rooms[Placed_room]); Placed_door = Placed_door = -1; } // Set globals Placed_room = placed_room; Placed_room_face = placed_room_face; Placed_room_angle = 0; Placed_baseroomp = NULL; if (align_to_terrain) Placed_room_orient.fvec = TerrainNormals[MAX_TERRAIN_LOD - 1][cellnum].normal1; else Placed_room_orient.fvec = Identity_matrix.uvec; // Compute attach point on terrain ComputeTerrainSegmentCenter(&Placed_room_attachpoint, cellnum); // Compute attach points on each face #ifndef NEWEDITOR ComputeCenterPointOnFace(&Placed_room_origin, placedroomp, placed_room_face); #else ComputeFaceBoundingCircle(&Placed_room_origin, placedroomp, placed_room_face); #endif // Compute initial orientation matrix ComputePlacedRoomMatrix(); // Set the flag State_changed = 1; } // Find all the matching faces between two rooms and join them // Returns the number of new portals created int JoinAllMatchingFaces(room *rp0, room *rp1) { face *fp0, *fp1; int f0, f1; int join_count = 0; // Check each face in room 0 against each in room 1 for (f0 = 0, fp0 = rp0->faces; f0 < rp0->num_faces; f0++, fp0++) { // Check if face 0 is already part of portal if (fp0->portal_num != -1) continue; // Check current face against all faces in room 1 for (f1 = 0, fp1 = rp1->faces; f1 < rp1->num_faces; f1++, fp1++) { // Check if face 1 is already part of portal if (fp1->portal_num != -1) continue; // First check if faces have same number of verts // This used to check if the normals were the same, but I decided that was bad. -MT, 3/30/99 if ((fp0->num_verts == fp1->num_verts)) { int i, j, n; // Find one point in common for (i = 0; i < fp0->num_verts; i++) { for (j = 0; j < fp1->num_verts; j++) if (PointsAreSame(&rp0->verts[fp0->face_verts[i]], &rp1->verts[fp1->face_verts[j]])) break; if (j < fp1->num_verts) break; } if (i >= fp0->num_verts) // Couldn't find a match continue; //..so go on to next face // Trace through verts in faces, making sure they match for (n = 1; n < fp0->num_verts; n++) { vector *v0, *v1; v0 = &rp0->verts[fp0->face_verts[(i + n) % fp0->num_verts]]; v1 = &rp1->verts[fp1->face_verts[(j - n + fp1->num_verts) % fp1->num_verts]]; if (!PointsAreSame(v0, v1)) // Found mismatch break; *v0 = *v1; // make points *exactly* the same } if (n < fp0->num_verts) // Found a mismatch continue; //..so go on to next face // Found extact match, so join LinkRoomsSimple(Rooms, ROOMNUM(rp0), f0, ROOMNUM(rp1), f1); // Increment count join_count++; } } } return join_count; } // Find all the overlapping faces between two rooms and join them // Returns the number of new portals created int JoinAllOverlappingFaces(room *rp0, room *rp1) { face *fp0, *fp1; int f0, f1; int join_count = 0; // Check each face in room 0 against each in room 1 for (f0 = 0, fp0 = rp0->faces; f0 < rp0->num_faces; f0++, fp0++) { // Check if face 0 is already part of portal if (fp0->portal_num != -1) continue; // Check current face against all faces in room 1 for (f1 = 0, fp1 = rp1->faces; f1 < rp1->num_faces; f1++, fp1++) { // Check if face 1 is already part of portal if (fp1->portal_num != -1) continue; // This used to check if normals the same, but I decided that was bad. -MT, 3/30/99 { int i; vector *planepoint, *norm; // Normals are same, so check if points are all in the same plane planepoint = &rp1->verts[fp1->face_verts[0]]; norm = &fp1->normal; for (i = 0; i < fp0->num_verts; i++) if (CheckPointToPlane(&rp0->verts[fp0->face_verts[i]], planepoint, norm)) break; if (i < fp0->num_verts) continue; planepoint = &rp0->verts[fp0->face_verts[0]]; norm = &fp0->normal; for (i = 0; i < fp1->num_verts; i++) if (CheckPointToPlane(&rp1->verts[fp1->face_verts[i]], planepoint, norm)) break; if (i < fp1->num_verts) continue; // Clip the connecting faces against each other if (ClipFacePair(rp0, f0, rp1, f1)) { // Make the two faces match exactly MatchPortalFaces(rp0, f0, rp1, f1); // Create the portals between the rooms LinkRoomsSimple(Rooms, ROOMNUM(rp0), f0, ROOMNUM(rp1), f1); // Reset face pointer, which may have been changed by the clip fp0 = &rp0->faces[f0]; // Increment count join_count++; // Stop checking this face, since we've formed a portal for it break; } else { // it's kind of bad if the clip fails, but we have to live with it // Reset face pointers, which may have been changed by the clip fp0 = &rp0->faces[f0]; fp1 = &rp1->faces[f1]; } } } } return join_count; } // Find all the adjacent faces between two rooms and join them void JoinAllAdjacentFaces(room *rp0, room *rp1) { int join_count; int answer; answer = OutrageMessageBox(MBOX_YESNOCANCEL, "Do you want to include non-matching faces?\n\n" "Selecting YES means faces will be joined if they overlap.\n" "Selecting NO means faces much match exactly."); if (answer == IDCANCEL) return; if (answer == IDYES) join_count = JoinAllOverlappingFaces(rp0, rp1); else join_count = JoinAllMatchingFaces(rp0, rp1); World_changed = 1; OutrageMessageBox("%d new portals created.\n", join_count); } int FindNeighbor(room *rp, int facenum, int edgenum) { face *fp = &rp->faces[facenum]; int v0, v1, f, v; v0 = fp->face_verts[edgenum]; v1 = fp->face_verts[(edgenum + 1) % fp->num_verts]; for (f = 0, fp = rp->faces; f < rp->num_faces; f++, fp++) for (v = 0; v < fp->num_verts; v++) if ((fp->face_verts[v] == v1) && (fp->face_verts[(v + 1) % fp->num_verts] == v0)) return f; return -1; } void PropagateFromFace(room *rp, int facenum, uint8_t *face_flags, bool matching_faces_only) { face *fp = &rp->faces[facenum]; face_flags[facenum] = 1; for (int v = 0; v < fp->num_verts; v++) { int t; t = FindNeighbor(rp, facenum, v); if ((t != -1) && !face_flags[t]) if (!matching_faces_only || (rp->faces[t].tmap == fp->tmap)) { HTexturePropagateToFace(rp, t, rp, facenum); PropagateFromFace(rp, t, face_flags, matching_faces_only); } } } // Propagate a texture from one face to all the faces in the room // If matching_faces_only is set, only propagate if the face has the same texture void PropagateToAllFaces(room *rp, int facenum, bool matching_faces_only) { uint8_t face_flags[MAX_FACES_PER_ROOM]; // Clear flags for (int i = 0; i < rp->num_faces; i++) face_flags[i] = 0; // Start recursive function PropagateFromFace(rp, facenum, face_flags, matching_faces_only); // Set flag World_changed = 1; } // Splits a face unto triangles, by fanning // Parameters: rp,facenum - the face to triangulate // vertnum - the vert that is the base of the fan void TriangulateFace(room *rp, int facenum, int vertnum) { face *fp = &rp->faces[facenum], *nfp; int num_new_faces, first_new_face, i; if (fp->num_verts == 3) { OutrageMessageBox("Face %d has three verts and cannot be split.", facenum); return; } if (fp->portal_num != -1) { OutrageMessageBox("You cannot split a face that's part of a portal."); return; } if (fp->flags & (FF_HAS_TRIGGER + FF_FLOATING_TRIG)) { OutrageMessageBox("You cannot split a face that has a trigger."); return; } // Add new faces num_new_faces = fp->num_verts - 2; first_new_face = RoomAddFaces(rp, num_new_faces); // Reset pointer to our old face fp = &rp->faces[facenum]; // Set up each face for (i = 0, nfp = &rp->faces[first_new_face]; i < num_new_faces; i++, nfp++) { InitRoomFace(nfp, 3); // Set verts & uvls for this face nfp->face_verts[0] = fp->face_verts[vertnum]; nfp->face_uvls[0] = fp->face_uvls[vertnum]; for (int v = 1; v < 3; v++) { nfp->face_verts[v] = fp->face_verts[(vertnum + i + v) % fp->num_verts]; nfp->face_uvls[v] = fp->face_uvls[(vertnum + i + v) % fp->num_verts]; } // Copy other data for this face CopyFaceFlags(nfp, fp); #ifndef NEWEDITOR nfp->tmap = fp->tmap; #else // Apply texture HTextureApplyToRoomFace(rp, first_new_face + i, nfp->tmap); #endif nfp->light_multiple = fp->light_multiple; // Compute the normal for this face ComputeFaceNormal(rp, first_new_face + i); } // Delete original face DeleteRoomFace(rp, facenum); // Check for degenerate faces (caused by colinear points) and merge with adjacent faces first_new_face--; // original face deleted, causing shift bool ok; do { // Check first face ok = ComputeFaceNormal(rp, first_new_face); if (!ok) { face *fp = &rp->faces[first_new_face]; ASSERT(first_new_face < rp->num_faces - 1); AddVertToFace(rp, first_new_face, rp->faces[first_new_face + 1].face_verts[2], fp->num_verts - 1); rp->faces[first_new_face].face_uvls[rp->faces[first_new_face].num_verts - 1] = rp->faces[first_new_face + 1].face_uvls[2]; DeleteRoomFace(rp, first_new_face + 1); num_new_faces--; } } while (!ok); do { // Check last face ok = ComputeFaceNormal(rp, rp->num_faces - 1); if (!ok) { face *fp = &rp->faces[rp->num_faces - 2]; ASSERT(first_new_face < rp->num_faces - 1); AddVertToFace(rp, rp->num_faces - 1, rp->faces[rp->num_faces - 2].face_verts[1], 0); rp->faces[rp->num_faces - 1].face_uvls[1] = rp->faces[rp->num_faces - 2].face_uvls[1]; DeleteRoomFace(rp, rp->num_faces - 2); num_new_faces--; } } while (!ok); // We're done EditorStatus("Face %d replace by %d new faces", facenum, num_new_faces); #ifndef NEWEDITOR World_changed = 1; #endif } // Deletes the connection between two rooms. Deletes both portals. void DeletePortalPair(room *rp, int portalnum) { int roomnum = ROOMNUM(rp); portal *pp = &rp->portals[portalnum]; int croom = pp->croom, cportal = pp->cportal; // Make sure the connecting portal points back to this portal if ((Rooms[croom].portals[cportal].croom != roomnum) || (Rooms[croom].portals[cportal].cportal != portalnum)) { OutrageMessageBox("Cannot delete this portal: the connecting portal does not point back at it.\n"); return; } // Check all the faces in this portal to make sure they point back to the portal if (rp->faces[pp->portal_face].portal_num != portalnum) { OutrageMessageBox("Cannot delete this portal: its face does not point back at it."); return; } DeleteRoomPortal(rp, portalnum); DeleteRoomPortal(&Rooms[croom], cportal); EditorStatus("Deleted room %d portal %d and room %d portal %d", roomnum, portalnum, croom, cportal); World_changed = 1; } // Flips a face void FlipFace(room *rp, int facenum) { face *fp = &rp->faces[facenum]; ASSERT(fp->portal_num == -1); for (int i = 0; i < fp->num_verts / 2; i++) { int v = fp->face_verts[i]; fp->face_verts[i] = fp->face_verts[fp->num_verts - 1 - i]; fp->face_verts[fp->num_verts - 1 - i] = v; roomUVL uvl = fp->face_uvls[i]; fp->face_uvls[i] = fp->face_uvls[fp->num_verts - 1 - i]; fp->face_uvls[fp->num_verts - 1 - i] = uvl; } // Get new normal if (!ComputeFaceNormal(rp, facenum)) Int3(); // Bad! Get Matt! #ifndef NEWEDITOR World_changed = 1; EditorStatus("Room %d face %d flipped.", ROOMNUM(rp), facenum); #else if (ROOMNUM(rp) < MAX_ROOMS) EditorStatus("Room %d face %d flipped.", ROOMNUM(rp), facenum); else EditorStatus("Face %d flipped.", facenum); #endif } // Attempt to fix the cracks in a level void FixCracks() { int r, p, f, f2, v, v2; room *rp; face *fp, *fp2; int possible_tjoints = 0, tjoints_fixed = 0; int points_added = 0; int points_are_same_count = 0; if (OutrageMessageBox(MBOX_YESNO, "This function is somewhat dangerous.\n\n" "You should be sure to save your level beforehand, and you should check the " "results very carefully afterwards.\n\n" "Continue?") != IDYES) return; // First fix gaps at portals for (r = 0, rp = Rooms; r <= Highest_room_index; r++, rp++) { if (rp->used) { for (p = 0; p < rp->num_portals; p++) { portal *pp = &rp->portals[p]; points_added += MatchPortalFaces(rp, pp->portal_face, &Rooms[pp->croom], Rooms[pp->croom].portals[pp->cportal].portal_face); } } } // Now search for and fix t-joints for (r = 0, rp = Rooms; r <= Highest_room_index; r++, rp++) { if (rp->used) for (f = 0, fp = rp->faces; f < rp->num_faces; f++, fp++) { // if (fp->portal_num == -1) { // don't check portal faces recheck_face:; for (v = 0; v < fp->num_verts; v++) { int vv0 = fp->face_verts[v], vv1 = fp->face_verts[(v + 1) % fp->num_verts]; for (f2 = 0, fp2 = rp->faces; f2 < rp->num_faces; f2++, fp2++) { if (f2 != f) { for (v2 = 0; v2 < fp2->num_verts; v2++) { int tt0 = fp2->face_verts[v2], tt1 = fp2->face_verts[(v2 + 1) % fp2->num_verts]; if ((vv0 == tt1) && (vv1 == tt0)) break; // found one, so stop } if (v2 < fp2->num_verts) break; } } if (f2 == rp->num_faces) { // didn't find a match // mprintf(0,"Couldn't find shared edge for %d:%d edge %d\n",r,f,v); possible_tjoints++; // Look for vert on this edge for (f2 = 0, fp2 = rp->faces; f2 < rp->num_faces; f2++, fp2++) { if (f2 != f) { for (v2 = 0; v2 < fp2->num_verts; v2++) { int tt0 = fp2->face_verts[v2], tt1 = fp2->face_verts[(v2 + 1) % fp2->num_verts]; if (vv0 == tt1) { // one point maches; check if next is on the edge // if (PointsAreColinear(&rp->verts[vv0],&rp->verts[tt0],&rp->verts[vv1])) { if (PointsAreSame(&rp->verts[tt0], &rp->verts[vv0])) points_are_same_count++; if (PointsAreSame(&rp->verts[tt0], &rp->verts[vv1])) points_are_same_count++; if (CheckPointToPlane(&rp->verts[tt0], &rp->verts[vv0], &fp->normal) == 0) { if (CheckPointAgainstEdge(&rp->verts[tt0], &rp->verts[vv0], &rp->verts[vv1], &fp->normal) == 0) { // make sure the new point is actually between the two edge points float edge_len = vm_VectorDistance(&rp->verts[vv1], &rp->verts[vv0]); float d0 = vm_VectorDistance(&rp->verts[tt0], &rp->verts[vv0]); float d1 = vm_VectorDistance(&rp->verts[tt0], &rp->verts[vv1]); if ((d0 < edge_len) && (d1 < edge_len)) { mprintf(0, "Adding %d to edge %d of %d:%d (from face %d)\n", tt0, v, r, f, f2); AddVertToFace(rp, f, tt0, v); tjoints_fixed++; goto recheck_face; } } } } } } } } } } } } // Fix portals again because points may have been added when fixing t-joints for (r = 0, rp = Rooms; r <= Highest_room_index; r++, rp++) { if (rp->used) { for (p = 0; p < rp->num_portals; p++) { portal *pp = &rp->portals[p]; points_added += MatchPortalFaces(rp, pp->portal_face, &Rooms[pp->croom], Rooms[pp->croom].portals[pp->cportal].portal_face); } } } mprintf(0, "%d possible t-joints\n", possible_tjoints); mprintf(0, "points_are_same_count = %d\n", points_are_same_count); OutrageMessageBox("Level fix results:\n\n" " %d points added to portals\n" " %d T-joints fixed", points_added, tjoints_fixed); World_changed = 1; } // Variables for undoing a snap int Snap_roomnum = -1; int Snap_vertnum; vector Snap_old_point, Snap_new_point; // Moves a vertex to lie on a specified edge void SnapPointToEdge(room *rp, int vertnum, vector *v0, vector *v1) { vector *vp = &rp->verts[vertnum]; vector edge_vec, edge_normal; float d; // Save info for undo Snap_roomnum = ROOMNUM(rp); Snap_vertnum = vertnum; Snap_old_point = *vp; // Calculate new point edge_normal = edge_vec = *v1 - *v0; vm_NormalizeVector(&edge_normal); d = vm_DistToPlane(vp, &edge_normal, v0); *vp = *v0 + edge_normal * d; // Set the point Snap_new_point = *vp; // Done EditorStatus("Room %d point %d snapped.", Snap_roomnum, Snap_vertnum); World_changed = 1; } // Moves a vertex to be conincident with another vertex void SnapPointToPoint(room *rp, int vertnum, room *snapto_rp, int snapto_vertnum) { vector *vp = &rp->verts[vertnum]; // Save info for undo Snap_roomnum = ROOMNUM(rp); Snap_vertnum = vertnum; Snap_old_point = *vp; *vp = snapto_rp->verts[snapto_vertnum]; // Set the point Snap_new_point = *vp; // Done EditorStatus("Room %d point %d snapped.", Snap_roomnum, Snap_vertnum); World_changed = 1; } // Moves a vertex to lie on a specified plane void SnapPointToFace(room *rp, int vertnum, vector *v0, vector *normal) { vector *vp = &rp->verts[vertnum]; float d; // Save info for undo Snap_roomnum = ROOMNUM(rp); Snap_vertnum = vertnum; Snap_old_point = *vp; // Calculate new point d = vm_DistToPlane(vp, normal, v0); *vp -= *normal * d; // Set the point Snap_new_point = *vp; // Done EditorStatus("Room %d point %d snapped.", Snap_roomnum, Snap_vertnum); World_changed = 1; } // Undoes the most recently-performed snap void UndoSnap() { if (Snap_roomnum == -1) return; if ((Snap_roomnum <= Highest_room_index) && Rooms[Snap_roomnum].used) { vector *vp = &Rooms[Snap_roomnum].verts[Snap_vertnum]; if ((vp->x == Snap_new_point.x) && (vp->y == Snap_new_point.y) && (vp->z == Snap_new_point.z)) { *vp = Snap_old_point; World_changed = 1; return; } } OutrageMessageBox("Cannot Undo: The snapped point has changed since the snap."); Snap_roomnum = -1; } // Gets the angle between two vectors in the range 0..1 // Parameters: base - the start of both vectors // v0,v1 - the end points of the two vectors // normal - the surface normal of the face these verts lie in // Returns the cosine of the angle between and float GetAngle(vector *base, vector *v0, vector *v1, vector *normal) { vector edge0, edge1, cross; float dot; vm_GetNormalizedDir(&edge0, v0, base); vm_GetNormalizedDir(&edge1, v1, base); dot = edge0 * edge1; cross = edge0 ^ edge1; if (cross * *normal > 0) return 0.25 - dot / 4.0; else return 0.75 + dot / 4.0; } // Connects two rooms in a pleasing way void BuildSmoothBridge(room *rp0, int facenum0, room *rp1, int facenum1) { room *rp; int nv0, nv1, nverts, nfaces; int i; vector tverts[MAX_VERTS_PER_FACE]; // Get values from current faces face *fp0 = &rp0->faces[facenum0]; face *fp1 = &rp1->faces[facenum1]; nv0 = fp0->num_verts; nv1 = fp1->num_verts; // Check for portal already here if (fp0->portal_num != -1) { OutrageMessageBox("Cannot build bridge: There is already a connection at %d:%d.", ROOMNUM(rp0), facenum0); return; } if (fp1->portal_num != -1) { OutrageMessageBox("Cannot build bridge: There is already a connection at %d:%d.", ROOMNUM(rp1), facenum1); return; } // Get values for new room nverts = nv0 + nv1; nfaces = nverts + 2; // Get a pointer to our room rp = CreateNewRoom(nverts, nfaces); if (rp == NULL) { OutrageMessageBox("Cannot build bridge: No free rooms."); return; } // Set the vertices for the room for (i = 0; i < nv0; i++) rp->verts[i] = rp0->verts[fp0->face_verts[i]]; for (i = 0; i < nv1; i++) rp->verts[nv0 + i] = rp1->verts[fp1->face_verts[nv1 - 1 - i]]; // Create the connecting faces // Compute normalized vector from attach face to base face vector c0, c1, delta_vec; #ifndef NEWEDITOR ComputeCenterPointOnFace(&c0, rp0, facenum0); ComputeCenterPointOnFace(&c1, rp1, facenum1); #else ComputeFaceBoundingCircle(&c0, rp0, facenum0); ComputeFaceBoundingCircle(&c1, rp1, facenum1); #endif vm_GetNormalizedDir(&delta_vec, &c1, &c0); // Compute the cosine of the angle between our vector and the normal of the base face float cos = delta_vec * fp1->normal; // Get a pointer to a vertex on face1 vector *vp = &rp->verts[nv0]; // Project the verts from end face0 into end face1 for (i = 0; i < nv0; i++) { // Get distance from base face to this vertex float dist = ((*vp - rp->verts[i]) * fp1->normal) / cos; // Get the new vert tverts[i] = rp->verts[i] + (delta_vec * dist); } // Find closest face0 point to point 0 of face1 int smallest_i = -1; float smallest_angle = 1.0; vector tnorm = -fp1->normal; for (i = 0; i < nv0; i++) { float angle = GetAngle(&c1, &rp->verts[nv0], &tverts[i], &tnorm); if (angle > 0.5) angle = 1.0 - angle; // we want closest in either direction if (angle < smallest_angle) { smallest_i = i; smallest_angle = angle; } } ASSERT(smallest_i != -1); // Generate new faces int prev1 = 0, // set up the previous edge prev0 = smallest_i; for (i = 0; i < nverts; i++) { float angle0, angle1; face *fp = &rp->faces[i]; InitRoomFace(fp, 3); fp->face_verts[0] = nv0 + prev1; fp->face_verts[1] = prev0; if (i == nverts - 1) { // special case for last edge if ((rp->faces[0].face_verts[0] != fp->face_verts[0]) && (rp->faces[0].face_verts[0] != fp->face_verts[1])) fp->face_verts[2] = rp->faces[0].face_verts[0]; else { ASSERT((rp->faces[0].face_verts[1] != fp->face_verts[0]) && (rp->faces[0].face_verts[0] != fp->face_verts[1])); fp->face_verts[2] = rp->faces[0].face_verts[1]; } break; } // Pick point for third vert angle1 = GetAngle(&c1, &rp->verts[nv0 + prev1], &rp->verts[nv0 + (prev1 + 1) % nv1], &tnorm); angle0 = GetAngle(&c1, &rp->verts[nv0 + prev1], &tverts[(prev0 + 1) % nv0], &tnorm); if (angle1 < angle0) { prev1 = (prev1 + 1) % nv1; fp->face_verts[2] = nv0 + prev1; } else prev0 = fp->face_verts[2] = (prev0 + 1) % nv0; } // Set the two end faces for the room InitRoomFace(&rp->faces[nfaces - 2], fp0->num_verts); for (i = 0; i < fp0->num_verts; i++) rp->faces[nfaces - 2].face_verts[i] = nv0 - 1 - i; InitRoomFace(&rp->faces[nfaces - 1], fp1->num_verts); for (i = 0; i < fp1->num_verts; i++) rp->faces[nfaces - 1].face_verts[i] = fp0->num_verts + i; // Set normals & textures for each face for (i = 0; i < nfaces; i++) { if (!ComputeFaceNormal(rp, i)) Int3(); // Bad! Get Matt! } // Attempt to combine faces for (i = 0; i < nfaces - 2; i++) { int t = (i + 1) % (nfaces - 2); // Try compbining the faces if (CombineFaces(rp, i, t)) { i--; // check this face again nfaces--; } } // Set textures for each face #ifndef NEWEDITOR for (i = 0; i < nfaces; i++) rp->faces[i].tmap = i + 1; #else int texnum = Editor_state.GetCurrentTexture(); for (i = 0; i < nfaces; i++) { // Apply texture HTextureApplyToRoomFace(rp, i, texnum); } #endif // Set UVs for all the faces AssignDefaultUVsToRoom(rp); // Form the portals between the rooms LinkRoomsSimple(Rooms, ROOMNUM(rp0), facenum0, ROOMNUM(rp), nfaces - 2); LinkRoomsSimple(Rooms, ROOMNUM(rp1), facenum1, ROOMNUM(rp), nfaces - 1); // Make the new room the current room #ifndef NEWEDITOR Curroomp = rp; Curface = nfaces - 2; Curedge = Curvert = 0; Curportal = -1; #else theApp.m_pLevelWnd->SetPrim(rp, nfaces - 2, -1, 0, 0); #endif // Set the flag World_changed = 1; } int16_t New_face_verts[MAX_VERTS_PER_FACE]; int New_face_num_verts = -1; room *New_face_roomp; void AddNewFaceVert() { if (Curroomp != New_face_roomp) { OutrageMessageBox("All points in the new face must be in the same room"); return; } if (New_face_num_verts == -1) { OutrageMessageBox("You must start a new face before adding to it"); return; } New_face_verts[New_face_num_verts++] = Curroomp->faces[Curface].face_verts[Curvert]; EditorStatus("Added %d as vert #%d in new face", Curroomp->faces[Curface].face_verts[Curvert], New_face_num_verts); } void StartNewFace() { New_face_roomp = Curroomp; New_face_num_verts = 0; AddNewFaceVert(); EditorStatus("Starting new face with vertex %d", Curroomp->faces[Curface].face_verts[Curvert]); } void EndNewFace() { AddNewFaceVert(); if (New_face_num_verts == -1) { OutrageMessageBox("You must start a new face before ending it"); return; } if (New_face_num_verts < 3) { OutrageMessageBox("A face must have at least three vertices"); return; } int facenum; facenum = RoomAddFaces(Curroomp, 1); face *fp = &Curroomp->faces[facenum]; InitRoomFace(fp, New_face_num_verts); for (int i = 0; i < New_face_num_verts; i++) fp->face_verts[i] = New_face_verts[i]; #ifndef NEWEDITOR fp->tmap = 0; #else int texnum = Editor_state.GetCurrentTexture(); // Apply texture HTextureApplyToRoomFace(Curroomp, facenum, texnum); #endif ComputeFaceNormal(Curroomp, facenum); AssignDefaultUVsToRoomFace(Curroomp, facenum); EditorStatus("New face %d created", facenum); New_face_num_verts = -1; World_changed = 1; } void ObjRelink(int objnum, int newroomnum); int RemoveDuplicatePoints(room *rp); trigger *FindTrigger(int roomnum, int facenum); // Combine the two rooms. base_rp stays, att_rp goes away // Returns true if combine sucessful, false if can't join bool CombineRooms(room *base_rp, room *att_rp) { int i; face *fp; bool connected = 0, rendered_portals = 0; // Make sure these rooms share a portal for (i = 0; i < base_rp->num_portals; i++) { if (base_rp->portals[i].croom == ROOMNUM(att_rp)) { connected = 1; if ((base_rp->faces[base_rp->portals[i].portal_face].flags & FF_HAS_TRIGGER) || (att_rp->faces[att_rp->portals[base_rp->portals[i].cportal].portal_face].flags & FF_HAS_TRIGGER)) { OutrageMessageBox("Cannot combine rooms: a portal connecting them has a trigger."); return 0; } if ((base_rp->portals[i].flags & PF_RENDER_FACES) || (att_rp->portals[base_rp->portals[i].cportal].flags & PF_RENDER_FACES)) { rendered_portals = 1; } } } if (!connected) { OutrageMessageBox("Cannot combine rooms: the rooms are not connected."); return 0; } if (rendered_portals) { if (OutrageMessageBox( MBOX_YESNO, "One or more of the portals connecting these rooms has the Render Faces flag set. " "The rendered face will remain in the room after combine.\n\nDo you still want to combine?") != IDYES) return 0; } // Check for doors if (base_rp->doorway_data || att_rp->doorway_data) { OutrageMessageBox("Cannot combine rooms: one room is a door."); return 0; } // Allocate new verts & faces int first_new_vert = RoomAddVertices(base_rp, att_rp->num_verts); int first_new_face = RoomAddFaces(base_rp, att_rp->num_faces); // Copy verts to base room for (i = 0; i < att_rp->num_verts; i++) base_rp->verts[first_new_vert + i] = att_rp->verts[i]; // Copy faces to base room for (i = 0, fp = &base_rp->faces[first_new_face]; i < att_rp->num_faces; fp++, i++) { // Copy the face CopyFace(fp, &att_rp->faces[i]); fp->flags = att_rp->faces[i].flags; // Update the vertices for (int v = 0; v < fp->num_verts; v++) fp->face_verts[v] += first_new_vert; // Move triggers from attach to base room if (att_rp->faces[i].flags & FF_HAS_TRIGGER) { trigger *tp = FindTrigger(ROOMNUM(att_rp), i); tp->roomnum = ROOMNUM(base_rp); tp->facenum += first_new_face; } } // Deal with attach room's portals for (i = 0; i < att_rp->num_portals; i++) { int facenum, croom, cface, flags, cflags; facenum = att_rp->portals[i].portal_face + first_new_face; flags = att_rp->portals[i].flags; croom = att_rp->portals[i].croom; cface = Rooms[croom].portals[att_rp->portals[i].cportal].portal_face; cflags = Rooms[croom].portals[att_rp->portals[i].cportal].flags; // Delete old portal from attach room DeletePortalPair(att_rp, i); // If this portal attaches to the base room, mark the portal faces for deletion if (croom == ROOMNUM(base_rp)) { if (!(flags & PF_RENDER_FACES)) base_rp->faces[facenum].tmap = -1; //-1 means to delete the face if (!(cflags & PF_RENDER_FACES)) base_rp->faces[cface].tmap = -1; //-1 means to delete the face } else { // Attaches to third room, so make attachment to base room LinkRooms(Rooms, ROOMNUM(base_rp), facenum, croom, cface); portal *pp = &base_rp->portals[base_rp->num_portals - 1]; pp->flags |= (flags & (PF_RENDER_FACES + PF_RENDERED_FLYTHROUGH)); Rooms[pp->croom].portals[pp->cportal].flags |= (cflags * (PF_RENDER_FACES + PF_RENDERED_FLYTHROUGH)); } i--; // adjust for new portal numbering } // Delete obsolete faces for (i = 0; i < base_rp->num_faces; i++) if (base_rp->faces[i].tmap == -1) { DeleteRoomFace(base_rp, i); i--; } // Link objects to new room while (att_rp->objects != -1) ObjRelink(att_rp->objects, ROOMNUM(base_rp)); // Delete the old room DeleteRoomFromMine(att_rp); // Get rid of duplicate verts RemoveDuplicatePoints(base_rp); // Done! World_changed = 1; return 1; } // Creates a an external room and links the specified faces to it // Parameters: rp - the room to connect to the new room // nfaces - how many faces connect to the new room (this becomes the number of //portals) facenums - the list of faces to connect void LinkToExternalRoom(room *rp, int nfaces, int *facenums) { int vert_xlate[MAX_VERTS_PER_ROOM]; int v, i, nverts = 0; for (i = 0; i < rp->num_verts; i++) vert_xlate[i] = -1; for (i = 0; i < nfaces; i++) { face *fp = &rp->faces[facenums[i]]; for (v = 0; v < fp->num_verts; v++) { int vertnum = fp->face_verts[v]; if (vert_xlate[vertnum] == -1) vert_xlate[vertnum] = nverts++; } } room *newrp = CreateNewRoom(nverts, nfaces); newrp->flags |= RF_EXTERNAL; for (i = 0; i < rp->num_verts; i++) { if (vert_xlate[i] != -1) newrp->verts[vert_xlate[i]] = rp->verts[i]; } for (i = 0; i < nfaces; i++) { face *newfp = &newrp->faces[i], *fp = &rp->faces[facenums[i]]; InitRoomFace(newfp, fp->num_verts); for (v = 0; v < fp->num_verts; v++) newfp->face_verts[v] = vert_xlate[fp->face_verts[fp->num_verts - v - 1]]; #ifndef NEWEDITOR newfp->tmap = fp->tmap; #else // Apply texture HTextureApplyToRoomFace(newrp, i, newfp->tmap); #endif if (!ComputeFaceNormal(newrp, i)) Int3(); LinkRooms(Rooms, ROOMNUM(rp), facenums[i], ROOMNUM(newrp), i); } AssignDefaultUVsToRoom(newrp); World_changed = 1; } extern bool Disable_editor_rendering; // Splits a face into two pieces // Parameters: rp,facenum - the face to split // v0,v1 - the two verts that will form the new edge void SplitFace(room *rp, int facenum, int v0, int v1) { face *fp = &rp->faces[facenum], *nfp; int first_new_face, i; if (v0 == v1) { OutrageMessageBox("You cannot split a face using the same point twice."); return; } if (v1 < v0) { int t = v1; v1 = v0; v0 = t; } if (v1 == (v0 + 1) % fp->num_verts) { OutrageMessageBox("You cannot split a face using two adjacent points."); return; } if (fp->num_verts == 3) { OutrageMessageBox("Face %d has three verts and cannot be split.", facenum); return; } if (fp->portal_num != -1) { OutrageMessageBox("You cannot split a face that's part of a portal."); return; } if (fp->flags & (FF_HAS_TRIGGER + FF_FLOATING_TRIG)) { OutrageMessageBox("You cannot split a face that has a trigger."); return; } Disable_editor_rendering = 1; // Add new faces first_new_face = RoomAddFaces(rp, 2); // Reset pointer to our old face fp = &rp->faces[facenum]; // Set up each face bool ok[2]; for (i = 0, nfp = &rp->faces[first_new_face]; i < 2; i++, nfp++) { int nv, startv; if (i == 0) { nv = v1 - v0 + 1; startv = v0; } else { nv = fp->num_verts - (v1 - v0) + 1; startv = v1; } InitRoomFace(nfp, nv); // Set verts & uvls for this face for (int v = 0; v < nv; v++) { nfp->face_verts[v] = fp->face_verts[(startv + v) % fp->num_verts]; nfp->face_uvls[v] = fp->face_uvls[(startv + v) % fp->num_verts]; } // Copy other data for this face CopyFaceFlags(nfp, fp); #ifndef NEWEDITOR nfp->tmap = fp->tmap; #else // Apply texture HTextureApplyToRoomFace(rp, first_new_face + i, fp->tmap); #endif nfp->light_multiple = fp->light_multiple; // Compute the normal for this face ok[i] = ComputeFaceNormal(rp, first_new_face + i); } // Bail if either face is degenerate if (!ok[0] || !ok[1]) { OutrageMessageBox("Cannot form face here: one new face is degenerate."); DeleteRoomFace(rp, first_new_face); DeleteRoomFace(rp, first_new_face + 1); Disable_editor_rendering = 0; return; } // Delete original face DeleteRoomFace(rp, facenum); // We're done EditorStatus("Face %d replaced by new faces %d & %d", facenum, first_new_face - 1, first_new_face); Disable_editor_rendering = 0; #ifndef NEWEDITOR World_changed = 1; #endif } // Adds a point to an edge void SplitEdge(room *rp, int facenum, int edgenum, float position) { face *fp = &rp->faces[facenum]; int vert1 = (edgenum + 1) % fp->num_verts; vector *v0, *v1, newv; v0 = &rp->verts[fp->face_verts[edgenum]]; v1 = &rp->verts[fp->face_verts[vert1]]; newv = *v0 + (*v1 - *v0) * position; AddPointToAllEdges(rp, fp->face_verts[edgenum], fp->face_verts[vert1], &newv); World_changed = 1; } #define MIN_NORMAL_MAG 0.035 int FindConnectedFace(room *rp, int facenum, int edgenum, int startface); // Deletes a vertex from an face // Only deletes if the two adjacent edges are colinear, and there isn't a T-joint at this point void DeleteVert(room *rp, int facenum, int vertnum) { face *fp = &rp->faces[facenum]; vector normal; float mag; int f0, f1; int prev_vertnum = (vertnum + fp->num_verts - 1) % fp->num_verts; mag = vm_GetNormal(&normal, &rp->verts[fp->face_verts[prev_vertnum]], &rp->verts[fp->face_verts[vertnum]], &rp->verts[fp->face_verts[(vertnum + 1) % fp->num_verts]]); if (mag >= MIN_NORMAL_MAG) { OutrageMessageBox("Can't remove point: edges aren't colinear."); return; } // Make sure there isn't a t-joint at this face f0 = FindConnectedFace(rp, facenum, prev_vertnum, 0); f1 = FindConnectedFace(rp, facenum, vertnum, 0); if ((f0 != -1) && (f1 != -1) && (f0 != f1)) { OutrageMessageBox("Can't delete vertex: apparent T-joint with faces %d & %d\n", f0, f1); return; } // Delete the point on our passed face DeletePointFromFace(rp, facenum, vertnum); // Delete the point on the connected face face *fp2 = &rp->faces[f0]; int v2; for (v2 = 0; v2 < fp2->num_verts; v2++) if (fp2->face_verts[v2] == fp->face_verts[vertnum]) break; ASSERT(v2 < fp2->num_verts); DeletePointFromFace(rp, f0, v2); #ifndef NEWEDITOR World_changed = 1; #endif } // Moves a vertex along its two adjacent edges // Only moves if the two adjacent edges are colinear void MoveVert(room *rp, int facenum, int vertnum, float new_position) { face *fp = &rp->faces[facenum]; vector normal; float mag; int prev_vertnum = (vertnum + fp->num_verts - 1) % fp->num_verts; int next_vertnum = (vertnum + 1) % fp->num_verts; vector *v0, *v1, newv; mag = vm_GetNormal(&normal, &rp->verts[fp->face_verts[prev_vertnum]], &rp->verts[fp->face_verts[vertnum]], &rp->verts[fp->face_verts[next_vertnum]]); if (mag >= MIN_NORMAL_MAG) { OutrageMessageBox("Can't move point: edges aren't colinear."); return; } v0 = &rp->verts[fp->face_verts[prev_vertnum]]; v1 = &rp->verts[fp->face_verts[next_vertnum]]; newv = *v0 + (*v1 - *v0) * new_position; rp->verts[fp->face_verts[vertnum]] = newv; World_changed = 1; } #include "polymodel.h" // Incorporates the geometry from the specified object into the specified room. Deletes the object. // Returns true if worked, false if some error bool MergeObjectIntoRoom(room *rp, int objnum) { object *objp = &Objects[objnum]; ASSERT(objp->render_type == RT_POLYOBJ); poly_model *pm = GetPolymodelPointer(objp->rtype.pobj_info.model_num); if (pm->n_models > 1) { #ifndef NEWEDITOR OutrageMessageBox("Cannot merge: Only supported for objects with one submodel.\n\nIf you really need this " "functions for objects with multiple submodles, talk to MattT."); #else OutrageMessageBox("Cannot merge: Only supported for objects with one submodel."); #endif return 0; } if (!pm->n_ground) { OutrageMessageBox("Cannot merge: The object must have a ground plane to merge."); return 0; } if (OBJECT_OUTSIDE(objp)) { OutrageMessageBox("Cannot merge: The object is outside."); return 0; } if (objp->roomnum != ROOMNUM(rp)) { OutrageMessageBox("Cannot merge: The object is not in the specified room."); return 0; } // Get points to model data bsp_info *sm = &pm->submodel[0]; // Add verts to room int first_new_vert = RoomAddVertices(rp, sm->nverts); // Rotate the points and copy them to the room matrix rotmat = ~objp->orient; for (int i = 0; i < sm->nverts; i++) { rp->verts[first_new_vert + i] = objp->pos + sm->verts[i] * rotmat; } // Add faces to the room int first_new_face = RoomAddFaces(rp, sm->num_faces); // Copy the faces for (int i = 0; i < sm->num_faces; i++) { polyface *pfp = &sm->faces[i]; face *fp = &rp->faces[first_new_face + i]; InitRoomFace(fp, pfp->nverts); for (int v = 0; v < fp->num_verts; v++) { fp->face_verts[v] = first_new_vert + pfp->vertnums[v]; fp->face_uvls[v].u = pfp->u[v]; fp->face_uvls[v].v = pfp->v[v]; } #ifndef NEWEDITOR fp->tmap = pm->textures[pfp->texnum]; #else // Apply texture HTextureApplyToRoomFace(rp, first_new_face + i, pm->textures[pfp->texnum]); #endif if (!ComputeFaceNormal(rp, first_new_face + i)) Int3(); // Bad! Get Matt! } // Kill the object ObjDelete(objnum); // Done! World_changed = 1; return 1; } void MarkFaceForDeletion(room *rp, int facenum) { face *fp = &rp->faces[facenum]; // Mark this face fp->tmap = -1; // Find connect faces for (int e = 0; e < fp->num_verts; e++) { int t = -1; while ((t = FindConnectedFace(rp, facenum, e, t + 1)) != -1) { if (rp->faces[t].tmap != -1) MarkFaceForDeletion(rp, t); } } } // Delete all the faces connected to the specified face void DeleteAllConnectedFaces(room *rp, int facenum) { // Make sure no faces using tmap of -1 for (int f = 0; f < rp->num_faces; f++) ASSERT(rp->faces[f].tmap != -1); // Do recursive marking MarkFaceForDeletion(rp, facenum); // Now delete the faces for (int f = 0; f < rp->num_faces;) { if (rp->faces[f].tmap == -1) DeleteRoomFace(rp, f); else f++; } // Done World_changed = 1; } // returns the number of faces changed int PropagateToConnectedFacesSub(room *rp, int facenum, bool *face_checked) { int n_changed = 0; face_checked[facenum] = 1; face *fp = &rp->faces[facenum]; for (int e = 0; e < fp->num_verts; e++) { int t = -1; do { t = FindConnectedFace(rp, facenum, e, t + 1); if ((t != -1) && !face_checked[t]) { if (NormalsAreSame(&fp->normal, &rp->faces[t].normal)) { HTexturePropagateToFace(rp, t, rp, facenum); n_changed++; n_changed += PropagateToConnectedFacesSub(rp, t, face_checked); } } } while (t != -1); } return n_changed; } void PropagateToConnectedFaces(room *rp, int facenum) { bool face_checked[MAX_FACES_PER_ROOM]; int f, n_changed; for (f = 0; f < rp->num_faces; f++) face_checked[f] = 0; face_checked[facenum] = 1; n_changed = PropagateToConnectedFacesSub(rp, facenum, face_checked); EditorStatus("%d faces were retextured.", n_changed); if (n_changed) World_changed = 1; }