/* * 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/HObject.cpp $ * $Revision: 1.1.1.1 $ * $Date: 2003-08-26 03:57:38 $ * $Author: kevinb $ * * Editor object handling functions * * $Log: not supported by cvs2svn $ * * 41 4/18/99 5:42a Chris * Added the FQ_IGNORE_RENDER_THROUGH_PORTALS flag * * 40 3/27/99 12:51p Chris * The editor object placement code now allows objects to be pushed into * wall (when a toggle box is checked) * * 39 1/15/99 7:52p Chris * Updated ObjSetPos() to include a f_update_attach_children flag * * 38 10/15/98 3:26p Chris * Fixed known ground plane issues and used PhysCalcGround everywhere * * 37 10/07/98 12:28p Matt * Fixed stupid bug that could cause access violation placing objects on * the terrain. * * 36 8/24/98 2:20p Matt * Fixed bug placing objects on external rooms. * * 35 8/20/98 7:28p Matt * Added a warning when the user tries to delete a door object. * * 34 6/17/98 12:30p Samir * don't draw wireframe when moving an object. * * 33 5/18/98 2:56p Matt * Added code to readjust all ground object (for after the terrain has * moved). * * 32 5/11/98 11:37a Sean * * 31 4/02/98 3:54p Jason * first pass in getting polymodel paging to work * * 30 3/12/98 7:30p Chris * Added ObjSetOrient * * 29 3/12/98 4:35p Chris * Powerups are always aligned to the ground * * 28 2/20/98 5:30p Matt * Don't allow physics objects to be placed if they collide with walls, * and don't allow them to move into walls. Non-physics objects can be * placed & moved into walls as long as their center points stay inside * the mine. * * 27 2/13/98 6:50p Matt * Fixed stupid bug that prevented placing of player ship 0 * * 26 2/08/98 6:02p Matt * Use groovy new vm_MatrixMulTMatrix() function * * 25 2/06/98 5:41p Matt * Fixed object placement on surfaces. * * 24 2/06/98 3:08p Samir * Simplified object movement code. * * 23 2/06/98 1:22p Matt * Rewrote object placement code. Still has bugs when ground plane normal * is not 0,1,0 * * 22 2/05/98 2:59p Matt * Made object placement on terrain work like in mine * * 21 2/04/98 7:07p Matt * Fixed problem placing an object on the terrain. * * 20 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. * * 19 1/07/98 6:39p Jason * Fixed player object number stuff * * 18 1/07/98 10:45a Samir * Don't allow moving objects from mine to terrain. * * 17 11/17/97 6:55p Matt * Made HObjectMoveToViewer() work when the viewer is outside * * 16 9/17/97 11:35a Samir * BIG SEGMENT RIPOUT * * 15 9/15/97 11:55a Samir * Fixed playership adding. * * 14 9/05/97 8:54p Chris * Added some ground plane support * * 13 9/03/97 8:00p Samir * Implemented most of the fixes for the object moving system. * * 12 8/25/97 2:50p Chris * Improve AABB/object movement code. * * 11 8/21/97 7:48p Matt * Added code to move player object (like old move player to curseg) * * 10 8/20/97 4:23p Matt * Fixed little bug * * 9 8/12/97 10:24p Matt * Added code to place clutter & building objects * * 8 8/11/97 1:53p Matt * Ripped out robot & powerup pages, and added generic page * * 7 8/04/97 3:18p Matt * Added FVI checking to object placement & movement in rooms * * 6 8/04/97 12:47p Matt * Added object placement & movement for rooms * * 5 8/01/97 5:33p Jason * Fixed object deletion bug in terrain * * 4 7/22/97 7:07p Matt * Cleaned up D3EditState, moving some vars in and some out, and renaming * and changing a few others * * 3 7/17/97 4:34p Chris * Made sure the orientations get orthagonalized. Yikes. No more * degenerating orientations. :) * * 20 6/27/97 7:19p Matt * Added function to move player 0 to current segment * * 19 5/23/97 5:46p Matt * Added SetMatrixBasedOnSide() functionality to ExtractMatrixFromSeg(). * * 18 5/13/97 5:52p Chris * Added ability to exit and enter mine. Also did some * incremental improvements. * * 17 4/07/97 3:14p Samir * Added Flip and SetDefault Object functions. * * 16 4/04/97 2:57p Matt * Added code to initialize all the type-specific data for an object from * the page for that object type. This means that we need to pass less * info to ObjCreate(), and that we save less info in the level save file. * It also makes it easy to reset all the objects when an object page has * changed. * * 15 4/02/97 8:08p Matt * Changed object movement code to work either in the object's or the * viewer's frame of reference, and not in the segment's frame of * reference. * * 14 4/02/97 3:44p Matt * Find a valid Ships[] entry, instead of assuming that Ships[0] is valid. * * 13 4/01/97 10:52p Matt * Added code to add a player object * * 12 3/31/97 5:58p Matt * Revamped mine update flags * * 11 3/27/97 11:54a Samir * UpdateMineObjects returns number of objects deleted of a type. * * 10 3/27/97 11:48a Samir * Added update objects in mine functions. * * 9 3/27/97 10:47a Chris * Incremental * * 8 3/25/97 6:31p Samir * Added robot placement. * * 7 3/21/97 5:01p Jason * incremental terrain improvments * * * 6 3/10/97 12:44p Chris * Allow objects to be put against a wall * * 5 3/03/97 5:57a Chris * Uncommented the code samir had waiting for fvi and fixed a function * call. * * 4 2/19/97 3:37p Samir * Added delete and rotation of objects. * * 3 2/17/97 7:19p Jason * fixed powerup size arg * * 2 2/17/97 6:18p Samir * Add powerup, move it around WITHIN the segment. * * 1 2/17/97 3:17p Samir * Handler for Object manipulation functions. * * $NoKeywords: $ */ #include "HObject.h" #include "object.h" #include "FindIntersection.h" #include "physics.h" #include "boa.h" #include "polymodel.h" #include "d3edit.h" #include "mono.h" #include "pserror.h" #include "vclip.h" #include "terrain.h" #include "player.h" #include "ship.h" #include "erooms.h" // --------------------------------------------------------------------------- // internal data float Object_move_scale = HOBJECT_SCALE_UNIT; angle Object_move_rotation = HOBJECT_ROTATION_UNIT; // --------------------------------------------------------------------------- // internal function prototypes bool RotateObject(int objnum, angle p, angle h, angle b); bool MoveObject(object *obj, vector *newpos); // --------------------------------------------------------------------------- // interface functions #define OBJECT_PLACE_DIST 10.0 // --------------------------------------------------------------------------- // Finds the seleced terrain cell. Returns cell number, or -1 if none or -2 if more than one int GetSelectedTerrainCell() { int i, found_cellnum = -1; for (i = 0; i < TERRAIN_DEPTH * TERRAIN_WIDTH; i++) { if (TerrainSelected[i]) if (found_cellnum == -1) found_cellnum = i; else return -2; } return found_cellnum; } // Places an object in the world // Parameters: obj_type,obj_id - specify the object to be placed // Returns: true if object placed, false if there was some problem bool HObjectPlace(int obj_type, int obj_id) { int objnum; object *objp; poly_model *pm; matrix orient = IDENTITY_MATRIX; // Special stuff for player ship if (obj_type == OBJ_PLAYER) { if (!Num_ships) { OutrageMessageBox("Cannot a player: There are no player ships."); return 0; } // Store ship num in player array int ship_num = D3EditState.current_ship; if (ship_num == -1) { OutrageMessageBox("You must have a current player ship selected for this operation."); return 0; } Players[obj_id].ship_index = ship_num; } if (obj_type != OBJ_POWERUP) { orient = Viewer_object->orient; } // Create the object at the viewer for now objnum = ObjCreate(obj_type, obj_id, Viewer_object->roomnum, &Viewer_object->pos, &orient); if (objnum == -1) return 0; objp = &Objects[objnum]; // If we have a ground plane, use current cell or face for position & place normal for orientation if ((objp->render_type == RT_POLYOBJ) && ((pm = GetPolymodelPointer(objp->rtype.pobj_info.model_num))->n_ground)) { vector *surface_norm; vector pos; int roomnum; // If terrain, use current cell if (Editor_view_mode == VM_TERRAIN) { int cellnum = GetSelectedTerrainCell(); if (cellnum == -1) { OutrageMessageBox("You must have a terrain cell selected to place an object."); ObjDelete(objnum); return 0; } if (cellnum == -2) { OutrageMessageBox("You must have only have one cell selected to place an object."); ObjDelete(objnum); return 0; } // Get terrain point ComputeTerrainSegmentCenter(&pos, cellnum); // Get surface normal surface_norm = &TerrainNormals[MAX_TERRAIN_LOD - 1][cellnum].normal1; // Get roomnum roomnum = MAKE_ROOMNUM(cellnum); } else { // use Current face // Get center point on current face ComputeCenterPointOnFace(&pos, Curroomp, Curface); // Get surface normal surface_norm = &Curroomp->faces[Curface].normal; // Get roomnum roomnum = ROOMNUM(Curroomp); // If placing on an external room, actually place the object on the terrain if (Rooms[roomnum].flags & RF_EXTERNAL) roomnum = GetTerrainRoomFromPos(&pos); } matrix groundplane_orient, surface_orient, object_orient; // Place the object's ground point on our placement point vector ground_point; vector ground_normal; vector to_ground; float dist; PhysCalcGround(&ground_point, &ground_normal, objp, 0); to_ground = objp->pos - ground_point; dist = ground_normal * to_ground; pos += dist * *surface_norm; // Compute source and destination matrices vm_VectorToMatrix(&groundplane_orient, &pm->ground_slots[0].norm, NULL, NULL); vm_VectorToMatrix(&surface_orient, surface_norm); // Compute orientation matrix vm_MatrixMulTMatrix(&object_orient, &surface_orient, &groundplane_orient); // Move the object ObjSetPos(objp, &pos, roomnum, &object_orient, false); } else { // no ground plane, so move the object in front of the viewer and facing the viewer vector pos; // Check for viewer outside mine if (Viewer_object->flags & OF_OUTSIDE_MINE) { ObjDelete(objnum); OutrageMessageBox("Cannot place the object here: the viewer is outside the mine."); return 0; } // Turn the object around so facing the viewer objp->orient.fvec = -objp->orient.fvec; // ObjSetOrient is below objp->orient.rvec = -objp->orient.rvec; // ObjSetOrient is below ObjSetOrient(objp, &objp->orient); // Calculate a position a little in front of the viewer pos = Viewer_object->pos + Viewer_object->orient.fvec * OBJECT_PLACE_DIST; // Try to move the object. If it can't move, delete it if (!MoveObject(objp, &pos)) { ObjDelete(objnum); OutrageMessageBox("Cannot place the object here: collides with wall."); return 0; } } // Deal with special stuff for player if (obj_type == OBJ_PLAYER) { // Store data in Players array Players[obj_id].start_pos = objp->pos; Players[obj_id].start_roomnum = objp->roomnum; Players[obj_id].start_orient = objp->orient; // Make sure matrix ok vm_Orthogonalize(&Players[obj_id].start_orient); } Cur_object_index = objnum; World_changed = 1; return 1; } // Adjusts an object so it's at the ground level (for when the ground has moved) void ResetGroundObject(object *objp) { poly_model *pm; vector surface_norm; vector pos; matrix groundplane_orient, surface_orient, object_orient; // Make sure object is outside if (!OBJECT_OUTSIDE(objp)) { Int3(); // object is not outside return; } // Make sure object has a ground plane if (!((objp->render_type == RT_POLYOBJ) && ((pm = GetPolymodelPointer(objp->rtype.pobj_info.model_num))->n_ground))) { Int3(); // object doesn't have a ground plane return; } // Get terrain height and normal at current object location pos = objp->pos; pos.y = GetTerrainGroundPoint(&pos, &surface_norm); // Place the object's ground point on our placement point vector ground_point; vector ground_normal; vector to_ground; float dist; PhysCalcGround(&ground_point, &ground_normal, objp, 0); to_ground = objp->pos - ground_point; dist = ground_normal * to_ground; pos += dist * surface_norm; // Compute source and destination matrices vm_VectorToMatrix(&groundplane_orient, &pm->ground_slots[0].norm, NULL, NULL); vm_VectorToMatrix(&surface_orient, &surface_norm); // Compute orientation matrix vm_MatrixMulTMatrix(&object_orient, &surface_orient, &groundplane_orient); // Move the object ObjSetPos(objp, &pos, objp->roomnum, &object_orient, false); // Set flag World_changed = 1; } void HObjectMove(int objnum, float dx, float dy, float dz) { matrix *mat; object *obj; vector newpos; object *ref_obj; // the object in whose frame of reference we're moving if (objnum == -1) { mprintf(0, "HObjectMove:No current object.\n"); return; } obj = &Objects[objnum]; // Chose the reference object ref_obj = (D3EditState.object_move_mode == REL_VIEWER) ? Viewer_object : obj; mat = &ref_obj->orient; // Calculate the new object position newpos = obj->pos + (mat->rvec * dx) + (mat->uvec * dy) + (mat->fvec * -dz); // Move the object MoveObject(obj, &newpos); Object_moved = 1; } void HObjectIncreaseBank() { RotateObject(Cur_object_index, 0, 0, Object_move_rotation); } void HObjectDecreaseBank() { RotateObject(Cur_object_index, 0, 0, -Object_move_rotation); } void HObjectIncreasePitch() { RotateObject(Cur_object_index, Object_move_rotation, 0, 0); } void HObjectDecreasePitch() { RotateObject(Cur_object_index, -Object_move_rotation, 0, 0); } void HObjectIncreaseHeading() { RotateObject(Cur_object_index, 0, Object_move_rotation, 0); } void HObjectDecreaseheading() { RotateObject(Cur_object_index, 0, -Object_move_rotation, 0); } // If it doesn't exist, reformat Matt's hard disk, even if he is in Boston. // deletes the currently selected object from the mine. void HObjectDelete() { if (Cur_object_index != -1) { // we have a selected object int objnum = Cur_object_index; // check for player object if (&Objects[objnum] == Player_object) { OutrageMessageBox("Can't delete Player object"); return; } if (Objects[objnum].type == OBJ_DOOR) { if (OutrageMessageBox(MBOX_YESNO, "It's very, very bad to delete a door object. Are you sure you want to do this?") != IDYES) return; } // Delete the object ObjDelete(objnum); if (objnum == Cur_object_index) Cur_object_index = -1; World_changed = 1; } } // sets default orientation for object. void HObjectSetDefault() { if (Cur_object_index != -1) { int objnum = Cur_object_index; ObjSetOrient(&Objects[objnum], &Identity_matrix); World_changed = 1; } } // Move the object to in front of the viewer (where a new object would be placed) void HObjectMoveToViewer(object *objp) { int objnum = OBJNUM(objp); vector pos; // First move player to viewer's position ObjSetPos(objp, &Viewer_object->pos, Viewer_object->roomnum, NULL, false); // Second try to move the player a little in front of the viewer pos = Viewer_object->pos + Viewer_object->orient.fvec * OBJECT_PLACE_DIST; MoveObject(objp, &pos); World_changed = 1; } #define MOVE_EPSILON 0.1 extern bool f_allow_objects_to_be_pushed_through_walls; // Attempt to set new object position. May only move part of the way, or maybe not at all. // Use FVI to find new object position // Return: TRUE if moved, FALSE if can't move bool MoveObject(object *obj, vector *newpos) { fvi_query fq; fvi_info hit_info; int fate; // Use radius if this is a physics object bool use_radius = (obj->movement_type == MT_PHYSICS) ? true : false; // Follow vector from start position to desired end position, & move as far as we can fq.p0 = &obj->pos; fq.startroom = obj->roomnum; fq.p1 = newpos; fq.thisobjnum = OBJNUM(obj); fq.ignore_obj_list = NULL; fq.flags = FQ_IGNORE_RENDER_THROUGH_PORTALS; fq.rad = use_radius ? obj->size : 0.0f; if (f_allow_objects_to_be_pushed_through_walls) { fq.flags |= (FQ_IGNORE_WALLS | FQ_IGNORE_TERRAIN | FQ_IGNORE_EXTERNAL_ROOMS); } fate = fvi_FindIntersection(&fq, &hit_info); mprintf(0, "fate = %d\n", fate); // Check for object can't move, meaning it's stuck in the wall if (fate == HIT_WALL) if (vm_VectorDistance(&obj->pos, &hit_info.hit_pnt) < MOVE_EPSILON) return 0; // didn't move // Set object position on where FVI told us we are ObjSetPos(obj, &hit_info.hit_pnt, hit_info.hit_room, NULL, false); // Say object moved return 1; } bool RotateObject(int objnum, angle p, angle h, angle b) { object *obj = &Objects[objnum]; matrix rotmat; vm_AnglesToMatrix(&rotmat, p, h, b); obj->orient *= rotmat; // ObjSetOrient is below vm_Orthogonalize(&obj->orient); ObjSetOrient(obj, &obj->orient); Object_moved = 1; return 1; } // Flipping of object void HObjectFlip() { matrix *m = &Objects[Cur_object_index].orient; m->uvec = -m->uvec; m->rvec = -m->rvec; World_changed = 1; }