From 2e174e655de7a2a61b587c39c9cdcb5fb8a4ee0b Mon Sep 17 00:00:00 2001 From: SteelT Date: Wed, 23 Dec 2020 02:45:27 -0500 Subject: [PATCH] Initial commit --- COPYING | 339 +++++ DOOM/level.cpp | 1210 ++++++++++++++++++ DOOM/level.hpp | 341 +++++ DOOM/lineDef.hpp | 159 +++ DOOM/thing.hpp | 100 ++ DOOM/wad.cpp | 1223 ++++++++++++++++++ DOOM/wad.hpp | 322 +++++ README | 22 + ZenNode/ZenMain.cpp | 1030 +++++++++++++++ ZenNode/ZenNode.cpp | 1890 ++++++++++++++++++++++++++++ ZenNode/ZenNode.hpp | 235 ++++ ZenNode/ZenRMB.cpp | 407 ++++++ ZenNode/ZenReject-util.cpp | 198 +++ ZenNode/ZenReject.cpp | 2437 ++++++++++++++++++++++++++++++++++++ ZenNode/blockmap.cpp | 258 ++++ ZenNode/bspdiff.cpp | 462 +++++++ ZenNode/bspinfo.cpp | 439 +++++++ ZenNode/compare.cpp | 494 ++++++++ ZenNode/console.cpp | 574 +++++++++ ZenNode/console.hpp | 78 ++ ZenNode/geometry.hpp | 91 ++ ZenNode/makefile | 139 ++ common/common.hpp | 258 ++++ common/logger.hpp | 185 +++ 24 files changed, 12891 insertions(+) create mode 100644 COPYING create mode 100644 DOOM/level.cpp create mode 100644 DOOM/level.hpp create mode 100644 DOOM/lineDef.hpp create mode 100644 DOOM/thing.hpp create mode 100644 DOOM/wad.cpp create mode 100644 DOOM/wad.hpp create mode 100644 README create mode 100644 ZenNode/ZenMain.cpp create mode 100644 ZenNode/ZenNode.cpp create mode 100644 ZenNode/ZenNode.hpp create mode 100644 ZenNode/ZenRMB.cpp create mode 100644 ZenNode/ZenReject-util.cpp create mode 100644 ZenNode/ZenReject.cpp create mode 100644 ZenNode/blockmap.cpp create mode 100644 ZenNode/bspdiff.cpp create mode 100644 ZenNode/bspinfo.cpp create mode 100644 ZenNode/compare.cpp create mode 100644 ZenNode/console.cpp create mode 100644 ZenNode/console.hpp create mode 100644 ZenNode/geometry.hpp create mode 100644 ZenNode/makefile create mode 100644 common/common.hpp create mode 100644 common/logger.hpp diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..a43ea21 --- /dev/null +++ b/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 675 Mass Ave, Cambridge, MA 02139, USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + Appendix: How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + 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 2 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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/DOOM/level.cpp b/DOOM/level.cpp new file mode 100644 index 0000000..dfa4b3c --- /dev/null +++ b/DOOM/level.cpp @@ -0,0 +1,1210 @@ +//---------------------------------------------------------------------------- +// +// File: level.cpp +// Date: 26-Oct-1994 +// Programmer: Marc Rousseau +// +// Description: Object classes for manipulating Doom Maps +// +// Copyright (c) 1994-2004 Marc Rousseau, All Rights Reserved. +// +// 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 2 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, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. +// +// Revision History: +// +//---------------------------------------------------------------------------- + +#include +#include +#include +#include +#include "common.hpp" +#include "logger.hpp" +#include "level.hpp" +#include "wad.hpp" + +DBG_REGISTER ( __FILE__ ); + +#define SWAP_ENDIAN_16(x) (((( x ) & 0xFF ) << 8 ) | ((( x ) >> 8 ) & 0xFF )) + +//#define min(x,y) ((( x ) < ( y )) ? x : y ) + +#if defined ( __GNUC__ ) || defined ( __INTEL_COMPILER ) + +char *strupr ( char *ptr ) +{ + for ( int i = 0; ptr[i]; i++ ) { + ptr[i] = toupper ( ptr[i] ); + } + return ptr; +} + +#endif + +DoomLevel::sLevelLump::sLevelLump () : + changed ( false ), + byteOrder ( BYTE_ORDER ), + elementSize ( 0 ), + elementCount ( 0 ), + dataSize ( 0 ), + rawData ( NULL ) +{ +} + +DoomLevel::DoomLevel ( const char *_name, WAD *_wad, bool bLoadData ) : + m_Wad ( _wad ), + m_IsHexen ( false ), + m_Title ( NULL ), + m_Music ( NULL ), + m_Cluster ( 0 ), + m_ThingData ( NULL ), + m_LineDefData ( NULL ) +{ + FUNCTION_ENTRY ( this, "DoomLevel ctor", true ); + + m_Title = NULL; + m_Music = NULL; + m_Cluster = -1; + m_IsHexen = false; + + memset ( m_Name, 0, sizeof ( m_Name )); + for ( int i = 0; i < 8; i++ ) { + if ( _name[i] == '\0' ) break; + m_Name[i] = ( char ) toupper ( _name[i] ); + } + + m_Map.elementSize = 1; + m_Thing.elementSize = 1; + m_LineDef.elementSize = 1; + m_SideDef.elementSize = sizeof ( wSideDef ); + m_Vertex.elementSize = sizeof ( wVertex ); + m_Sector.elementSize = sizeof ( wSector ); + m_Segs.elementSize = sizeof ( wSegs ); + m_SubSector.elementSize = sizeof ( wSSector ); + m_Node.elementSize = sizeof ( wNode ); + m_Reject.elementSize = 1; + m_BlockMap.elementSize = 1; + m_Behavior.elementSize = 1; + + if ( bLoadData == true ) Load (); + + LoadHexenInfo (); +} + +DoomLevel::~DoomLevel () +{ + FUNCTION_ENTRY ( this, "DoomLevel dtor", true ); + + if ( m_Title != NULL ) free (( char * ) m_Title ); + if ( m_Music != NULL ) free (( char * ) m_Music ); + CleanUp (); +} + +bool DoomLevel::IsValid ( bool checkBSP, bool print ) const +{ + FUNCTION_ENTRY ( this, "DoomLevel::isValid", true ); + + bool isValid = true; + + bool *used = new bool [ SideDefCount () ]; + memset ( used, false, sizeof ( bool ) * SideDefCount ()); + + // Sanity check for LINEDEFS + const wLineDef *lineDef = GetLineDefs (); + + for ( int i = 0; i < LineDefCount (); i++ ) { + if ( lineDef [i].start >= VertexCount ()) { + if ( print == true ) fprintf ( stderr, "LINEDEFS[%d].%s vertex is invalid (%d/%d)\n", i, "start", lineDef [i].start, VertexCount ()); + isValid = false; + } + if ( lineDef [i].end >= VertexCount ()) { + if ( print == true ) fprintf ( stderr, "LINEDEFS[%d].%s vertex is invalid (%d/%d)\n", i, "end", lineDef [i].end, VertexCount ()); + isValid = false; + } + if ( lineDef [i].sideDef [ LEFT_SIDEDEF ] != NO_SIDEDEF ) { + if ( lineDef [i].sideDef [ LEFT_SIDEDEF ] >= SideDefCount ()) { + if ( print == true ) fprintf ( stderr, "LINEDEFS[%d].sideDef[%s] is invalid (%d/%d)\n", i, "left", lineDef [i].sideDef [LEFT_SIDEDEF], SideDefCount ()); + isValid = false; + } else { + used [ lineDef [i].sideDef [ LEFT_SIDEDEF ]] = true; + } + } + if ( lineDef [i].sideDef [ RIGHT_SIDEDEF ] != NO_SIDEDEF ) { + if ( lineDef [i].sideDef [ RIGHT_SIDEDEF ] >= SideDefCount ()) { + if ( print == true ) fprintf ( stderr, "LINEDEFS[%d].sideDef[%s] is invalid (%d/%d)\n", i, "right", lineDef [i].sideDef [RIGHT_SIDEDEF], SideDefCount ()); + isValid = false; + } else { + used [ lineDef [i].sideDef [ RIGHT_SIDEDEF ]] = true; + } + } + } + + // Sanity check for SIDEDEFS + const wSideDef *sideDef = GetSideDefs (); + + for ( int i = 0; i < SideDefCount (); i++ ) { + if (( sideDef [i].sector >= SectorCount ()) && ( used [i] == true )) { + if ( print == true ) fprintf ( stderr, "SIDEDEFS[%d].sector is invalid (%d/%d)\n", i, sideDef [i].sector, SectorCount ()); + isValid = false; + } + } + + delete [] used; + + if ( checkBSP == true ) { + + // Sanity check for SEGS + const wSegs *segs = GetSegs (); + + for ( int i = 0; i < SegCount (); i++ ) { + if ( segs [i].start >= VertexCount ()) { + if ( print == true ) fprintf ( stderr, "SEGS[%d].%s m_Vertex is invalid (%d/%d)\n", i, "start", segs [i].start, VertexCount ()); + isValid = false; + } + if ( segs [i].end >= VertexCount ()) { + if ( print == true ) fprintf ( stderr, "SEGS[%d].%s m_Vertex is invalid (%d/%d)\n", i, "end", segs [i].end, VertexCount ()); + isValid = false; + } + if ( segs [i].lineDef >= LineDefCount ()) { + if ( print == true ) fprintf ( stderr, "SEGS[%d].lineDef is invalid (%d/%d)\n", i, segs [i].lineDef, LineDefCount ()); + isValid = false; + } + if ( segs [i].start == segs [i].end ) { + if ( print == true ) fprintf ( stderr, "SEGS[%d] is zero length\n", i ); + isValid = false; + } + } + + // Sanity check for SSECTORS + const wSSector *subSector = GetSubSectors (); + + for ( int i = 0; i < SubSectorCount (); i++ ) { + if ( subSector [i].first >= SegCount ()) { + if ( print == true ) fprintf ( stderr, "SSECTORS[%d].first is invalid (%d/%d)\n", i, subSector [i].first, SegCount ()); + isValid = false; + } + if ( subSector [i].first + subSector [i].num > SegCount ()) { + if ( print == true ) fprintf ( stderr, "SSECTORS[%d].num is invalid (%d/%d)\n", i, subSector [i].num, SegCount ()); + isValid = false; + } + } + + // Sanity check for NODES + const wNode *node = GetNodes (); + + if ( NodeCount () < 2 ) { + if ( print == true ) fprintf ( stderr, "NODES structure is invalid\n" ); + isValid = false; + } + + for ( int i = 0; i < NodeCount (); i++ ) { + UINT16 child; + child = node [i].child [0]; + if (( node [i].dx == 0 ) && ( node[i].dy == 0 )) { + if ( print == true ) fprintf ( stderr, "NODES[%d] is invalid (dx == dy == 0)\n", i ); + isValid = false; + } + if ( child & 0x8000 ) { + if (( child & 0x7FFF ) >= SubSectorCount ()) { + if ( print == true ) fprintf ( stderr, "NODES[%d].child[%d] is invalid (0x8000 | %d/%d)\n", i, 0, child & 0x7FFF, SubSectorCount ()); + isValid = false; + } + } else { + if ( child >= NodeCount ()) { + if ( print == true ) fprintf ( stderr, "NODES[%d].child[%d] is invalid (%d/%d)\n", i, 0, child, NodeCount ()); + isValid = false; + } + } + child = node [i].child [1]; + if ( child & 0x8000 ) { + if (( child & 0x7FFF ) >= SubSectorCount ()) { + if ( print == true ) fprintf ( stderr, "NODES[%d].child[%d] is invalid (0x8000 | %d/%d)\n", i, 1, child & 0x7FFF, SubSectorCount ()); + isValid = false; + } + } else { + if ( child >= NodeCount ()) { + if ( print == true ) fprintf ( stderr, "NODES[%d].child[%d] is invalid (%d/%d)\n", i, 1, child, NodeCount ()); + isValid = false; + } + } + } + } + + return isValid; +} + +bool DoomLevel::IsDirty () const +{ + FUNCTION_ENTRY ( this, "DoomLevel::IsDirty", true ); + + if ( m_Map.changed == true ) return true; + if ( m_Thing.changed == true ) return true; + if ( m_LineDef.changed == true ) return true; + if ( m_SideDef.changed == true ) return true; + if ( m_Vertex.changed == true ) return true; + if ( m_Sector.changed == true ) return true; + if ( m_Segs.changed == true ) return true; + if ( m_SubSector.changed == true ) return true; + if ( m_Node.changed == true ) return true; + if ( m_Reject.changed == true ) return true; + if ( m_BlockMap.changed == true ) return true; + if ( m_Behavior.changed == true ) return true; + + return false; +} + +void DoomLevel::CleanUpEntry ( sLevelLump *entry ) +{ + FUNCTION_ENTRY ( this, "DoomLevel::CleanUpEntry", true ); + + delete [] ( char * ) entry->rawData; + + entry->changed = false; + entry->byteOrder = BYTE_ORDER; + entry->elementCount = 0; + entry->dataSize = 0; + entry->rawData = NULL; +} + +void DoomLevel::CleanUp () +{ + FUNCTION_ENTRY ( this, "DoomLevel::CleanUp", true ); + + CleanUpEntry ( &m_Map ); + CleanUpEntry ( &m_Thing ); + CleanUpEntry ( &m_LineDef ); + CleanUpEntry ( &m_SideDef ); + CleanUpEntry ( &m_Vertex ); + CleanUpEntry ( &m_Sector ); + CleanUpEntry ( &m_Segs ); + CleanUpEntry ( &m_SubSector ); + CleanUpEntry ( &m_Node ); + CleanUpEntry ( &m_Reject ); + CleanUpEntry ( &m_BlockMap ); + CleanUpEntry ( &m_Behavior ); + + delete [] m_ThingData; + delete [] m_LineDefData; + + m_ThingData = NULL; + m_LineDefData = NULL; +} + +#if ( BYTE_ORDER == BIG_ENDIAN ) + +void DoomLevel::AdjustByteOrderMap ( int byteOrder ) +{ + FUNCTION_ENTRY ( this, "DoomLevel::AdjustByteOrderMap", true ); + + if ( m_Map.byteOrder != byteOrder ) { + // Nothing to do for this type + } + + m_Map.byteOrder = byteOrder; +} + +void DoomLevel::AdjustByteOrderThing ( int byteOrder ) +{ + FUNCTION_ENTRY ( this, "DoomLevel::AdjustByteOrderThing", true ); + + if ( m_Thing.byteOrder != byteOrder ) { + wThing *thing = m_ThingData; + for ( int i = 0; i < m_Thing.elementCount; i++ ) { + thing [i].xPos = SWAP_ENDIAN_16 ( thing [i].xPos ); + thing [i].yPos = SWAP_ENDIAN_16 ( thing [i].yPos ); + thing [i].angle = SWAP_ENDIAN_16 ( thing [i].angle ); + thing [i].type = SWAP_ENDIAN_16 ( thing [i].type ); + thing [i].attr = SWAP_ENDIAN_16 ( thing [i].attr ); + thing [i].tid = SWAP_ENDIAN_16 ( thing [i].tid ); + thing [i].altitude = SWAP_ENDIAN_16 ( thing [i].altitude ); + } + } + + m_Thing.byteOrder = byteOrder; +} + +void DoomLevel::AdjustByteOrderLineDef ( int byteOrder ) +{ + FUNCTION_ENTRY ( this, "DoomLevel::AdjustByteOrderLineDef", true ); + + if ( m_LineDef.byteOrder != byteOrder ) { + wLineDef *lineDef = m_LineDefData; + for ( int i = 0; i < m_LineDef.elementCount; i++ ) { + lineDef [i].start = SWAP_ENDIAN_16 ( lineDef [i].start ); + lineDef [i].end = SWAP_ENDIAN_16 ( lineDef [i].end ); + lineDef [i].flags = SWAP_ENDIAN_16 ( lineDef [i].flags ); + lineDef [i].type = SWAP_ENDIAN_16 ( lineDef [i].type ); + lineDef [i].tag = SWAP_ENDIAN_16 ( lineDef [i].tag ); + lineDef [i].sideDef [0] = SWAP_ENDIAN_16 ( lineDef [i].sideDef [0] ); + lineDef [i].sideDef [1] = SWAP_ENDIAN_16 ( lineDef [i].sideDef [1] ); + } + } + + m_LineDef.byteOrder = byteOrder; +} + +void DoomLevel::AdjustByteOrderSideDef ( int byteOrder ) +{ + FUNCTION_ENTRY ( this, "DoomLevel::AdjustByteOrderSideDef", true ); + + if ( m_SideDef.byteOrder != byteOrder ) { + wSideDef *sideDef = ( wSideDef * ) m_SideDef.rawData; + for ( int i = 0; i < m_SideDef.elementCount; i++ ) { + sideDef [i].xOff = SWAP_ENDIAN_16 ( sideDef [i].xOff ); + sideDef [i].yOff = SWAP_ENDIAN_16 ( sideDef [i].yOff ); + sideDef [i].sector = SWAP_ENDIAN_16 ( sideDef [i].sector ); + } + } + + m_SideDef.byteOrder = byteOrder; +} + +void DoomLevel::AdjustByteOrderVertex ( int byteOrder ) +{ + FUNCTION_ENTRY ( this, "DoomLevel::AdjustByteOrderVertex", true ); + + if ( m_Vertex.byteOrder != byteOrder ) { + wVertex *vertex = ( wVertex * ) m_Vertex.rawData; + for ( int i = 0; i < m_Vertex.elementCount; i++ ) { + vertex [i].x = SWAP_ENDIAN_16 ( vertex [i].x ); + vertex [i].y = SWAP_ENDIAN_16 ( vertex [i].y ); + } + } + + m_Vertex.byteOrder = byteOrder; +} + +void DoomLevel::AdjustByteOrderSector ( int byteOrder ) +{ + FUNCTION_ENTRY ( this, "DoomLevel::AdjustByteOrderSector", true ); + + if ( m_Sector.byteOrder != byteOrder ) { + wSector *sector = ( wSector * ) m_Sector.rawData; + for ( int i = 0; i < m_Sector.elementCount; i++ ) { + sector [i].floorh = SWAP_ENDIAN_16 ( sector [i].floorh ); + sector [i].ceilh = SWAP_ENDIAN_16 ( sector [i].ceilh ); + sector [i].light = SWAP_ENDIAN_16 ( sector [i].light ); + sector [i].special = SWAP_ENDIAN_16 ( sector [i].special ); + sector [i].trigger = SWAP_ENDIAN_16 ( sector [i].trigger ); + } + } + + m_Sector.byteOrder = byteOrder; +} + +void DoomLevel::AdjustByteOrderSegs ( int byteOrder ) +{ + FUNCTION_ENTRY ( this, "DoomLevel::AdjustByteOrderSegs", true ); + + if ( m_Segs.byteOrder != byteOrder ) { + wSegs *segs = ( wSegs * ) m_Segs.rawData; + for ( int i = 0; i < m_Segs.elementCount; i++ ) { + segs [i].start = SWAP_ENDIAN_16 ( segs [i].start ); + segs [i].end = SWAP_ENDIAN_16 ( segs [i].end ); + segs [i].angle = SWAP_ENDIAN_16 ( segs [i].angle ); + segs [i].lineDef = SWAP_ENDIAN_16 ( segs [i].lineDef ); + segs [i].flip = SWAP_ENDIAN_16 ( segs [i].flip ); + segs [i].offset = SWAP_ENDIAN_16 ( segs [i].offset ); + } + } + + m_Segs.byteOrder = byteOrder; +} + +void DoomLevel::AdjustByteOrderSubSector ( int byteOrder ) +{ + FUNCTION_ENTRY ( this, "DoomLevel::AdjustByteOrderSubSector", true ); + + if ( m_SubSector.byteOrder != byteOrder ) { + wSSector *ssector = ( wSSector * ) m_SubSector.rawData; + for ( int i = 0; i < m_SubSector.elementCount; i++ ) { + ssector [i].num = SWAP_ENDIAN_16 ( ssector [i].num ); + ssector [i].first = SWAP_ENDIAN_16 ( ssector [i].first ); + } + } + + m_SubSector.byteOrder = byteOrder; +} + +void DoomLevel::AdjustByteOrderNode ( int byteOrder ) +{ + FUNCTION_ENTRY ( this, "DoomLevel::AdjustByteOrderNode", true ); + + if ( m_Node.byteOrder != byteOrder ) { + // The entire structure is composed of 16-bit entries + UINT16 *ptr = ( UINT16 * ) m_Node.rawData; + for ( int i = 0; i < ( int ) m_Node.dataSize / 2; i++ ) { + ptr [i] = SWAP_ENDIAN_16 ( ptr [i] ); + } + } + + m_Node.byteOrder = byteOrder; +} + +void DoomLevel::AdjustByteOrderReject ( int byteOrder ) +{ + FUNCTION_ENTRY ( this, "DoomLevel::AdjustByteOrderReject", true ); + + if ( m_Reject.byteOrder != byteOrder ) { + // Nothing to do for this type + } + + m_Reject.byteOrder = byteOrder; +} + +void DoomLevel::AdjustByteOrderBlockMap ( int byteOrder ) +{ + FUNCTION_ENTRY ( this, "DoomLevel::AdjustByteOrderBlockMap", true ); + + + if ( m_BlockMap.byteOrder != byteOrder ) { + // The entire structure is composed of 16-bit entries + UINT16 *ptr = ( UINT16 * ) m_BlockMap.rawData; + for ( int i = 0; i < ( int ) m_BlockMap.dataSize / 2; i++ ) { + ptr [i] = SWAP_ENDIAN_16 ( ptr [i] ); + } + } + + m_BlockMap.byteOrder = byteOrder; +} + +void DoomLevel::AdjustByteOrderBehavior ( int byteOrder ) +{ + FUNCTION_ENTRY ( this, "DoomLevel::AdjustByteOrderBehavior", true ); + + if ( m_Behavior.byteOrder != byteOrder ) { + // Nothing to do for this type + } + + m_Behavior.byteOrder = byteOrder; +} + +void DoomLevel::AdjustByteOrder ( int byteOrder ) +{ + FUNCTION_ENTRY ( this, "DoomLevel::AdjustByteOrder", true ); + + AdjustByteOrderMap ( byteOrder ); + AdjustByteOrderThing ( byteOrder ); + AdjustByteOrderLineDef ( byteOrder ); + AdjustByteOrderSideDef ( byteOrder ); + AdjustByteOrderVertex ( byteOrder ); + AdjustByteOrderSector ( byteOrder ); + AdjustByteOrderSegs ( byteOrder ); + AdjustByteOrderSubSector ( byteOrder ); + AdjustByteOrderNode ( byteOrder ); + AdjustByteOrderReject ( byteOrder ); + AdjustByteOrderBlockMap ( byteOrder ); + AdjustByteOrderBehavior ( byteOrder ); +} + +#endif + +void DoomLevel::ReplaceVertices ( int *map, wVertex *newVertices, int count ) +{ + wLineDef *lineDef = ( wLineDef * ) GetLineDefs (); + for ( int i = 0; i < LineDefCount (); i++ ) { + lineDef [i].start = ( UINT16 ) map [ lineDef [i].start ]; + lineDef [i].end = ( UINT16 ) map [ lineDef [i].end ]; + } + + m_LineDef.changed = true; + + wSegs *segs = ( wSegs * ) GetSegs (); + for ( int i = 0; i < SegCount (); i++ ) { + segs [i].start = ( UINT16 ) map [ segs [i].start ]; + segs [i].end = ( UINT16 ) map [ segs [i].end ]; + } + + m_Segs.changed = true; + + delete [] ( char * ) m_Vertex.rawData; + delete [] map; + + m_Vertex.changed = true; + m_Vertex.elementCount = count; + m_Vertex.rawData = newVertices; +} + +void DoomLevel::TrimVertices () +{ + FUNCTION_ENTRY ( this, "DoomLevel::TrimVertices", true ); + + int *used = new int [ VertexCount () ]; + memset ( used, 0, sizeof ( int ) * VertexCount ()); + + wLineDef *lineDef = ( wLineDef * ) GetLineDefs (); + for ( int i = 0; i < LineDefCount (); i++ ) { + used [ lineDef [i].start ] = 1; + used [ lineDef [i].end ] = 1; + } + + wSegs *segs = ( wSegs * ) GetSegs (); + for ( int i = 0; i < SegCount (); i++ ) { + used [ segs [i].start ] = 1; + used [ segs [i].end ] = 1; + } + + const wVertex *oldVertices = GetVertices (); + wVertex *newVertices = new wVertex [ VertexCount () ]; + + int count = 0; + for ( int i = 0; i < VertexCount (); i++ ) { + if ( used [i] == 1 ) { + newVertices [count] = oldVertices [i]; + used [i] = count++; + } + } + + if ( VertexCount () == count ) { + delete [] newVertices; + delete [] used; + return; + } + + ReplaceVertices ( used, newVertices, count ); +} + +void DoomLevel::PackVertices () +{ + FUNCTION_ENTRY ( this, "DoomLevel::PackVertices", true ); + + int *used = new int [ VertexCount () ]; + memset ( used, 0, sizeof ( int ) * VertexCount ()); + + int count = 0; + UINT32 *vert = ( UINT32 * ) m_Vertex.rawData; + + for ( int i = 0, j; i < VertexCount (); i++ ) { + UINT32 currentVert = vert [i]; + for ( j = 0; j < i; j++ ) { + if ( vert [j] == currentVert ) break; + } + used [i] = j; + if ( i == j ) count++; + } + + if ( VertexCount () == count ) { + delete [] used; + return; + } + + const wVertex *oldVertices = GetVertices (); + wVertex *newVertices = new wVertex [ count ]; + + count = 0; + for ( int i = 0; i < VertexCount (); i++ ) { + if ( used [i] == i ) { + newVertices [count] = oldVertices [i]; + used [i] = count++; + } else { + used [i] = used [ used [i]]; + } + } + + ReplaceVertices ( used, newVertices, count ); +} + +void DoomLevel::ConvertRaw1ToThing ( int max, wThing1 *src, wThing *dest ) +{ + FUNCTION_ENTRY ( NULL, "DoomLevel::ConvertRaw1ToThing", true ); + + memset ( dest, 0, sizeof ( wThing ) * max ); + for ( int i = 0; i < max; i++ ) { + memcpy ( &dest [i], &src [i], sizeof ( wThing1 )); + } +} + +void DoomLevel::ConvertRaw2ToThing ( int max, wThing2 *src, wThing *dest ) +{ + FUNCTION_ENTRY ( NULL, "DoomLevel::ConvertRaw2ToThing", true ); + + memset ( dest, 0, sizeof ( wThing ) * max ); + for ( int i = 0; i < max; i++ ) { + dest [i].xPos = src [i].xPos; + dest [i].yPos = src [i].yPos; + dest [i].angle = src [i].angle; + dest [i].type = src [i].type; + dest [i].attr = src [i].attr; + dest [i].tid = src [i].tid; + dest [i].altitude = src [i].altitude; + dest [i].special = src [i].special; + memcpy ( dest [i].arg, src [i].arg, sizeof ( src[i].arg )); + } +} + +void DoomLevel::ConvertThingToRaw1 ( int max, wThing *src, wThing1 *dest ) +{ + FUNCTION_ENTRY ( NULL, "DoomLevel::ConvertThingToRaw1", true ); + + memset ( dest, 0, sizeof ( wThing1 ) * max ); + for ( int i = 0; i < max; i++ ) { + memcpy ( &dest [i], &src [i], sizeof ( wThing1 )); + } +} + +void DoomLevel::ConvertThingToRaw2 ( int max, wThing *src, wThing2 *dest ) +{ + FUNCTION_ENTRY ( NULL, "DoomLevel::ConvertThingToRaw2", true ); + + memset ( dest, 0, sizeof ( wThing2 ) * max ); + for ( int i = 0; i < max; i++ ) { + dest [i].xPos = src [i].xPos; + dest [i].yPos = src [i].yPos; + dest [i].angle = src [i].angle; + dest [i].type = src [i].type; + dest [i].attr = src [i].attr; + dest [i].tid = src [i].tid; + dest [i].altitude = src [i].altitude; + dest [i].special = src [i].special; + memcpy ( dest [i].arg, src [i].arg, sizeof ( dest[i].arg )); + } +} + +void DoomLevel::ConvertRaw1ToLineDef ( int max, wLineDef1 *src, wLineDef *dest ) +{ + FUNCTION_ENTRY ( NULL, "DoomLevel::ConvertRaw1ToLineDef", true ); + + memset ( dest, 0, sizeof ( wLineDef ) * max ); + for ( int i = 0; i < max; i++ ) { + memcpy ( &dest [i], &src [i], sizeof ( wLineDef1 )); + } +} + +void DoomLevel::ConvertRaw2ToLineDef ( int max, wLineDef2 *src, wLineDef *dest ) +{ + FUNCTION_ENTRY ( NULL, "DoomLevel::ConvertRaw2toLineDef", true ); + + memset ( dest, 0, sizeof ( wLineDef ) * max ); + for ( int i = 0; i < max; i++ ) { + dest [i].start = src [i].start; + dest [i].end = src [i].end; + dest [i].flags = src [i].flags; + dest [i].type = 0; + dest [i].tag = 0; + dest [i].sideDef[0] = src [i].sideDef[0]; + dest [i].sideDef[1] = src [i].sideDef[1]; + dest [i].special = src [i].special; + memcpy ( dest [i].arg, src [i].arg, sizeof ( src[i].arg )); + } +} + +void DoomLevel::ConvertLineDefToRaw1 ( int max, wLineDef *src, wLineDef1 *dest ) +{ + FUNCTION_ENTRY ( NULL, "DoomLevel::ConvertLineDefToRaw1", true ); + + memset ( dest, 0, sizeof ( wLineDef1 ) * max ); + for ( int i = 0; i < max; i++ ) { + memcpy ( &dest [i], &src [i], sizeof ( wLineDef1 )); + } +} + +void DoomLevel::ConvertLineDefToRaw2 ( int max, wLineDef *src, wLineDef2 *dest ) +{ + FUNCTION_ENTRY ( NULL, "DoomLevel::ConvertLineDefToRaw2", true ); + + memset ( dest, 0, sizeof ( wLineDef2 ) * max ); + for ( int i = 0; i < max; i++ ) { + dest [i].start = src [i].start; + dest [i].end = src [i].end; + dest [i].flags = src [i].flags; + dest [i].sideDef[0] = src [i].sideDef[0]; + dest [i].sideDef[1] = src [i].sideDef[1]; + dest [i].special = src [i].special; + memcpy ( dest [i].arg, src [i].arg, sizeof ( dest[i].arg )); + } +} + +bool DoomLevel::LoadThings ( bool hexenFormat ) +{ + FUNCTION_ENTRY ( this, "DoomLevel::LoadThings", true ); + + int size = 0; + int count = 0; + + delete [] m_ThingData; + + if ( hexenFormat == false ) { + size = sizeof ( wThing1 ); + count = m_Thing.dataSize / size; + m_ThingData = new wThing [ count ]; + ConvertRaw1ToThing ( count, ( wThing1 * ) m_Thing.rawData, m_ThingData ); + } else { + size = sizeof ( wThing2 ); + count = m_Thing.dataSize / size; + m_ThingData = new wThing [ count ]; + ConvertRaw2ToThing ( count, ( wThing2 * ) m_Thing.rawData, m_ThingData ); + } + + m_Thing.byteOrder = LITTLE_ENDIAN; + m_Thing.elementCount = count; + m_Thing.elementSize = size; + + return (( int ) m_Thing.dataSize == size * count ) ? true : false; +} + +bool DoomLevel::LoadLineDefs ( bool hexenFormat ) +{ + FUNCTION_ENTRY ( this, "DoomLevel::LoadLineDefs", true ); + + int size = 0; + int count = 0; + + delete [] m_LineDefData; + + if ( hexenFormat == false ) { + size = sizeof ( wLineDef1 ); + count = m_LineDef.dataSize / size; + m_LineDefData = new wLineDef [ count ]; + ConvertRaw1ToLineDef ( count, ( wLineDef1 * ) m_LineDef.rawData, m_LineDefData ); + } else { + size = sizeof ( wLineDef2 ); + count = m_LineDef.dataSize / size; + m_LineDefData = new wLineDef [ count ]; + ConvertRaw2ToLineDef ( count, ( wLineDef2 * ) m_LineDef.rawData, m_LineDefData ); + } + + m_LineDef.byteOrder = LITTLE_ENDIAN; + m_LineDef.elementCount = count; + m_LineDef.elementSize = size; + + return (( int ) m_LineDef.dataSize == size * count ) ? true : false; +} + +bool DoomLevel::ReadEntry ( sLevelLump *entry, const char *name, const wadDirEntry *start, const wadDirEntry *end, bool required ) +{ + FUNCTION_ENTRY ( this, "DoomLevel::ReadEntry", true ); + + const wadDirEntry *dir = m_Wad->FindDir ( name, start, end ); + + if ( dir == NULL ) return ( required == true ) ? false : true; + + entry->rawData = m_Wad->ReadEntry ( dir, &entry->dataSize ); + entry->elementCount = entry->dataSize / entry->elementSize; + entry->byteOrder = LITTLE_ENDIAN; + + return true; +} + +void DoomLevel::DetermineType () +{ + FUNCTION_ENTRY ( this, "DoomLevel::DetermineType", true ); + + m_IsHexen = false; + + // Look for the easy things first + if ( m_Behavior.rawData != NULL ) { + m_IsHexen = true; + return; + } + + // See if we have an exact match in structure sizes in only 1 of the two formats + int isType1 = ( m_Thing.dataSize % sizeof ( wThing1 )) + ( m_LineDef.dataSize % sizeof ( wLineDef1 )); + int isType2 = ( m_Thing.dataSize % sizeof ( wThing2 )) + ( m_LineDef.dataSize % sizeof ( wLineDef2 )); + + if ( isType1 != isType2 ) { + if ( isType1 == 0 ) return; + if ( isType2 == 0 ) { + m_IsHexen = true; + return; + } + } + + // See if we have a valid level in only 1 of the two formats + LoadThings ( false ); + LoadLineDefs ( false ); + + // Make sure we have the correct byte order + AdjustByteOrder ( BYTE_ORDER ); + + bool isValid1 = IsValid ( false, false ); + + LoadThings ( true ); + LoadLineDefs ( true ); + + // Make sure we have the correct byte order + AdjustByteOrder ( BYTE_ORDER ); + + bool isValid2 = IsValid ( false, false ); + + if ( isValid1 != isValid2 ) { + if ( isValid1 == true ) return; + if ( isValid2 == true ) { + m_IsHexen = true; + return; + } + } + + // Pick the one with the fewest invalid things found? + fprintf ( stderr, "Argh!!!\n" ); +} + +bool DoomLevel::Load () +{ + FUNCTION_ENTRY ( this, "DoomLevel::Load", true ); + + if ( m_Wad == NULL ) return false; + + const wadDirEntry *start = m_Wad->FindDir ( Name ()); + + if ( start == NULL ) return false; + + const wadDirEntry *end = start + min ( 11, ( int ) ( m_Wad->DirSize () - 1 )); + + bool valid = true; + + valid &= ReadEntry ( &m_Map, Name (), start, end, true ); + valid &= ReadEntry ( &m_Thing, "THINGS", start, end, true ); + valid &= ReadEntry ( &m_LineDef, "LINEDEFS", start, end, true ); + valid &= ReadEntry ( &m_SideDef, "SIDEDEFS", start, end, true ); + valid &= ReadEntry ( &m_Vertex, "VERTEXES", start, end, true ); + valid &= ReadEntry ( &m_Sector, "SECTORS", start, end, true ); + valid &= ReadEntry ( &m_Segs, "SEGS", start, end, false ); + valid &= ReadEntry ( &m_SubSector, "SSECTORS", start, end, false ); + valid &= ReadEntry ( &m_Node, "NODES", start, end, false ); + valid &= ReadEntry ( &m_Reject, "REJECT", start, end, false ); + valid &= ReadEntry ( &m_BlockMap, "BLOCKMAP", start, end, false ); + valid &= ReadEntry ( &m_Behavior, "BEHAVIOR", start, end, false ); + + if ( RejectSize () != 0 ) { + int mask = ( 0xFF >> ( RejectSize () * 8 - SectorCount () * SectorCount ())) & 0xFF; + (( UINT8 * ) m_Reject.rawData ) [ RejectSize () - 1 ] &= ( UINT8 ) mask; + } + + DetermineType (); + + LoadThings ( m_IsHexen ); + LoadLineDefs ( m_IsHexen ); + + // Switch to native byte ordering + AdjustByteOrder ( BYTE_ORDER ); + + return valid; +} + +bool DoomLevel::LoadHexenInfo () +{ + FUNCTION_ENTRY ( this, "DoomLevel::LoadHexenInfo", true ); + + if ( m_Wad == NULL ) return false; + + const wadDirEntry *dir = m_Wad->FindDir ( "MAPINFO" ); + + if ( dir == NULL ) return false; + + int level; + sscanf ( m_Name, "MAP%02d", &level ); + + UINT32 Size; + char *buffer = ( char * ) m_Wad->ReadEntry ( dir, &Size, true ); + char *ptr = buffer; + + if ( m_Title != NULL ) free (( char * ) m_Title ); + m_Title = NULL; + + do { + if (( ptr = strstr ( ptr, "\nmap " )) == NULL ) break; + if ( atoi ( &ptr[5] ) == level ) { + while ( *ptr++ != '"' ); + strtok ( ptr, "\"" ); + m_Title = strdup ( ptr ); + ptr += strlen ( ptr ) + 1; + char *clstr = strstr ( ptr, "\ncluster " ); + char *next = strstr ( ptr, "\n\r" ); + if ( clstr && ( clstr < next )) { + m_Cluster = atoi ( clstr + 9 ); + } + break; + } else { + ptr++; + } + } while ( ptr && *ptr ); + + delete [] buffer; + + if ( m_Title != NULL ) { + ptr = ( char * ) m_Title + 1; + while ( *ptr ) { + *ptr = ( char ) tolower ( *ptr ); + if ( *ptr == ' ' ) { + while ( *ptr == ' ' ) ptr++; + if ( strncmp ( ptr, "OF ", 3 ) == 0 ) ptr--; + } + if ( *ptr ) ptr++; + } + } + + dir = m_Wad->FindDir ( "SNDINFO" ); + if ( dir == NULL ) return true; + + buffer = ( char * ) m_Wad->ReadEntry ( dir, &Size, true ); + ptr = buffer; + + do { + if (( ptr = strstr ( ptr, "\n$MAP" )) == NULL ) break; + if ( atoi ( &ptr[5] ) == level ) { + ptr += 25; + strtok ( ptr, "\r" ); + m_Music = strupr ( strdup ( ptr )); + break; + } else { + ptr++; + } + } while ( ptr && *ptr ); + + delete [] buffer; + + return true; +} + +void DoomLevel::AddToWAD ( WAD *m_Wad ) +{ + FUNCTION_ENTRY ( this, "DoomLevel::AddToWAD", true ); + + // Make sure data is in little endian when writing to the file + AdjustByteOrder ( LITTLE_ENDIAN ); + + StoreThings (); + StoreLineDefs (); + + m_Wad->InsertAfter (( const wLumpName * ) Name (), m_Map.dataSize, m_Map.rawData, false ); + m_Wad->InsertAfter (( const wLumpName * ) "THINGS", m_Thing.dataSize, m_Thing.rawData, false ); + m_Wad->InsertAfter (( const wLumpName * ) "LINEDEFS", m_LineDef.dataSize, m_LineDef.rawData, false ); + m_Wad->InsertAfter (( const wLumpName * ) "SIDEDEFS", m_SideDef.dataSize, m_SideDef.rawData, false ); + m_Wad->InsertAfter (( const wLumpName * ) "VERTEXES", m_Vertex.dataSize, m_Vertex.rawData, false ); + m_Wad->InsertAfter (( const wLumpName * ) "SEGS", m_Segs.dataSize, m_Segs.rawData, false ); + m_Wad->InsertAfter (( const wLumpName * ) "SSECTORS", m_SubSector.dataSize, m_SubSector.rawData, false ); + m_Wad->InsertAfter (( const wLumpName * ) "NODES", m_Node.dataSize, m_Node.rawData, false ); + m_Wad->InsertAfter (( const wLumpName * ) "SECTORS", m_Sector.dataSize, m_Sector.rawData, false ); + m_Wad->InsertAfter (( const wLumpName * ) "REJECT", m_Reject.dataSize, m_Reject.rawData, false ); + m_Wad->InsertAfter (( const wLumpName * ) "BLOCKMAP", m_BlockMap.dataSize, m_BlockMap.rawData, false ); + + if (( m_IsHexen == true ) && ( m_Behavior.rawData != NULL )) { + m_Wad->InsertAfter (( const wLumpName * ) "BEHAVIOR", m_Behavior.dataSize, m_Behavior.rawData, false ); + } + + // Switch back to native byte ordering + AdjustByteOrder ( BYTE_ORDER ); +} + +void DoomLevel::StoreThings () +{ + FUNCTION_ENTRY ( this, "DoomLevel::StoreThings", true ); + + AdjustByteOrderThing ( LITTLE_ENDIAN ); + + if (( int ) m_Thing.dataSize < ThingCount () * m_Thing.elementSize ) { + delete [] ( char * ) m_Thing.rawData; + m_Thing.rawData = new char [ ThingCount () * m_Thing.elementSize ]; + } + + if ( m_IsHexen == false ) { + ConvertThingToRaw1 ( ThingCount (), m_ThingData, ( wThing1 * ) m_Thing.rawData ); + } else { + ConvertThingToRaw2 ( ThingCount (), m_ThingData, ( wThing2 * ) m_Thing.rawData ); + } + + m_LineDef.dataSize = ThingCount () * m_LineDef.elementSize; +} + +void DoomLevel::StoreLineDefs () +{ + FUNCTION_ENTRY ( this, "DoomLevel::StoreLineDefs", true ); + + AdjustByteOrderLineDef ( LITTLE_ENDIAN ); + + if (( int ) m_LineDef.dataSize < LineDefCount () * m_LineDef.elementSize ) { + delete [] ( char * ) m_LineDef.rawData; + m_LineDef.rawData = new char [ LineDefCount () * m_LineDef.elementSize ]; + } + + if ( m_IsHexen == false ) { + ConvertLineDefToRaw1 ( LineDefCount (), m_LineDefData, ( wLineDef1 * ) m_LineDef.rawData ); + } else { + ConvertLineDefToRaw2 ( LineDefCount (), m_LineDefData, ( wLineDef2 * ) m_LineDef.rawData ); + } + + m_LineDef.dataSize = LineDefCount () * m_LineDef.elementSize; +} + +bool DoomLevel::UpdateEntry ( sLevelLump *lump, const char *name, const char *follows, bool required ) +{ + FUNCTION_ENTRY ( this, "DoomLevel::UpdateEntry", true ); + + if ( lump->changed == false ) return false; + + lump->changed = false; + + const wadDirEntry *start = m_Wad->FindDir ( Name ()); + const wadDirEntry *end = start + min ( 10, ( int ) ( m_Wad->DirSize () - 1 )); + + bool changed = false; + + const wadDirEntry *dir = m_Wad->FindDir ( name, start, end ); + if ( dir == NULL ) { + if ( required == true ) { + fprintf ( stderr, "Map %s is missing required lump: %s\n", Name (), name ); + } else { + const wadDirEntry *last = m_Wad->FindDir ( follows, start, end ); + changed |= m_Wad->InsertAfter (( const wLumpName * ) name, lump->dataSize, lump->rawData, false, last ); + } + } else { + changed |= m_Wad->WriteEntry ( dir, lump->dataSize, lump->rawData, false ); + } + + return changed; +} + +bool DoomLevel::UpdateWAD () +{ + FUNCTION_ENTRY ( this, "DoomLevel::UpdateWAD", true ); + + if ( m_Wad == NULL ) return false; + if ( m_Wad->FindDir ( Name ()) == NULL ) return false; + if ( IsDirty () == false ) return false; + + // Make sure data is in little endian when writing to the file + AdjustByteOrder ( LITTLE_ENDIAN ); + + StoreThings (); + StoreLineDefs (); + + bool changed = false; + + changed |= UpdateEntry ( &m_Thing, "THINGS", NULL, true ); + changed |= UpdateEntry ( &m_LineDef, "LINEDEFS", NULL, true ); + changed |= UpdateEntry ( &m_SideDef, "SIDEDEFS", NULL, true ); + changed |= UpdateEntry ( &m_Vertex, "VERTEXES", NULL, true ); + changed |= UpdateEntry ( &m_Segs, "SEGS", "VERTEXES", false ); + changed |= UpdateEntry ( &m_SubSector, "SSECTORS", "SEGS", false ); + changed |= UpdateEntry ( &m_Node, "NODES", "SSECTORS", false ); + changed |= UpdateEntry ( &m_Sector, "SECTORS", "NODES", false ); + changed |= UpdateEntry ( &m_Reject, "REJECT", "SECTORS", false ); + changed |= UpdateEntry ( &m_BlockMap, "BLOCKMAP", "REJECT", false ); + + if (( m_IsHexen == true ) && ( m_Behavior.rawData != NULL )) { + changed |= UpdateEntry ( &m_Behavior, "BEHAVIOR", "BLOCKMAP", false ); + } + + // Switch back to native byte ordering + AdjustByteOrder ( BYTE_ORDER ); + + return changed; +} + +void DoomLevel::NewEntry ( sLevelLump *entry, int newCount, void *newData ) +{ + FUNCTION_ENTRY ( this, "DoomLevel::NewEntry", true ); + + delete [] ( char * ) entry->rawData; + + entry->byteOrder = BYTE_ORDER; + entry->changed = true; + entry->elementCount = newCount; + entry->dataSize = newCount * entry->elementSize; + entry->rawData = newData; +} + +void DoomLevel::NewThings ( int newCount, wThing *newData ) +{ + FUNCTION_ENTRY ( this, "DoomLevel::NewThings", true ); + + delete [] ( char * ) m_ThingData; + + m_ThingData = newData; + + m_Thing.byteOrder = BYTE_ORDER; + m_Thing.changed = true; + m_Thing.elementCount = newCount; +} + +void DoomLevel::NewLineDefs ( int newCount, wLineDef *newData ) +{ + FUNCTION_ENTRY ( this, "DoomLevel::NewLineDefs", true ); + + delete [] ( char * ) m_LineDefData; + + m_LineDefData = newData; + + m_LineDef.byteOrder = BYTE_ORDER; + m_LineDef.changed = true; + m_LineDef.elementCount = newCount; +} + +void DoomLevel::NewSideDefs ( int newCount, wSideDef *newData ) +{ + FUNCTION_ENTRY ( this, "DoomLevel::NewSideDefs", true ); + + NewEntry ( &m_SideDef, newCount, newData ); +} + +void DoomLevel::NewVertices ( int newCount, wVertex *newData ) +{ + FUNCTION_ENTRY ( this, "DoomLevel::NewVertices", true ); + + NewEntry ( &m_Vertex, newCount, newData ); +} + +void DoomLevel::NewSectors ( int newCount, wSector *newData ) +{ + FUNCTION_ENTRY ( this, "DoomLevel::NewSectors", true ); + + NewEntry ( &m_Sector, newCount, newData ); +} + +void DoomLevel::NewSegs ( int newCount, wSegs *newData ) +{ + FUNCTION_ENTRY ( this, "DoomLevel::NewSegs", true ); + + NewEntry ( &m_Segs, newCount, newData ); +} + +void DoomLevel::NewSubSectors ( int newCount, wSSector *newData ) +{ + FUNCTION_ENTRY ( this, "DoomLevel::NewSubSectors", true ); + + NewEntry ( &m_SubSector, newCount, newData ); +} + +void DoomLevel::NewNodes ( int newCount, wNode *newData ) +{ + FUNCTION_ENTRY ( this, "DoomLevel::NewNodes", true ); + + NewEntry ( &m_Node, newCount, newData ); +} + +void DoomLevel::NewReject ( int newSize, UINT8 *newData ) +{ + FUNCTION_ENTRY ( this, "DoomLevel::NewReject", true ); + + int mask = ( 0xFF >> ( newSize * 8 - SectorCount () * SectorCount ())) & 0xFF; + + newData [ newSize - 1 ] &= ( UINT8 ) mask; + + NewEntry ( &m_Reject, newSize, newData ); +} + +void DoomLevel::NewBlockMap ( int newSize, wBlockMap *newData ) +{ + FUNCTION_ENTRY ( this, "DoomLevel::NewBlockMap", true ); + + NewEntry ( &m_BlockMap, newSize, newData ); +} + +void DoomLevel::NewBehavior ( int newSize, char *newData ) +{ + FUNCTION_ENTRY ( this, "DoomLevel::NewBehavior", true ); + + NewEntry ( &m_Behavior, newSize, newData ); +} diff --git a/DOOM/level.hpp b/DOOM/level.hpp new file mode 100644 index 0000000..909c2ca --- /dev/null +++ b/DOOM/level.hpp @@ -0,0 +1,341 @@ +//---------------------------------------------------------------------------- +// +// File: level.hpp +// Date: 26-Oct-1994 +// Programmer: Marc Rousseau +// +// Description: Object classes for manipulating Doom Maps +// +// Copyright (c) 1994-2004 Marc Rousseau, All Rights Reserved. +// +// 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 2 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, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. +// +// Revision History: +// +//---------------------------------------------------------------------------- + +#ifndef LEVEL_HPP_ +#define LEVEL_HPP_ + +#if ! defined ( COMMON_HPP_ ) + #include "common.hpp" +#endif + +#if ! defined ( WAD_HPP_ ) + #include "wad.hpp" +#endif + +#include "thing.hpp" +#include "lineDef.hpp" + +struct wThing1 { + INT16 xPos; // x position + INT16 yPos; // y position + UINT16 angle; // direction + UINT16 type; // thing type + UINT16 attr; // attributes of thing +}; + +struct wThing2 { // HEXEN + UINT16 tid; // Thing ID - for scripts & specials + INT16 xPos; // x position + INT16 yPos; // y position + UINT16 altitude; // starting altitude + UINT16 angle; // direction + UINT16 type; // thing type + UINT16 attr; // attributes of thing + UINT8 special; // special type + UINT8 arg [5]; // special arguments +}; + +struct wThing { + INT16 xPos; // x position + INT16 yPos; // y position + UINT16 angle; // in degrees not BAM + UINT16 type; // thing type + UINT16 attr; // attributes of thing + UINT16 tid; // Thing ID - for scripts & specials + UINT16 altitude; // starting altitude + UINT8 special; // special type + UINT8 arg [5]; // special arguments +}; + +struct wLineDef1 { + UINT16 start; // from this vertex ... + UINT16 end; // ... to this vertex + UINT16 flags; + UINT16 type; + UINT16 tag; // crossing this linedef activates the sector with the same tag + UINT16 sideDef[2]; // sidedef +}; + +struct wLineDef2 { // HEXEN + UINT16 start; // from this vertex ... + UINT16 end; // ... to this vertex + UINT16 flags; + UINT8 special; // special type + UINT8 arg [5]; // special arguments + UINT16 sideDef[2]; // sidedef +}; + +struct wLineDef { + UINT16 start; // from this vertex ... + UINT16 end; // ... to this vertex + UINT16 flags; + UINT16 type; + UINT16 tag; // crossing this linedef activates the sector with the same tag + UINT16 sideDef[2]; // sidedef + UINT8 special; // special type + UINT8 arg [5]; // special arguments +}; + +#define NO_SIDEDEF (( UINT16 ) -1 ) +#define RIGHT_SIDEDEF (( UINT16 ) 0 ) +#define LEFT_SIDEDEF (( UINT16 ) 1 ) + +#define EMPTY_TEXTURE 0x002D // UINT16 version of ASCII "-" + +struct wSideDef { + INT16 xOff; // X offset for texture + INT16 yOff; // Y offset for texture + char text1[MAX_LUMP_NAME]; // texture name for the part above + char text2[MAX_LUMP_NAME]; // texture name for the part below + char text3[MAX_LUMP_NAME]; // texture name for the regular part + UINT16 sector; // adjacent sector +}; + +struct wVertex { + INT16 x; // X coordinate + INT16 y; // Y coordinate +}; + +struct wSector { + INT16 floorh; // floor height + INT16 ceilh; // ceiling height + char floorTexture[MAX_LUMP_NAME]; // floor texture + char ceilTexture[MAX_LUMP_NAME]; // ceiling texture + UINT16 light; // light level (0-255) + UINT16 special; // special behaviour (0 = normal, 9 = secret, ...) + UINT16 trigger; // sector activated by a linedef with the same tag +}; + +struct wSegs { + UINT16 start; // from this vertex ... + UINT16 end; // ... to this vertex + UINT16 angle; // angle (0 = east, 16384 = north, ...) + UINT16 lineDef; // linedef that this seg goes along*/ + UINT16 flip; // true if not the same direction as linedef + UINT16 offset; // distance from starting point +}; + +struct wSSector { + UINT16 num; // number of Segs in this Sub-Sector + UINT16 first; // first Seg +}; + +struct wBound { + INT16 maxy, miny; + INT16 minx, maxx; // bounding rectangle +}; + +struct wNode { + INT16 x, y; // starting point + INT16 dx, dy; // offset to ending point + wBound side[2]; + UINT16 child[2]; // Node or SSector (if high bit is set) +}; + +struct wReject { + UINT16 dummy; +}; + +struct wBlockMap { + INT16 xOrigin; + INT16 yOrigin; + UINT16 noColumns; + UINT16 noRows; +// UINT16 data []; +}; + +class DoomLevel { + + struct sLevelLump { + bool changed; + int byteOrder; + int elementSize; + int elementCount; + UINT32 dataSize; + void *rawData; + + sLevelLump (); + }; + + WAD *m_Wad; + wLumpName m_Name; + + bool m_IsHexen; + const char *m_Title; + const char *m_Music; + int m_Cluster; + + wThing *m_ThingData; + wLineDef *m_LineDefData; + + sLevelLump m_Map; + sLevelLump m_Thing; + sLevelLump m_LineDef; + sLevelLump m_SideDef; + sLevelLump m_Vertex; + sLevelLump m_Sector; + sLevelLump m_Segs; + sLevelLump m_SubSector; + sLevelLump m_Node; + sLevelLump m_Reject; + sLevelLump m_BlockMap; + sLevelLump m_Behavior; + + static void ConvertRaw1ToThing ( int, wThing1 *, wThing * ); + static void ConvertRaw2ToThing ( int, wThing2 *, wThing * ); + static void ConvertThingToRaw1 ( int, wThing *, wThing1 * ); + static void ConvertThingToRaw2 ( int, wThing *, wThing2 * ); + + static void ConvertRaw1ToLineDef ( int, wLineDef1 *, wLineDef * ); + static void ConvertRaw2ToLineDef ( int, wLineDef2 *, wLineDef * ); + static void ConvertLineDefToRaw1 ( int, wLineDef *, wLineDef1 * ); + static void ConvertLineDefToRaw2 ( int, wLineDef *, wLineDef2 * ); + + void ReplaceVertices ( int *, wVertex *, int ); + + void DetermineType (); + + bool Load (); + bool LoadHexenInfo (); + + bool LoadThings ( bool ); + bool LoadLineDefs ( bool ); + + void StoreThings (); + void StoreLineDefs (); + + void NewEntry ( sLevelLump *, int, void * ); + + bool ReadEntry ( sLevelLump *, const char *, const wadDirEntry *, const wadDirEntry *, bool ); + bool UpdateEntry ( sLevelLump *, const char *, const char *, bool ); + + void AdjustByteOrderMap ( int ); + void AdjustByteOrderThing ( int ); + void AdjustByteOrderLineDef ( int ); + void AdjustByteOrderSideDef ( int ); + void AdjustByteOrderVertex ( int ); + void AdjustByteOrderSector ( int ); + void AdjustByteOrderSegs ( int ); + void AdjustByteOrderSubSector ( int ); + void AdjustByteOrderNode ( int ); + void AdjustByteOrderReject ( int ); + void AdjustByteOrderBlockMap ( int ); + void AdjustByteOrderBehavior ( int ); + + void AdjustByteOrder ( int ); + + void CleanUpEntry ( sLevelLump * ); + void CleanUp (); + +public: + + DoomLevel ( const char *, WAD *, bool = true ); + ~DoomLevel (); + + const WAD *GetWAD () const { return m_Wad; } + + const char *Name () const { return m_Name; } + const char *Title () const { return m_Title ? m_Title : m_Name; } + const char *Music () const { return m_Music ? m_Music : NULL; } + int MapCluster () const { return m_Cluster; } + + int ThingCount () const { return m_Thing.elementCount; } + int LineDefCount () const { return m_LineDef.elementCount; } + int SideDefCount () const { return m_SideDef.elementCount; } + int VertexCount () const { return m_Vertex.elementCount; } + int SectorCount () const { return m_Sector.elementCount; } + int SegCount () const { return m_Segs.elementCount; } + int SubSectorCount () const { return m_SubSector.elementCount; } + int NodeCount () const { return m_Node.elementCount; } + int RejectSize () const { return m_Reject.dataSize; } + int BlockMapSize () const { return m_BlockMap.dataSize; } + int BehaviorSize () const { return m_Behavior.dataSize; } + + const wThing *GetThings () const { return m_ThingData; } + const wLineDef *GetLineDefs () const { return m_LineDefData; } + const wSideDef *GetSideDefs () const { return ( wSideDef * ) m_SideDef.rawData; } + const wVertex *GetVertices () const { return ( wVertex * ) m_Vertex.rawData; } + const wSector *GetSectors () const { return ( wSector * ) m_Sector.rawData; } + const wSegs *GetSegs () const { return ( wSegs * ) m_Segs.rawData; } + const wSSector *GetSubSectors () const { return ( wSSector * ) m_SubSector.rawData; } + const wNode *GetNodes () const { return ( wNode * ) m_Node.rawData; } + const wReject *GetReject () const { return ( wReject * ) m_Reject.rawData; } + const wBlockMap *GetBlockMap () const { return ( wBlockMap * ) m_BlockMap.rawData; } + const UINT8 *GetBehavior () const { return ( UINT8 * ) m_Behavior.rawData; } + + void NewThings ( int, wThing * ); + void NewLineDefs ( int, wLineDef * ); + void NewSideDefs ( int, wSideDef * ); + void NewVertices ( int, wVertex * ); + void NewSectors ( int, wSector * ); + void NewSegs ( int, wSegs * ); + void NewSubSectors ( int, wSSector * ); + void NewNodes ( int, wNode * ); + void NewReject ( int, UINT8 * ); + void NewBlockMap ( int, wBlockMap * ); + void NewBehavior ( int, char * ); + + bool IsValid ( bool, bool = true ) const; + bool IsDirty () const; + + void TrimVertices (); + void PackVertices (); + + void PackSideDefs (); + void UnPackSideDefs (); + + bool UpdateWAD (); + void AddToWAD ( WAD *wad ); + + sThingDesc *FindThing ( int type ); + static sLineDefDesc *FindLineDef ( int type ); + + sThingDesc *GetThing ( int index ); + static sLineDefDesc *GetLineDef ( int index ); +}; + +#if ( BYTE_ORDER == LITTLE_ENDIAN ) + + inline void DoomLevel::AdjustByteOrderMap ( int ) {} + inline void DoomLevel::AdjustByteOrderThing ( int ) {} + inline void DoomLevel::AdjustByteOrderLineDef ( int ) {} + inline void DoomLevel::AdjustByteOrderSideDef ( int ) {} + inline void DoomLevel::AdjustByteOrderVertex ( int ) {} + inline void DoomLevel::AdjustByteOrderSector ( int ) {} + inline void DoomLevel::AdjustByteOrderSegs ( int ) {} + inline void DoomLevel::AdjustByteOrderSubSector ( int ) {} + inline void DoomLevel::AdjustByteOrderNode ( int ) {} + inline void DoomLevel::AdjustByteOrderReject ( int ) {} + inline void DoomLevel::AdjustByteOrderBlockMap ( int ) {} + inline void DoomLevel::AdjustByteOrderBehavior ( int ) {} + + inline void DoomLevel::AdjustByteOrder ( int ) {} + +#endif + +#endif diff --git a/DOOM/lineDef.hpp b/DOOM/lineDef.hpp new file mode 100644 index 0000000..674eb8a --- /dev/null +++ b/DOOM/lineDef.hpp @@ -0,0 +1,159 @@ +//---------------------------------------------------------------------------- +// +// File: lineDef.hpp +// Date: 23-August-1995 +// Programmer: Marc Rousseau +// +// Description: Object classes for manipulating Doom Map LineDefs +// +// Copyright (c) 1994-2004 Marc Rousseau, All Rights Reserved. +// +// 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 2 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, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. +// +// Revision History: +// +//---------------------------------------------------------------------------- + +#ifndef LINEDEF_HPP_ +#define LINEDEF_HPP_ + +#if ! defined ( LEVEL_HPP_ ) + #include "level.hpp" +#endif + +// WAD LINEDEF Flags +#define LDF_IMPASSABLE 0x0001 +#define LDF_BLOCK_MONSTERS 0x0002 +#define LDF_TWO_SIDED 0x0004 +#define LDF_UPPER_UNPEGGED 0x0008 +#define LDF_LOWER_UNPEGGED 0x0010 +#define LDF_SECRET 0x0020 +#define LDF_BLOCK_SOUND 0x0040 +#define LDF_NOT_ON_MAP 0x0080 +#define LDF_ALREADY_ON_MAP 0x0100 + +enum LD_LINE_CLASS { + LDC_NONE, // "--" + LDC_SPECIAL, // "Special" + LDC_DOOR, // "Door" + LDC_REMOTE, // "Remote Door" + LDC_CEILING, // "Ceiling" + LDC_LIFT, // "Lift" + LDC_FLOOR, // "Floor" + LDC_STAIRS, // "Stairs" + LDC_MOVE, // "Moving Floor" + LDC_CRUSH, // "Crushing Ceiling" + LDC_EXIT, // "Exit" + LDC_TELEPORT, // "Teleporter" + LDC_LIGHT, // "Light" + LDC_UNKNOWN, // "???" +}; + +enum LD_LINE_SPEED { + LDS_NONE, + LDS_SLOW, + LDS_MED, + LDS_FAST, + LDS_TURBO +}; + +enum LD_LINE_ACTION { + LDA_NONE, + LDA_SCROLL, + LDA_OPEN, + LDA_CLOSE, + LDA_RAISE, + LDA_LOWER, + LDA_START, + LDA_STOP, + LDA_CHANGE, + LDA_TELEPORT, + LDA_END, + LDA_END_SECRET +}; + +enum LD_LINE_EFFECTS { + LDE_NONE = 0x00000000, + + LDE_NEEDTAG = 0x80000000, + + LDE_LOCK = 0x40000000, + + LDE_MONSTER = 0x20000000, + LDE_MONSTER_ONLY = 0x10000000, + + LDE_TRIGGER_MODEL = 0x08000000, + LDE_NUMERIC_MODEL = 0x04000000, + LDE_MODEL_MASK = 0x0C000000, + + LDE_TX_TEXTURE = 0x02000000, + LDE_TX_SPECIAL = 0x01000000, + LDE_TX_MASK = 0x03000000, + + LDE_MAX = 0x00800000, + LDE_MIN = 0x00400000, + + LDE_MV_HIGHEST = 0x00C00000, + LDE_MV_NEXT_HIGHEST = 0x00800000, + LDE_MV_LOWEST = 0x00400000, + + LDE_MV_SHORTEST = 0x00300000, + LDE_MV_FLOOR = 0x00200000, + LDE_MV_CEILING = 0x00100000, + + LDE_MV_EXCLUSIVE = 0x00080000, + LDE_MV_INCLUSIVE = 0x00040000, + + LDE_SLOW_HURT = 0x00020000, + LDE_FAST_HURT = 0x00010000, + LDE_CRUSH = 0x00008000, + LDE_SILENT = 0x00004000, + LDE_BLINKING = 0x00002000, + + LDE_KEY_BLUE = 0x00000C00, + LDE_KEY_YELLOW = 0x00000800, + LDE_KEY_RED = 0x00000400, + LDE_KEY_MASK = 0x00000C00, + + LDE_MODIFIER_MASK = 0x000003FF +}; + +#define LDE_TX ( LDE_TRIGGER_MODEL | LDE_TX_TEXTURE ) +#define LDE_TXP ( LDE_TRIGGER_MODEL | LDE_TX_TEXTURE | LDE_TX_SPECIAL ) +#define LDE_NXP ( LDE_NUMERIC_MODEL | LDE_TX_TEXTURE | LDE_TX_SPECIAL ) +#define LDE_HE ( LDE_MV_HIGHEST | LDE_MV_EXCLUSIVE ) +#define LDE_HEF ( LDE_MV_HIGHEST | LDE_MV_EXCLUSIVE | LDE_MV_FLOOR ) +#define LDE_HEC ( LDE_MV_HIGHEST | LDE_MV_EXCLUSIVE | LDE_MV_CEILING ) +#define LDE_LE ( LDE_MV_LOWEST | LDE_MV_EXCLUSIVE ) +#define LDE_LEF ( LDE_MV_LOWEST | LDE_MV_EXCLUSIVE | LDE_MV_FLOOR ) +#define LDE_LIC ( LDE_MV_LOWEST | LDE_MV_INCLUSIVE | LDE_MV_CEILING ) +#define LDE_nhEF ( LDE_MV_NEXT_HIGHEST | LDE_MV_EXCLUSIVE | LDE_MV_FLOOR ) +#define LDE_F ( LDE_MV_FLOOR ) +#define LDE_NUM(x) ((x) & LDE_MODIFIER_MASK ) + +struct sLineDefDesc { + INT16 Type; + UINT8 Class; + const char *Trigger; + UINT8 Speed; + UINT8 Duration; + UINT8 Action; + UINT32 Effects; + + const char *GetClass (); + const char *GetDescription (); +}; + +#endif diff --git a/DOOM/thing.hpp b/DOOM/thing.hpp new file mode 100644 index 0000000..dc75db3 --- /dev/null +++ b/DOOM/thing.hpp @@ -0,0 +1,100 @@ +//---------------------------------------------------------------------------- +// +// File: thing.hpp +// Date: 23-August-1995 +// Programmer: Marc Rousseau +// +// Description: Object classes for manipulating Doom Map Things +// +// Copyright (c) 1994-2004 Marc Rousseau, All Rights Reserved. +// +// 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 2 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, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. +// +// Revision History: +// +//---------------------------------------------------------------------------- + +#ifndef THING_HPP_ +#define THING_HPP_ + +#if ! defined ( LEVEL_HPP_ ) + #include "level.hpp" +#endif + +#ifndef SHORT + typedef short SHORT; +#endif + +#define THING_SKILL_1_2 0x0001 +#define THING_SKILL_3 0x0002 +#define THING_SKILL_4_5 0x0004 +#define THING_DEAF 0x0008 +#define THING_MULTIPLAYER 0x0010 +#define THING_a 0x0020 +#define THING_b 0x0040 +#define THING_c 0x0080 +#define THING_d 0x0100 +#define THING_e 0x0200 +#define THING_f 0x0400 + + +#define TP_NONE 0x00000 +#define TP_BLOCK 0x00001 // Blocks the player +#define TP_PICK 0x00002 // Can be picked up +#define TP_SOUND 0x00004 // Sound only: invisible, can be outside of map +#define TP_INVIS 0x00008 // Invisible or blurred +#define TP_FLOAT 0x00010 // Floats or hangs from the ceiling +#define TP_LIGHT 0x00020 // Can be seen in a dark room +#define TP_ITEM 0x00040 // Counts towards the item ratio at the end +#define TP_KILL 0x00080 // Counts towards the kill ratio at the end +#define TP_PLAYER 0x00100 // Player starting point +#define TP_HEALTH ( 0x00200 | TP_PICK ) +#define TP_ARTIFACT ( 0x00400 | TP_PICK | TP_ITEM ) // Artifact +#define TP_ARMOR ( 0x00800 | TP_PICK +#define TP_WEAPON ( 0x01000 | TP_PICK | TP_ITEM ) // Weapon +#define TP_AMMO 0x02000 // Ammunition +#define TP_KEY ( 0x04000 | TP_PICK | TP_ITEM ) +#define TP_MONSTER ( 0x08000 | TP_BLOCK | TP_KILL ) +#define TP_BAD -1 // Invalid Thing - should not be used + +#define TP_POLYOBJ 0x10000 + +#define TP_BONUS ( TP_PICK | TP_ITEM ) +#define TP_DECORATE TP_BLOCK +#define TP_CORPSE TP_DECORATE + +struct sThingDesc { + SHORT Type; + const char *Name; + const char *Sprite; + const char *Sequence; + SHORT Radius; + SHORT Height; + SHORT Mass; + SHORT Health; + SHORT Speed; + SHORT Damage; + int Properties; +}; + +/* + Bullets 10 + Shotgun 70 ( 7 pellets * 10 ) + Plasma 20 + Rockets 100 + BFG 1000 +*/ + +#endif diff --git a/DOOM/wad.cpp b/DOOM/wad.cpp new file mode 100644 index 0000000..c635513 --- /dev/null +++ b/DOOM/wad.cpp @@ -0,0 +1,1223 @@ +//---------------------------------------------------------------------------- +// +// File: wad.cpp +// Date: 26-Oct-1994 +// Programmer: Marc Rousseau +// +// Description: Object classes for manipulating Doom WAD files +// +// Copyright (c) 1994-2004 Marc Rousseau, All Rights Reserved. +// +// 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 2 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, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. +// +// Revision History: +// +// 04-25-01 Added little/big endian conversions +// +//---------------------------------------------------------------------------- + +#include +#include +#include +#include +#include +#include +#include "common.hpp" +#include "wad.hpp" +#include "level.hpp" + +#if defined ( __GNUC__ ) || defined ( __INTEL_COMPILER ) + #include + #define stricmp strcasecmp + #define TEMP_DIR "TMPDIR=" +#else + #include + #if ! defined ( _MSC_VER ) + #include + #endif + #include + #define TEMP_DIR "TMP=" +#endif + +int WAD::sm_NoFilters; +wadFilter **WAD::sm_Filter; + +static int _init () +{ + if ( sizeof ( wadHeader ) != 12 ) fprintf ( stderr, "sanity check: sizeof ( %s ) = %d (expected %d)\n", "wadHeader", sizeof ( wadHeader ), 12 ); + if ( sizeof ( wadDirEntry ) != 16 ) fprintf ( stderr, "sanity check: sizeof ( %s ) = %d (expected %d)\n", "wadDirEntry", sizeof ( wadDirEntry ), 16 ); + return 0; +} + +static int foo = _init (); + +#if defined ( __GNUC__ ) || defined ( __INTEL_COMPILER ) + void _fullpath ( char *full, const char *name, int max ) + { + strncpy ( full, name, max ); + } +#endif + +#if defined ( WIN32 ) + + int mkstemp ( char *filename ) + { + if ( filename == NULL ) return -1; + + size_t len = strlen ( filename ); + if ( len < 6 ) return -1; + + char *basePtr = filename + len - 6; + for ( int i = 0; i < 6; i++ ) if ( basePtr [i] != 'X' ) return -1; + + int handle = -1; + while ( handle == -1 ) { + char *ptr = basePtr; + for ( int i = 0; i < 6; i++ ) { + int x = rand () % 62; + if ( x >= 52 ) { ptr [i] = '0' + x - 52; continue; } + if ( x >= 26 ) { ptr [i] = 'A' + x - 26; continue; } + ptr [i] = 'a' + x; + } + handle = open ( filename, O_CREAT | O_EXCL | O_RDWR | O_BINARY, S_IREAD | S_IWRITE ); + } + return handle; + } + +#endif + +#if ( BYTE_ORDER == BIG_ENDIAN ) + UINT32 swap_uint32 ( const unsigned char *ptr ) + { + UINT32 res = ptr [3]; + res = ( res << 8 ) | ptr [2]; + res = ( res << 8 ) | ptr [1]; + res = ( res << 8 ) | ptr [0]; + return res; + } + + UINT16 swap_uint16 ( const unsigned char *ptr ) + { + UINT16 res = ptr [1]; + res = ( res << 8 ) | ptr [0]; + return res; + } +#endif + +WAD::WAD ( const char *filename ) : + m_Name ( NULL ), + m_File ( NULL ), + m_List ( NULL ), + m_bValid ( false ), + m_bRegistered ( false ), + m_bDirChanged ( false ), + m_Directory ( NULL ), + m_DirInfo ( NULL ), + m_Status ( ws_UNKNOWN ), + m_Type ( wt_UNKNOWN ), + m_Style ( wst_UNKNOWN ), + m_MapStart ( NULL ), + m_MapEnd ( NULL ), + m_SpriteStart ( NULL ), + m_SpriteEnd ( NULL ), + m_PatchStart ( NULL ), + m_PatchEnd ( NULL ), + m_FlatStart ( NULL ), + m_FlatEnd ( NULL ), + m_NewData ( NULL ) +{ + m_Name = ( filename != NULL ) ? strdup ( filename ) : strdup ( "" ); + + memset ( &m_Header, 0, sizeof ( m_Header )); + + if ( filename != NULL ) { + OpenFile (); + } +} + +WAD::~WAD () +{ + CloseFile (); + + free ( m_Name ); +} + +bool WAD::EnlargeDirectory ( int holePos, int entries ) +{ + int newSize = m_Header.dirSize + entries; + + wadDirEntry *newDir = new wadDirEntry [ newSize ]; + wadDirInfo *newInfo = new wadDirInfo [ newSize ]; + + if (( newDir == NULL ) || ( newInfo == NULL )) { + if ( newDir != NULL ) delete [] newDir; + if ( newInfo != NULL ) delete [] newInfo; + return false; + } + + int loCount = holePos; + int hiCount = m_Header.dirSize - holePos; + + memset ( newDir, 0, sizeof ( wadDirEntry ) * newSize ); + memset ( newInfo, 0, sizeof ( wadDirInfo ) * newSize ); + + memcpy ( newDir, m_Directory, sizeof ( wadDirEntry ) * loCount ); + memcpy ( newDir + loCount + entries, m_Directory + loCount, sizeof ( wadDirEntry ) * hiCount ); + + memcpy ( newInfo, m_DirInfo, sizeof ( wadDirInfo ) * loCount ); + memcpy ( newInfo + loCount + entries, m_DirInfo + loCount, sizeof ( wadDirInfo ) * hiCount ); + + if ( m_Directory != NULL ) delete [] m_Directory; + m_Directory = newDir; + + if ( m_DirInfo != NULL ) delete [] m_DirInfo; + m_DirInfo = newInfo; + + m_Header.dirSize = newSize; + FindMarkers (); + + return true; +} + +bool WAD::ReduceDirectory ( int holePos, int entries ) +{ + if ( holePos + entries > ( int ) m_Header.dirSize ) entries = m_Header.dirSize - holePos; + int hiCount = m_Header.dirSize - ( holePos + entries ); + + if ( hiCount > 0 ) { + memcpy ( m_Directory + holePos, m_Directory + holePos + entries, sizeof ( wadDirEntry ) * hiCount ); + memcpy ( m_DirInfo + holePos, m_DirInfo + holePos + entries, sizeof ( wadDirInfo ) * hiCount ); + } + m_Header.dirSize -= entries; + + if ( m_List != NULL ) m_List->UpdateDirectory (); + + return true; +} + +void WAD::FindMarkers () +{ + m_MapStart = m_MapEnd = NULL; + UINT32 s; + for ( s = 0; s < m_Header.dirSize; s++ ) { + if ( IsMap ( m_Directory [s].name )) { + m_MapStart = &m_Directory [s]; + break; + } + } + for ( UINT32 e = m_Header.dirSize - 1; e >= s; e-- ) { + if ( IsMap ( m_Directory [e].name )) { + m_MapEnd = &m_Directory [e]; + break; + } + } + + if ( m_MapEnd != NULL ) m_MapEnd += 10; + + m_SpriteStart = FindDir ( "S_START" ); + m_SpriteEnd = FindDir ( "S_END", m_SpriteStart ); + + m_PatchStart = FindDir ( "P_START" ); + m_PatchEnd = FindDir ( "P_END", m_PatchStart ); + + m_FlatStart = FindDir ( "F_START" ); + m_FlatEnd = FindDir ( "F_END", m_FlatStart ); +} + +bool WAD::ReadHeader ( wadHeader *header ) +{ + ReadBytes ( header, sizeof ( wadHeader )); + +#if ( BYTE_ORDER == BIG_ENDIAN ) + header->dirSize = swap_uint32 (( UINT8 * ) &header->dirSize ); + header->dirStart = swap_uint32 (( UINT8 * ) &header->dirStart ); +#endif + + if ( ! IS_TYPE ( header->type, IWAD_ID ) && ! IS_TYPE ( header->type, PWAD_ID )) { + fprintf ( stderr, "Invalid WAD header type '%4.4s' (expected '%4.4s' or '%4.4s')\n", header->type, &IWAD_ID, &PWAD_ID ); + return false; + } + + return true; +} + +bool WAD::WriteHeader ( FILE *file, wadHeader *header ) +{ +#if ( BYTE_ORDER == BIG_ENDIAN ) + wadHeader temp = *header; + + temp.dirSize = swap_uint32 (( UINT8 * ) &temp.dirSize ); + temp.dirStart = swap_uint32 (( UINT8 * ) &temp.dirStart ); + + header = &temp; +#endif + + return ( fwrite ( header, sizeof ( wadHeader ), 1, file ) == 1 ) ? true : false; +} + +bool WAD::ReadDirEntry ( wadDirEntry *entry ) +{ + ReadBytes ( entry, sizeof ( wadDirEntry ), 1 ); + +#if ( BYTE_ORDER == BIG_ENDIAN ) + entry->offset = swap_uint32 (( UINT8 * ) &entry->offset ); + entry->size = swap_uint32 (( UINT8 * ) &entry->size ); +#endif + + return true; +} + +bool WAD::WriteDirEntry ( FILE *file, wadDirEntry *entry ) +{ +#if ( BYTE_ORDER == BIG_ENDIAN ) + wadDirEntry temp = *entry; + + temp.offset = swap_uint32 (( UINT8 * ) &temp.offset ); + temp.size = swap_uint32 (( UINT8 * ) &temp.size ); + + entry = &temp; +#endif + + return ( fwrite ( entry, sizeof ( wadDirEntry ), 1, file ) == 1 ) ? true : false; +} + +bool WAD::ReadDirectory () +{ + if ( m_Directory != NULL ) delete [] m_Directory; + + m_DirInfo = new wadDirInfo [ m_Header.dirSize ]; + + for ( UINT32 i = 0; i < m_Header.dirSize; i++ ) { + m_DirInfo [i].newData = NULL; + m_DirInfo [i].cacheData = NULL; + m_DirInfo [i].type = wl_UNCHECKED; + } + + Seek ( m_Header.dirStart ); + + m_Directory = new wadDirEntry [ m_Header.dirSize ]; + + for ( UINT32 i = 0; i < m_Header.dirSize; i++ ) { + ReadDirEntry ( &m_Directory [i] ); + } + + FindMarkers (); + + return true; +} + +bool WAD::WriteDirectory ( FILE *file ) +{ + for ( UINT32 i = 0; i < m_Header.dirSize; i++ ) { + if ( WriteDirEntry ( file, &m_Directory [i] ) == false ) { + return false; + } + } + + return true; +} + +UINT32 WAD::IndexOf ( const wadDirEntry *entry ) const +{ + return (( entry < m_Directory ) || ( entry > m_Directory + m_Header.dirSize )) ? -1 : entry - m_Directory; +} + +void WAD::SetList ( wadList *_list ) +{ + m_List = _list; +} + +bool WAD::IsMap ( const char *name ) +{ + if ( name == NULL ) return false; + if (( name[0] != 'M' ) && ( name[0] != 'E' )) return false; + if ( strncmp ( name, "MAP", 3 ) == 0 ) { + if ( isdigit ( name[3] ) == false && !(isupper(name[3]) && isalpha(name[3]))) return false; + if ( isdigit ( name[4] ) == false && !(!isdigit(name[3]) && isupper(name[4]) && isalpha(name[4]))) return false; + if ( name[5] != '\0' ) return false; + + if(isdigit(name[3])) + { + int level; + if ( sscanf ( name+3, "%d", &level ) == 0 ) return false; + return (( level >= 1 ) && ( level <= 99 )) ? true : false; + } + else return true; + } + if (( name[0] == 'E' ) && ( name[2] == 'M' )) { + int episode = name[1], mission = name[3]; + if (( episode < '1' ) || ( episode > '4' )) return false; + if (( mission < '1' ) || ( mission > '9' )) return false; + if ( name[4] != '\0' ) return false; + return true; + } + return false; +} + +UINT32 WAD::FileSize () const +{ + UINT32 totalSize = sizeof ( wadHeader ); + + for ( UINT32 i = 0; i < m_Header.dirSize; i++ ) { + totalSize += sizeof ( wadDirEntry ) + m_Directory [i].size; + } + + return totalSize; +} + +bool WAD::AddFilter ( wadFilter *newFilter ) +{ + wadFilter **newList = new wadFilter * [ sm_NoFilters + 1 ]; + + if ( sm_Filter != NULL ) { + memcpy ( newList, sm_Filter, sizeof ( wadFilter * ) * sm_NoFilters ); + delete [] sm_Filter; + } + + sm_Filter = newList; + sm_Filter [ sm_NoFilters++ ] = newFilter; + + return true; +} + +bool WAD::HasChanged ( const wadDirEntry *entry ) const +{ + UINT32 index = IndexOf ( entry ); + return ( index == ( UINT32 ) -1 ) ? false : m_DirInfo [ index ].newData ? true : false; +} + +void WAD::Seek ( UINT32 offset ) +{ + m_Status = ws_OK; + if ( m_File == NULL ) { + m_Status = ws_INVALID_FILE; + } else if ( fseek ( m_File, offset, SEEK_SET )) { + m_Status = ws_SEEK_ERROR; + } +} + +void WAD::ReadBytes ( void *ptr , UINT32 size, UINT32 count ) +{ + m_Status = ws_OK; + if ( m_File == NULL ) { + m_Status = ws_INVALID_FILE; + } else if ( fread ( ptr, count, size, m_File ) != size ) { + m_Status = ws_READ_ERROR; + } +} + +void *WAD::ReadEntry ( const char *name, UINT32 *size, const wadDirEntry *start, const wadDirEntry *end, bool cache ) +{ + return ReadEntry ( FindDir ( name, start, end ), size, cache ); +} + +void *WAD::ReadEntry ( const wadDirEntry *entry, UINT32 *size, bool cache ) +{ + char *buffer = NULL; + if ( size ) *size = 0; + UINT32 index = IndexOf ( entry ); + if ( index != ( UINT32 ) -1 ) { + buffer = new char [ entry->size + 1 ]; + if ( size ) *size = entry->size; + if ( m_DirInfo [ index ].newData ) { + memcpy ( buffer, m_DirInfo [ index ].newData, entry->size ); + } else if ( m_DirInfo [ index ].cacheData ) { + memcpy ( buffer, m_DirInfo [ index ].cacheData, entry->size ); + } else { + Seek ( entry->offset ); + if ( m_Status == ws_OK ) ReadBytes ( buffer, entry->size ); + if ( cache ) { + m_DirInfo [ index ].cacheData = new UINT8 [ entry->size + 1 ]; + memcpy ( m_DirInfo [ index ].cacheData, buffer, entry->size ); + m_DirInfo [ index ].cacheData [ entry->size ] = '\0'; + } + } + buffer [ entry->size ] = '\0'; + } + return ( void * ) buffer; +} + +bool WAD::WriteEntry ( const char *name, UINT32 newSize, void *newStuff, bool owner, const wadDirEntry *start, const wadDirEntry *end ) +{ + const wadDirEntry *entry = FindDir ( name, start, end ); + return WriteEntry ( entry, newSize, newStuff, owner ); +} + +bool WAD::WriteEntry ( const wadDirEntry *entry, UINT32 newSize, void *newStuff, bool owner ) +{ + UINT32 index = IndexOf ( entry ); + if ( index == ( UINT32 ) -1 ) return false; + + if ( newSize && ( newSize == entry->size )) { + char *oldStuff = ( char * ) ReadEntry ( entry, NULL ); + if ( memcmp ( newStuff, oldStuff, newSize ) == 0 ) { + delete [] oldStuff; + if ( owner == true ) delete [] ( char * ) newStuff; + return false; + } + delete [] oldStuff; + } + + UINT8 *temp = ( UINT8 * ) newStuff; + if ( owner == false ) { + temp = new UINT8 [ newSize ]; + memcpy ( temp, newStuff, newSize ); + } + if ( m_DirInfo [ index ].cacheData ) { + delete m_DirInfo [ index ].cacheData; + m_DirInfo [ index ].cacheData = NULL; + } + + m_DirInfo [ index ].newData = temp; + m_Directory [ index ].size = newSize; + m_Directory [ index ].offset = ( UINT32 ) -1; + + return true; +} + +void WAD::OpenFile () +{ + if ( m_File != NULL ) fclose ( m_File ); + m_File = NULL; + + int handle = open ( m_Name, O_RDONLY ); + if ( handle < 0 ) { + m_Status = ( errno == ENOENT ) ? ws_INVALID_FILE : ws_CANT_READ; + return; + } else { + close ( handle ); + } + + if (( m_File = fopen ( m_Name, "rb" )) == NULL ) { + m_Status = ws_INVALID_FILE; + return; + } + + // Read in the WAD's header + if ( ReadHeader ( &m_Header ) == false ) { + m_Status = ws_INVALID_WAD; + return; + } + m_Status = ws_OK; + + // Read in the WAD's directory info + m_bValid = true; + ReadDirectory (); + + if ( FindDir ( "TEXTURE2" )) m_bRegistered = true; + + if ( FindDir ( "BEHAVIOR" )) m_Type = wt_HEXEN; + else if ( FindDir ( "M_HTIC" )) m_Type = wt_HERETIC; + else if ( FindDir ( "SHT2A0" )) m_Type = wt_DOOM2; + + switch ( m_Type ) { + case wt_DOOM : m_Style = wst_FORMAT_1; break; + case wt_DOOM2 : m_Style = wst_FORMAT_2; break; + case wt_HERETIC : m_Style = wst_FORMAT_1; break; + case wt_HEXEN : m_Style = wst_FORMAT_3; break; + default : + if ( m_MapStart != NULL ) { + m_Style = ( toupper ( m_MapStart->name[0] ) == 'E' ) ? wst_FORMAT_1 : wst_FORMAT_2; + } + } + if ( m_Type == wt_UNKNOWN ) { + if ( m_Style == wst_FORMAT_2 ) { + m_Type = wt_DOOM2; + } + } +} + +void WAD::CloseFile () +{ + m_bValid = false; + m_bRegistered = false; + m_bDirChanged = false; + + if ( m_DirInfo != NULL ) { + for ( UINT32 i = 0; i < m_Header.dirSize; i++ ) { + if ( m_DirInfo [i].newData ) delete [] ( char * ) m_DirInfo [i].newData; + m_DirInfo [i].newData = NULL; + if ( m_DirInfo [i].cacheData ) delete [] ( char * ) m_DirInfo [i].cacheData; + m_DirInfo [i].cacheData = NULL; + m_DirInfo [i].type = wl_UNCHECKED; + } + delete [] m_DirInfo; + } + m_DirInfo = NULL; + + if ( m_Directory != NULL ) delete [] m_Directory; + m_Directory = NULL; + + m_MapStart = m_MapEnd = NULL; + + if ( m_File != NULL ) fclose ( m_File ); + m_File = NULL; + + memset ( &m_Header, 0, sizeof ( m_Header )); +} + +const wadDirEntry *WAD::GetDir ( UINT32 index ) const +{ + return ( index >= m_Header.dirSize ) ? ( const wadDirEntry * ) NULL : &m_Directory [index]; +} + +const wadDirEntry *WAD::FindDir ( const char *name, const wadDirEntry *start, const wadDirEntry *end ) const +{ + UINT32 i = 0, last = m_Header.dirSize - 1; + if ( start != NULL ) { + UINT32 index = IndexOf ( start ); + if ( index == ( UINT32 ) -1 ) return NULL; + if ( index > i ) i = index; + } + if ( end != NULL ) { + UINT32 index = IndexOf ( end ); + if ( index == ( UINT32 ) -1 ) return NULL; + if ( index < last ) last = index; + } + const wadDirEntry *dir = &m_Directory [i]; + for ( ; i <= last; i++, dir++ ) { + if ( dir->name[0] != name[0] ) continue; + if ( strncmp ( dir->name, name, 8 ) == 0 ) return dir; + } + return NULL; +} + +bool WAD::HasChanged () const +{ + if ( m_bDirChanged ) return true; + bool changed = false; + for ( UINT32 i = 0; ! changed && ( i < m_Header.dirSize ); i++ ) { + if ( m_DirInfo [i].newData ) changed = true; + } + + return changed; +} + +bool WAD::InsertBefore ( const wLumpName *name, UINT32 newSize, void *newStuff, bool owner, const wadDirEntry *entry ) +{ + UINT32 index = IndexOf ( entry ); + if ( entry && ( index == ( UINT32 ) -1 )) return false; + + if ( entry == NULL ) index = 0; + if ( ! EnlargeDirectory ( index, 1 )) return false; + + wadDirEntry *newDir = &m_Directory [ index ]; + strncpy ( newDir->name, ( char * ) name, sizeof ( wLumpName )); + + bool retVal = WriteEntry ( newDir, newSize, newStuff, owner ); + + if ( m_List != NULL ) m_List->UpdateDirectory (); + + return retVal; +} + +bool WAD::InsertAfter ( const wLumpName *name, UINT32 newSize, void *newStuff, bool owner, const wadDirEntry *entry ) +{ + UINT32 index = IndexOf ( entry ); + if ( entry && ( index == ( UINT32 ) -1 )) return false; + + if ( entry == NULL ) index = m_Header.dirSize; + else index += 1; + + if ( ! EnlargeDirectory ( index, 1 )) return false; + + wadDirEntry *newDir = &m_Directory [ index ]; + strncpy ( newDir->name, ( char * ) name, sizeof ( wLumpName )); + + bool retVal = WriteEntry ( newDir, newSize, newStuff, owner ); + + if ( m_List != NULL ) m_List->UpdateDirectory (); + + return retVal; +} + +bool WAD::Remove ( const wLumpName *lump, const wadDirEntry *start, const wadDirEntry *end ) +{ + const wadDirEntry *entry = FindDir ( *lump, start, end ); + + UINT32 index = IndexOf ( entry ); + if ( index == ( UINT32 ) -1 ) return false; + + return ReduceDirectory ( index, 1 ); +} + +/* +// TBD +int InsertBefore ( const wLumpName *, UINT32, void *, bool, const wadDirEntry * = NULL ); +int InsertAfter ( const wLumpName *, UINT32, void *, bool, const wadDirEntry * = NULL ); +// TBD +*/ + +bool WAD::SaveFile ( const char *newName ) +{ + if ( newName == NULL ) newName = m_Name; + const char *tempName = newName; + + char wadPath [MAXPATH], newPath [MAXPATH], tmpPath [MAXPATH]; + _fullpath ( wadPath, m_Name, MAXPATH ); + _fullpath ( newPath, newName, MAXPATH ); + + FILE *tmpFile = NULL; + + if ( stricmp ( wadPath, newPath ) == 0 ) { + if ( HasChanged () == false ) return true; + sprintf ( tmpPath, "%s.XXXXXX", newPath ); + int tmp = mkstemp ( tmpPath ); + if ( tmp == -1 ) { + fprintf ( stderr, "\nERROR: WAD::SaveFile - Error creating temporary file." ); + return false; + } + tempName = tmpPath; + tmpFile = fdopen ( tmp, "wb" ); + } + + if ( tmpFile == NULL ) { + tmpFile = fopen ( tempName, "wb" ); + } + + if ( tmpFile == NULL ) return false; + + bool errors = false; + if ( fwrite ( &m_Header, sizeof ( m_Header ), 1, tmpFile ) != 1 ) { + fprintf ( stderr, "ERROR: WAD::SaveFile - Error writing dummy header.\n" ); + errors = true; + } + + wadDirEntry *dir = m_Directory; + for ( UINT32 i = 0; i < m_Header.dirSize; i++ ) { + UINT32 offset = ftell ( tmpFile ); + if ( dir->size ) { + if ( m_DirInfo [i].newData ) { + if ( fwrite ( m_DirInfo [i].newData, dir->size, 1, tmpFile ) != 1 ) { + fprintf ( stderr, "ERROR: WAD::SaveFile - Error writing entry %8.8s. (newData)\n", dir->name ); + errors = true; + } + } else if ( m_DirInfo [i].cacheData ) { + if ( fwrite ( m_DirInfo [i].cacheData, dir->size, 1, tmpFile ) != 1 ) { + fprintf ( stderr, "ERROR: WAD::SaveFile - Error writing entry %8.8s. (cached)\n", dir->name ); + errors = true; + } + } else { + char *ptr = ( char * ) ReadEntry ( dir, NULL ); + if ( m_Status != ws_OK ) { + fprintf ( stderr, "ERROR: WAD::SaveFile - Error reading entry %8.8s. (%04x)\n", dir->name, m_Status ); + errors = true; + } + if ( fwrite ( ptr, dir->size, 1, tmpFile ) != 1 ) { + fprintf ( stderr, "ERROR: WAD::SaveFile - Error writing entry %8.8s. (file copy)\n", dir->name ); + errors = true; + } + delete [] ptr; + } + } + dir->offset = offset; + dir++; + } + + m_Header.dirStart = ftell ( tmpFile ); + + if ( WriteDirectory ( tmpFile ) == false ) { + fprintf ( stderr, "\nERROR: WAD::SaveFile - Error writing directory." ); + errors = true; + } + + fseek ( tmpFile, 0, SEEK_SET ); + + if ( WriteHeader ( tmpFile, &m_Header ) == false ) { + fprintf ( stderr, "\nERROR: WAD::SaveFile - Error writing header." ); + errors = true; + } + + fclose ( tmpFile ); + + if ( errors == true ) { + remove ( tempName ); + return false; + } + + if ( stricmp ( wadPath, newPath ) == 0 ) { + if ( m_File != NULL ) fclose ( m_File ); + if ( remove ( m_Name ) != 0 ) { + fprintf ( stderr, "\nERROR: WAD::SaveFile - Unable to remove %s.", m_Name ); + return false; + } + if ( rename ( tempName, m_Name ) != 0 ) { + fprintf ( stderr, "\nERROR: WAD::SaveFile - Unable to rename %s to %s.", tempName, m_Name ); + return false; + } + m_File = fopen ( m_Name, "rb" ); + } + + for ( UINT32 i = 0; i < m_Header.dirSize; i++ ) { + if ( m_DirInfo [i].newData ) { + if ( m_DirInfo [i].cacheData ) delete m_DirInfo [i].cacheData; + m_DirInfo [i].cacheData = m_DirInfo [i].newData; + m_DirInfo [i].newData = NULL; + } + } + + return true; +} + +wadList::wadList () : + m_DirSize ( 0 ), + m_MaxSize ( 0 ), + m_Directory ( NULL ), + m_Type ( wt_UNKNOWN ), + m_Style ( wst_UNKNOWN ), + m_List ( NULL ) +{ +} + +wadList::~wadList () +{ + while ( m_List != NULL ) { + wadListEntry *temp = m_List->Next; + delete m_List->wad; + delete m_List; + m_List = temp; + } + + if ( m_Directory != NULL ) delete [] m_Directory; +} + +int wadList::wadCount () const +{ + int size = 0; + + wadListEntry *ptr = m_List; + while ( ptr != NULL ) { + size++; + ptr = ptr->Next; + } + + return size; +} + +UINT32 wadList::FileSize () const +{ + UINT32 totalSize = sizeof ( wadHeader ); + + for ( UINT32 i = 0; i < m_DirSize; i++ ) { + totalSize += sizeof ( wadDirEntry ) + m_Directory [i].entry->size; + } + + return totalSize; +} + +WAD *wadList::GetWAD ( int index ) const +{ + wadListEntry *ptr = m_List; + while ( ptr && index-- ) ptr = ptr->Next; + return ptr ? ptr->wad : NULL; +} + +void wadList::Clear () +{ + wadListEntry *ptr = m_List; + while ( ptr ) { + wadListEntry *next = ptr->Next; + delete ptr->wad; + delete ptr; + ptr = next; + } + if ( m_Directory != NULL ) delete [] m_Directory; + + m_DirSize = 0; + m_MaxSize = 0; + m_Directory = NULL; + m_List = NULL; + m_Type = wt_UNKNOWN; + m_Style = wst_UNKNOWN; +} + +void wadList::UpdateDirectory () +{ + m_DirSize = 0; + + wadListEntry *ptr = m_List; + while ( ptr != NULL ) { + AddDirectory ( ptr->wad ); + ptr = ptr->Next; + } +} + +bool wadList::Add ( WAD *wad ) +{ + if (( m_Type == wt_UNKNOWN ) && ( m_Style == wst_UNKNOWN )) { + m_Type = wad->Type (); + m_Style = wad->Style (); + } + + if (( m_Type != wt_UNKNOWN ) && ( wad->Type () == wt_UNKNOWN ) + && ( wad->Format () == PWAD_ID )) { + const wadDirEntry *dir = wad->FindDir ( "SECTORS" ); + if ( dir != NULL ) { + UINT32 temp; + wSector *sector = ( wSector * ) wad->ReadEntry ( dir, &temp, true ); + int noSectors = temp / sizeof ( wSector ); + char tempName [ MAX_LUMP_NAME + 1 ]; + tempName [ MAX_LUMP_NAME ] = '\0'; + int i; + for ( i = 0; i < noSectors; i++ ) { + strncpy ( tempName, sector[i].floorTexture, MAX_LUMP_NAME ); + if ( FindWAD ( tempName ) == NULL ) break; + strncpy ( tempName, sector[i].ceilTexture, MAX_LUMP_NAME ); + if ( FindWAD ( tempName ) == NULL ) break; + } + if ( i == noSectors ) wad->Type ( m_Type ); + delete [] ( char * ) sector; + } + } + + if ( m_Type != wad->Type ()) return false; + if ( m_Style != wad->Style ()) return false; + + wadListEntry *newNode = new wadListEntry; + newNode->wad = wad; + newNode->Next = NULL; + + if ( m_List == NULL ) { + m_List = newNode; + } else { + wadListEntry *ptr = m_List; + while ( ptr->Next != NULL ) { + ptr = ptr->Next; + } + ptr->Next = newNode; + } + + AddDirectory ( wad, m_List->Next ? true : false ); + + wad->SetList ( this ); + + return true; +} + +bool wadList::Remove ( WAD *wad ) +{ + bool found = false; + wadListEntry *ptr = m_List; + + if ( m_List->wad == wad ) { + found = true; + m_List = m_List->Next; + delete ptr; + } else { + while ( ptr->Next != NULL ) { + if ( ptr->Next->wad == wad ) { + found = true; + wadListEntry *next = ptr->Next->Next; + delete ptr->Next; + ptr->Next = next; + break; + } + ptr = ptr->Next; + } + } + + if ( found ) { + wad->SetList ( NULL ); + m_DirSize = 0; + ptr = m_List; + while ( ptr != NULL ) { + AddDirectory ( ptr->wad ); + ptr = ptr->Next; + } + } + + if ( m_DirSize == 0 ) m_Type = wt_UNKNOWN; + + return found; +} + +UINT32 wadList::IndexOf ( const wadListDirEntry *entry ) const +{ + return (( entry < m_Directory ) || ( entry > m_Directory + m_DirSize )) ? -1 : entry - m_Directory; +} + +int wadList::AddLevel ( UINT32 index, const wadDirEntry *&entry, WAD *wad ) +{ + int size = 0; + const wadDirEntry *start = entry + 1; + const wadDirEntry *end = entry + 11; + + if ( wad->FindDir ( "THINGS", start, end )) size++; + if ( wad->FindDir ( "LINEDEFS", start, end )) size++; + if ( wad->FindDir ( "SIDEDEFS", start, end )) size++; + if ( wad->FindDir ( "VERTEXES", start, end )) size++; + if ( wad->FindDir ( "SEGS", start, end )) size++; + if ( wad->FindDir ( "SSECTORS", start, end )) size++; + if ( wad->FindDir ( "NODES", start, end )) size++; + if ( wad->FindDir ( "SECTORS", start, end )) size++; + if ( wad->FindDir ( "REJECT", start, end )) size++; + if ( wad->FindDir ( "BLOCKMAP", start, end )) size++; + if ( wad->FindDir ( "BEHAVIOR", start, end )) size++; + + if ( index == m_DirSize ) { + m_DirSize += size; + for ( int i = 0; i < size; i++ ) { + m_Directory [ index ].wad = wad; + m_Directory [ index ].entry = ++entry; + index++; + } + } else { + for ( int i = 0; i < size; i++ ) { +/* TBD proper replacement of level lumps + const wadListDirEntry *entry = FindWAD ( entry[1].name, index, index + 10 ); + UINT32 index = IndexOf ( entry ); +*/ + m_Directory [ index ].wad = wad; + m_Directory [ index ].entry = ++entry; + index++; + } + } + + return size; +} + +void wadList::AddDirectory ( WAD *wad, bool check ) +{ + // Make sure AddDirectory has enough room to work + if ( m_DirSize + wad->DirSize () > m_MaxSize ) { + m_MaxSize = m_DirSize + wad->DirSize (); + wadListDirEntry *temp = new wadListDirEntry [ m_MaxSize ]; + if ( m_Directory != NULL ) { + memcpy ( temp, m_Directory, sizeof ( wadListDirEntry ) * m_DirSize ); + delete [] m_Directory; + } + m_Directory = temp; + } + + const wadDirEntry *newDir = wad->GetDir ( 0 ); + UINT32 count = wad->DirSize (); + while ( count ) { + const wadListDirEntry *entry = check ? FindWAD ( newDir->name ) : NULL; + if ( entry != NULL ) { + UINT32 index = IndexOf ( entry ); + m_Directory [ index ].wad = wad; + m_Directory [ index ].entry = newDir; + if ( WAD::IsMap ( newDir->name )) { + count -= AddLevel ( index + 1, newDir, wad ); + } + } else { + UINT32 index = m_DirSize++; + m_Directory [ index ].wad = wad; + m_Directory [ index ].entry = newDir; + if ( WAD::IsMap ( newDir->name )) { + count -= AddLevel ( index + 1, newDir, wad ); + } + } + newDir++; + count--; + } +} + +const wadListDirEntry *wadList::GetDir ( UINT32 index ) const +{ + return ( index >= m_DirSize ) ? ( const wadListDirEntry * ) NULL : &m_Directory [index]; +} + +const wadListDirEntry *wadList::FindWAD ( const char *name, const wadListDirEntry *start, const wadListDirEntry *end ) const +{ + UINT32 i = 0, last = m_DirSize; + + if ( start != NULL ) i = IndexOf ( start ); + if ( end != NULL ) last = IndexOf ( end ); + + + for ( ; i < last; i++ ) { + const wadListDirEntry *dir = &m_Directory [i]; + if ( dir->entry->name[0] != name[0] ) continue; + if ( strncmp ( dir->entry->name, name, 8 ) == 0 ) return dir; + } + + return NULL; +} + +bool wadList::HasChanged () const +{ + wadListEntry *ptr = m_List; + while ( ptr != NULL ) { + if ( ptr->wad->HasChanged ()) return true; + ptr = ptr->Next; + } + return false; +} + +bool wadList::Contains ( WAD *wad ) const +{ + wadListEntry *ptr = m_List; + while ( ptr != NULL ) { + if ( ptr->wad == wad ) return true; + ptr = ptr->Next; + } + return false; +} + +bool wadList::Save ( const char *newName ) +{ + if ( IsEmpty ()) return false; + + if ( m_List->Next ) { + + wadListEntry *ptr = m_List; + const char *m_Name = NULL; + char wadPath [MAXPATH], newPath [MAXPATH], tmpPath [MAXPATH]; + _fullpath ( newPath, newName, MAXPATH ); + while ( ptr->Next != NULL ) { + m_Name = ptr->wad->Name (); + _fullpath ( wadPath, m_Name, MAXPATH ); + if ( stricmp ( wadPath, newPath ) == 0 ) break; + ptr = ptr->Next; + } + + FILE *tmpFile = NULL; + + if ( newName == NULL ) newName = m_Name; + const char *tempName = newName; + + if ( stricmp ( wadPath, newPath ) == 0 ) { + sprintf ( tmpPath, "%s.XXXXXX", newPath ); + int tmp = mkstemp ( tmpPath ); + if ( tmp == -1 ) { + fprintf ( stderr, "\nERROR: wadList::SaveFile - Error creating temporary file." ); + return false; + } + tempName = tmpPath; + tmpFile = fdopen ( tmp, "wb" ); + } + + if ( tmpFile == NULL ) { + tmpFile = fopen ( tempName, "wb" ); + } + + if ( tmpFile == NULL ) return false; + + bool errors = false; + wadHeader m_Header; + if ( fwrite ( &m_Header, sizeof ( m_Header ), 1, tmpFile ) != 1 ) { + errors = true; + fprintf ( stderr, "\nERROR: wadList::Save - Error writing dummy header." ); + } + + wadListDirEntry *srcDir = m_Directory; + wadDirEntry *dir = new wadDirEntry [ m_DirSize ]; + for ( UINT32 i = 0; i < m_DirSize; i++ ) { + dir[i] = *srcDir->entry; + long offset = ftell ( tmpFile ); + char *ptr = ( char * ) srcDir->wad->ReadEntry ( srcDir->entry, NULL ); + if ( srcDir->wad->Status () != ws_OK ) { + errors = true; + fprintf ( stderr, "\nERROR: wadList::Save - Error reading entry %8.8s. (%04X)", srcDir->entry->name, srcDir->wad->Status ()); + } + if (( dir[i].size > 0 ) && ( fwrite ( ptr, dir[i].size, 1, tmpFile ) != 1 )) { + errors = true; + fprintf ( stderr, "\nERROR: wadList::Save - Error writing entry %8.8s.", srcDir->entry->name ); + } + delete [] ptr; + dir[i].offset = offset; + srcDir++; + } + + WAD *wad = ptr->wad; + + * ( UINT32 * ) m_Header.type = wad->Format (); + m_Header.dirSize = m_DirSize; + m_Header.dirStart = ftell ( tmpFile ); + + if (( UINT32 ) fwrite ( dir, sizeof ( wadDirEntry ), m_DirSize, tmpFile ) != m_DirSize ) { + errors = true; + fprintf ( stderr, "\nERROR: wadList::Save - Error writing directory." ); + } + delete dir; + + fseek ( tmpFile, 0, SEEK_SET ); + if ( fwrite ( &m_Header, sizeof ( m_Header ), 1, tmpFile ) != 1 ) { + errors = true; + fprintf ( stderr, "\nERROR: wadList::Save - Error writing header." ); + } + + fclose ( tmpFile ); + + if ( errors == true ) { + remove ( tempName ); + return false; + } + + if ( stricmp ( wadPath, newPath ) == 0 ) { + wad->CloseFile (); + if ( remove ( m_Name ) != 0 ) { + fprintf ( stderr, "\nERROR: wadList::Save - Unable to remove %s.", m_Name ); + return false; + } + if ( rename ( tempName, m_Name ) != 0 ) { + fprintf ( stderr, "\nERROR: wadList::Save - Unable to rename %s to %s.", tempName, m_Name ); + return false; + } + wad->OpenFile (); + } + + } else { + return m_List->wad->SaveFile ( newName ); + } + + return true; +} + +bool wadList::Extract ( const wLumpName *res, const char *m_Name ) +{ + UINT32 size; + const wadListDirEntry *dir; + + WAD *newWad = new WAD ( NULL ); + + bool hasMaps = false; + for ( int i = 0; res [i][0]; i++ ) { + if ( WAD::IsMap ( res[i] )) { + hasMaps = true; + const wadListDirEntry *dir = FindWAD ( res[i] ); + DoomLevel *level = new DoomLevel ( res[i], dir->wad, true ); + level->AddToWAD ( newWad ); + delete level; + } else { + if (( dir = FindWAD ( res[i], NULL, NULL )) != NULL ) { + void *ptr = dir->wad->ReadEntry ( dir->entry, &size, false ); + newWad->InsertAfter (( const wLumpName * ) res[i], size, ptr, true ); + } + } + } + + if ( hasMaps ) { + if (( dir = FindWAD ( "MAPINFO" )) != NULL ) { + void *ptr = dir->wad->ReadEntry ( dir->entry, &size, false ); + newWad->InsertAfter (( const wLumpName * ) "MAPINFO", size, ptr, true ); + } + if (( dir = FindWAD ( "SNDINFO" )) != NULL ) { + void *ptr = dir->wad->ReadEntry ( dir->entry, &size, false ); + newWad->InsertAfter (( const wLumpName * ) "SNDINFO", size, ptr, true ); + } + } + + newWad->Format ( PWAD_ID ); + + char filename [ 256 ]; + if ( m_Name ) strcpy ( filename, m_Name ); + else sprintf ( filename, "%s.WAD", res [0] ); + bool retVal = newWad->SaveFile ( filename ); + delete newWad; + + return retVal; +} diff --git a/DOOM/wad.hpp b/DOOM/wad.hpp new file mode 100644 index 0000000..ca4a4c4 --- /dev/null +++ b/DOOM/wad.hpp @@ -0,0 +1,322 @@ +//---------------------------------------------------------------------------- +// +// File: wad.hpp +// Date: 26-Oct-1994 +// Programmer: Marc Rousseau +// +// Description: Object classes for manipulating Doom WAD files +// +// Copyright (c) 1994-2004 Marc Rousseau, All Rights Reserved. +// +// 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 2 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, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. +// +// Revision History: +// +// 04-25-01 Added little/big endian conversions +// +//---------------------------------------------------------------------------- + +#ifndef WAD_HPP_ +#define WAD_HPP_ + +#if ! defined ( COMMON_HPP_ ) + #include "common.hpp" +#endif + +#if ! defined ( __STDIO_H ) + #include +#endif + +#if ( BYTE_ORDER == LITTLE_ENDIAN ) + const UINT32 IWAD_ID = 0x44415749; // ASCII - 'IWAD' + const UINT32 PWAD_ID = 0x44415750; // ASCII - 'PWAD' +#else + const UINT32 IWAD_ID = 0x49574144; // ASCII - 'IWAD' + const UINT32 PWAD_ID = 0x50574144; // ASCII - 'PWAD' + + extern UINT32 swap_uint32 ( const unsigned char *ptr ); + extern UINT16 swap_uint16 ( const unsigned char *ptr ); +#endif + +#define IS_TYPE(x,y) (( * ( UINT32 * ) ( x )) == (( UINT32 ) y )) + +#define MAX_LUMP_NAME 8 + +enum eLumpType { + wl_UNCHECKED, + wl_UNKNOWN, + wl_PALETTE, + wl_COLORMAP, + wl_DEMO, + wl_TEXTURE_LIST, + wl_PATCH_NAMES, + wl_MIDI_MAPPING, + wl_GRAVIS_PATCH, + wl_MAP_NAME, + wl_MAP_DATA, + wl_PC_SPEAKER, + wl_SOUND_EFFECT, + wl_MUSIC, + wl_FLAT, + wl_PATCH, + wl_SPRITE, + wl_GRAPHIC, + wl_SCREEN_SHOT, + wl_TEXT_SCREEN, + wl_SOUND_INFO, + wl_SCRIPT, + wl_SPECIAL +}; + +enum eWadType { + wt_UNKNOWN, + wt_DOOM, + wt_DOOM2, + wt_HERETIC, + wt_HEXEN +}; + +enum eWadStyle { + wst_UNKNOWN, + wst_FORMAT_1, // DOOM / Heretic + wst_FORMAT_2, // DOOM ][ + wst_FORMAT_3 // Hexen +}; + +enum eWadStatus { + ws_UNKNOWN, + ws_OK, + ws_CANT_READ, + ws_CANT_WRITE, + ws_INVALID_WAD, + ws_INVALID_FILE, + ws_SEEK_ERROR, + ws_READ_ERROR, + ws_WRITE_ERROR +}; + +struct wadHeader { + char type [4]; + UINT32 dirSize; // number of Lumps in WAD + UINT32 dirStart; // offset to start of directory +}; + +typedef char wLumpName [ MAX_LUMP_NAME ]; + +struct wadDirEntry { + UINT32 offset; // offset to start of data + UINT32 size; // byte size of data + wLumpName name; // name of data block +}; + +struct wadDirInfo { + UINT8 *cacheData; + UINT8 *newData; + eLumpType type; +}; + +class wadList; + +class wadFilter { +public: + virtual const char *getFileSpec () const = 0; + virtual bool isRecognized ( UINT32, void * ) const = 0; + virtual bool isRecognized ( const char * ) const = 0; + virtual bool readData ( FILE *, UINT32 *, void ** ) const = 0; + virtual bool writeData ( FILE *, UINT32, void * ) const = 0; +}; + +class WAD { + + char *m_Name; + FILE *m_File; + wadList *m_List; + + bool m_bValid; + bool m_bRegistered; + bool m_bDirChanged; // wadDirEntry added/deleted + + wadHeader m_Header; + wadDirEntry *m_Directory; + wadDirInfo *m_DirInfo; + eWadStatus m_Status; + eWadType m_Type; + eWadStyle m_Style; + + const wadDirEntry *m_MapStart; + const wadDirEntry *m_MapEnd; + const wadDirEntry *m_SpriteStart; + const wadDirEntry *m_SpriteEnd; + const wadDirEntry *m_PatchStart; + const wadDirEntry *m_PatchEnd; + const wadDirEntry *m_FlatStart; + const wadDirEntry *m_FlatEnd; + + void **m_NewData; + + static int sm_NoFilters; + static wadFilter **sm_Filter; + + bool EnlargeDirectory ( int holePos, int entries ); + bool ReduceDirectory ( int holePos, int entries ); + + void FindMarkers (); + + bool ReadHeader ( wadHeader * ); + bool WriteHeader ( FILE *, wadHeader * ); + + bool ReadDirEntry ( wadDirEntry * ); + bool WriteDirEntry ( FILE *, wadDirEntry * ); + + bool ReadDirectory (); + bool WriteDirectory ( FILE * ); + + UINT32 IndexOf ( const wadDirEntry * ) const; + +public: + + WAD ( const char * ); + ~WAD (); + + void SetList ( wadList * ); + + static bool IsMap ( const char * ); + static bool AddFilter ( wadFilter * ); + + // Called by wadList::Save + void OpenFile (); + void CloseFile (); + + bool SaveFile ( const char * = NULL ); + + void Type ( eWadType ); + void Style ( eWadStyle ); + void Format ( UINT32 ); + + const char *Name () const; + eWadStatus Status () const; + eWadType Type () const; + eWadStyle Style () const; + UINT32 Format () const; + UINT32 FileSize () const; + + bool IsValid () const; + bool IsRegistered () const; + bool HasChanged () const; + bool HasChanged ( const wadDirEntry * ) const; + + eLumpType GetLumpType ( const wadDirEntry * ); + const char *GetLumpDescription ( eLumpType ) const; + const char *GetLumpDescription ( const wadDirEntry * ); + + void Seek ( UINT32 ); + void ReadBytes ( void *, UINT32, UINT32 = 1 ); + void WriteBytes ( void *, UINT32, UINT32 = 1 ); + void CopyBytes ( WAD *, UINT32, UINT32 = 1 ); + + UINT32 DirSize () const; + + const wadDirEntry *GetDir ( UINT32 ) const; + const wadDirEntry *FindDir ( const char *, const wadDirEntry * = NULL, const wadDirEntry * = NULL ) const; + + void *ReadEntry ( const char *, UINT32 *, const wadDirEntry * = NULL, const wadDirEntry * = NULL, bool = false ); + void *ReadEntry ( const wadDirEntry *, UINT32 *, bool = false ); + + bool WriteEntry ( const char *, UINT32, void *, bool, const wadDirEntry * = NULL, const wadDirEntry * = NULL ); + bool WriteEntry ( const wadDirEntry *, UINT32, void *, bool ); + + bool Extract ( const wLumpName *, const char * = NULL ) const; + + bool InsertBefore ( const wLumpName *, const char *, bool, const wadDirEntry * = NULL ); + bool InsertAfter ( const wLumpName *, const char *, bool, const wadDirEntry * = NULL ); + + bool InsertBefore ( const wLumpName *, UINT32, void *, bool, const wadDirEntry * = NULL ); + bool InsertAfter ( const wLumpName *, UINT32, void *, bool, const wadDirEntry * = NULL ); + + bool Remove ( const wLumpName *, const wadDirEntry * = NULL, const wadDirEntry * = NULL ); +}; + +inline void WAD::Format ( UINT32 newFormat ) { * ( UINT32 * ) m_Header.type = newFormat; } +inline void WAD::Type ( eWadType newType ) { m_Type = newType; } +inline void WAD::Style ( eWadStyle newStyle ) { m_Style = newStyle; } + +inline const char *WAD::Name () const { return m_Name; } +inline UINT32 WAD::DirSize () const { return m_Header.dirSize; } +inline UINT32 WAD::Format () const { return * ( UINT32 * ) m_Header.type; } +inline eWadStatus WAD::Status () const { return m_Status; } +inline eWadType WAD::Type () const { return m_Type; } +inline eWadStyle WAD::Style () const { return m_Style; } +inline bool WAD::IsValid () const { return m_bValid; } +inline bool WAD::IsRegistered () const { return m_bRegistered; } + +struct wadListDirEntry { + WAD *wad; + const wadDirEntry *entry; +}; + +class wadList { + + struct wadListEntry { + WAD *wad; + wadListEntry *Next; + }; + + UINT32 m_DirSize; + UINT32 m_MaxSize; + wadListDirEntry *m_Directory; + eWadType m_Type; + eWadStyle m_Style; + wadListEntry *m_List; + + UINT32 IndexOf ( const wadListDirEntry * ) const; + + int AddLevel ( UINT32, const wadDirEntry *&, WAD * ); + void AddDirectory ( WAD *, bool = true ); + +public: + + wadList (); + ~wadList (); + + bool Add ( WAD * ); + bool Remove ( WAD * ); + void Clear (); + void UpdateDirectory (); + + int wadCount () const; + UINT32 FileSize () const; + WAD *GetWAD ( int ) const; + eWadType Type () const; + eWadStyle Style () const; + + bool Save ( const char * = NULL ); + bool Extract ( const wLumpName *, const char *file = NULL ); + + UINT32 DirSize () const; + + const wadListDirEntry *GetDir ( UINT32 ) const; + const wadListDirEntry *FindWAD ( const char *, const wadListDirEntry * = NULL, const wadListDirEntry * = NULL ) const; + + bool Contains ( WAD * ) const; + bool IsEmpty () const; + bool HasChanged () const; +}; + +inline UINT32 wadList::DirSize () const { return m_DirSize; } +inline bool wadList::IsEmpty () const { return ( m_List == NULL ) ? true : false; } +inline eWadType wadList::Type () const { return m_Type; } +inline eWadStyle wadList::Style () const { return m_Style; } + +#endif diff --git a/README b/README new file mode 100644 index 0000000..e8b721c --- /dev/null +++ b/README @@ -0,0 +1,22 @@ +ZenNode v1.2.0 +============== + +This is directory tree includes the source code necessary to build ZenNode. +Since I don't have a lot of time to spend maintaining the code and there +haven't been too many changes in the past few years, I've decided to release +the source under the GPL license. See the file COPYING for details. + +Several of the files (i.e. those in the DOOM directory) evolved from earlier +projects. They contain functionality that is not used in ZenNode and may not +have been kept up to date. I think it all works, but I won't make any +guarantees. + +There is a makefile included in the ZenNode directory that can be used as is +under Linux. To build ZenNode in any other environment, you can use the +makefile as a guide. I've built and tested it under Windows using Borland +and Microsoft compilers. Older versions have been compiled under OS/2 using +Borland. The code should be portable to other compilers, but it has never +been tested. + +Enjoy, +Marc diff --git a/ZenNode/ZenMain.cpp b/ZenNode/ZenMain.cpp new file mode 100644 index 0000000..b53f701 --- /dev/null +++ b/ZenNode/ZenMain.cpp @@ -0,0 +1,1030 @@ +//---------------------------------------------------------------------------- +// +// File: ZenMain.cpp +// Date: 26-Oct-1994 +// Programmer: Marc Rousseau +// +// Description: The application specific code for ZenNode +// +// Copyright (c) 1994-2004 Marc Rousseau, All Rights Reserved. +// +// 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 2 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, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. +// +// Revision History: +// +// 06-??-95 Added Win32 support +// 07-19-95 Updated command line & screen logic +// 11-19-95 Updated command line again +// 12-06-95 Add config & customization file support +// 11-??-98 Added Linux support +// 01-31-04 Disabled unique sectors as a default option in the BSP options +// 01-31-04 Added RMB option support +// +//---------------------------------------------------------------------------- + +#include +#include +#include +#include + +#if defined ( __OS2__ ) + #include + #include + #include + #define INCL_DOS + #define INCL_SUB + #include +#elif defined ( __WIN32__ ) + #include + #include +#elif defined ( __LINUX__ ) + #include +#else + #error This platform is not supported +#endif + +#if defined ( __BORLANDC__ ) + #include +#endif + +#include "common.hpp" +#include "logger.hpp" +#include "wad.hpp" +#include "level.hpp" +#include "console.hpp" +#include "ZenNode.hpp" + +DBG_REGISTER ( __FILE__ ); + +#define VERSION "1.2.1" + +const char BANNER [] = "ZenNode Version " VERSION " (c) 1994-2004 Marc Rousseau"; +const char CONFIG_FILENAME [] = "ZenNode.cfg"; +const int MAX_LEVELS = 99; +const int MAX_OPTIONS = 256; +const int MAX_WADS = 32; + +char HammingTable [ 256 ]; + +struct sOptions { + sBlockMapOptions BlockMap; + sNodeOptions Nodes; + sRejectOptions Reject; + bool WriteWAD; + bool Extract; +} config; + +struct sOptionsRMB { + const char *wadName; + sRejectOptionRMB *option [MAX_OPTIONS]; +}; + +sOptionsRMB rmbOptionTable [MAX_WADS]; + +#if defined ( __GNUC__ ) || defined ( __INTEL_COMPILER ) + extern char *strupr ( char * ); +#endif + +void printHelp () +{ + FUNCTION_ENTRY ( NULL, "printHelp", true ); + + fprintf ( stdout, "Usage: ZenNode {-options} filename[.wad] [level{+level}] {-o|x output[.wad]}\n" ); + fprintf ( stdout, "\n" ); + fprintf ( stdout, " -x+ turn on option -x- turn off option %c = default\n", DEFAULT_CHAR ); + fprintf ( stdout, "\n" ); + fprintf ( stdout, " -b[c] %c - Rebuild BLOCKMAP\n", config.BlockMap.Rebuild ? DEFAULT_CHAR : ' ' ); + fprintf ( stdout, " c %c - Compress BLOCKMAP\n", config.BlockMap.Compress ? DEFAULT_CHAR : ' ' ); + fprintf ( stdout, "\n" ); + fprintf ( stdout, " -n[a=1,2,3|q|u|i] %c - Rebuild NODES\n", config.Nodes.Rebuild ? DEFAULT_CHAR : ' ' ); + fprintf ( stdout, " a - Partition Selection Algorithm\n" ); + fprintf ( stdout, " %c 1 = Minimize splits\n", ( config.Nodes.Method == 1 ) ? DEFAULT_CHAR : ' ' ); + fprintf ( stdout, " %c 2 = Minimize BSP depth\n", ( config.Nodes.Method == 2 ) ? DEFAULT_CHAR : ' ' ); + fprintf ( stdout, " %c 3 = Minimize time\n", ( config.Nodes.Method == 3 ) ? DEFAULT_CHAR : ' '); + fprintf ( stdout, " q %c - Don't display progress bar\n", config.Nodes.Quiet ? DEFAULT_CHAR : ' ' ); + fprintf ( stdout, " u %c - Ensure all sub-sectors contain only 1 sector\n", config.Nodes.Unique ? DEFAULT_CHAR : ' ' ); + fprintf ( stdout, " i %c - Ignore non-visible lineDefs\n", config.Nodes.ReduceLineDefs ? DEFAULT_CHAR : ' ' ); + fprintf ( stdout, "\n" ); + fprintf ( stdout, " -r[zfgm] %c - Rebuild REJECT resource\n", config.Reject.Rebuild ? DEFAULT_CHAR : ' ' ); + fprintf ( stdout, " z %c - Insert empty REJECT resource\n", config.Reject.Empty ? DEFAULT_CHAR : ' ' ); + fprintf ( stdout, " f %c - Rebuild even if REJECT effects are detected\n", config.Reject.Force ? DEFAULT_CHAR : ' ' ); + fprintf ( stdout, " g %c - Use graphs to reduce LOS calculations\n", config.Reject.UseGraphs ? DEFAULT_CHAR : ' ' ); + fprintf ( stdout, " m{b} %c - Process RMB option file (.rej)\n", config.Reject.UseRMB ? DEFAULT_CHAR : ' ' ); + fprintf ( stdout, "\n" ); + fprintf ( stdout, " -t %c - Don't write output file (test mode)\n", ! config.WriteWAD ? DEFAULT_CHAR : ' ' ); + fprintf ( stdout, "\n" ); + fprintf ( stdout, " level - ExMy for DOOM/Heretic or MAPxx for DOOM II/HEXEN\n" ); +} + +bool parseBLOCKMAPArgs ( char *&ptr, bool setting ) +{ + FUNCTION_ENTRY ( NULL, "parseBLOCKMAPArgs", true ); + + config.BlockMap.Rebuild = setting; + while ( *ptr ) { + int option = *ptr++; + bool setting = true; + if (( *ptr == '+' ) || ( *ptr == '-' )) { + setting = ( *ptr++ == '+' ) ? true : false; + } + switch ( option ) { + case 'C' : config.BlockMap.Compress = setting; break; + default : return true; + } + config.BlockMap.Rebuild = true; + } + return false; +} + +bool parseNODESArgs ( char *&ptr, bool setting ) +{ + FUNCTION_ENTRY ( NULL, "parseNODESArgs", true ); + + config.Nodes.Rebuild = setting; + while ( *ptr ) { + int option = *ptr++; + bool setting = true; + if (( *ptr == '+' ) || ( *ptr == '-' )) { + setting = ( *ptr++ == '+' ) ? true : false; + } + switch ( option ) { + case '1' : config.Nodes.Method = 1; break; + case '2' : config.Nodes.Method = 2; break; + case '3' : config.Nodes.Method = 3; break; + case 'Q' : config.Nodes.Quiet = setting; break; + case 'U' : config.Nodes.Unique = setting; break; + case 'I' : config.Nodes.ReduceLineDefs = setting; break; + default : return true; + } + config.Nodes.Rebuild = true; + } + return false; +} + +bool parseREJECTArgs ( char *&ptr, bool setting ) +{ + FUNCTION_ENTRY ( NULL, "parseREJECTArgs", true ); + + config.Reject.Rebuild = setting; + while ( *ptr ) { + int option = *ptr++; + bool setting = true; + if (( *ptr == '+' ) || ( *ptr == '-' )) { + setting = ( *ptr++ == '+' ) ? true : false; + } + switch ( option ) { + case 'Z' : config.Reject.Empty = setting; break; + case 'F' : config.Reject.Force = setting; break; + case 'G' : config.Reject.UseGraphs = setting; break; + case 'M' : if (( ptr [-1] == 'M' ) && ( *ptr == 'B' )) { + ptr++; + if (( *ptr == '+' ) || ( *ptr == '-' )) { + setting = ( *ptr++ == '+' ) ? true : false; + } + } + config.Reject.UseRMB = setting; + break; + default : return true; + } + config.Reject.Rebuild = true; + } + return false; +} + +int parseArgs ( int index, const char *argv [] ) +{ + FUNCTION_ENTRY ( NULL, "parseArgs", true ); + + bool errors = false; + while ( argv [ index ] ) { + + if ( argv [index][0] != '-' ) break; + + char *localCopy = strdup ( argv [ index ]); + char *ptr = localCopy + 1; + strupr ( localCopy ); + + bool localError = false; + while ( *ptr && ( localError == false )) { + int option = *ptr++; + bool setting = true; + if (( *ptr == '+' ) || ( *ptr == '-' )) { + setting = ( *ptr == '-' ) ? false : true; + ptr++; + } + switch ( option ) { + case 'B' : localError = parseBLOCKMAPArgs ( ptr, setting ); break; + case 'N' : localError = parseNODESArgs ( ptr, setting ); break; + case 'R' : localError = parseREJECTArgs ( ptr, setting ); break; + case 'T' : config.WriteWAD = ! setting; break; + default : localError = true; + } + } + if ( localError ) { + errors = true; + int offset = ptr - localCopy - 1; + size_t width = strlen ( ptr ) + 1; + fprintf ( stderr, "Unrecognized parameter '%*.*s'\n", width, width, argv [index] + offset ); + } + free ( localCopy ); + index++; + } + + if ( errors ) fprintf ( stderr, "\n" ); + + return index; +} + +void ReadConfigFile ( const char *argv [] ) +{ + FUNCTION_ENTRY ( NULL, "ReadConfigFile", true ); + + FILE *configFile = fopen ( CONFIG_FILENAME, "rt" ); + if ( configFile == NULL ) { + char fileName [ 256 ]; + strcpy ( fileName, argv [0] ); + char *ptr = &fileName [ strlen ( fileName )]; + while (( --ptr >= fileName ) && ( *ptr != SEPERATOR )); + *++ptr = '\0'; + strcat ( ptr, CONFIG_FILENAME ); + configFile = fopen ( fileName, "rt" ); + } + if ( configFile == NULL ) return; + + char ch = ( char ) fgetc ( configFile ); + bool errors = false; + while ( ! feof ( configFile )) { + ungetc ( ch, configFile ); + + char lineBuffer [ 256 ]; + fgets ( lineBuffer, sizeof ( lineBuffer ), configFile ); + char *basePtr = strupr ( lineBuffer ); + while ( *basePtr == ' ' ) basePtr++; + basePtr = strtok ( basePtr, "\n\x1A" ); + + if ( basePtr ) { + char *ptr = basePtr; + bool localError = false; + while ( *ptr && ( localError == false )) { + int option = *ptr++; + bool setting = true; + if (( *ptr == '+' ) || ( *ptr == '-' )) { + setting = ( *ptr++ == '-' ) ? false : true; + } + switch ( option ) { + case 'B' : localError = parseBLOCKMAPArgs ( ptr, setting ); break; + case 'N' : localError = parseNODESArgs ( ptr, setting ); break; + case 'R' : localError = parseREJECTArgs ( ptr, setting ); break; + case 'T' : config.WriteWAD = ! setting; break; + default : localError = true; + } + } + if ( localError ) { + errors = true; + int offset = basePtr - lineBuffer - 1; + size_t width = strlen ( basePtr ) + 1; + fprintf ( stderr, "Unrecognized configuration option '%*.*s'\n", width, width, lineBuffer + offset ); + } + } + ch = ( char ) fgetc ( configFile ); + } + fclose ( configFile ); + if ( errors ) fprintf ( stderr, "\n" ); +} + +int getLevels ( int argIndex, const char *argv [], char names [][MAX_LUMP_NAME], wadList *list ) +{ + FUNCTION_ENTRY ( NULL, "getLevels", true ); + + int index = 0, errors = 0; + + char buffer [128]; + buffer [0] = '\0'; + if ( argv [argIndex] ) { + strcpy ( buffer, argv [argIndex] ); + strupr ( buffer ); + } + char *ptr = strtok ( buffer, "+" ); + + // See if the user requested specific levels + if ( WAD::IsMap ( ptr )) { + argIndex++; + while ( ptr ) { + if ( WAD::IsMap ( ptr )) { + if ( list->FindWAD ( ptr )) { + strcpy ( names [index++], ptr ); + } else { + fprintf ( stderr, " Could not find %s\n", ptr, errors++ ); + } + } else { + fprintf ( stderr, " %s is not a valid name for a level\n", ptr, errors++ ); + } + ptr = strtok ( NULL, "+" ); + } + } else { + int size = list->DirSize (); + const wadListDirEntry *dir = list->GetDir ( 0 ); + for ( int i = 0; i < size; i++ ) { + if ( dir->wad->IsMap ( dir->entry->name )) { + // Make sure it's really a level + if ( strcmp ( dir[1].entry->name, "THINGS" ) == 0 ) { + if ( index == MAX_LEVELS ) { + fprintf ( stderr, "ERROR: Too many levels in WAD - ignoring %s!\n", dir->entry->name, errors++ ); + } else { + memcpy ( names [index++], dir->entry->name, MAX_LUMP_NAME ); + } + } + } + dir++; + } + } + memset ( names [index], 0, MAX_LUMP_NAME ); + + if ( errors ) fprintf ( stderr, "\n" ); + + return argIndex; +} + +bool ReadOptionsRMB ( const char *wadName, sOptionsRMB *options ) +{ + FUNCTION_ENTRY ( NULL, "ReadOptionsRMB", true ); + + char fileName [ 256 ]; + strcpy ( fileName, wadName ); + char *ptr = &fileName [ strlen ( fileName )]; + while ( *--ptr != '.' ); + *++ptr = '\0'; + strcat ( ptr, "rej" ); + while (( ptr > fileName ) && ( *ptr != SEPERATOR )) ptr--; + if (( ptr < fileName ) || ( *ptr == SEPERATOR )) ptr++; + FILE *optionFile = fopen ( ptr, "rt" ); + if ( optionFile == NULL ) { + optionFile = fopen ( fileName, "rt" ); + if ( optionFile == NULL ) return false; + } + + memset ( options, 0, sizeof ( sOptionsRMB )); + + options->wadName = strdup ( wadName ); + + fprintf ( stdout, "Parsing RMB option file %s", fileName ); + + int line = 0, index = 0; + char buffer [512]; + + while ( fgets ( buffer, sizeof ( buffer ) - 1, optionFile ) != NULL ) { + if ( index >= MAX_OPTIONS ) { + fprintf ( stderr, " - Too many RMB options\n" ); + break; + } + sRejectOptionRMB tempOption; + if ( ParseOptionRMB ( ++line, buffer, &tempOption ) == true ) { + options->option [index] = new sRejectOptionRMB; + *options->option [index] = tempOption; + index++; + } + } + + fclose ( optionFile ); + + if ( index != 0 ) { + fprintf ( stdout, " - %d valid RMB options detected\n", index ); + return true; + } + + return false; +} + +void EnsureExtension ( char *fileName, const char *ext ) +{ + FUNCTION_ENTRY ( NULL, "EnsureExtension", true ); + + // See if the file exists first + FILE *file = fopen ( fileName, "rb" ); + if ( file != NULL ) { + fclose ( file ); + return; + } + + size_t length = strlen ( fileName ); + if ( stricmp ( &fileName [length-4], ext ) != 0 ) { + strcat ( fileName, ext ); + } +} + +const char *TypeName ( eWadType type ) +{ + FUNCTION_ENTRY ( NULL, "TypeName", true ); + + const char *name = NULL; + switch ( type ) { + case wt_DOOM : name = "DOOM"; break; + case wt_DOOM2 : name = "DOOM2"; break; + case wt_HERETIC : name = "Heretic"; break; + case wt_HEXEN : name = "Hexen"; break; + default : name = ""; break; + } + return name; +} + +wadList *getInputFiles ( const char *cmdLine, char *wadFileName ) +{ + FUNCTION_ENTRY ( NULL, "getInputFiles", true ); + + char *listNames = wadFileName; + wadList *myList = new wadList; + + if ( cmdLine == NULL ) return myList; + + char temp [ 256 ]; + strcpy ( temp, cmdLine ); + char *ptr = strtok ( temp, "+" ); + + int errors = 0; + int index = 0; + + while ( ptr && *ptr ) { + char wadName [ 256 ]; + strcpy ( wadName, ptr ); + EnsureExtension ( wadName, ".wad" ); + + WAD *wad = new WAD ( wadName ); + if ( wad->Status () != ws_OK ) { + const char *msg; + switch ( wad->Status ()) { + case ws_INVALID_FILE : msg = "The file %s does not exist\n"; break; + case ws_CANT_READ : msg = "Can't open the file %s for read access\n"; break; + case ws_INVALID_WAD : msg = "%s is not a valid WAD file\n"; break; + default : msg = "** Unexpected Error opening %s **\n"; break; + } + fprintf ( stderr, msg, wadName ); + delete wad; + } else { + if ( ! myList->IsEmpty ()) { + cprintf ( "Merging: %s with %s\r\n", wadName, listNames ); + *wadFileName++ = '+'; + } + if ( myList->Add ( wad ) == false ) { + errors++; + if ( myList->Type () != wt_UNKNOWN ) { + fprintf ( stderr, "ERROR: %s is not a %s PWAD.\n", wadName, TypeName ( myList->Type ())); + } else { + fprintf ( stderr, "ERROR: %s is not the same type.\n", wadName ); + } + delete wad; + } else { + if (( config.Reject.UseRMB == true ) && ( index < MAX_WADS )) { + if ( ReadOptionsRMB ( wadName, &rmbOptionTable [index] ) == true ) index++; + } else if ( index == MAX_WADS ) { + fprintf ( stderr, "WARNING: Too many wads specified - RMB options ignored" ); + index++; + } + char *end = wadName + strlen ( wadName ) - 1; + while (( end > wadName ) && ( *end != SEPERATOR )) end--; + if ( *end == SEPERATOR ) end++; + wadFileName += sprintf ( wadFileName, "%s", end ); + } + } + ptr = strtok ( NULL, "+" ); + } + + if ( wadFileName [-1] == '+' ) wadFileName [-1] = '\0'; + if ( myList->wadCount () > 1 ) cprintf ( "\r\n" ); + if ( errors ) fprintf ( stderr, "\n" ); + if ( index != 0 ) fprintf ( stdout, "\n" ); + + return myList; +} + +void ReadSection ( FILE *file, int max, bool *array ) +{ + FUNCTION_ENTRY ( NULL, "ReadSection", true ); + + char ch = ( char ) fgetc ( file ); + while (( ch != '[' ) && ! feof ( file )) { + ungetc ( ch, file ); + char lineBuffer [ 256 ]; + fgets ( lineBuffer, sizeof ( lineBuffer ), file ); + strtok ( lineBuffer, "\n\x1A" ); + char *ptr = lineBuffer; + while ( *ptr == ' ' ) ptr++; + + bool value = true; + if ( *ptr == '!' ) { + value = false; + ptr++; + } + ptr = strtok ( ptr, "," ); + while ( ptr ) { + int low = -1, high = 0, count = 0; + if ( stricmp ( ptr, "all" ) == 0 ) { + memset ( array, value, sizeof ( bool ) * max ); + } else { + count = sscanf ( ptr, "%d-%d", &low, &high ); + } + ptr = strtok ( NULL, "," ); + if (( low < 0 ) || ( low >= max )) continue; + switch ( count ) { + case 1 : array [low] = value; + break; + case 2 : if ( high >= max ) high = max - 1; + for ( int i = low; i <= high; i++ ) { + array [i] = value; + } + break; + } + if ( count == 0 ) break; + } + ch = ( char ) fgetc ( file ); + } + ungetc ( ch, file ); +} + +void ReadCustomFile ( DoomLevel *curLevel, wadList *myList, sBSPOptions *options ) +{ + FUNCTION_ENTRY ( NULL, "ReadCustomFile", true ); + + char fileName [ 256 ]; + const wadListDirEntry *dir = myList->FindWAD ( curLevel->Name (), NULL, NULL ); + strcpy ( fileName, dir->wad->Name ()); + char *ptr = &fileName [ strlen ( fileName )]; + while ( *--ptr != '.' ); + *++ptr = '\0'; + strcat ( ptr, "zen" ); + while (( ptr > fileName ) && ( *ptr != SEPERATOR )) ptr--; + if (( ptr < fileName ) || ( *ptr == SEPERATOR )) ptr++; + FILE *optionFile = fopen ( ptr, "rt" ); + if ( optionFile == NULL ) { + optionFile = fopen ( fileName, "rt" ); + if ( optionFile == NULL ) return; + } + + char ch = ( char ) fgetc ( optionFile ); + bool foundMap = false; + do { + while ( ! feof ( optionFile ) && ( ch != '[' )) ch = ( char ) fgetc ( optionFile ); + char lineBuffer [ 256 ]; + fgets ( lineBuffer, sizeof ( lineBuffer ), optionFile ); + strtok ( lineBuffer, "\n\x1A]" ); + if ( WAD::IsMap ( lineBuffer )) { + if ( strcmp ( lineBuffer, curLevel->Name ()) == 0 ) { + foundMap = true; + } else if ( foundMap ) { + break; + } + } + if ( ! foundMap ) { + ch = ( char ) fgetc ( optionFile ); + continue; + } + + int maxIndex = 0; + bool isSectorSplit = false; + bool *array = NULL; + if ( stricmp ( lineBuffer, "ignore-linedefs" ) == 0 ) { + maxIndex = curLevel->LineDefCount (); + if ( options->ignoreLineDef == NULL ) { + options->ignoreLineDef = new bool [ maxIndex ]; + memset ( options->ignoreLineDef, false, sizeof ( bool ) * maxIndex ); + } + array = options->ignoreLineDef; + } else if ( stricmp ( lineBuffer, "dont-split-linedefs" ) == 0 ) { + maxIndex = curLevel->LineDefCount (); + if ( options->dontSplit == NULL ) { + options->dontSplit = new bool [ maxIndex ]; + memset ( options->dontSplit, false, sizeof ( bool ) * maxIndex ); + } + array = options->dontSplit; + } else if ( stricmp ( lineBuffer, "dont-split-sectors" ) == 0 ) { + isSectorSplit = true; + maxIndex = curLevel->LineDefCount (); + if ( options->dontSplit == NULL ) { + options->dontSplit = new bool [ maxIndex ]; + memset ( options->dontSplit, false, sizeof ( bool ) * maxIndex ); + } + maxIndex = curLevel->SectorCount (); + array = new bool [ maxIndex ]; + memset ( array, false, sizeof ( bool ) * maxIndex ); + } else if ( stricmp ( lineBuffer, "unique-sectors" ) == 0 ) { + maxIndex = curLevel->SectorCount (); + if ( options->keepUnique == NULL ) { + options->keepUnique = new bool [ maxIndex ]; + memset ( options->keepUnique, false, sizeof ( bool ) * maxIndex ); + } + array = options->keepUnique; + } + if ( array != NULL ) { + ReadSection ( optionFile, maxIndex, array ); + if ( isSectorSplit == true ) { + const wLineDef *lineDef = curLevel->GetLineDefs (); + const wSideDef *sideDef = curLevel->GetSideDefs (); + for ( int side, i = 0; i < curLevel->LineDefCount (); i++, lineDef++ ) { + side = lineDef->sideDef [0]; + if (( side != NO_SIDEDEF ) && ( array [ sideDef [ side ].sector ])) { + options->dontSplit [i] = true; + } + side = lineDef->sideDef [1]; + if (( side != NO_SIDEDEF ) && ( array [ sideDef [ side ].sector ])) { + options->dontSplit [i] = true; + } + } + delete [] array; + } + } + + ch = ( char ) fgetc ( optionFile ); + + } while ( ! feof ( optionFile )); + + fclose ( optionFile ); +} + +int CheckREJECT ( DoomLevel *curLevel ) +{ + FUNCTION_ENTRY ( NULL, "CheckREJECT", true ); + + static bool initialized = false; + if ( ! initialized ) { + initialized = true; + for ( int i = 0; i < 256; i++ ) { + int val = i, count = 0; + for ( int j = 0; j < 8; j++ ) { + if ( val & 1 ) count++; + val >>= 1; + } + HammingTable [i] = ( char ) count; + } + } + + int size = curLevel->RejectSize (); + int noSectors = curLevel->SectorCount (); + int mask = ( 0xFF00 >> ( size * 8 - noSectors * noSectors )) & 0xFF; + int count = 0; + if ( curLevel->GetReject () != 0 ) { + UINT8 *ptr = ( UINT8 * ) curLevel->GetReject (); + while ( size-- ) count += HammingTable [ *ptr++ ]; + count -= HammingTable [ ptr [-1] & mask ]; + } + + return ( int ) ( 1000.0 * count / ( noSectors * noSectors ) + 0.5 ); +} + +void PrintTime ( UINT32 time ) +{ + FUNCTION_ENTRY ( NULL, "PrintTime", false ); + + GotoXY ( 65, startY ); + cprintf ( "%3ld.%03ld sec%s", time / 1000, time % 1000, ( time == 1000 ) ? "" : "s" ); +} + +bool ProcessLevel ( char *name, wadList *myList, UINT32 *ellapsed ) +{ + FUNCTION_ENTRY ( NULL, "ProcessLevel", true ); + + UINT32 dummyX = 0; + + *ellapsed = 0; + + cprintf ( "\r %-*.*s: ", MAX_LUMP_NAME, MAX_LUMP_NAME, name ); + GetXY ( &startX, &startY ); + + const wadListDirEntry *dir = myList->FindWAD ( name ); + DoomLevel *curLevel = new DoomLevel ( name, dir->wad ); + if ( curLevel->IsValid ( ! config.Nodes.Rebuild ) == false ) { + cprintf ( "This level is not valid... " ); + cprintf ( "\r\n" ); + delete curLevel; + return false; + } + + int rows = 0; + + if ( config.BlockMap.Rebuild ) { + + rows++; + + int oldSize = curLevel->BlockMapSize (); + UINT32 blockTime = CurrentTime (); + int saved = CreateBLOCKMAP ( curLevel, config.BlockMap ); + *ellapsed += blockTime = CurrentTime () - blockTime; + int newSize = curLevel->BlockMapSize (); + + Status ( "" ); + GotoXY ( startX, startY ); + + if ( saved >= 0 ) { + cprintf ( "BLOCKMAP - %5d/%-5d ", newSize, oldSize ); + if ( oldSize ) cprintf ( "(%3d%%)", ( int ) ( 100.0 * newSize / oldSize + 0.5 )); + else cprintf ( "(****)" ); + cprintf ( " Compressed: " ); + if ( newSize + saved ) cprintf ( "%3d%%", ( int ) ( 100.0 * newSize / ( newSize + saved ) + 0.5 )); + else cprintf ( "(****)" ); + } else { + cprintf ( "BLOCKMAP - * Level too big to create valid BLOCKMAP *" ); + } + + PrintTime ( blockTime ); + cprintf ( "\r\n" ); + GetXY ( &dummyX, &startY ); + } + + if ( config.Nodes.Rebuild ) { + + rows++; + + int oldNodeCount = curLevel->NodeCount (); + int oldSegCount = curLevel->SegCount (); + + bool *keep = new bool [ curLevel->SectorCount ()]; + memset ( keep, config.Nodes.Unique, sizeof ( bool ) * curLevel->SectorCount ()); + + sBSPOptions options; + options.algorithm = config.Nodes.Method; + options.showProgress = ! config.Nodes.Quiet; + options.reduceLineDefs = config.Nodes.ReduceLineDefs; + options.ignoreLineDef = NULL; + options.dontSplit = NULL; + options.keepUnique = keep; + + ReadCustomFile ( curLevel, myList, &options ); + + UINT32 nodeTime = CurrentTime (); + CreateNODES ( curLevel, &options ); + *ellapsed += nodeTime = CurrentTime () - nodeTime; + + if ( options.ignoreLineDef ) delete [] options.ignoreLineDef; + if ( options.dontSplit ) delete [] options.dontSplit; + if ( options.keepUnique ) delete [] options.keepUnique; + + Status ( "" ); + GotoXY ( startX, startY ); + + cprintf ( "NODES - %4d/%-4d ", curLevel->NodeCount (), oldNodeCount ); + if ( oldNodeCount ) cprintf ( "(%3d%%)", ( int ) ( 100.0 * curLevel->NodeCount () / oldNodeCount + 0.5 )); + else cprintf ( "(****)" ); + cprintf ( " " ); + cprintf ( "SEGS - %5d/%-5d ", curLevel->SegCount (), oldSegCount ); + if ( oldSegCount ) cprintf ( "(%3d%%)", ( int ) ( 100.0 * curLevel->SegCount () / oldSegCount + 0.5 )); + else cprintf ( "(****)" ); + + PrintTime ( nodeTime ); + cprintf ( "\r\n" ); + GetXY ( &dummyX, &startY ); + } + + if ( config.Reject.Rebuild ) { + + rows++; + + int oldEfficiency = CheckREJECT ( curLevel ); + + UINT32 rejectTime = CurrentTime (); + bool special = CreateREJECT ( curLevel, config.Reject ); + *ellapsed += rejectTime = CurrentTime () - rejectTime; + + int newEfficiency = CheckREJECT ( curLevel ); + + if ( special == false ) { + Status ( "" ); + GotoXY ( startX, startY ); + cprintf ( "REJECT - Efficiency: %3ld.%1ld%%/%2ld.%1ld%% Sectors: %5d", newEfficiency / 10, newEfficiency % 10, + oldEfficiency / 10, oldEfficiency % 10, curLevel->SectorCount ()); + PrintTime ( rejectTime ); + } else { + cprintf ( "REJECT - Special effects detected - use -rf to force an update" ); + } + + cprintf ( "\r\n" ); + GetXY ( &dummyX, &startY ); + } + + bool changed = false; + if ( rows != 0 ) { + Status ( "Updating Level ... " ); + changed = curLevel->UpdateWAD (); + Status ( "" ); + if ( changed ) { + MoveUp ( rows ); + cprintf ( "\r *" ); + MoveDown ( rows ); + } + } else { + cprintf ( "Nothing to do here ... " ); + cprintf ( "\r\n" ); + } + + int noSectors = curLevel->SectorCount (); + int rejectSize = (( noSectors * noSectors ) + 7 ) / 8; + if (( curLevel->RejectSize () != rejectSize ) && ( config.Reject.Rebuild == false )) { + fprintf ( stderr, "WARNING: The REJECT structure for %s is the wrong size - try using -r\n", name ); + } + + delete curLevel; + + return changed; +} + +void PrintStats ( int totalLevels, UINT32 totalTime, int totalUpdates ) +{ + FUNCTION_ENTRY ( NULL, "PrintStats", true ); + + if ( totalLevels != 0 ) { + + cprintf ( "%d Level%s processed in ", totalLevels, totalLevels > 1 ? "s" : "" ); + if ( totalTime > 60000 ) { + UINT32 minutes = totalTime / 60000; + UINT32 tempTime = totalTime - minutes * 60000; + cprintf ( "%ld minute%s %ld.%03ld second%s - ", minutes, minutes > 1 ? "s" : "", tempTime / 1000, tempTime % 1000, ( tempTime == 1000 ) ? "" : "s" ); + } else { + cprintf ( "%ld.%03ld second%s - ", totalTime / 1000, totalTime % 1000, ( totalTime == 1000 ) ? "" : "s" ); + } + + if ( totalUpdates ) { + cprintf ( "%d Level%s need%s updating.\r\n", totalUpdates, totalUpdates > 1 ? "s" : "", config.WriteWAD ? "ed" : "" ); + } else { + cprintf ( "No Levels need%s updating.\r\n", config.WriteWAD ? "ed" : "" ); + } + + if ( totalTime == 0 ) { + cprintf ( "WOW! Whole bunches of levels/sec!\r\n" ); + } else if ( totalTime < 1000 ) { + cprintf ( "%f levels/sec\r\n", 1000.0 * totalLevels / totalTime ); + } else if ( totalLevels > 1 ) { + cprintf ( "%f secs/level\r\n", totalTime / ( totalLevels * 1000.0 )); + } + } +} + +int getOutputFile ( int index, const char *argv [], char *wadFileName ) +{ + FUNCTION_ENTRY ( NULL, "getOutputFile", true ); + + strtok ( wadFileName, "+" ); + + const char *ptr = argv [ index ]; + if ( ptr && ( *ptr == '-' )) { + char ch = ( char ) toupper ( *++ptr ); + if (( ch == 'O' ) || ( ch == 'X' )) { + index++; + if ( *++ptr ) { + if ( *ptr++ != ':' ) { + fprintf ( stderr, "\nUnrecognized argument '%s'\n", argv [ index ] ); + config.Extract = false; + return index + 1; + } + } else { + ptr = argv [ index++ ]; + } + if ( ptr ) strcpy ( wadFileName, ptr ); + if ( ch == 'X' ) config.Extract = true; + } + } + + EnsureExtension ( wadFileName, ".wad" ); + + return index; +} + +char *ConvertNumber ( UINT32 value ) +{ + FUNCTION_ENTRY ( NULL, "ConvertNumber", true ); + + static char buffer [ 25 ]; + char *ptr = &buffer [ 20 ]; + + while ( value ) { + if ( value < 1000 ) sprintf ( ptr, "%4d", value ); + else sprintf ( ptr, ",%03d", value % 1000 ); + if ( ptr < &buffer [ 20 ] ) ptr [4] = ','; + value /= 1000; + if ( value ) ptr -= 4; + } + while ( *ptr == ' ' ) ptr++; + return ptr; +} + +int main ( int argc, const char *argv [] ) +{ + FUNCTION_ENTRY ( NULL, "main", true ); + + SaveConsoleSettings (); + HideCursor (); + + cprintf ( "%s\r\n\r\n", BANNER ); + if ( ! isatty ( fileno ( stdout ))) fprintf ( stdout, "%s\n\n", BANNER ); + if ( ! isatty ( fileno ( stderr ))) fprintf ( stderr, "%s\n\n", BANNER ); + + config.BlockMap.Rebuild = true; + config.BlockMap.Compress = true; + + config.Nodes.Rebuild = true; + config.Nodes.Method = 1; + config.Nodes.Quiet = isatty ( fileno ( stdout )) ? false : true; + config.Nodes.Unique = false; + config.Nodes.ReduceLineDefs = false; + + config.Reject.Rebuild = true; + config.Reject.Empty = false; + config.Reject.Force = false; + config.Reject.UseGraphs = true; + config.Reject.UseRMB = false; + + config.WriteWAD = true; + + if ( argc == 1 ) { + printHelp (); + return -1; + } + + ReadConfigFile ( argv ); + + int argIndex = 1; + int totalLevels = 0, totalTime = 0, totalUpdates = 0; + + while ( KeyPressed ()) GetKey (); + + do { + + config.Extract = false; + argIndex = parseArgs ( argIndex, argv ); + if ( argIndex >= argc ) break; + + char wadFileName [ 256 ]; + wadList *myList = getInputFiles ( argv [argIndex++], wadFileName ); + if ( myList->IsEmpty () == false ) { + + cprintf ( "Working on: %s\r\n\n", wadFileName ); + + TRACE ( "Processing " << wadFileName ); + + char levelNames [MAX_LEVELS+1][MAX_LUMP_NAME]; + argIndex = getLevels ( argIndex, argv, levelNames, myList ); + + if ( levelNames [0][0] == '\0' ) { + fprintf ( stderr, "Unable to find any valid levels in %s\n", wadFileName ); + break; + } + + int noLevels = 0; + // Trick the code into writing an output file if two or more wads are being merged + int updateCount = myList->wadCount () - 1; + + do { + + UINT32 ellapsedTime; + if ( ProcessLevel ( levelNames [noLevels++], myList, &ellapsedTime )) updateCount++; + totalTime += ellapsedTime; + if ( KeyPressed () && ( GetKey () == 0x1B )) break; + + } while ( levelNames [noLevels][0] ); + + config.Extract = false; + argIndex = getOutputFile ( argIndex, argv, wadFileName ); + + if ( updateCount || config.Extract ) { + if ( config.WriteWAD ) { + cprintf ( "\r\n%s to %s...", config.Extract ? "Extracting" : "Saving", wadFileName ); + if ( config.Extract ) { + if ( myList->Extract ( levelNames, wadFileName ) == false ) { + fprintf ( stderr," Error writing to file!\n" ); + } + } else { + if ( myList->Save ( wadFileName ) == false ) { + fprintf ( stderr," Error writing to file!\n" ); + } + } + cprintf ( "\r\n" ); + } else { + cprintf ( "\r\nChanges would have been written to %s ( %s bytes )\n", wadFileName, ConvertNumber ( myList->FileSize ())); + } + } + cprintf ( "\r\n" ); + + // Undo the bogus update level count + updateCount -= myList->wadCount () - 1; + + totalLevels += noLevels; + totalUpdates += updateCount; + } + + delete myList; + + } while ( argv [argIndex] ); + + PrintStats ( totalLevels, totalTime, totalUpdates ); + RestoreConsoleSettings (); + + return 0; +} diff --git a/ZenNode/ZenNode.cpp b/ZenNode/ZenNode.cpp new file mode 100644 index 0000000..58d199d --- /dev/null +++ b/ZenNode/ZenNode.cpp @@ -0,0 +1,1890 @@ +//---------------------------------------------------------------------------- +// +// File: ZenNode.cpp +// Date: 26-Oct-1994 +// Programmer: Marc Rousseau +// +// Description: This module contains the logic for the NODES builder. +// +// Copyright (c) 1994-2004 Marc Rousseau, All Rights Reserved. +// +// 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 2 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, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. +// +// Revision History: +// +// 06-??-95 Added LineDef alias list to speed up the process. +// 07-07-95 Added currentAlias/Side/Flipped to speed up WhichSide. +// 07-11-95 Initialized global variables in CreateNODES. +// Changed logic for static variable last in CreateSSector. +// 10-05-95 Added convexList & extended the use of lineUsed. +// 10-25-95 Changed from doubly linked lists to an array of SEGs. +// 10-27-95 Added header to each function describing what it does. +// 11-14-95 Fixed sideInfo so that a SEG is always to it's own right. +// 12-06-95 Added code to support selective unique sectors & don't splits +// 05-09-96 Added nodePool to reduced calls to new/delete for NODEs +// 05-15-96 Reduced memory requirements for convexList & sectorInfo +// 05-23-96 Added FACTOR_XXX to reduced memory requirements +// 05-24-96 Removed all calls to new/delete during CreateNode +// 05-31-96 Made WhichSide inline & moved the bulk of code to _WhichSide +// 10-01-96 Reordered functions & removed all function prototypes +// 07-31-00 Increased max subsector factor from 15 to 256 +// 10-29-00 Fixed _WhichSide & DivideSeg so that things finally(?) work! +// 02-21-01 Fixed _WhichSide & DivideSeg so that things finally(?) work! +// 02-22-01 Added vertSplitAlias to help _WhichSide's accuracy problem +// 02-26-01 Removed vertSplitAlias - switched to 64-bit ints to solve overflow problem +// 03-05-01 Fixed _WhichSide & DivideSeg so that things finally(?) work! +// 03-05-01 Switched to 64-bit ints to solve overflow problem +// 04-13-02 Changed vertex & ssector allocation to reallocate the free pool +// 04-14-02 Fixed _WhichSide so that things finally(?) work! :-) +// 04-16-02 Added more consistancy checks in DEBUG mode +// 04-20-02 Switched to floating point math +// 04-24-02 Fixed bug in _WhichSide for zero-length SEGs +// 05-02-02 Simplified CreateNode & eliminated NODES structure +// 05-04-02 Simplified use of aliases and flipping in WhichSide +// 05-05-02 Fixed double-int conversions to round correctly +// 11-16-03 Converted code to work properly on 64 bit machines +// 01-31-04 Added extra sector sort to SortByLineDef +// +//---------------------------------------------------------------------------- + +#include +#include +#include +#include +#include +#include +#include +#include "common.hpp" +#include "logger.hpp" + +#include "level.hpp" +#include "ZenNode.hpp" +#include "console.hpp" + +DBG_REGISTER ( __FILE__ ); + +#define ANGLE_MASK 0x7FFF + +#define EPSILON 0.0001 + +// Emperical values derived from a test of numerous .WAD files +#define FACTOR_VERTEX 1.0 // 1.662791 - ??? +#define FACTOR_SEGS 2.0 // 1.488095 - ??? +#define FACTOR_NODE 0.6 // 0.590830 - MAP01 - csweeper.wad + +static int maxSegs; +static int maxVertices; + +static int nodesLeft; +static wNode *nodePool; +static int nodeCount; // Number of NODES stored + +static SEG *tempSeg; +static SEG *segStart; +static int segCount; // Number of SEGS stored + +static int ssectorsLeft; +static wSSector *ssectorPool; +static int ssectorCount; // Number of SSECTORS stored + +static wVertex *newVertices; +static int noVertices; + +// +// Variables used by WhichSide to speed up side calculations +// +static char *currentSide; +static int currentAlias; + +static int *convexList; +static int *convexPtr; +static int sectorCount; + +static int showProgress; +static UINT8 *usedSector; +static bool *keepUnique; +static bool uniqueSubsectors; +static char *lineUsed; +static char *lineChecked; +static int noAliases; +static int *lineDefAlias; +static char **sideInfo; +static double DY, DX, X, Y, H; +static long ANGLE; + +static sScoreInfo *score; + +// metric = S ? ( L * R ) / ( X1 ? X1 * S / X2 : 1 ) - ( X3 * S + X4 ) * S : ( L * R ); +static long X1 = getenv ( "ZEN_X1" ) ? atol ( getenv ( "ZEN_X1" )) : 20; +static long X2 = getenv ( "ZEN_X2" ) ? atol ( getenv ( "ZEN_X2" )) : 10; +static long X3 = getenv ( "ZEN_X3" ) ? atol ( getenv ( "ZEN_X3" )) : 1; +static long X4 = getenv ( "ZEN_X4" ) ? atol ( getenv ( "ZEN_X4" )) : 25; + +static long Y1 = getenv ( "ZEN_Y1" ) ? atol ( getenv ( "ZEN_Y1" )) : 1; +static long Y2 = getenv ( "ZEN_Y2" ) ? atol ( getenv ( "ZEN_Y2" )) : 7; +static long Y3 = getenv ( "ZEN_Y3" ) ? atol ( getenv ( "ZEN_Y3" )) : 1; +static long Y4 = getenv ( "ZEN_Y4" ) ? atol ( getenv ( "ZEN_Y4" )) : 0; + +static SEG *(*PartitionFunction) ( SEG *, int ); + +//---------------------------------------------------------------------------- +// Create a list of SEGs from the *important* sidedefs. A sidedef is +// considered important if: +// - It has non-zero length +// - It's linedef has different sectors on each side +// - It has at least one visible texture +//---------------------------------------------------------------------------- + +static SEG *CreateSegs ( DoomLevel *level, sBSPOptions *options ) +{ + FUNCTION_ENTRY ( NULL, "CreateSegs", true ); + + // Get a rough count of how many SideDefs we're starting with + segCount = maxSegs = 0; + const wLineDef *lineDef = level->GetLineDefs (); + const wSideDef *sideDef = level->GetSideDefs (); + + for ( int i = 0; i < level->LineDefCount (); i++ ) { + if ( lineDef [i].sideDef [0] != NO_SIDEDEF ) maxSegs++; + if ( lineDef [i].sideDef [1] != NO_SIDEDEF ) maxSegs++; + } + tempSeg = new SEG [ maxSegs ]; + maxSegs = ( int ) ( maxSegs * FACTOR_SEGS ); + segStart = new SEG [ maxSegs ]; + memset ( segStart, 0, sizeof ( SEG ) * maxSegs ); + + SEG *seg = segStart; + for ( int i = 0; i < level->LineDefCount (); i++, lineDef++ ) { + + wVertex *vertS = &newVertices [ lineDef->start ]; + wVertex *vertE = &newVertices [ lineDef->end ]; + long dx = vertE->x - vertS->x; + long dy = vertE->y - vertS->y; + + if (( dx == 0 ) && ( dy == 0 )) continue; + + int rSide = lineDef->sideDef [0]; + int lSide = lineDef->sideDef [1]; + const wSideDef *sideRight = ( rSide == NO_SIDEDEF ) ? ( const wSideDef * ) NULL : &sideDef [ rSide ]; + const wSideDef *sideLeft = ( lSide == NO_SIDEDEF ) ? ( const wSideDef * ) NULL : &sideDef [ lSide ]; + + // Ignore line if both sides point to the same sector & neither side has any visible texture + if ( options->reduceLineDefs && sideRight && sideLeft && ( sideRight->sector == sideLeft->sector )) { + if ( * ( UINT16 * ) sideLeft->text3 == ( UINT16 ) EMPTY_TEXTURE ) { + sideLeft = ( const wSideDef * ) NULL; + } + if ( * ( UINT16 * ) sideRight->text3 == ( UINT16 ) EMPTY_TEXTURE ) { + sideRight = ( const wSideDef * ) NULL; + } + if ( ! sideLeft && ! sideRight ) continue; + } + + if ( options->ignoreLineDef && options->ignoreLineDef [i] ) continue; + + BAM angle = ( dy == 0 ) ? ( BAM ) (( dx < 0 ) ? BAM180 : 0 ) : + ( dx == 0 ) ? ( BAM ) (( dy < 0 ) ? BAM270 : BAM90 ) : + ( BAM ) ( atan2 (( float ) dy, ( float ) dx ) * BAM180 / M_PI + 0.5 * sgn ( dy )); + + bool split = options->dontSplit ? options->dontSplit [i] : false; + + if ( sideRight ) { + seg->Data.start = lineDef->start; + seg->Data.end = lineDef->end; + seg->Data.angle = angle; + seg->Data.lineDef = ( UINT16 ) i; + seg->Data.flip = 0; + seg->LineDef = lineDef; + seg->Sector = sideRight->sector; + seg->DontSplit = split; + seg->start.x = vertS->x; + seg->start.y = vertS->y; + seg->start.l = 0.0; + seg->end.x = vertE->x; + seg->end.y = vertE->y; + seg->end.l = 1.0; + seg++; + } + + if ( sideLeft ) { + seg->Data.start = lineDef->end; + seg->Data.end = lineDef->start; + seg->Data.angle = ( BAM ) ( angle + BAM180 ); + seg->Data.lineDef = ( UINT16 ) i; + seg->Data.flip = 1; + seg->LineDef = lineDef; + seg->Sector = sideLeft->sector; + seg->DontSplit = split; + seg->start.x = vertE->x; + seg->start.y = vertE->y; + seg->start.l = 0.0; + seg->end.x = vertS->x; + seg->end.y = vertS->y; + seg->end.l = 1.0; + seg++; + } + } + + segCount = seg - segStart; + + return segStart; +} + +//---------------------------------------------------------------------------- +// Calculate the set of variables used frequently that are based on the +// currently selected SEG to be used as a partition line. +//---------------------------------------------------------------------------- + +static void ComputeStaticVariables ( SEG *pSeg ) +{ + FUNCTION_ENTRY ( NULL, "ComputeStaticVariables", true ); + + if ( pSeg->final == false ) { + + currentAlias = lineDefAlias [ pSeg->Data.lineDef ]; + currentSide = sideInfo ? sideInfo [ currentAlias ] : NULL; + + wVertex *vertS = &newVertices [ pSeg->AliasFlip ? pSeg->Data.end : pSeg->Data.start ]; + wVertex *vertE = &newVertices [ pSeg->AliasFlip ? pSeg->Data.start : pSeg->Data.end ]; + X = vertS->x; + Y = vertS->y; + DX = vertE->x - vertS->x; + DY = vertE->y - vertS->y; + + } else { + + currentAlias = 0; + + X = pSeg->start.x; + Y = pSeg->start.y; + DX = pSeg->end.x - pSeg->start.x; + DY = pSeg->end.y - pSeg->start.y; + + } + + H = ( DX * DX ) + ( DY * DY ); + +#if defined ( DIAGNOSTIC ) + if (((int) DX == 0 ) && ((int) DY == 0 )) fprintf ( stderr, "DX & DY are both 0!\n" ); +#endif + + ANGLE = pSeg->Data.angle; +} + +//---------------------------------------------------------------------------- +// Determine if the given SEG is co-linear (ie: they lie on the same line) +// with the currently selected partition. +//---------------------------------------------------------------------------- + +static bool CoLinear ( SEG *seg ) +{ + FUNCTION_ENTRY ( NULL, "CoLinear", true ); + + // If they're not at the same angle ( ñ180ø ), bag it + if (( ANGLE & ANGLE_MASK ) != ( seg->Data.angle & ANGLE_MASK )) return false; + + // Do the math stuff + if ( DX == 0.0 ) return ( seg->start.x == X ) ? true : false; + if ( DY == 0.0 ) return ( seg->start.y == Y ) ? true : false; + + // Rotate vertS about (X,Y) by é degrees to get y offset + // Y = Hùsin(é) ³ 1 0 0 ³³ cos(é) -sin(é) 0 ³ + // X = Hùcos(é) ³x y 1³³ 0 1 0 ³³ sin(é) cos(é) 0 ³ + // H = (Xý+Yý)^« ³ -X -Y 1 ³³ 0 0 1 ³ + + return ( DX * ( seg->start.y - Y ) == DY * ( seg->start.x - X )) ? true : false; +} + +//---------------------------------------------------------------------------- +// Given a list of SEGs, determine the bounding rectangle. +//---------------------------------------------------------------------------- + +static void FindBounds ( wBound *bound, SEG *seg, int noSegs ) +{ + FUNCTION_ENTRY ( NULL, "FindBounds", true ); + + bound->minx = bound->maxx = ( INT16 ) lrint ( seg->start.x ); + bound->miny = bound->maxy = ( INT16 ) lrint ( seg->start.y ); + + for ( int i = 0; i < noSegs; i++ ) { + + int startX = lrint ( seg [i].start.x ); + int endX = lrint ( seg [i].end.x ); + int startY = lrint ( seg [i].start.y ); + int endY = lrint ( seg [i].end.y ); + + int loX = startX, hiX = startX; + if ( loX < endX ) hiX = endX; else loX = endX; + int loY = startY, hiY = startY; + if ( loY < endY ) hiY = endY; else loY = endY; + + if ( loX < bound->minx ) bound->minx = ( INT16 ) loX; + if ( hiX > bound->maxx ) bound->maxx = ( INT16 ) hiX; + if ( loY < bound->miny ) bound->miny = ( INT16 ) loY; + if ( hiY > bound->maxy ) bound->maxy = ( INT16 ) hiY; + } +} + +//---------------------------------------------------------------------------- +// Determine which side of the partition line the given SEG lies. A quick +// check is made based on the sector containing the SEG. If the sector +// is split by the partition, a more detailed examination is made using +// _WhichSide. +// +// Returns: +// -1 - SEG is on the left of the partition +// 0 - SEG is split by the partition +// +1 - SEG is on the right of the partition +//---------------------------------------------------------------------------- + +static int _WhichSide ( SEG *seg ) +{ + FUNCTION_ENTRY ( NULL, "_WhichSide", true ); + + sVertex *vertS = &seg->start; + sVertex *vertE = &seg->end; + double y1, y2; + + if ( DX == 0.0 ) { + if ( DY > 0.0 ) { + y1 = ( lrint ( X ) - lrint ( vertS->x )), y2 = ( lrint ( X ) - lrint ( vertE->x )); + } else { + y1 = ( lrint ( vertS->x ) - lrint ( X )), y2 = ( lrint ( vertE->x ) - lrint ( X )); + } + } else if ( DY == 0.0 ) { + if ( DX > 0.0 ) { + y1 = ( lrint ( vertS->y ) - lrint ( Y )), y2 = ( lrint ( vertE->y ) - lrint ( Y )); + } else { + y1 = ( lrint ( Y ) - lrint ( vertS->y )), y2 = ( lrint ( Y ) - lrint ( vertE->y )); + } + } else { + + y1 = DX * ( vertS->y - Y ) - DY * ( vertS->x - X ); + y2 = DX * ( vertE->y - Y ) - DY * ( vertE->x - X ); + + if (( y1 * y2 != 0.0 ) && (( fabs ( y1 ) <= H ) || ( fabs ( y2 ) <= H ))) { + + const wLineDef *lineDef = seg->LineDef; + wVertex *_vertS = &newVertices [ lineDef->start ]; + wVertex *_vertE = &newVertices [ lineDef->end ]; + + double dx = _vertE->x - _vertS->x; + double dy = _vertE->y - _vertS->y; + double det = dx * DY - dy * DX; + + if ( det != 0.0 ) { + + double num = DX * ( _vertS->y - Y ) - DY * ( _vertS->x - X ); + double l = num / det; + + if ( seg->Data.flip != 0 ) l = 1.0 - l; + + if ( l < vertS->l ) { y1 = 0.0; goto xx; } + if ( l > vertE->l ) { y2 = 0.0; goto xx; } + + long x = lrint ( _vertS->x + num * dx / det ); + long y = lrint ( _vertS->y + num * dy / det ); + + if (( lrint ( vertS->x ) == x ) && ( lrint ( vertS->y ) == y )) y1 = 0.0; + if (( lrint ( vertE->x ) == x ) && ( lrint ( vertE->y ) == y )) y2 = 0.0; + } + } + } + + if ( fabs ( y1 ) < EPSILON ) y1 = 0.0; + if ( fabs ( y2 ) < EPSILON ) y2 = 0.0; + +xx: + + // If its co-linear, decide based on direction + if (( y1 == 0.0 ) && ( y2 == 0.0 )) { + double x1 = DX * ( vertS->x - X ) + DY * ( vertS->y - Y ); + double x2 = DX * ( vertE->x - X ) + DY * ( vertE->y - Y ); + return ( x1 <= x2 ) ? SIDE_RIGHT : SIDE_LEFT; + } + + // Otherwise: + // Left -1 : ( y1 >= 0 ) && ( y2 >= 0 ) + // Both 0 : (( y1 < 0 ) && ( y2 > 0 )) || (( y1 > 0 ) && ( y2 < 0 )) + // Right 1 : ( y1 <= 0 ) && ( y2 <= 0 ) + + return ( y1 < 0.0 ) ? (( y2 <= 0.0 ) ? SIDE_RIGHT : SIDE_SPLIT ) : + ( y1 == 0.0 ) ? (( y2 <= 0.0 ) ? SIDE_RIGHT : SIDE_LEFT ) : + (( y2 >= 0.0 ) ? SIDE_LEFT : SIDE_SPLIT ); +} + +static int WhichSide ( SEG *seg ) +{ + FUNCTION_ENTRY ( NULL, "WhichSide", true ); + + // Treat split partition/seg differently + if (( seg->Split == true ) || ( currentAlias == 0 )) { + return _WhichSide ( seg ); + } + + // See if partition & seg lie on the same line + int alias = lineDefAlias [ seg->Data.lineDef ]; + if ( alias == currentAlias ) { + return seg->AliasFlip ^ SIDE_RIGHT; + } + + // See if we've already categorized the LINEDEF for this SEG + int side = currentSide [ seg->Data.lineDef ]; + if ( IS_LEFT_RIGHT ( side )) return side; + + side = _WhichSide ( seg ); + + if ( seg->Split == false ) { + currentSide [ seg->Data.lineDef ] = ( char ) side; + } + + return side; +} + +#if defined ( DEBUG ) + + static int dbgWhichSide ( SEG *seg ) + { + FUNCTION_ENTRY ( NULL, "dbgWhichSide", true ); + + int side = WhichSide ( seg ); + if ( side != _WhichSide ( seg )) { + ERROR ( "WhichSide is wigging out!" ); + } + return side; + } + +#define WhichSide dbgWhichSide + +#endif + +//---------------------------------------------------------------------------- +// Create a list of aliases vs LINEDEFs that indicates which side of a given +// alias a LINEDEF is on. +//---------------------------------------------------------------------------- + +static void CreateSideInfo ( DoomLevel *level ) +{ + FUNCTION_ENTRY ( NULL, "CreateSideInfo", true ); + + long size = ( sizeof ( char * ) + level->LineDefCount ()) * ( long ) noAliases; + char *temp = new char [ size ]; + + sideInfo = ( char ** ) temp; + memset ( temp, 0, sizeof ( char * ) * noAliases ); + + temp += sizeof ( char * ) * noAliases; + memset ( temp, SIDE_UNKNOWN, level->LineDefCount () * noAliases ); + + for ( int i = 0; i < noAliases; i++ ) { + sideInfo [i] = ( char * ) temp; + temp += level->LineDefCount (); + } +} + +//---------------------------------------------------------------------------- +// Return an index for a vertex at (x,y). If an existing vertex exists, +// return it, otherwise, create a new one if room is left. +//---------------------------------------------------------------------------- + +static int AddVertex ( int x, int y ) +{ + FUNCTION_ENTRY ( NULL, "AddVertex", true ); + + for ( int i = 0; i < noVertices; i++ ) { + if (( newVertices [i].x == x ) && ( newVertices [i].y == y )) return i; + } + + if ( noVertices == maxVertices ) { + maxVertices = ( 110 * maxVertices ) / 100 + 1; + newVertices = ( wVertex * ) realloc ( newVertices, sizeof ( wVertex ) * maxVertices ); + } + + newVertices [ noVertices ].x = ( UINT16 ) x; + newVertices [ noVertices ].y = ( UINT16 ) y; + + return noVertices++; +} + +//---------------------------------------------------------------------------- +// Sort two SEGS so that the one with the lowest numbered LINEDEF is first. +//---------------------------------------------------------------------------- + +static int SortByLineDef ( const void *ptr1, const void *ptr2 ) +{ + FUNCTION_ENTRY ( NULL, "SortByLineDef", true ); + + int dif1 = (( SEG * ) ptr1)->Sector - (( SEG * ) ptr2)->Sector; + if ( dif1 ) return dif1; + + int dif = (( SEG * ) ptr1)->Data.lineDef - (( SEG * ) ptr2)->Data.lineDef; + if ( dif ) return dif; + + return (( SEG * ) ptr1)->Data.flip - (( SEG * ) ptr2)->Data.flip; +} + +//---------------------------------------------------------------------------- +// Create a SSECTOR and record the index of the 1st SEG and the total number +// of SEGs. +//---------------------------------------------------------------------------- + +static UINT16 CreateSSector ( SEG *segs, int noSegs ) +{ + FUNCTION_ENTRY ( NULL, "CreateSSector", true ); + + if ( ssectorsLeft-- == 0 ) { + int delta = ( 10 * ssectorCount ) / 100 + 1; + ssectorPool = ( wSSector * ) realloc ( ssectorPool, sizeof ( wSSector ) * ( ssectorCount + delta )); + ssectorsLeft += delta; + } + + // Splits may have 'upset' the lineDef ordering - some special effects + // assume the SEGS appear in the same order as the LINEDEFS + if ( noSegs > 1 ) { + qsort ( segs, noSegs, sizeof ( SEG ), SortByLineDef ); + } + + int count = noSegs; + int first = segs - segStart; + +#if defined ( DIAGNOSTIC ) + bool errors = false; + for ( int i = 0; i < noSegs; i++ ) { + double dx = segs [i].end.x - segs [i].start.x; + double dy = segs [i].end.y - segs [i].start.y; + double h = ( dx * dx ) + ( dy * dy ); + for ( int j = 0; j < noSegs; j++ ) { + double y1 = ( dx * ( segs [j].start.y - segs [i].start.y ) - dy * ( segs [j].start.x - segs [i].start.x )) / h; + double y2 = ( dx * ( segs [j].end.y - segs [i].start.y ) - dy * ( segs [j].end.x - segs [i].start.x )) / h; + if (( y1 > 0.5 ) || ( y2 > 0.5 )) errors = true; + } + } + if ( errors == true ) { + fprintf ( stdout, "SSECTOR [%d]:\n", ssectorCount ); + double minx = segs[0].start.x; + double miny = segs[0].start.y; + for ( int i = 0; i < noSegs; i++ ) { + if ( segs[i].start.x < minx ) minx = segs[i].start.x; + if ( segs[i].start.y < miny ) miny = segs[i].start.y; + if ( segs[i].end.x < minx ) minx = segs[i].end.x; + if ( segs[i].end.y < miny ) miny = segs[i].end.y; + } + for ( int i = 0; i < noSegs; i++ ) { + fprintf ( stdout, " [%d] (%8.1f,%8.1f)-(%8.1f,%8.1f) S:%5d LD:%5d\n", i, segs[i].start.x - minx, segs[i].start.y - miny, segs[i].end.x - minx, segs[i].end.y - miny, segs[i].Sector, segs[i].Data.lineDef ); + } + for ( int i = 0; i < noSegs; i++ ) { + double dx = segs [i].end.x - segs [i].start.x; + double dy = segs [i].end.y - segs [i].start.y; + double h = ( dx * dx ) + ( dy * dy ); + for ( int j = 0; j < noSegs; j++ ) { + double y1 = ( dx * ( segs [j].start.y - segs [i].start.y ) - dy * ( segs [j].start.x - segs [i].start.x )) / h; + double y2 = ( dx * ( segs [j].end.y - segs [i].start.y ) - dy * ( segs [j].end.x - segs [i].start.x )) / h; + if (( y1 > 0.5 ) || ( y2 > 0.5 )) fprintf ( stdout, "<< ERROR - seg[%d] is not to the right of seg[%d] (%9.2f,%9.2f) >>\n", j, i, y1, y2 ); + } + } + fprintf ( stdout, "\n" ); + } +#endif + + // Eliminate zero length SEGs and assign vertices + for ( int i = 0; i < noSegs; i++ ) { +#if defined ( DEBUG ) + if (( fabs ( segs->start.x - segs->end.x ) < EPSILON ) && ( fabs ( segs->start.y - segs->end.y ) < EPSILON )) { + fprintf ( stderr, "Eliminating 0 length SEG from list\n" ); + memcpy ( segs, segs + 1, sizeof ( SEG ) * ( noSegs - i - 1 )); + count--; + continue; + } +#endif + segs->Data.start = ( UINT16 ) AddVertex ( lrint ( segs->start.x ), lrint ( segs->start.y )); + segs->Data.end = ( UINT16 ) AddVertex ( lrint ( segs->end.x ), lrint ( segs->end.y )); + segs++; + } + if ( count == 0 ) { + WARNING ( "No valid SEGS left in list!" ); + } + + wSSector *ssec = &ssectorPool [ssectorCount]; + ssec->num = ( UINT16 ) count; + ssec->first = ( UINT16 ) first; + + return ( UINT16 ) ssectorCount++; +} + +static int SortByAngle ( const void *ptr1, const void *ptr2 ) +{ + FUNCTION_ENTRY ( NULL, "SortByAngle", true ); + + int dif = ( ANGLE_MASK & (( SEG * ) ptr1)->Data.angle ) - ( ANGLE_MASK & (( SEG * ) ptr2)->Data.angle ); + if ( dif ) return dif; + dif = (( SEG * ) ptr1)->Data.lineDef - (( SEG * ) ptr2)->Data.lineDef; + if ( dif ) return dif; + return (( SEG * ) ptr1)->Data.flip - (( SEG * ) ptr2)->Data.flip; +} + +//---------------------------------------------------------------------------- +// Create a list of aliases. These are all the unique lines within the map. +// Each linedef is assigned an alias. All subsequent calculations are +// based on the aliases rather than the linedefs, since there are usually +// significantly fewer aliases than linedefs. +//---------------------------------------------------------------------------- + +static int GetLineDefAliases ( DoomLevel *level, SEG *segs, int noSegs ) +{ + FUNCTION_ENTRY ( NULL, "GetLineDefAliases", true ); + + // Reserve alias 0 + int noAliases = 1; + + lineDefAlias = new int [ level->LineDefCount () ]; + memset ( lineDefAlias, -1, sizeof ( int ) * ( level->LineDefCount ())); + + SEG **segAlias = new SEG * [ level->LineDefCount () + 2 ]; + + qsort ( segs, noSegs, sizeof ( SEG ), SortByAngle ); + + int lowIndex = 1; + int lastAngle = -1; + + for ( int i = 0; i < noSegs; i++ ) { + + // If the LINEDEF has been covered, skip this SEG + int *alias = &lineDefAlias [ segs [i].Data.lineDef ]; + + if ( *alias == -1 ) { + + ComputeStaticVariables ( &segs [i] ); + + // Compare against existing aliases with the same angle + int x = lowIndex; + while ( x < noAliases ) { + if ( CoLinear ( segAlias [x] )) break; + x++; + } + + if ( x >= noAliases ) { + segAlias [ x = noAliases++ ] = &segs [i]; + if ( lastAngle != ( ANGLE & ANGLE_MASK )) { + lowIndex = x; + lastAngle = ANGLE & ANGLE_MASK; + } + } + + *alias = x; + } + + segs [i].AliasFlip = ( segs [i].Data.angle == segAlias [*alias]->Data.angle ) ? 0 : SIDE_FLIPPED; + } + + delete [] segAlias; + + qsort ( segs, noSegs, sizeof ( SEG ), SortByLineDef ); + + return noAliases; +} + +#if defined ( DEBUG ) + + static void DumpSegs ( SEG *seg, int noSegs ) + { + FUNCTION_ENTRY ( NULL, "DumpSegs", true ); + + for ( int i = 0; i < noSegs; i++ ) { + sVertex *vertS = &seg->start; + sVertex *vertE = &seg->end; + int alias = lineDefAlias [ seg->Data.lineDef ]; + WARNING (( lineUsed [ alias ] ? "*" : " " ) << + " lineDef: " << seg->Data.lineDef << + " (" << vertS->x << "," << vertS->y << ") -" << + " (" << vertE->x << "," << vertE->y << ")" ); + seg++; + } + } + +#endif + +//---------------------------------------------------------------------------- +// +// Partition line: +// DXùx - DYùy + C = 0 ³ DX -DY ³ ³-C³ +// rSeg line: ³ ³=³ ³ +// dxùx - dyùy + c = 0 ³ dx -dy ³ ³-c³ +// +//---------------------------------------------------------------------------- + +static void DivideSeg ( SEG *rSeg, SEG *lSeg ) +{ + FUNCTION_ENTRY ( NULL, "DivideSeg", true ); + + const wLineDef *lineDef = rSeg->LineDef; + wVertex *vertS = &newVertices [ lineDef->start ]; + wVertex *vertE = &newVertices [ lineDef->end ]; + + // Minimum precision required to avoid overflow/underflow: + // dx, dy - 16 bits required + // c - 33 bits required + // det - 32 bits required + // x, y - 50 bits required + + double dx = vertE->x - vertS->x; + double dy = vertE->y - vertS->y; + double num = DX * ( vertS->y - Y ) - DY * ( vertS->x - X ); + double det = dx * DY - dy * DX; + + if ( det == 0.0 ) { + ERROR ( "SEG is parallel to partition line" ); + } + + double l = num / det; + double x = vertS->x + num * dx / det; + double y = vertS->y + num * dy / det; + + if ( rSeg->final == true ) { + x = lrint ( x ); + y = lrint ( y ); +#if defined ( DEBUG ) + if ((( rSeg->start.x == x ) && ( rSeg->start.y == y )) || + (( lSeg->start.x == x ) && ( lSeg->start.y == y ))) { + fprintf ( stderr, "\nNODES: End point duplicated in DivideSeg: LineDef #%d", rSeg->Data.lineDef ); + fprintf ( stderr, "\n Partition: from (%f,%f) to (%f,%f)", X, Y, X + DX, Y + DY ); + fprintf ( stderr, "\n LineDef: from (%d,%d) to (%d,%d) split at (%f,%f)", vertS->x, vertS->y, vertE->x, vertE->y, x, y ); + fprintf ( stderr, "\n SEG: from (%f,%f) to (%f,%f)", rSeg->start.x, rSeg->start.y, rSeg->end.x, rSeg->end.y ); + fprintf ( stderr, "\n dif: (%f,%f) (%f,%f)", rSeg->start.x - x, rSeg->start.y - y, rSeg->end.x - x, rSeg->end.y - y ); + } +#endif + } + + // Determine which sided of the partition line the start point is on + double sideS = DX * ( rSeg->start.y - Y ) - DY * ( rSeg->start.x - X ); + + // Get the correct endpoint of the base LINEDEF for the offset calculation + if ( rSeg->Data.flip ) vertS = vertE; + if ( rSeg->Data.flip ) l = 1.0 - l; + + rSeg->Split = true; + lSeg->Split = true; + + // Fill in the parts of lSeg & rSeg that have changed + if ( sideS < 0.0 ) { +#if defined ( DEBUG ) + if (( lrint ( x ) == lrint ( lSeg->start.x )) && ( lrint ( y ) == lrint ( lSeg->start.y ))) { + fprintf ( stderr, "DivideSeg: split didn't work (%10.3f,%10.3f) == L(%10.3f,%10.3f) - %d\n", x, y, lSeg->start.x, lSeg->start.y, currentAlias ); + } else if (( l < lSeg->start.l ) || ( l > lSeg->end.l )) { + fprintf ( stderr, "DivideSeg: warning - split is outside line segment (%7.5f-%7.5f) %7.5f\n", lSeg->start.l, lSeg->end.l, l ); + } +#endif + rSeg->end.x = x; + rSeg->end.y = y; + rSeg->end.l = l; + lSeg->start.x = x; + lSeg->start.y = y; + lSeg->start.l = l; + lSeg->Data.offset = ( UINT16 ) ( hypot (( double ) ( x - vertS->x ), ( double ) ( y - vertS->y )) + 0.5 ); + } else { +#if defined ( DEBUG ) + if (( lrint ( x ) == lrint ( rSeg->start.x )) && ( lrint ( y ) == lrint ( rSeg->start.y ))) { + fprintf ( stderr, "DivideSeg: split didn't work (%10.3f,%10.3f) == R(%10.3f,%10.3f) - %d\n", x, y, rSeg->start.x, rSeg->start.y, currentAlias ); + } else if (( l < lSeg->start.l ) || ( l > lSeg->end.l )) { + fprintf ( stderr, "DivideSeg: warning - split is outside line segment (%7.5f-%7.5f) %7.5f\n", lSeg->start.l, lSeg->end.l, l ); + } +#endif + lSeg->end.x = x; + lSeg->end.y = y; + lSeg->end.l = l; + rSeg->start.x = x; + rSeg->start.y = y; + rSeg->start.l = l; + rSeg->Data.offset = ( UINT16 ) ( hypot (( double ) ( x - vertS->x ), ( double ) ( y - vertS->y )) + 0.5 ); + } + +#if defined ( DEBUG ) + if ( _WhichSide ( rSeg ) != SIDE_RIGHT ) { + fprintf ( stderr, "DivideSeg: %s split invalid\n", "right" ); + } + if ( _WhichSide ( lSeg ) != SIDE_LEFT ) { + fprintf ( stderr, "DivideSeg: %s split invalid\n", "left" ); + } +#endif +} + +//---------------------------------------------------------------------------- +// Split the list of SEGs in two and adjust each copy to the appropriate +// values. +//---------------------------------------------------------------------------- + +static void SplitSegs ( SEG *segs, int noSplits ) +{ + FUNCTION_ENTRY ( NULL, "SplitSegs", true ); + + segCount += noSplits; + if ( segCount > maxSegs ) { + fprintf ( stderr, "\nError: Too many SEGs have been split!\n" ); + exit ( -1 ); + } + + int count = segCount - ( segs - segStart ) - noSplits; + memmove ( segs + noSplits, segs, count * sizeof ( SEG )); + + for ( int i = 0; i < noSplits; i++ ) { + DivideSeg ( segs, segs + noSplits ); + segs++; + } +} + +static void SortSegs ( SEG *pSeg, SEG *seg, int noSegs, int *noLeft, int *noRight ) +{ + FUNCTION_ENTRY ( NULL, "SortSegs", true ); + + int count [3]; + + if ( pSeg == NULL ) { +#if defined ( DEBUG ) + for ( int x = 0; x < noSegs; x++ ) { + // Make sure that all SEGs are actually on the right side of each other + ComputeStaticVariables ( &seg [x] ); + if (( fabs ( DX ) < EPSILON ) && ( fabs ( DY ) < EPSILON )) continue; + + count [0] = count [1] = count [2] = 0; + for ( int i = 0; i < noSegs; i++ ) { + count [( seg [i].Side = _WhichSide ( &seg [i] )) + 1 ]++; + } + + if (( count [0] * count [2] ) || count [1] ) { + DumpSegs ( seg, noSegs ); + ERROR ( "Something weird is going on! (" << count [0] << "|" << count [1] << "|" << count [2] << ") " << noSegs ); + break; + } + } +#endif + *noRight = noSegs; + *noLeft = 0; + return; + } + + ComputeStaticVariables ( pSeg ); + + count [0] = count [1] = count [2] = 0; + int i; + for ( i = 0; i < noSegs; i++ ) { + count [( seg [i].Side = WhichSide ( &seg [i] )) + 1 ]++; + } + + ASSERT (( count [0] * count [2] != 0 ) || ( count [1] != 0 )); + + *noLeft = count [0]; + *noRight = count [2]; + + SEG *rSeg = seg; + for ( i = 0; seg [i].Side == SIDE_RIGHT; i++ ) rSeg++; + + if (( i < count [2] ) || count [1] ) { + SEG *sSeg = tempSeg; + SEG *lSeg = sSeg + count [1]; + for ( ; i < noSegs; i++ ) { + switch ( seg [i].Side ) { + case SIDE_LEFT : *lSeg++ = seg [i]; break; + case SIDE_SPLIT : *sSeg++ = seg [i]; break; + case SIDE_RIGHT : *rSeg++ = seg [i]; break; + } + } + memcpy ( rSeg, tempSeg, ( noSegs - count [2] ) * sizeof ( SEG )); + } + + if ( count [1] != 0 ) { + SplitSegs ( &seg [ *noRight ], count [1] ); + *noLeft += count [1]; + *noRight += count [1]; + } + + return; +} + +//---------------------------------------------------------------------------- +// Use the requested algorithm to select a partition for the list of SEGs. +// After a valid partition is selected, the SEGs are re-ordered. All SEGs +// to the right of the partition are placed first, then those that will +// be split, followed by those that are to the left. +//---------------------------------------------------------------------------- + +static bool ChoosePartition ( SEG *seg, int noSegs, int *noLeft, int *noRight ) +{ + FUNCTION_ENTRY ( NULL, "ChoosePartition", true ); + + bool check = true; + +retry: + + if ( seg->final == false ) { + memcpy ( lineChecked, lineUsed, sizeof ( char ) * noAliases ); + } else { + memset ( lineChecked, 0, sizeof ( char ) * noAliases ); + } + + // Find the best SEG to be used as a partition + SEG *pSeg = PartitionFunction ( seg, noSegs ); + + // Resort the SEGS (right followed by left) and do the splits as necessary + SortSegs ( pSeg, seg, noSegs, noLeft, noRight ); + + // Make sure the set of SEGs is still convex after we convert to integer coordinates + if (( pSeg == NULL ) && ( check == true )) { + check = false; + double error = 0.0; + for ( int i = 0; i < noSegs; i++ ) { + + int startX = lrint ( seg [i].start.x ); + int startY = lrint ( seg [i].start.y ); + int endX = lrint ( seg [i].end.x ); + int endY = lrint ( seg [i].end.y ); + + error += fabs ( seg [i].start.x - startX ); + error += fabs ( seg [i].start.y - startY ); + error += fabs ( seg [i].end.x - endX ); + error += fabs ( seg [i].end.y - endY ); + + seg [i].start.x = startX; + seg [i].start.y = startY; + seg [i].end.x = endX; + seg [i].end.y = endY; + seg [i].final = true; + } + if ( error > EPSILON ) { + // Force a check of each line + goto retry; + } + } + + return pSeg ? true : false; +} + +//---------------------------------------------------------------------------- +// ALGORITHM 1: 'ZenNode Classic' +// This is the original algorithm used by ZenNode. It simply attempts +// to minimize the number of SEGs that are split. This actually yields +// very small BSP trees, but usually results in trees that are not well +// balanced and run deep. +//---------------------------------------------------------------------------- + +static SEG *Algorithm1 ( SEG *segs, int noSegs ) +{ + FUNCTION_ENTRY ( NULL, "Algorithm1", true ); + + SEG *pSeg = NULL, *testSeg = segs; + int count [3]; + int &lCount = count [0], &sCount = count [1], &rCount = count [2]; + // Compute the maximum value maxMetric can possibly reach + long maxMetric = ( noSegs / 2 ) * ( noSegs - noSegs / 2 ); + long bestMetric = LONG_MIN, bestSplits = LONG_MAX; + + for ( int i = 0; i < noSegs; i++ ) { + if ( showProgress && (( i & 15 ) == 0 )) ShowProgress (); + int alias = testSeg->Split ? 0 : lineDefAlias [ testSeg->Data.lineDef ]; + if (( alias == 0 ) || ( lineChecked [ alias ] == false )) { + lineChecked [ alias ] = -1; + count [0] = count [1] = count [2] = 0; + ComputeStaticVariables ( testSeg ); + if (( fabs ( DX ) < EPSILON ) && ( fabs ( DY ) < EPSILON )) goto next; + if ( bestMetric < 0 ) for ( int j = 0; j < noSegs; j++ ) { + count [ WhichSide ( &segs [j] ) + 1 ]++; + } else for ( int j = 0; j < noSegs; j++ ) { + count [ WhichSide ( &segs [j] ) + 1 ]++; + if ( sCount > bestSplits ) goto next; + } + + // Only consider SEG if it is not a boundary line + if ( lCount * rCount + sCount != 0 ) { + long metric = ( long ) lCount * ( long ) rCount; + if ( sCount ) { + long temp = X1 * sCount; + if ( X2 < temp ) metric = X2 * metric / temp; + metric -= ( X3 * sCount + X4 ) * sCount; + } + if ( ANGLE & 0x3FFF ) metric--; + if ( metric == maxMetric ) return testSeg; + if ( metric > bestMetric ) { + pSeg = testSeg; + bestSplits = sCount + 2; + bestMetric = metric; + } + } else if ( alias != 0 ) { + // Eliminate outer edges of the map from here & down + *convexPtr++ = alias; + } + } +#if defined ( DEBUG ) + else if ( lineChecked [alias] > 0 ) { + count [0] = count [1] = count [2] = 0; + ComputeStaticVariables ( testSeg ); + int side = WhichSide ( testSeg ); + if (( fabs ( DX ) < EPSILON ) && ( fabs ( DY ) < EPSILON )) continue; + for ( int j = 0; j < noSegs; j++ ) { + switch ( WhichSide ( &segs [j] )) { + case SIDE_LEFT : + if ( side == SIDE_RIGHT ) { + WARNING ( "lineDef " << segs [j].Data.lineDef << " should not to the left of lineDef " << testSeg->Data.lineDef ); + } + break; + case SIDE_SPLIT : + WARNING ( "lineDef " << segs [j].Data.lineDef << " should not be split by lineDef " << testSeg->Data.lineDef ); + break; + case SIDE_RIGHT : + if ( side == SIDE_LEFT ) { + WARNING ( "lineDef " << segs [j].Data.lineDef << " should not to the right of lineDef " << testSeg->Data.lineDef ); + } + break; + default : + break; + } + } + } +#endif + +next: + testSeg++; + } + + return pSeg; +} + +//---------------------------------------------------------------------------- +// ALGORITHM 2: 'ZenNode Quality' +// This is the 2nd algorithm used by ZenNode. It attempts to keep the +// resulting BSP tree balanced based on the number of sectors on each side of +// the partition line in addition to the number of SEGs. This seems more +// reasonable since a given SECTOR is usually made up of one or more SSECTORS. +//---------------------------------------------------------------------------- + +int sortTotalMetric ( const void *ptr1, const void *ptr2 ) +{ + FUNCTION_ENTRY ( NULL, "sortTotalMetric", true ); + + int dif; + dif = (( sScoreInfo * ) ptr1)->invalid - (( sScoreInfo * ) ptr2)->invalid; + if ( dif ) return dif; + dif = (( sScoreInfo * ) ptr1)->total - (( sScoreInfo * ) ptr2)->total; + if ( dif ) return dif; + dif = (( sScoreInfo * ) ptr1)->index - (( sScoreInfo * ) ptr2)->index; + return dif; +} + +int sortMetric1 ( const void *ptr1, const void *ptr2 ) +{ + FUNCTION_ENTRY ( NULL, "sortMetric1", true ); + + if ((( sScoreInfo * ) ptr2)->metric1 < (( sScoreInfo * ) ptr1)->metric1 ) return -1; + if ((( sScoreInfo * ) ptr2)->metric1 > (( sScoreInfo * ) ptr1)->metric1 ) return 1; + if ((( sScoreInfo * ) ptr2)->metric2 < (( sScoreInfo * ) ptr1)->metric2 ) return -1; + if ((( sScoreInfo * ) ptr2)->metric2 > (( sScoreInfo * ) ptr1)->metric2 ) return 1; + return (( sScoreInfo * ) ptr1)->index - (( sScoreInfo * ) ptr2)->index; +} + +int sortMetric2 ( const void *ptr1, const void *ptr2 ) +{ + FUNCTION_ENTRY ( NULL, "sortMetric2", true ); + + if ((( sScoreInfo * ) ptr2)->metric2 < (( sScoreInfo * ) ptr1)->metric2 ) return -1; + if ((( sScoreInfo * ) ptr2)->metric2 > (( sScoreInfo * ) ptr1)->metric2 ) return 1; + if ((( sScoreInfo * ) ptr2)->metric1 < (( sScoreInfo * ) ptr1)->metric1 ) return -1; + if ((( sScoreInfo * ) ptr2)->metric1 > (( sScoreInfo * ) ptr1)->metric1 ) return 1; + return (( sScoreInfo * ) ptr1)->index - (( sScoreInfo * ) ptr2)->index; +} + +static SEG *Algorithm2 ( SEG *segs, int noSegs ) +{ + FUNCTION_ENTRY ( NULL, "Algorithm2", true ); + + SEG *testSeg = segs; + int count [3], noScores = 0, rank, i; + int &lCount = count [0], &sCount = count [1], &rCount = count [2]; + + memset ( score, -1, sizeof ( sScoreInfo ) * noAliases ); + score [0].index = 0; + + for ( i = 0; i < noSegs; i++ ) { + if ( showProgress && (( i & 15 ) == 0 )) ShowProgress (); + int alias = testSeg->Split ? 0 : lineDefAlias [ testSeg->Data.lineDef ]; + if (( alias == 0 ) || ( lineChecked [ alias ] == false )) { + lineChecked [ alias ] = -1; + count [0] = count [1] = count [2] = 0; + ComputeStaticVariables ( testSeg ); + if (( fabs ( DX ) < EPSILON ) && ( fabs ( DY ) < EPSILON )) goto next; + sScoreInfo *curScore = &score [noScores]; + curScore->invalid = 0; + memset ( usedSector, 0, sizeof ( UINT8 ) * sectorCount ); + SEG *destSeg = segs; + for ( int j = 0; j < noSegs; j++, destSeg++ ) { + switch ( WhichSide ( destSeg )) { + case SIDE_LEFT : lCount++; usedSector [ destSeg->Sector ] |= 0xF0; break; + case SIDE_SPLIT : if ( destSeg->DontSplit ) curScore->invalid++; + sCount++; usedSector [ destSeg->Sector ] |= 0xFF; break; + case SIDE_RIGHT : rCount++; usedSector [ destSeg->Sector ] |= 0x0F; break; + } + } + // Only consider SEG if it is not a boundary line + if ( lCount * rCount + sCount ) { + int lsCount = 0, rsCount = 0, ssCount = 0; + for ( int j = 0; j < sectorCount; j++ ) { + switch ( usedSector [j] ) { + case 0xF0 : lsCount++; break; + case 0xFF : ssCount++; break; + case 0x0F : rsCount++; break; + } + } + + curScore->index = i; + curScore->metric1 = ( long ) ( lCount + sCount ) * ( long ) ( rCount + sCount ); + curScore->metric2 = ( long ) ( lsCount + ssCount ) * ( long ) ( rsCount + ssCount ); + + if ( sCount ) { + long temp = X1 * sCount; + if ( X2 < temp ) curScore->metric1 = X2 * curScore->metric1 / temp; + curScore->metric1 -= ( X3 * sCount + X4 ) * sCount; + } + if ( ssCount ) { + long temp = X1 * ssCount; + if ( X2 < temp ) curScore->metric2 = X2 * curScore->metric2 / temp; + curScore->metric2 -= ( X3 * ssCount + X4 ) * sCount; + } + + noScores++; + } else if ( alias != 0 ) { + // Eliminate outer edges of the map + *convexPtr++ = alias; + } + } +#if defined ( DEBUG ) + else if ( lineChecked [alias] > 0 ) { + count [0] = count [1] = count [2] = 0; + ComputeStaticVariables ( testSeg ); + int side = WhichSide ( testSeg ); + if (( fabs ( DX ) < EPSILON ) && ( fabs ( DY ) < EPSILON )) continue; + for ( int j = 0; j < noSegs; j++ ) { + switch ( WhichSide ( &segs [j] )) { + case SIDE_LEFT : + if ( side == SIDE_RIGHT ) { + WARNING ( "lineDef " << segs [j].Data.lineDef << " should not to the left of lineDef " << testSeg->Data.lineDef ); + } + break; + case SIDE_SPLIT : + WARNING ( "lineDef " << segs [j].Data.lineDef << " should not be split by lineDef " << testSeg->Data.lineDef ); + break; + case SIDE_RIGHT : + if ( side == SIDE_LEFT ) { + WARNING ( "lineDef " << segs [j].Data.lineDef << " should not to the right of lineDef " << testSeg->Data.lineDef ); + } + break; + default : + break; + } + } + } +#endif + +next: + testSeg++; + } + + if ( noScores > 1 ) { + qsort ( score, noScores, sizeof ( sScoreInfo ), sortMetric1 ); + for ( rank = i = 0; i < noScores; i++ ) { + score [i].total = rank; + if ( score [i].metric1 != score [i+1].metric1 ) rank++; + } + qsort ( score, noScores, sizeof ( sScoreInfo ), sortMetric2 ); + for ( rank = i = 0; i < noScores; i++ ) { + score [i].total += rank; + if ( score [i].metric2 != score [i+1].metric2 ) rank++; + } + qsort ( score, noScores, sizeof ( sScoreInfo ), sortTotalMetric ); + } + + if ( noScores && score [0].invalid ) { + int noBad = 0; + for ( int i = 0; i < noScores; i++ ) if ( score [i].invalid ) noBad++; + WARNING ( "Non-splittable linedefs have been split! ("<< noBad << "/" << noScores << ")" ); + } + + SEG *pSeg = noScores ? &segs [ score [0].index ] : NULL; + return pSeg; +} + +//---------------------------------------------------------------------------- +// ALGORITHM 3: 'ZenNode Lite' +// This is a modified version of the original algorithm used by ZenNode. +// It uses the same logic for picking the partition, but only looks at the +// first 30 segs for a valid partition. If none is found, the search is +// continued until one is found or all segs have been searched. +//---------------------------------------------------------------------------- + +static SEG *Algorithm3 ( SEG *segs, int noSegs ) +{ + FUNCTION_ENTRY ( NULL, "Algorithm3", true ); + + SEG *pSeg = NULL, *testSeg = segs; + int count [3]; + int &lCount = count [0], &sCount = count [1], &rCount = count [2]; + // Compute the maximum value maxMetric can possibly reach + long maxMetric = ( long ) ( noSegs / 2 ) * ( long ) ( noSegs - noSegs / 2 ); + long bestMetric = LONG_MIN, bestSplits = LONG_MAX; + + int i = 0, max = ( noSegs < 30 ) ? noSegs : 30; + +retry: + + for ( ; i < max; i++ ) { + if ( showProgress && (( i & 15 ) == 0 )) ShowProgress (); + int alias = testSeg->Split ? 0 : lineDefAlias [ testSeg->Data.lineDef ]; + if (( alias == 0 ) || ( lineChecked [ alias ] == false )) { + lineChecked [ alias ] = -1; + count [0] = count [1] = count [2] = 0; + ComputeStaticVariables ( testSeg ); + if (( fabs ( DX ) < EPSILON ) && ( fabs ( DY ) < EPSILON )) goto next; + if ( bestMetric < 0 ) for ( int j = 0; j < noSegs; j++ ) { + count [ WhichSide ( &segs [j] ) + 1 ]++; + } else for ( int j = 0; j < noSegs; j++ ) { + count [ WhichSide ( &segs [j] ) + 1 ]++; + if ( sCount > bestSplits ) goto next; + } + if ( lCount * rCount + sCount ) { + long metric = ( long ) lCount * ( long ) rCount; + if ( sCount ) { + long temp = X1 * sCount; + if ( X2 < temp ) metric = X2 * metric / temp; + metric -= ( X3 * sCount + X4 ) * sCount; + } + if ( ANGLE & 0x3FFF ) metric--; + if ( metric == maxMetric ) return testSeg; + if ( metric > bestMetric ) { + pSeg = testSeg; + bestSplits = sCount; + bestMetric = metric; + } + } else if ( alias != 0 ) { + // Eliminate outer edges of the map from here & down + *convexPtr++ = alias; + } + } +#if defined ( DEBUG ) + else if ( lineChecked [alias] > 0 ) { + count [0] = count [1] = count [2] = 0; + ComputeStaticVariables ( testSeg ); + int side = WhichSide ( testSeg ); + if (( fabs ( DX ) < EPSILON ) && ( fabs ( DY ) < EPSILON )) continue; + for ( int j = 0; j < noSegs; j++ ) { + switch ( WhichSide ( &segs [j] )) { + case SIDE_LEFT : + if ( side == SIDE_RIGHT ) { + WARNING ( "lineDef " << segs [j].Data.lineDef << " should not to the left of lineDef " << testSeg->Data.lineDef ); + } + break; + case SIDE_SPLIT : + WARNING ( "lineDef " << segs [j].Data.lineDef << " should not be split by lineDef " << testSeg->Data.lineDef ); + break; + case SIDE_RIGHT : + if ( side == SIDE_LEFT ) { + WARNING ( "lineDef " << segs [j].Data.lineDef << " should not to the right of lineDef " << testSeg->Data.lineDef ); + } + break; + default : + break; + } + } + } +#endif + +next: + testSeg++; + } + + if (( pSeg == NULL ) && ( max < noSegs )) { + max += 5; + if ( max > noSegs ) max = noSegs; + goto retry; + } + + return pSeg; +} + +//---------------------------------------------------------------------------- +// Check to see if the list of segs contains more than one sector and at least +// one of them requires "unique subsectors". +//---------------------------------------------------------------------------- + +static bool KeepUniqueSubsectors ( SEG *segs, int noSegs ) +{ + FUNCTION_ENTRY ( NULL, "KeepUniqueSubsectors", true ); + + if ( uniqueSubsectors == true ) { + bool requireUnique = false; + int lastSector = segs->Sector; + for ( int i = 0; i < noSegs; i++ ) { + if ( keepUnique [ segs [i].Sector ] == true ) requireUnique = true; + if ( segs [i].Sector != lastSector ) { + if ( requireUnique == true ) return true; + lastSector = segs [i].Sector; + } + } + } + + return false; +} + +bool overlappingSegs; + +static int SortByOrientation ( const void *ptr1, const void *ptr2 ) +{ + FUNCTION_ENTRY ( NULL, "SortByOrientation", false ); + + SEG *seg1 = ( SEG * ) ptr1; + SEG *seg2 = ( SEG * ) ptr2; + + // If they're at different angles it's easy... + int dif1 = seg2->Data.angle - seg1->Data.angle; + if ( dif1 != 0 ) return dif1; + + // They're colinear, sort them in a clockwise direction + double dif2 = 0.0; + double dx = seg1->end.x - seg1->start.x; + if ( dx > 0.0 ) { + dif2 = seg1->start.x - seg2->start.x; + } else if ( dx < 0.0 ) { + dif2 = seg2->start.x - seg1->start.x; + } else { + double dy = seg1->end.y - seg1->start.y; + if ( dy > 0.0 ) { + dif2 = seg1->start.y - seg2->start.y; + } else if ( dy < 0.0 ) { + dif2 = seg2->start.y - seg1->start.y; + } + } + + if ( dif2 != 0.0 ) return ( dif2 < 0.0 ) ? -1 : 1; + + // Mark these SEGs as overlapping + overlappingSegs = true; + + // OK, we have overlapping lines! + return ( seg1->Data.lineDef < seg2->Data.lineDef ) ? -1 : 1; +} + +#if defined ( DIAGNOSTIC ) + +static void PrintKeepUniqueSegs ( SEG *segs, int noSegs, const char *msg = NULL ) +{ + fprintf ( stdout, "keep-unique SEGS:\n" ); + + if ( msg != NULL ) fprintf ( stdout, " << ERROR - %s >>\n", msg ); + + int lastAngle = 0x10000; + + for ( int i = 0; i < noSegs; i++ ) { + double dx = segs[i].end.x - segs [i].start.x; + double dy = segs[i].end.y - segs [i].start.y; + fprintf ( stdout, " [%d] (%8.1f,%8.1f)-(%8.1f,%8.1f) S:%5d dx:%8.1f dy:%8.1f %04X LD: %5d alias: %5d\n", i, segs [i].start.x, segs [i].start.y, segs [i].end.x, segs [i].end.y, segs[i].Sector, dx, dy, segs [i].Data.angle, segs[i].Data.lineDef, lineDefAlias [ segs[i].Data.lineDef ] ); + if ( segs [i].Data.angle == lastAngle ) { + double dx = segs[i-1].end.x - segs[i-1].start.x; + double dy = segs[i-1].end.y - segs[i-1].start.y; + double y1 = dx * ( segs[i].start.y - segs[i-1].start.y ) - dy * ( segs[i].start.x - segs[i-1].start.x ); + if ( y1 != 0.0 ) fprintf ( stdout, "<< ERROR - seg[%d] is not colinear with seg[%d] - %f!!! >>\n", i, i-1, y1 ); + } + lastAngle = segs [i].Data.angle; + } + fprintf ( stdout, "\n" ); +} + +#endif + +//---------------------------------------------------------------------------- +// Reorder the SEGs so that they are in order around the enclosing subsector +//---------------------------------------------------------------------------- +static void ArrangeSegs ( SEG *segs, int noSegs ) +{ + FUNCTION_ENTRY ( NULL, "ArrangeSegs", true ); + +overlappingSegs = false; + + // Sort the around the center of the polygon + qsort ( segs, noSegs, sizeof ( SEG ), SortByOrientation ); + +#if defined ( DIAGNOSTIC ) + bool badSegs = false; + + int lastAngle = 0x10000; + for ( int i = 0; i < noSegs; i++ ) { + if ( segs [i].Data.angle == lastAngle ) { + double dx = segs[i-1].end.x - segs[i-1].start.x; + double dy = segs[i-1].end.y - segs[i-1].start.y; + double y1 = dx * ( segs[i].start.y - segs[i-1].start.y ) - dy * ( segs[i].start.x - segs[i-1].start.x ); + if ( y1 != 0.0 ) { badSegs = true; } + } + lastAngle = segs [i].Data.angle; + } + if (badSegs||overlappingSegs) PrintKeepUniqueSegs (segs, noSegs, overlappingSegs ? "Overlapping SEGs were detected" : NULL ); +#endif + + // See if the 1st sector wraps at the end of the list + int j = noSegs; + while (( j > 0 ) && ( segs [j-1].Sector == segs [0].Sector )) j--; + + if ( j != noSegs ) { + memcpy ( tempSeg, segs + j, sizeof ( SEG ) * ( noSegs - j )); + memmove ( segs + ( noSegs - j ), segs, sizeof ( SEG ) * j ); + memcpy ( segs, tempSeg, sizeof ( SEG ) * ( noSegs - j )); + } +} + +static void MakePartition ( SEG *segs, int noSegs, int *noLeft, int *noRight ) +{ + FUNCTION_ENTRY ( NULL, "MakePartition", true ); + + // Find the end of the first run of SEGs that are unique (or at don't require + // unique subsectors). These will form the right SSECTOR. + int lastSector = segs [0].Sector; + bool uniqueFlag = keepUnique [ lastSector ]; + + int right = 0; + while ( right < noSegs ) { + int thisSector = segs [right+1].Sector; + if (( thisSector != lastSector ) && + (( uniqueFlag == true ) || ( keepUnique [ thisSector ] == true ))) { + break; + } + lastSector = thisSector; + right++; + } + + // Now we need to find a partition line that will isolate the 'right' SEGs + + X = segs [right].end.x; + Y = segs [right].end.y; + + // Look for the easy case first + if ( segs [0].Data.angle != segs [right].Data.angle ) { + + *noRight = right + 1; + DX = segs [0].start.x - X; + DY = segs [0].start.y - Y; + + } else { + + // There are three potential sets of SEGs: + // 'target' - the SEGs at the start of the list + // 'head' - the SEGs immediately following the 'target' SEGs up to the first + // SEG in the 'tail' list + // 'tail' - SEGs at the end of the list that have at least one endpoint on the same + // line as the first 'target' SEG + + // Find the last point not on the target SEGs line + int split = noSegs; + double dx = segs [0].end.x - segs [0].start.x; + double dy = segs [0].end.y - segs [0].start.y; + while ( split > right ) { + double y2 = dx * ( segs [split-1].end.y - segs [0].start.y ) - dy * ( segs [split-1].end.x - segs [0].start.x ); + if ( y2 < 0.0 ) break; + split--; + double y1 = dx * ( segs [split].start.y - segs [0].start.y ) - dy * ( segs [split].start.x - segs [0].start.x ); + if ( y1 < 0.0 ) break; + } + + // See if we found a candidate ending point (the dividing line between 'head' and 'tail' segments + if ( split > right ) { + int tail = noSegs - split; + if ( tail != 0 ) { + // We're going to split off the 'target'+'head' SEGs (right) from the 'tail' SEGs (left) + *noRight = split; + X = ( segs [split-1].end.x + segs [split].start.x ) / 2.0; + Y = ( segs [split-1].end.y + segs [split].start.y ) / 2.0; + DX = segs [0].start.x - X; + DY = segs [0].start.y - Y; + } else { + // No 'tail' SEGS - split the 'target' (right) and 'head' (left) SEGs + *noRight = right + 1; + DX = segs [noSegs-1].end.x - X; + DY = segs [noSegs-1].end.y - Y; + } + } else { + // All the line segments are colinear + int split = noSegs; + double dx = segs [0].end.x - segs [0].start.x; + if ( dx != 0.0 ) { + while (( segs [split-1].end.x - segs [0].start.x ) / dx <= 0.0 ) split--; + } else { + double dy = segs [0].end.y - segs [0].start.y; + while (( segs [split-1].end.y - segs [0].start.y ) / dy <= 0.0 ) split--; + } + + int tail = noSegs - split; + if ( tail != 0 ) { + // We're going to split off the 'target'+'head' SEGs (right) from the 'tail' SEGs (left) + *noRight = split; + X = segs [0].start.x; + Y = segs [0].start.y; + DX = segs [0].start.y - segs [0].end.y; + DY = segs [0].end.x - segs [0].start.x; + } else { + // No 'tail' SEGS - split the 'target' (right) and 'head' (left) SEGs + *noRight = right + 1; + DX = segs [0].end.y - segs [0].start.y; + DY = segs [0].start.x - segs [0].end.x; + } + } + } + + *noLeft = noSegs - *noRight; +} + +#if defined ( DIAGNOSTIC ) + +static void VerifyNode ( SEG *segs, int noSegs, double x1, double y1, double x2, double y2 ) +{ + bool errors = false; + double dx = x2 - x1; + double dy = y2 - y1; + double h = ( dx * dx ) + ( dy * dy ); + for ( int j = 0; j < noSegs; j++ ) { + double p1 = ( dx * ( segs [j].start.y - y1 ) - dy * ( segs [j].start.x - x1 )) / h; + double p2 = ( dx * ( segs [j].end.y - y1 ) - dy * ( segs [j].end.x - x1 )) / h; + if (( p1 > 0.25 ) || ( p2 > 0.25 )) errors = true; + } + if ( errors == true ) { + fprintf ( stdout, "Partition line (%8.1f,%8.1f)-(%8.1f,%8.1f) is not correct!\n", x1, y1, x2, y2 ); + for ( int j = 0; j < noSegs; j++ ) { + double p1 = ( dx * ( segs [j].start.y - y1 ) - dy * ( segs [j].start.x - x1 )) / h; + double p2 = ( dx * ( segs [j].end.y - y1 ) - dy * ( segs [j].end.x - x1 )) / h; + fprintf ( stdout, " [%d] (%8.1f,%8.1f)-(%8.1f,%8.1f) S:%5d LD:%5d y1:%10.3f y2:%10.3f %s\n", j, segs[j].start.x, segs[j].start.y, segs[j].end.x, segs[j].end.y, segs[j].Sector, segs[j].Data.lineDef, p1, p2, (( p1 > 0.25 ) || ( p2 > 0.25 )) ? "<---" : "" ); + } + } +} + +#endif + +//---------------------------------------------------------------------------- +// Recursively create the actual NODEs. This procedure is very similar to +// CreateNode except that we know up front that the SEGs form a convex +// polygon and only need to be broken up by unique sectors. The main difference +// here is that the partition line is not taken from the SEGs but is an +// arbitrary line chosen to break up the SEGs properly to create unique SSECTORs. +//---------------------------------------------------------------------------- +static UINT16 GenerateUniqueSectors ( SEG *segs, int noSegs ) +{ + FUNCTION_ENTRY ( NULL, "GenerateUniqueSectors", true ); + + if ( KeepUniqueSubsectors ( segs, noSegs ) == false ) { + if ( showProgress ) ShowDone (); + return ( UINT16 ) ( 0x8000 | CreateSSector ( segs, noSegs )); + } + + int noLeft, noRight; + + MakePartition ( segs, noSegs, &noLeft, &noRight ); + + wNode tempNode; + + // Store the NODE info set in ComputeStaticVariables + tempNode.x = ( INT16 ) lrint ( X ); + tempNode.y = ( INT16 ) lrint ( Y ); + tempNode.dx = ( INT16 ) lrint ( DX ); + tempNode.dy = ( INT16 ) lrint ( DY ); + +#if defined ( DIAGNOSTIC ) + double x1 = X; + double y1 = Y; + double x2 = X + DX; + double y2 = Y + DY; +#endif + + FindBounds ( &tempNode.side [0], segs, noRight ); + FindBounds ( &tempNode.side [1], segs + noRight, noLeft ); + + if ( showProgress ) GoRight (); + + UINT16 rNode = GenerateUniqueSectors ( segs, noRight ); + + if ( showProgress ) GoLeft (); + + UINT16 lNode = GenerateUniqueSectors ( segs + noRight, noLeft ); + +#if defined ( DIAGNOSTIC ) + VerifyNode ( segs, noRight, x1, y1, x2, y2 ); + VerifyNode ( segs+noRight, noLeft, x2, y2, x1, y1 ); +#endif + + if ( showProgress ) Backup (); + + if ( nodesLeft-- == 0 ) { + int delta = ( 10 * nodeCount ) / 100 + 1; + nodePool = ( wNode * ) realloc ( nodePool, sizeof ( wNode ) * ( nodeCount + delta )); + nodesLeft += delta; + } + + wNode *node = &nodePool [nodeCount]; + *node = tempNode; + node->child [0] = rNode; + node->child [1] = lNode; + + if ( showProgress ) ShowDone (); + + return ( UINT16 ) nodeCount++; +} + +//---------------------------------------------------------------------------- +// Recursively create the actual NODEs. The given list of SEGs is analyzed +// and a partition is chosen. If no partition can be found, a leaf node +// is created. Otherwise, the right and left SEGs are analyzed. Features: +// - A list of 'convex' aliases is maintained. These are lines that border +// the list of SEGs and can never be partitions. A line is marked as +// convex for this and all children, and unmarked before returing. +// - Similarly, the alias chosen as the partition is marked as convex +// since it will be convex for all children. +//---------------------------------------------------------------------------- +static UINT16 CreateNode ( SEG *segs, int *noSegs ) +{ + FUNCTION_ENTRY ( NULL, "CreateNode", true ); + + int noLeft, noRight; + int *cptr = convexPtr; + + if (( *noSegs <= 1 ) || ( ChoosePartition ( segs, *noSegs, &noLeft, &noRight ) == false )) { + convexPtr = cptr; + if ( KeepUniqueSubsectors ( segs, *noSegs ) == true ) { + ArrangeSegs ( segs, *noSegs ); + return GenerateUniqueSectors ( segs, *noSegs ); + } + if ( showProgress ) ShowDone (); + return ( UINT16 ) ( 0x8000 | CreateSSector ( segs, *noSegs )); + } + + wNode tempNode; + + // Store the NODE info set in ComputeStaticVariables + tempNode.x = ( INT16 ) lrint ( X ); + tempNode.y = ( INT16 ) lrint ( Y ); + tempNode.dx = ( INT16 ) lrint ( DX ); + tempNode.dy = ( INT16 ) lrint ( DY ); + +#if defined ( DIAGNOSTIC ) + double x1 = X; + double y1 = Y; + double x2 = X + DX; + double y2 = Y + DY; +#endif + + FindBounds ( &tempNode.side [0], segs, noRight ); + FindBounds ( &tempNode.side [1], segs + noRight, noLeft ); + + int alias = currentAlias; + + lineUsed [ alias ] = 1; + for ( int *tempPtr = cptr; tempPtr != convexPtr; tempPtr++ ) { + lineUsed [ *tempPtr ] = 2; + } + + if ( showProgress ) GoRight (); + + UINT16 rNode = CreateNode ( segs, &noRight ); + + if ( showProgress ) GoLeft (); + + UINT16 lNode = CreateNode ( segs + noRight, &noLeft ); + + if ( showProgress ) Backup (); + + *noSegs = noLeft + noRight; + +#if defined ( DIAGNOSTIC ) + VerifyNode ( segs, noRight, x1, y1, x2, y2 ); + VerifyNode ( segs+noRight, noLeft, x2, y2, x1, y1 ); +#endif + + while ( convexPtr != cptr ) lineUsed [ *--convexPtr ] = false; + lineUsed [ alias ] = false; + + if ( nodesLeft-- == 0 ) { + int delta = ( 10 * nodeCount ) / 100 + 1; + nodePool = ( wNode * ) realloc ( nodePool, sizeof ( wNode ) * ( nodeCount + delta )); + nodesLeft += delta; + } + + wNode *node = &nodePool [nodeCount]; + *node = tempNode; + node->child [0] = rNode; + node->child [1] = lNode; + + if ( showProgress ) ShowDone (); + + return ( UINT16 ) nodeCount++; +} + +wVertex *GetVertices () +{ + FUNCTION_ENTRY ( NULL, "GetVertices", true ); + + wVertex *vert = new wVertex [ noVertices ]; + memcpy ( vert, newVertices, sizeof ( wVertex ) * noVertices ); + + return vert; +} + +wNode *GetNodes ( wNode *nodeList, int noNodes ) +{ + FUNCTION_ENTRY ( NULL, "GetNodes", true ); + + wNode *nodes = new wNode [ noNodes ]; + + for ( int i = 0; i < noNodes; i++ ) { + nodes [i] = nodeList [i]; + } + + return nodes; +} + +static wSegs *finalSegs; + +wSSector *GetSSectors ( wSSector *ssectorList, int noSSectors ) +{ + FUNCTION_ENTRY ( NULL, "GetSSectors", true ); + + wSegs *segs = finalSegs = new wSegs [ segCount ]; + + for ( int i = 0; i < noSSectors; i++ ) { + + int start = ssectorList[i].first; + ssectorList[i].first = ( UINT16 ) ( segs - finalSegs ); + + // Copy the used SEGs to the final SEGs list + for ( int x = 0; x < ssectorList [i].num; x++ ) { + segs [x] = segStart [start+x].Data; + } + + segs += ssectorList [i].num; + } + + delete [] segStart; + segCount = segs - finalSegs; + + wSSector *ssector = new wSSector [ noSSectors ]; + memcpy ( ssector, ssectorList, sizeof ( wSSector ) * noSSectors ); + + return ssector; +} + +wSegs *GetSegs () +{ + FUNCTION_ENTRY ( NULL, "GetSegs", true ); + + // The list of wSegs is generated in GetSSectors + + return finalSegs; +} + +//---------------------------------------------------------------------------- +// Wrapper function that calls all the necessary functions to prepare the +// BSP tree and insert the new data into the level. All screen I/O is +// done in this routine (with the exception of progress indication). +//---------------------------------------------------------------------------- + +void CreateNODES ( DoomLevel *level, sBSPOptions *options ) +{ + FUNCTION_ENTRY ( NULL, "CreateNODES", true ); + + TRACE ( "Processing " << level->Name ()); + + // Sanity check on environment variables + if ( X2 <= 0 ) X2 = 1; + if ( Y2 <= 0 ) Y2 = 1; + + // Copy options to global variables + showProgress = options->showProgress; + uniqueSubsectors = options->keepUnique ? true : false; + + PartitionFunction = Algorithm1; + if ( options->algorithm == 2 ) PartitionFunction = Algorithm2; + if ( options->algorithm == 3 ) PartitionFunction = Algorithm3; + + nodeCount = 0; + ssectorCount = 0; + + // Get rid of old SEGS and associated vertices + level->NewSegs ( 0, NULL ); + level->TrimVertices (); + level->PackVertices (); + + noVertices = level->VertexCount (); + sectorCount = level->SectorCount (); + usedSector = new UINT8 [ sectorCount ]; + keepUnique = new bool [ sectorCount ]; + if ( options->keepUnique ) { + uniqueSubsectors = true; + memcpy ( keepUnique, options->keepUnique, sizeof ( bool ) * sectorCount ); + } else { + memset ( keepUnique, true, sizeof ( bool ) * sectorCount ); + } + maxVertices = ( int ) ( noVertices * FACTOR_VERTEX ); + newVertices = ( wVertex * ) malloc ( sizeof ( wVertex ) * maxVertices ); + memcpy ( newVertices, level->GetVertices (), sizeof ( wVertex ) * noVertices ); + + Status ( "Creating SEGS ... " ); + segStart = CreateSegs ( level, options ); + + Status ( "Getting LineDef Aliases ... " ); + noAliases = GetLineDefAliases ( level, segStart, segCount ); + + lineChecked = new char [ noAliases ]; + lineUsed = new char [ noAliases ]; + memset ( lineUsed, false, sizeof ( char ) * noAliases ); + + Status ( "Creating Side Info ... " ); + CreateSideInfo ( level ); + + score = ( options->algorithm == 2 ) ? new sScoreInfo [ noAliases ] : NULL; + + convexList = new int [ noAliases ]; + convexPtr = convexList; + + Status ( "Creating NODES ... " ); + + nodesLeft = ( int ) ( FACTOR_NODE * level->SideDefCount ()); + nodePool = ( wNode * ) malloc( sizeof ( wNode ) * nodesLeft ); + + ssectorsLeft = ( int ) ( FACTOR_NODE * level->SideDefCount ()); + ssectorPool = ( wSSector * ) malloc ( sizeof ( wSSector ) * ssectorsLeft ); + + int noSegs = segCount; + CreateNode ( segStart, &noSegs ); + + delete [] convexList; + if ( score ) delete [] score; + + // Clean up temporary buffers + Status ( "Cleaning up ... " ); + delete [] sideInfo; + delete [] lineDefAlias; + delete [] lineChecked; + delete [] lineUsed; + delete [] keepUnique; + delete [] usedSector; + + sideInfo = NULL; + + level->NewVertices ( noVertices, GetVertices ()); + level->NewNodes ( nodeCount, GetNodes ( nodePool, nodeCount )); + level->NewSubSectors ( ssectorCount, GetSSectors ( ssectorPool, ssectorCount )); + level->NewSegs ( segCount, GetSegs ()); + + free ( newVertices ); + free ( ssectorPool ); + free ( nodePool ); + + delete [] tempSeg; +} diff --git a/ZenNode/ZenNode.hpp b/ZenNode/ZenNode.hpp new file mode 100644 index 0000000..0699508 --- /dev/null +++ b/ZenNode/ZenNode.hpp @@ -0,0 +1,235 @@ +//---------------------------------------------------------------------------- +// +// File: ZenNode.hpp +// Date: 26-Oct-1994 +// Programmer: Marc Rousseau +// +// Description: Definitions of structures used by the ZenNode routines +// +// Copyright (c) 1994-2004 Marc Rousseau, All Rights Reserved. +// +// 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 2 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, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. +// +// Revision History: +// +// 06-??-95 Added LineDef alias list to speed up the process +// +//---------------------------------------------------------------------------- + +#ifndef ZENNODE_HPP_ +#define ZENNODE_HPP_ + +#if ! defined ( LEVEL_HPP_ ) + #include "level.hpp" +#endif + +struct sBlockMapOptions { + bool Rebuild; + bool Compress; +}; + +struct sNodeOptions { + bool Rebuild; + int Method; + bool Quiet; + bool Unique; + bool ReduceLineDefs; +}; + +struct sBlockList { + int firstIndex; // Index of 1st blockList element matching this one + int offset; + int count; + int *line; +}; + +struct sBlockMap { + int xOrigin; + int yOrigin; + int noColumns; + int noRows; + sBlockList *data; +}; + +typedef unsigned short BAM; +typedef long double REAL; // Must have at least 50 significant bits + +struct sVertex { + double x; + double y; + double l; +}; + +struct SEG { + wSegs Data; + const wLineDef *LineDef; + int Sector; + int Side; + int AliasFlip; + bool Split; + bool DontSplit; + bool final; + sVertex start; + sVertex end; +}; + +struct sBSPOptions { + int algorithm; + bool showProgress; + bool reduceLineDefs; // global flag for invisible linedefs + bool *ignoreLineDef; // linedefs that can be left out + bool *dontSplit; // linedefs that can't be split + bool *keepUnique; // unique sector requirements +}; + +struct sScoreInfo { + int index; + long metric1; + long metric2; + int invalid; + int total; +}; + +#define sgn(a) ((0<(a))-((a)<0)) + +#define BAM90 (( BAM ) 0x4000 ) // BAM: 90ø ( «ã) +#define BAM180 (( BAM ) 0x8000 ) // BAM: 180ø ( ñã) +#define BAM270 (( BAM ) 0xC000 ) // BAM: 270ø (-«ã) +#define M_PI 3.14159265358979323846 + +#define SIDE_UNKNOWN -2 +#define SIDE_LEFT -1 +#define SIDE_SPLIT 0 +#define SIDE_RIGHT 1 + +#define SIDE_FLIPPED 0xFFFFFFFE +#define SIDE_NORMAL 0 + +#define LEFT 0 +#define SPLIT 1 +#define RIGHT 2 + +#define IS_LEFT_RIGHT(s) ( s & 1 ) +#define FLIP(c,s) ( c ^ s ) + +// ---- ZenReject structures to support RMB options ---- + +enum REJECT_OPTION_E { + OPTION_UNKNOWN, + OPTION_MAP_1, + OPTION_MAP_2, + OPTION_BAND, + OPTION_BLIND, + OPTION_BLOCK, + OPTION_DISTANCE, + OPTION_DOOR, + OPTION_EXCLUDE, + OPTION_GROUP, + OPTION_INCLUDE, + OPTION_LEFT, + OPTION_LENGTH, + OPTION_LINE, + OPTION_NODOOR, + OPTION_NOMAP, + OPTION_NOPROCESS, + OPTION_ONE, + OPTION_PERFECT, + OPTION_PREPROCESS, + OPTION_PROCESS, + OPTION_REPORT, + OPTION_RIGHT, + OPTION_SAFE, + OPTION_TRACE +}; + +struct sRejectOptionRMB; +struct sOptionTableInfo; + +typedef bool (*PARSE_FUNCTION) ( char *, const sOptionTableInfo &, sRejectOptionRMB * ); + +struct sOptionTableInfo { + const char *Name; + const char *Syntax; + REJECT_OPTION_E Type; + PARSE_FUNCTION ParseFunction; +}; + +struct sRejectOptionRMB { + const sOptionTableInfo *Info; + bool Inverted; + bool Banded; + int Data [2]; + int *List [2]; +}; + +struct sRejectOptions { + bool Rebuild; + bool Empty; + bool Force; + bool FindChildren; + bool UseGraphs; + bool UseRMB; + const sRejectOptionRMB *rmb; +}; + +bool ParseOptionRMB ( int, const char *, sRejectOptionRMB * ); + +// ----- C99 routines from Required by ZenNode ----- + +#if ( defined ( __GNUC__ ) && ( __GNUC__ < 3 )) || defined ( __INTEL_COMPILER ) || defined ( __BORLANDC__ ) + +#if defined ( X86 ) + + __inline long lrint ( double flt ) + { + int intgr; + + __asm__ __volatile__ ("fistpl %0" : "=m" (intgr) : "t" (flt) : "st"); + + return intgr; + } + +#else + __inline long lrint ( double flt ) + { + // return ( long ) ( flt + 0.5 * sgn ( flt )); + return ( long ) ( flt + 0.5 ); + } + +#endif + +#elif defined ( _MSC_VER ) + + __inline long lrint ( double flt ) + { + int intgr; + + _asm + { + fld flt + fistp intgr + }; + + return intgr; + } + +#endif + +extern sBlockMap *GenerateBLOCKMAP ( DoomLevel *level ); +extern int CreateBLOCKMAP ( DoomLevel *level, const sBlockMapOptions &options ); +extern void CreateNODES ( DoomLevel *level, sBSPOptions *options ); +extern bool CreateREJECT ( DoomLevel *level, const sRejectOptions &options ); + +#endif diff --git a/ZenNode/ZenRMB.cpp b/ZenNode/ZenRMB.cpp new file mode 100644 index 0000000..1941f10 --- /dev/null +++ b/ZenNode/ZenRMB.cpp @@ -0,0 +1,407 @@ +//---------------------------------------------------------------------------- +// +// File: ZenRMB.cpp +// Date: 30-Oct-2000 +// Programmer: Marc Rousseau +// +// Description: RMB configuration file support for ZenNode +// +// Copyright (c) 2000-2004 Marc Rousseau, All Rights Reserved. +// +// 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 2 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, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. +// +// Revision History: +// +//---------------------------------------------------------------------------- + +#include +#include +#include +#include +#include + +#include "common.hpp" +#include "logger.hpp" +#include "ZenNode.hpp" + +DBG_REGISTER ( __FILE__ ); + +bool ParseGeneric ( char *, const sOptionTableInfo &, sRejectOptionRMB * ); +bool ParseMap ( char *, const sOptionTableInfo &, sRejectOptionRMB * ); +bool ParseBAND ( char *, const sOptionTableInfo &, sRejectOptionRMB * ); +bool ParseINVERT ( char *, const sOptionTableInfo &, sRejectOptionRMB * ); +bool ParseNOPROCESS ( char *, const sOptionTableInfo &, sRejectOptionRMB * ); + +sOptionTableInfo ParseTable [] = { + { "BAND", "NNL", OPTION_BAND, ParseBAND }, + { "BLIND", "NL", OPTION_BLIND, ParseGeneric }, + { "BLOCK", "NN", OPTION_BLOCK, ParseGeneric }, + { "DISTANCE", "N", OPTION_DISTANCE, ParseGeneric }, + { "DOOR", "N", OPTION_DOOR, ParseGeneric }, + { "E*M*", NULL, OPTION_MAP_1, ParseMap }, + { "EXCLUDE", "LL", OPTION_EXCLUDE, ParseGeneric }, + { "GROUP", "NL", OPTION_GROUP, ParseGeneric }, + { "INCLUDE", "LL", OPTION_INCLUDE, ParseGeneric }, + { "INVERT", NULL, OPTION_UNKNOWN, ParseINVERT }, + { "LEFT", "N", OPTION_LEFT, ParseGeneric }, + { "LENGTH", "N", OPTION_LENGTH, ParseGeneric }, + { "LINE", "N", OPTION_LINE, ParseGeneric }, + { "MAP**", NULL, OPTION_MAP_2, ParseMap }, + { "NODOOR", "L", OPTION_NODOOR, ParseGeneric }, + { "NOMAP", NULL, OPTION_NOMAP, ParseGeneric }, + { "NOPROCESS", NULL, OPTION_NOPROCESS, ParseNOPROCESS }, + { "ONE", "NN", OPTION_ONE, ParseGeneric }, + { "PERFECT", NULL, OPTION_PERFECT, ParseGeneric }, + { "PREPROCESS", "N", OPTION_PREPROCESS, ParseGeneric }, + { "PROCESS", "L", OPTION_PROCESS, ParseGeneric }, + { "REPORT", "N", OPTION_REPORT, ParseGeneric }, + { "RIGHT", "N", OPTION_RIGHT, ParseGeneric }, + { "SAFE", "NL", OPTION_SAFE, ParseGeneric }, + { "TRACE", "L", OPTION_TRACE, ParseGeneric } +}; + +static const char *parseText; +static int parseLine; +static int lastLine; + +const sOptionTableInfo *FindByType ( REJECT_OPTION_E type ) +{ + for ( unsigned i = 0; i < SIZE ( ParseTable ); i++ ) { + if ( ParseTable [i].Type == type ) return &ParseTable [i]; + } + return NULL; +} + +void ParseError ( const char *fmt, ... ) +{ + FUNCTION_ENTRY ( NULL, "ParseError", true ); + + va_list args; + va_start ( args, fmt ); + + fprintf ( stderr, ( lastLine == parseLine ) ? " " : "Line %3d: ", parseLine ); + vfprintf ( stderr, fmt, args ); + fprintf ( stderr, "\n" ); + + lastLine = parseLine; +} + +int ParseNumber ( char *&text ) +{ + FUNCTION_ENTRY ( NULL, "ParseNumber", true ); + + if ( ! isdigit ( *text )) throw "expected a number"; + + int number = 0; + + while ( isdigit ( *text )) { + number *= 10; + number += *text++ - '0'; + } + + return number; +} + +int *ParseList ( char *&text, bool strict ) +{ + FUNCTION_ENTRY ( NULL, "ParseList", true ); + + if (( strict == true ) && ( *text != '(' )) { + int *list = new int [ 2 ]; + list [0] = ParseNumber ( text ); + list [1] = -1; + return list; + } + + bool close = false; + + if ( *text == '(' ) { + close = true; + text++; + } + + while ( isspace ( *text )) text++; + + if ( *text == '\0' ) throw "Invalid list"; + + int count = 0; + + char *ptr = text; + if ( isdigit ( *ptr )) { + while (( *ptr != '\0' ) && ( *ptr != ')' )) { + if ( ! isdigit ( *ptr )) throw "Invalid list"; + while ( isdigit ( *ptr )) ptr++; + if ( *ptr == ',' ) ptr++; + while ( isspace ( *ptr )) ptr++; + count++; + } +/* Is this valid??? + } else { + if ( strncmp ( ptr, "ALL", 3 ) != 0 ) throw "Invalid list"; + ptr += 3; + while ( isspace ( *ptr )) ptr++; +*/ + } + + if (( close == true ) && ( *ptr != ')' )) throw "List must end with a ')'"; + + int *list = new int [ count + 1 ]; + for ( int i = 0; i < count; i++ ) { + list [i] = atoi ( text ); + while ( isdigit ( *text )) text++; + if ( *text == ',' ) text++; + while ( isspace ( *text )) text++; + } + + if ( close == true ) text++; + + list [count] = -1; + + return list; +} + +bool ParseGeneric ( char *text, const sOptionTableInfo &info, sRejectOptionRMB *option ) +{ + FUNCTION_ENTRY ( NULL, "ParseGeneric", true ); + + int dataIndex = 0; + int listIndex = 0; + + const char *syntax = info.Syntax; + + try { + + if ( syntax != NULL ) while ( *syntax ) { + while ( isspace ( *text )) text++; + switch ( *syntax++ ) { + case 'N' : + option->Data [dataIndex++] = ParseNumber ( text ); + break; + case 'L' : + option->List [listIndex++] = ParseList ( text, ( *syntax != '\0' ) ? true : false ); + break; + } + } + + while ( isspace ( *text )) text++; + if (( *text != '\0' ) && ( *text != '#' )) throw "unexpected characters after command"; + + option->Info = &info; + } + + catch ( const char *message ) { + ParseError ( "Syntax error - %s.", message ); + return false; + } + + return true; +} + +bool ParseMap ( char *text, const sOptionTableInfo &info, sRejectOptionRMB *option ) +{ + option->Info = &info; + + while ( ! isdigit ( *text )) text--; + + switch ( info.Type ) { + case OPTION_MAP_1 : + option->Data [0] = text [-2] - '0'; + option->Data [1] = text [0] - '0'; + break; + case OPTION_MAP_2 : + option->Data [0] = ( text [-1] - '0' ) * 10 + ( text [0] - '0' ); + break; + default : + ParseError ( "Unable to parse map name" ); + break; + } + + return true; +} + +bool ParseBAND ( char *text, const sOptionTableInfo &info, sRejectOptionRMB *option ) +{ + FUNCTION_ENTRY ( NULL, "ParseBAND", true ); + + REJECT_OPTION_E type = OPTION_UNKNOWN; + + while ( isspace ( *text )) text++; + + if ( strncmp ( text, "BLIND", 5 ) == 0 ) { + type = OPTION_BLIND; + text += 5; + } else if ( strncmp ( text, "SAFE", 5 ) == 0 ) { + type = OPTION_SAFE; + text += 4; + } else { + goto bad_option; + } + + if ( isspace ( *text )) { + bool retVal = ParseGeneric ( text, info, option ); + option->Banded = true; + option->Info = FindByType ( type ); + return retVal; + } + +bad_option: + + ParseError ( "Invalid BAND option." ); + return false; +} + +bool ParseINVERT ( char *text, const sOptionTableInfo &info, sRejectOptionRMB *option ) +{ + FUNCTION_ENTRY ( NULL, "ParseINVERT", true ); + + if ( ParseOptionRMB ( -1, text, option ) == true ) { + switch ( option->Info->Type ) { + case OPTION_BAND : + case OPTION_BLIND : + case OPTION_SAFE : + option->Inverted = true; + break; + default : + ParseError ( "Invalid %s option '%s'.", info.Name, text ); + return false; + } + return true; + } else { + ParseError ( "Unable to process %s effect.", info.Name ); + } + + return false; +} + +bool ParseNOPROCESS ( char *text, const sOptionTableInfo &info, sRejectOptionRMB *option ) +{ + FUNCTION_ENTRY ( NULL, "ParseNOPROCESS", true ); + + return false; +} + +bool ParseOptionRMB ( int lineNumber, const char *lineText, sRejectOptionRMB *option ) +{ + FUNCTION_ENTRY ( NULL, "ParseOptionRMB", true ); + + ASSERT ( lineText != NULL ); + ASSERT ( option != NULL ); + + if ( lineNumber > 0 ) { + parseLine = lineNumber; + parseText = lineText; + } + + memset ( option, 0, sizeof ( sRejectOptionRMB )); + + const char *srcText = lineText; + while (( *srcText != '\0' ) && ( isspace ( *srcText ))) srcText++; + + if (( *srcText == '#' ) || ( *srcText == '\0' )) return false; + + size_t length = strlen ( srcText ); + char *buffer = new char [ length + 1 ]; + for ( size_t i = 0; i < length; i++ ) { + buffer [i] = toupper ( srcText [i] ); + if (( buffer [i] == '\r' ) || ( buffer [i] == '\n' )) length = i; + } + buffer [length] = '\0'; + + char *start = buffer; + char *end = buffer; + while (( *end != '\0' ) && ( ! isspace ( *end ))) end++; + + bool retVal = false; + + if ( start != end ) { + + for ( unsigned i = 0; i < SIZE ( ParseTable ); i++ ) { + char *src = start; + const char *tgt = ParseTable [i].Name; + while (( src != end ) && ( *tgt != '\0' )) { + if ( *src != *tgt ) { + if ( *tgt != '*' ) goto next; + if ( ! isdigit ( *src )) goto next; + } + src++; + tgt++; + } + if (( i + 1 < SIZE ( ParseTable )) && ( strncmp ( buffer, ParseTable [i+1].Name, src - buffer ) == 0 )) { + ParseError ( "'%*.*s' is not a unique identifier.", end - buffer, end - buffer, buffer ); + goto done; + } + while ( isspace ( *end )) end++; + retVal = ParseTable [i].ParseFunction ( end, ParseTable [i], option ); + goto done; + next: + ; + } + + ParseError ( "Unrecognized effect '%*.*s'.", end - buffer, end - buffer, buffer ); + } + +done: + + delete [] buffer; + + return retVal; +} + +void PrintOption ( FILE *file, sRejectOptionRMB *option ) +{ + const sOptionTableInfo *info = option->Info; + const char *syntax = info->Syntax; + + if ( option->Inverted ) fprintf ( file, "INVERT " ); + if ( option->Banded ) { + syntax = "NNL"; + fprintf ( file, "BAND " ); + } + + switch ( info->Type ) { + case OPTION_MAP_1 : + fprintf ( file, "E%dM%d", option->Data [0], option->Data [1] ); + break; + case OPTION_MAP_2 : + fprintf ( file, "MAP%02d", option->Data [0] ); + break; + default : + fprintf ( file, "%s", info->Name ); + break; + } + + int dataIndex = 0; + int listIndex = 0; + + if ( syntax != NULL ) while ( *syntax ) { + switch ( *syntax++ ) { + case 'N' : + fprintf ( file, " %d", option->Data [dataIndex++] ); + break; + case 'L' : + int *list; + fprintf ( file, " (" ); + list = option->List [listIndex++]; + while ( *list != -1 ) fprintf ( file, " %d", *list++ ); + fprintf ( file, " )" ); + break; + default : + fprintf ( stderr, "Internal error: Invalid syntax\n" ); + break; + } + } + + fprintf ( file, "\n" ); +} diff --git a/ZenNode/ZenReject-util.cpp b/ZenNode/ZenReject-util.cpp new file mode 100644 index 0000000..3a59bed --- /dev/null +++ b/ZenNode/ZenReject-util.cpp @@ -0,0 +1,198 @@ +void DotGraph ( sGraph *graph ) +{ + int maxMetric = 0; + + for ( int x = 0; x < graph->noSectors; x++ ) { + if ( graph->sector [x]->metric > maxMetric ) maxMetric = graph->sector [x]->metric; + } + + fprintf ( stderr, "digraph g {\n" ); + fprintf ( stderr, "\tsize = \"12,12\";\n" ); + fprintf ( stderr, "\tnode [shape=record];\n" ); + + for ( int x = 0; x < graph->noSectors; x++ ) { + sSector *sec1 = graph->sector [x]; + if ( sec1->graphParent != NULL ) fprintf ( stderr, "\t%d -> %d%s;\n", sec1->graphParent->index, sec1->index, ( sec1->loDFS < sec1->indexDFS ) ? " [color=green]" : "" ); + for ( int i = 0; i < sec1->noNeighbors; i++ ) { + sSector *sec2 = sec1->neighbor [i]; + if ( sec1->graphParent == sec2 ) continue; + if (( sec2->graphParent != sec1 ) && ( sec1->index > sec2->index )) fprintf ( stderr, "\t%d -> %d [color=red];\n", sec1->index, sec2->index ); + } + } + + for ( int x = 0; x < graph->noSectors; x++ ) { + sSector *sec = graph->sector [x]; + fprintf ( stderr, "\t%d [label=\"%d | { %d | %d | %d } | %d\"];\n", sec->index, sec->loDFS, sec->index, sec->indexDFS, sec->metric, sec->hiDFS ); + } + + fprintf ( stderr, "}\n" ); + +} + +void NeatoGraph ( sGraph *graph ) +{ + int maxMetric = 0; + + for ( int x = 0; x < graph->noSectors; x++ ) { + if ( graph->sector [x]->metric > maxMetric ) maxMetric = graph->sector [x]->metric; + } + + fprintf ( stderr, "graph g {\n" ); + fprintf ( stderr, "\tsize = \"12,12\";\n" ); + fprintf ( stderr, "\tnode [style=filled,fontcolor=white];\n" ); + + for ( int x = 0; x < graph->noSectors; x++ ) { + sSector *sec1 = graph->sector [x]; + for ( int i = 0; i < sec1->noNeighbors; i++ ) { + sSector *sec2 = sec1->neighbor [i]; + if ( sec1->index < sec2->index ) fprintf ( stderr, "\t%d -- %d;\n", sec1->index, sec2->index ); + } + } + + for ( int x = 0; x < graph->noSectors; x++ ) { + sSector *sec = graph->sector [x]; + int color = ( maxMetric != 0 ) ? ( int ) ( 0xFF * sqrt (( double ) sec->metric / ( double ) maxMetric )) : 0; + fprintf ( stderr, "\t%d [color=\"#%02X%02X%02X\"%s];\n", sec->index, color, 0, 0, ( color > 0x80 ) ? ",fontcolor=black" : "" ); + } + + fprintf ( stderr, "}\n" ); + +} + +void DumpGraph ( sGraph *graph ) +{ + fprintf ( stderr, "%08X:", graph ); + for ( int x = 0; x < graph->noSectors; x++ ) fprintf ( stderr, " %d", graph->sector [x]->index ); + fprintf ( stderr, "\n" ); +} + +void PrintGraph ( sGraph *graph ) +{ + fprintf ( stderr, "%08X:\n", graph ); + + int artCount = 0, childCount = 0; + + for ( int i = 0; i < graph->noSectors; i++ ) { + sSector *sec = graph->sector [i]; + if ( sec->isArticulation == true ) artCount++; + if ( sec->noChildren > 0 ) childCount++; + fprintf ( stderr, " %4d(%4d-%4d,%4d,%7d)%s %4d %3d/%3d", sec->index, sec->loDFS, sec->indexDFS, sec->hiDFS, sec->metric, sec->isArticulation ? "*" : " ", sec->noChildren, sec->noActiveNeighbors, sec->noNeighbors ); + if ( sec->noActiveNeighbors == 2 ) { + sSector *sec1 = sec->neighbor [0]; + sSector *sec2 = sec->neighbor [1]; + if (( sec1->isArticulation == true ) && ( sec2->isArticulation == true )) { + fprintf ( stderr, " -step-" ); + } + } + while ( sec->parent != NULL ) { + sec = sec->parent; + fprintf ( stderr, " %4d%s", sec->index, sec->isArticulation ? "*" : "" ); + } + fprintf ( stderr, "\n" ); + } + fprintf ( stderr, " %4d %3d\n", artCount, childCount ); + + fprintf ( stderr, "\n" ); +} + +int CompareREJECT ( UCHAR *srcPtr, UCHAR *tgtPtr, int noSectors ) +{ + FUNCTION_ENTRY ( NULL, "CompareREJECT", true ); + + bool match = true; + + int **vis2hid = new int * [ noSectors ]; + int **hid2vis = new int * [ noSectors ]; + + int *v2hCount = new int [ noSectors ]; + int *h2vCount = new int [ noSectors ]; + + int bits = 8; + int srcVal = *srcPtr++; + int tgtVal = *tgtPtr++; + int dif = srcVal ^ tgtVal; + for ( int i = 0; i < noSectors; i++ ) { + vis2hid [i] = new int [ noSectors ]; + hid2vis [i] = new int [ noSectors ]; + memset ( vis2hid [i], 0, noSectors * sizeof ( int )); + memset ( hid2vis [i], 0, noSectors * sizeof ( int )); + v2hCount [i] = 0; + h2vCount [i] = 0; + for ( int j = 0; j < noSectors; j++ ) { + if ( dif & 1 ) { + if ( srcVal & 1 ) { + hid2vis [i][h2vCount [i]++] = j; + } else { + vis2hid [i][v2hCount [i]++] = j; + } + match = false; + } + if ( --bits == 0 ) { + bits = 8; + srcVal = *srcPtr++; + tgtVal = *tgtPtr++; + dif = srcVal ^ tgtVal; + } else { + srcVal >>= 1; + tgtVal >>= 1; + dif >>= 1; + } + } + } + + bool first = true; + for ( int i = 0; i < noSectors; i++ ) { + if (( v2hCount [i] == 0 ) && ( h2vCount [i] == 0 )) continue; + bool v2h = false; + for ( int j = 0; j < v2hCount [i]; j++ ) { + int index = vis2hid [i][j]; + if (( v2hCount [i] > v2hCount [index] ) || + (( v2hCount [i] == v2hCount [index] ) && ( i > index ))) { + v2h = true; + break; + } + } + bool h2v = false; + for ( int j = 0; j < h2vCount [i]; j++ ) { + int index = hid2vis [i][j]; + if (( h2vCount [i] > h2vCount [index] ) || + (( h2vCount [i] == h2vCount [index] ) && ( i > index ))) { + h2v = true; + break; + } + } + if ( v2h == true ) { + if ( first == false ) printf ( " " ); + printf ( "vis->hid %5d:", i ); + for ( int j = 0; j < v2hCount [i]; j++ ) { + printf ( " %d", vis2hid [i][j] ); + } + printf ( "\n" ); + first = false; + } + if ( h2v == true ) { + if ( first == false ) printf ( " " ); + printf ( "hid->vis %5d:", i ); + for ( int j = 0; j < h2vCount [i]; j++ ) { + printf ( " %d", hid2vis [i][j] ); + } + printf ( "\n" ); + first = false; + } + } + + if ( match == true ) printf ( "Perfect Match\n" ); + + for ( int i = 0; i < noSectors; i++ ) { + delete [] vis2hid [i]; + delete [] hid2vis [i]; + } + + delete [] vis2hid; + delete [] hid2vis; + + delete [] v2hCount; + delete [] h2vCount; + + return match ? 0 : 1; +} diff --git a/ZenNode/ZenReject.cpp b/ZenNode/ZenReject.cpp new file mode 100644 index 0000000..8817c07 --- /dev/null +++ b/ZenNode/ZenReject.cpp @@ -0,0 +1,2437 @@ +//---------------------------------------------------------------------------- +// +// File: ZenReject.cpp +// Date: 15-Dec-1995 +// Programmer: Marc Rousseau +// +// Description: This module contains the logic for the REJECT builder. +// +// Copyright (c) 1995-2004 Marc Rousseau, All Rights Reserved. +// +// 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 2 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, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. +// +// Revision History: +// +// 06-12-99 Reordered functions & removed all function prototypes +// 06-14-99 Modified DrawBlockMapLine to elminate floating point & inlined calls to UpdateRow +// 07-19-99 Added code to track child sectors and active lines (36% faster!) +// 04-01-01 Added code to use graphs to reduce LOS calculations (way faster!) +// 12-02-02 Updated graph code to do a more thorough job (subsumes older child code) +// 01-18-04 Added support for RMB options +// +//---------------------------------------------------------------------------- + +#include +#include +#include +#include +#include +#include +#include "common.hpp" +#include "logger.hpp" +#include "level.hpp" +#include "ZenNode.hpp" +#include "console.hpp" +#include "geometry.hpp" + +DBG_REGISTER ( __FILE__ ); + +// ----- Local enum/structure definitions ----- + +const UINT8 VIS_UNKNOWN = 0x00; +const UINT8 VIS_VISIBLE = 0x01; // No actual LOS exists +const UINT8 VIS_HIDDEN = 0x02; // At least 1 valid LOS found +const UINT8 VIS_RMB_VISIBLE = 0x04; // Special - ignores VIS_RMB_HIDDEN +const UINT8 VIS_RMB_HIDDEN = 0x08; // Special - force sector to be hidden +const UINT8 VIS_RMB_MASK = 0x0C; // Special - RMB option present + +inline bool IsHidden ( UINT8 vis ) { return ( ! ( vis & VIS_VISIBLE ) | (( vis & VIS_RMB_MASK ) == VIS_RMB_HIDDEN )) ? true : false; } + +struct sMapLine { + int index; + const sPoint *start; + const sPoint *end; +}; + +struct sSolidLine : sMapLine { + bool ignore; +}; + +struct sTransLine : sMapLine { + int leftSector; + int rightSector; + long DY, DX, H; + REAL lo, hi; + sPoint *loPoint; + sPoint *hiPoint; +}; + +struct sPolyLine { + int noPoints; + int lastPoint; + const sPoint **point; +}; + +struct sLineSet { + sSolidLine **lines; + int loIndex; + int hiIndex; +}; + +struct sWorldInfo { + sTransLine *src; + sTransLine *tgt; + sLineSet solidSet; + sPolyLine upperPoly; + sPolyLine lowerPoly; +}; + +struct sBlockMapBounds { + int lo; + int hi; +}; + +struct sBlockMapArrayEntry { + bool *available; + sSolidLine *line; +}; + +struct sGraph; + +struct sSector { + int index; + int noLines; + sTransLine **line; + int noNeighbors; + sSector **neighbor; + + int metric; + sGraph *baseGraph; + + sGraph *graph; + sSector *graphParent; + bool isArticulation; + int indexDFS; + int loDFS; + int hiDFS; +}; + +struct sGraph { + int noSectors; + sSector **sector; +}; + +struct sGraphTable { + int noGraphs; + sGraph *graph; + sSector **sectorStart; + sSector **sectorPool; +}; + +struct sSectorRMB { + int Safe; + int SafeLo; + int SafeHi; + int Blind; + int BlindLo; + int BlindHi; +}; + +static sGraphTable graphTable; + +static int loRow; +static int hiRow; + +static sBlockMap *blockMap; +static sBlockMapBounds *blockMapBounds; +static sBlockMapArrayEntry ***blockMapArray; + +static UINT8 **rejectTable; + +static UINT8 *lineVisTable; + +static sPoint *vertices; +static int noSolidLines; +static sSolidLine *solidLines; +static int noTransLines; +static sTransLine *transLines; + +static sTransLine **sectorLines; +static sSector **neighborList; + +static int checkLineSize; +static bool *checkLine; +static bool *lineProcessed; +static sSolidLine **indexToSolid; +static sSolidLine **testLines; +static const sPoint **polyPoints; + +static int maxMapDistance; + +static long X, Y, DX, DY; + +bool FeaturesDetected ( DoomLevel *level ) +{ + FUNCTION_ENTRY ( NULL, "FeaturesDetected", true ); + + char *ptr = ( char * ) level->GetReject (); + if ( ptr == NULL ) return false; + + int noSectors = level->SectorCount (); + + // Make sure it's a valid REJECT structure before analyzing it + int rejectSize = (( noSectors * noSectors ) + 7 ) / 8; + if ( level->RejectSize () != rejectSize ) return false; + + int bits = 9; + int data = *ptr++; + bool **table = new bool * [ noSectors ]; + for ( int i = 0; i < noSectors; i++ ) { + table [i] = new bool [ noSectors ]; + for ( int j = 0; j < noSectors; j++ ) { + if ( --bits == 0 ) { + bits = 8; + data = *ptr++; + } + table [i][j] = data & 1; + data >>= 1; + } + } + + bool featureDetected = false; + + // Look for "special" features + for ( int i = 0; i < noSectors; i++ ) { + // Make sure each sector can see itself + if ( table [i][i] != 0 ) { + featureDetected = true; + goto done; + } + for ( int j = i + 1; j < noSectors; j++ ) { + // Make sure that if I can see J, then J can see I + if ( table [i][j] != table [j][i] ) { + featureDetected = true; + goto done; + } + } + } + +done: + + for ( int i = 0; i < noSectors; i++ ) { + delete [] table [i]; + } + delete [] table; + + return featureDetected; +} + +// +// Run through our rejectTable to create the actual REJECT resource +// +UINT8 *GetREJECT ( DoomLevel *level, bool empty ) +{ + FUNCTION_ENTRY ( NULL, "GetREJECT", true ); + + int noSectors = level->SectorCount (); + int rejectSize = (( noSectors * noSectors ) + 7 ) / 8; + + UINT8 *reject = new UINT8 [ rejectSize ]; + memset ( reject, 0, rejectSize ); + + if ( empty == false ) { + // The rejectTable's data is sequential, making it easy to unroll the loop + UINT8 *ptr = rejectTable [0]; + for ( int i = 0, index = 0; i < rejectSize; i++ ) { + int bits = 0; + if ( IsHidden ( *ptr++ )) bits |= 0x01; + if ( IsHidden ( *ptr++ )) bits |= 0x02; + if ( IsHidden ( *ptr++ )) bits |= 0x04; + if ( IsHidden ( *ptr++ )) bits |= 0x08; + if ( IsHidden ( *ptr++ )) bits |= 0x10; + if ( IsHidden ( *ptr++ )) bits |= 0x20; + if ( IsHidden ( *ptr++ )) bits |= 0x40; + if ( IsHidden ( *ptr++ )) bits |= 0x80; + reject [ index++ ] = ( UINT8 ) bits; + } + } + + return reject; +} + +void UpdateProgress ( int stage, double percent ) +{ + FUNCTION_ENTRY ( NULL, "UpdateProgress", false ); + + char buffer [32]; + sprintf ( buffer, ( stage == 1 ) ? "REJECT - Pruning sectors %0.1f%%" : + ( stage == 2 ) ? "REJECT - Analyzing lines %0.1f%%" : "REJECT - ??? %0.1f%%", percent ); + Status ( buffer ); +} + +void MarkVisibility ( int sector1, int sector2, UINT8 visibility ) +{ + FUNCTION_ENTRY ( NULL, "MarkVisibility", false ); + + if ( rejectTable [ sector1 ][ sector2 ] == VIS_UNKNOWN ) { + rejectTable [ sector1 ][ sector2 ] = visibility; + } + if ( rejectTable [ sector2 ][ sector1 ] == VIS_UNKNOWN ) { + rejectTable [ sector2 ][ sector1 ] = visibility; + } +} + +void CopyVertices ( DoomLevel *level ) +{ + FUNCTION_ENTRY ( NULL, "CopyVertices", true ); + + int noVertices = level->VertexCount (); + vertices = new sPoint [ noVertices ]; + const wVertex *vertex = level->GetVertices (); + + for ( int i = 0; i < noVertices; i++ ) { + vertices [i].x = vertex [i].x; + vertices [i].y = vertex [i].y; + } +} + +// +// Create lists of all the solid and see-thru lines in the map +// +bool SetupLines ( DoomLevel *level ) +{ + FUNCTION_ENTRY ( NULL, "SetupLines", true ); + + int noLineDefs = level->LineDefCount (); + + const wLineDef *lineDef = level->GetLineDefs (); + const wSideDef *sideDef = level->GetSideDefs (); + + checkLineSize = sizeof ( bool ) * noLineDefs; + checkLine = new bool [ noLineDefs ]; + lineProcessed = new bool [ noLineDefs ]; + + indexToSolid = new sSolidLine * [ noLineDefs ]; + memset ( indexToSolid, 0, sizeof ( sSolidLine * ) * noLineDefs ); + + noSolidLines = 0; + noTransLines = 0; + solidLines = new sSolidLine [ noLineDefs ]; + transLines = new sTransLine [ noLineDefs ]; + + for ( int i = 0; i < noLineDefs; i++ ) { + + sMapLine *line; + const sPoint *vertS = &vertices [ lineDef [i].start ]; + const sPoint *vertE = &vertices [ lineDef [i].end ]; + + // We can't handle 0 length lineDefs! + if ( vertS == vertE ) continue; + + if ( lineDef [i].flags & LDF_TWO_SIDED ) { + + int rSide = lineDef [i].sideDef [ RIGHT_SIDEDEF ]; + int lSide = lineDef [i].sideDef [ LEFT_SIDEDEF ]; + if (( lSide == NO_SIDEDEF ) || ( rSide == NO_SIDEDEF )) continue; + if ( sideDef [ lSide ].sector == sideDef [ rSide ].sector ) continue; + sTransLine *stLine = &transLines [ noTransLines++ ]; + line = ( sMapLine * ) stLine; + stLine->leftSector = sideDef [ lSide ].sector; + stLine->rightSector = sideDef [ rSide ].sector; + stLine->DX = vertE->x - vertS->x; + stLine->DY = vertE->y - vertS->y; + stLine->H = ( stLine->DX * stLine->DX ) + ( stLine->DY * stLine->DY ); + + } else { + + indexToSolid [i] = &solidLines [ noSolidLines++ ]; + line = ( sMapLine * ) indexToSolid [i]; + + } + + line->index = i; + line->start = vertS; + line->end = vertE; + } + + int lineVisSize = ( noTransLines - 1 ) * noTransLines / 2; + lineVisTable = new UINT8 [ lineVisSize ]; + memset ( lineVisTable, 0, sizeof ( UINT8 ) * lineVisSize ); + + return ( noTransLines > 0 ) ? true : false; +} + +// +// Mark sectors sec1 & sec2 as neighbors of each other +// +void MakeNeighbors ( sSector *sec1, sSector *sec2 ) +{ + FUNCTION_ENTRY ( NULL, "MakeNeighbors", false ); + + for ( int i = 0; i < sec1->noNeighbors; i++ ) { + if ( sec1->neighbor [i] == sec2 ) return; + } + + sec1->neighbor [ sec1->noNeighbors++ ] = sec2; + sec2->neighbor [ sec2->noNeighbors++ ] = sec1; +} + +// +// Create the sector table that records all the see-thru lines related to a +// sector and all of it's neighboring and child sectors. +// +sSector *CreateSectorInfo ( DoomLevel *level ) +{ + FUNCTION_ENTRY ( NULL, "CreateSectorInfo", true ); + + Status ( "Gathering sector information..." ); + + int noSectors = level->SectorCount (); + + sSector *sector = new sSector [ noSectors ]; + memset ( sector, 0, sizeof ( sSector ) * noSectors ); + + // Count the number of lines for each sector first + for ( int i = 0; i < noTransLines; i++ ) { + sTransLine *line = &transLines [ i ]; + sector [ line->leftSector ].noLines++; + sector [ line->rightSector ].noLines++; + } + + // Set up the line & neighbor array for each sector + sTransLine **lines = sectorLines = new sTransLine * [ noTransLines * 2 ]; + sSector **neighbors = neighborList = new sSector * [ noTransLines * 2 ]; + for ( int i = 0; i < noSectors; i++ ) { + sector [i].index = i; + sector [i].line = lines; + sector [i].neighbor = neighbors; + lines += sector [i].noLines; + neighbors += sector [i].noLines; + sector [i].noLines = 0; + } + + // Fill in line information & mark off neighbors + for ( int i = 0; i < noTransLines; i++ ) { + sTransLine *line = &transLines [ i ]; + sSector *sec1 = §or [ line->leftSector ]; + sSector *sec2 = §or [ line->rightSector ]; + sec1->line [ sec1->noLines++ ] = line; + sec2->line [ sec2->noLines++ ] = line; + MakeNeighbors ( sec1, sec2 ); + } + + return sector; +} + +int **CreateDistanceTable ( sSector *sector, int noSectors ) +{ + FUNCTION_ENTRY ( NULL, "CreateDistanceTable", true ); + + Status ( "Calculating sector distances..." ); + + char *distBuffer = new char [ sizeof ( int ) * noSectors * noSectors + sizeof ( int * ) * noSectors ]; + int **distanceTable = ( int ** ) distBuffer; + + distBuffer += sizeof ( int * ) * noSectors; + + int listRowSize = noSectors + 2; + + UINT16 *listBuffer = new UINT16 [ listRowSize * noSectors * 2 ]; + memset ( listBuffer, 0, sizeof ( UINT16 ) * listRowSize * noSectors * 2 ); + + UINT16 *list [2] = { listBuffer, listBuffer + listRowSize * noSectors }; + + for ( int i = 0; i < noSectors; i++ ) { + distanceTable [i] = ( int * ) distBuffer; + distBuffer += sizeof ( int ) * noSectors; + // Set up the initial distances + for ( int j = 0; j < noSectors; j++ ) { + distanceTable [i][j] = INT_MAX; + } + // Prime the first list + list [0][i*listRowSize+0] = ( UINT16 ) i; + list [0][i*listRowSize+1] = ( UINT16 ) i; + list [0][i*listRowSize+2+i] = true; + } + + int currIndex = 0, length = 0; + int loRow = 0, hiRow = noSectors - 1; + + int count; + + // Find the # of sectors between each pair of sectors + do { + count = 0; + + int nextIndex = ( currIndex + 1 ) % 2; + UINT16 *currList = list [currIndex] + 2 + loRow * listRowSize; + UINT16 *nextList = list [nextIndex] + 2 + loRow * listRowSize; + currIndex = nextIndex; + + int i = loRow, max = hiRow; + loRow = noSectors; + hiRow = 0; + + for ( ; i <= max; i++ ) { + int loIndex = currList [-2]; + int hiIndex = currList [-1]; + int minIndex = noSectors, maxIndex = 0; + // See if this row needs to be processed + if ( loIndex <= hiIndex ) { + int startCount = count; + for ( int j = loIndex; j <= hiIndex; j++ ) { + if ( currList [j] == false ) continue; + if ( length < distanceTable [i][j] ) { + distanceTable [i][j] = length; + for ( int x = 0; x < sector [j].noNeighbors; x++ ) { + int index = sector [j].neighbor [x] - sector; + nextList [index] = true; + if ( index < minIndex ) minIndex = index; + if ( index > maxIndex ) maxIndex = index; + } + count++; + } + currList [j] = false; + } + // Should we process this row next time around? + if ( startCount != count ) { + if ( i < loRow ) loRow = i; + if ( i > hiRow ) hiRow = i; + } + } + nextList [-2] = ( UINT16 ) minIndex; + nextList [-1] = ( UINT16 ) maxIndex; + currList += listRowSize; + nextList += listRowSize; + } + length++; + } while ( count ); + + // Now mark all sectors with no path to each other as hidden + for ( int i = 0; i < noSectors; i++ ) { + for ( int j = i + 1; j < noSectors; j++ ) { + if ( distanceTable [i][j] > length ) { + MarkVisibility ( i, j, VIS_HIDDEN ); + } + } + } + + delete [] listBuffer; + + return distanceTable; +} + +int DFS ( sGraph *graph, sSector *sector ) +{ + FUNCTION_ENTRY ( NULL, "DFS", false ); + + // Initialize the sector + sector->graph = graph; + sector->indexDFS = graph->noSectors; + sector->loDFS = graph->noSectors; + sector->isArticulation = false; + + // Add this sector to the graph + graph->sector [graph->noSectors++] = sector; + + int noChildren = 0; + + for ( int i = 0; i < sector->noNeighbors; i++ ) { + sSector *child = sector->neighbor [i]; + if ( child->graph != graph ) { + noChildren++; + child->graphParent = sector; + DFS ( graph, child ); + if ( child->loDFS < sector->loDFS ) { + sector->loDFS = child->loDFS; + } + if ( child->loDFS >= sector->indexDFS ) { + sector->isArticulation = true; + } + } else if ( child != sector->graphParent ) { + if ( child->indexDFS < sector->loDFS ) { + sector->loDFS = child->indexDFS; + } + } + } + + sector->hiDFS = graph->noSectors - 1; + + return noChildren; +} + +sGraph *CreateGraph ( sSector *root ) +{ + FUNCTION_ENTRY ( NULL, "CreateGraph", true ); + + sGraph *graph = &graphTable.graph [ graphTable.noGraphs++ ]; + + graph->sector = graphTable.sectorStart; + graph->noSectors = 0; + + root->graphParent = NULL; + root->isArticulation = ( DFS ( graph, root ) > 1 ) ? true : false; + + graphTable.sectorStart += graph->noSectors; + + return graph; +} + +void HideComponents ( sGraph *oldGraph, sGraph *newGraph ) +{ + FUNCTION_ENTRY ( NULL, "HideComponents", true ); + + for ( int i = 0; i < oldGraph->noSectors; i++ ) { + sSector *sec1 = oldGraph->sector [i]; + if ( sec1->graph == oldGraph ) { + for ( int j = 0; j < newGraph->noSectors; j++ ) { + sSector *sec2 = newGraph->sector [j]; + MarkVisibility ( sec1->index, sec2->index, VIS_HIDDEN ); + } + } + } +} + +void SplitGraph ( sGraph *oldGraph ) +{ + FUNCTION_ENTRY ( NULL, "SplitGraph", true ); + + int remainingSectors = oldGraph->noSectors - 1; + + for ( int i = 0; i < oldGraph->noSectors; i++ ) { + sSector *sec = oldGraph->sector [i]; + if ( sec->graph == oldGraph ) { + sGraph *newGraph = CreateGraph ( sec ); + if ( newGraph->noSectors < remainingSectors ) { + HideComponents ( oldGraph, newGraph ); + } + remainingSectors -= newGraph->noSectors - 1; + } + } +} + +void InitializeGraphs ( sSector *sector, int noSectors ) +{ + FUNCTION_ENTRY ( NULL, "InitializeGraphs", true ); + + Status ( "Creating sector graphs..." ); + + graphTable.noGraphs = 0; + graphTable.graph = new sGraph [ noSectors * 2 ]; + graphTable.sectorPool = new sSector * [ noSectors * 4 ]; + graphTable.sectorStart = graphTable.sectorPool; + + memset ( graphTable.graph, 0, sizeof ( sGraph ) * noSectors * 2 ); + memset ( graphTable.sectorPool, 0, sizeof ( sSector * ) * noSectors * 4 ); + + // Create the initial graph + sGraph *graph = &graphTable.graph [0]; + graph->noSectors = noSectors; + graph->sector = graphTable.sectorStart; + graphTable.sectorStart += noSectors; + graphTable.noGraphs++; + + // Put all sectors in the initial graph + for ( int i = 0; i < noSectors; i++ ) { + sector [i].graph = graph; + graph->sector [i] = §or [i]; + } + + // Separate the individual graphs + SplitGraph ( graph ); + + // Keep a permanent copy of the initial graph + for ( int i = 0; i < noSectors; i++ ) { + sector [i].baseGraph = sector [i].graph; + } + + // Calculate the sector metrics + for ( int i = 1; i < graphTable.noGraphs; i++ ) { + sGraph *graph = &graphTable.graph [i]; + for ( int j = 0; j < graph->noSectors; j++ ) { + sSector *sec = graph->sector [j]; + int sum = 0, left = graph->noSectors - 1; + for ( int x = 0; x < sec->noNeighbors; x++ ) { + sSector *child = sec->neighbor [x]; + if ( child->graphParent != sec ) continue; + if ( child->loDFS >= sec->indexDFS ) { + int num = child->hiDFS - child->indexDFS + 1; + left -= num; + sum += num * left; + } + } + sec->metric = sum; + } + } +} + +void HideSectorFromComponents ( sSector *key, sSector *root, sSector *sec ) +{ + FUNCTION_ENTRY ( NULL, "HideSectorFromComponents", false ); + + sGraph *graph = sec->graph; + + // Hide sec from all other sectors in its graph that are in different bi-connected components + for ( int i = 0; i < root->indexDFS; i++ ) { + MarkVisibility ( sec->index, graph->sector [i]->index, VIS_HIDDEN ); + } + for ( int i = root->hiDFS + 1; i < graph->noSectors; i++ ) { + MarkVisibility ( sec->index, graph->sector [i]->index, VIS_HIDDEN ); + } +} + +void AddGraph ( sGraph *graph, sSector *sector ) +{ + FUNCTION_ENTRY ( NULL, "AddGraph", false ); + + // Initialize the sector + sector->graph = graph; + sector->indexDFS = graph->noSectors; + sector->loDFS = graph->noSectors; + + // Add this sector to the graph + graph->sector [graph->noSectors++] = sector; + + // Add all this nodes children that aren't already in the graph + for ( int i = 0; i < sector->noNeighbors; i++ ) { + sSector *child = sector->neighbor [i]; + if ( child->graph == sector->baseGraph ) { + child->graphParent = sector; + AddGraph ( graph, child ); + if ( child->loDFS < sector->loDFS ) { + sector->loDFS = child->loDFS; + } + } else if ( child != sector->graphParent ) { + if ( child->indexDFS < sector->loDFS ) { + sector->loDFS = child->indexDFS; + } + } + } + + sector->hiDFS = graph->noSectors - 1; +} + +sGraph *QuickGraph ( sSector *root ) +{ + FUNCTION_ENTRY ( NULL, "QuickGraph", true ); + + sGraph *oldGraph = root->baseGraph; + for ( int i = 0; i < oldGraph->noSectors; i++ ) { + oldGraph->sector [i]->graph = oldGraph; + } + + sGraph *graph = &graphTable.graph [ graphTable.noGraphs ]; + + graph->sector = graphTable.sectorStart; + graph->noSectors = 0; + + root->graphParent = NULL; + + AddGraph ( graph, root ); + + return graph; +} + +void EliminateTrivialCases ( sSector *sector, int noSectors ) +{ + FUNCTION_ENTRY ( NULL, "EliminateTrivialCases", true ); + + // Each sector can see itself + for ( int i = 0; i < noSectors; i++ ) { + rejectTable [i][i] = VIS_VISIBLE; + } + + // Mark all sectors with no see-thru lines as hidden + for ( int i = 0; i < noSectors; i++ ) { + if ( sector [i].noLines == 0 ) { + for ( int j = 0; j < noSectors; j++ ) { + MarkVisibility ( i, j, VIS_HIDDEN ); + } + } + } + + // Each sector can see it's immediate neighbor(s) + for ( int i = 0; i < noSectors; i++ ) { + sSector *sec = §or [i]; + for ( int j = 0; j < sec->noNeighbors; j++ ) { + sSector *neighbor = sec->neighbor [j]; + if ( neighbor->index > sec->index ) { + MarkVisibility ( sec->index, neighbor->index, VIS_VISIBLE ); + } + } + } +} + +void PrepareREJECT ( int noSectors ) +{ + FUNCTION_ENTRY ( NULL, "PrepareREJECT", true ); + + // Allocate the whole table in 1 whole chunk (with 7 extra bytes to simplify GetREJECT) + int tableSize = noSectors * ( sizeof ( char * ) + noSectors ) + 7; + rejectTable = ( UINT8 ** ) malloc ( tableSize ); + memset ( rejectTable, 0, tableSize ); + + UINT8 *ptr = ( UINT8 * ) ( rejectTable + noSectors ); + for ( int i = 0; i < noSectors; i++ ) { + rejectTable [i] = ptr; + ptr += noSectors; + } + + // This is purely cosmetic - keep the unused bits in the last byte clear + ptr [0] = VIS_VISIBLE; + ptr [1] = VIS_VISIBLE; + ptr [2] = VIS_VISIBLE; + ptr [3] = VIS_VISIBLE; + ptr [4] = VIS_VISIBLE; + ptr [5] = VIS_VISIBLE; + ptr [6] = VIS_VISIBLE; +} + +void CleanUpREJECT ( int noSectors ) +{ + FUNCTION_ENTRY ( NULL, "CleanUpREJECT", true ); + + free ( rejectTable ); +} + +// +// Create a local BLOCKMAP that only contains entries for solid lines +// +void PrepareBLOCKMAP ( DoomLevel *level ) +{ + FUNCTION_ENTRY ( NULL, "PrepareBLOCKMAP", true ); + + blockMap = GenerateBLOCKMAP ( level ); + + blockMapArray = new sBlockMapArrayEntry ** [ blockMap->noRows ]; + blockMapBounds = new sBlockMapBounds [ blockMap->noRows ]; + for ( int index = 0, row = 0; row < blockMap->noRows; row++ ) { + blockMapArray [ row ] = new sBlockMapArrayEntry * [ blockMap->noColumns ]; + blockMapBounds [ row ].lo = blockMap->noColumns; + blockMapBounds [ row ].hi = -1; + for ( int col = 0; col < blockMap->noColumns; col++ ) { + sBlockMapArrayEntry *newPtr = NULL; + sBlockList *blockList = &blockMap->data [index++]; + if ( blockList->count > 0 ) { + int j = 0; + newPtr = new sBlockMapArrayEntry [blockList->count+1]; + for ( int i = 0; i < blockList->count; i++ ) { + int line = blockList->line [i]; + if ( indexToSolid [ line ] != NULL ) { + newPtr [j].available = &checkLine [ line ]; + newPtr [j].line = indexToSolid [ line ]; + j++; + } + } + if ( j == 0 ) { + delete [] newPtr; + newPtr = NULL; + } else { + newPtr [j].available = NULL; + } + } + blockMapArray [ row ][ col ] = newPtr; + } + } + + int totalSize = blockMap->noColumns * blockMap->noRows; + + for ( int i = 0; i < totalSize; i++ ) { + if ( blockMap->data [i].line ) free ( blockMap->data [i].line ); + } + + delete [] blockMap->data; +} + +void CleanUpBLOCKMAP () +{ + FUNCTION_ENTRY ( NULL, "CleanUpBLOCKMAP", true ); + + delete [] blockMapBounds; + for ( int row = 0; row < blockMap->noRows; row++ ) { + for ( int col = 0; col < blockMap->noColumns; col++ ) { + if ( blockMapArray [ row ][ col ] ) delete [] blockMapArray [ row ][ col ]; + } + delete [] blockMapArray [ row ]; + } + delete [] blockMapArray; + delete blockMap; +} + +// +// Adjust the two line so that: +// 1) If one line bisects the other: +// a) The bisecting line is tgt +// b) The point farthest from src is made both start & end +// 2) tgt is on the left side of src +// 3) src and tgt go in 'opposite' directions +// +bool AdjustLinePair ( sTransLine *src, sTransLine *tgt, bool *bisects ) +{ + FUNCTION_ENTRY ( NULL, "AdjustLinePair", true ); + + // Rotate & Translate so that src lies along the +X asix + long y1 = src->DX * ( tgt->start->y - src->start->y ) - src->DY * ( tgt->start->x - src->start->x ); + long y2 = src->DX * ( tgt->end->y - src->start->y ) - src->DY * ( tgt->end->x - src->start->x ); + + // The two lines are co-linear and should be ignored + if (( y1 == 0 ) && ( y2 == 0 )) return false; + + // Make sure that src doesn't bi-sect tgt + if ((( y1 > 0 ) && ( y2 < 0 )) || (( y1 < 0 ) && ( y2 > 0 ))) { + // Swap src & tgt then recalculate the endpoints + swap ( *src, *tgt ); + y1 = src->DX * ( tgt->start->y - src->start->y ) - src->DY * ( tgt->start->x - src->start->x ); + y2 = src->DX * ( tgt->end->y - src->start->y ) - src->DY * ( tgt->end->x - src->start->x ); + // See if these two lines actually intersect + if ((( y1 > 0 ) && ( y2 < 0 )) || (( y1 < 0 ) && ( y2 > 0 ))) { + fprintf ( stderr, "ERROR: Two lines (%d & %d) intersect\n", src->index, tgt->index ); + return false; + } + } + + // Make sure that tgt will end up on the correct (left) side + if (( y1 <= 0 ) && ( y2 <= 0 )) { + // Flip src + swap ( src->start, src->end ); + // Adjust values y1 and y2 end reflect new src + src->DX = -src->DX; + src->DY = -src->DY; + y1 = -y1; + y2 = -y2; + } + + // See if the lines are parallel + if ( y2 == y1 ) { + long x1 = src->DX * ( tgt->start->x - src->start->x ) + src->DY * ( tgt->start->y - src->start->y ); + long x2 = src->DX * ( tgt->end->x - src->start->x ) + src->DY * ( tgt->end->y - src->start->y ); + if ( x1 < x2 ) { swap ( tgt->start, tgt->end ); tgt->DX = -tgt->DX; tgt->DY = -tgt->DY; } + return true; + } + + // Now look at src from tgt's point of view + long x1 = tgt->DX * ( src->start->y - tgt->start->y ) - tgt->DY * ( src->start->x - tgt->start->x ); + long x2 = tgt->DX * ( src->end->y - tgt->start->y ) - tgt->DY * ( src->end->x - tgt->start->x ); + + // See if a line along tgt intersects src + if ((( x1 < 0 ) && ( x2 > 0 )) || (( x1 > 0 ) && ( x2 < 0 ))) { + *bisects = true; + // Make sure tgt points away from src + if ( y1 > y2 ) { + swap ( tgt->start, tgt->end ); tgt->DX = -tgt->DX; tgt->DY = -tgt->DY; + } + } else if (( x1 <= 0 ) && ( x2 <= 0 )) { + swap ( tgt->start, tgt->end ); tgt->DX = -tgt->DX; tgt->DY = -tgt->DY; + } + + return true; +} + +inline void UpdateRow ( int column, int row ) +{ + FUNCTION_ENTRY ( NULL, "UpdateRow", false ); + + sBlockMapBounds *bound = &blockMapBounds [ row ]; + if ( column < bound->lo ) bound->lo = column; + if ( column > bound->hi ) bound->hi = column; +} + +void DrawBlockMapLine ( const sPoint *p1, const sPoint *p2 ) +{ + FUNCTION_ENTRY ( NULL, "DrawBlockMapLine", false ); + + long x0 = p1->x - blockMap->xOrigin; + long y0 = p1->y - blockMap->yOrigin; + long x1 = p2->x - blockMap->xOrigin; + long y1 = p2->y - blockMap->yOrigin; + + int startX = x0 / 128, startY = y0 / 128; + int endX = x1 / 128, endY = y1 / 128; + + if ( startY < loRow ) loRow = startY; + if ( startY > hiRow ) hiRow = startY; + + if ( endY < loRow ) loRow = endY; + if ( endY > hiRow ) hiRow = endY; + + UpdateRow ( startX, startY ); + + if ( startX == endX ) { + + if ( startY != endY ) { // vertical line + int dy = (( endY - startY ) > 0 ) ? 1 : -1; + do { + startY += dy; + UpdateRow ( startX, startY ); + } while ( startY != endY ); + } + + } else { + + if ( startY != endY ) { // diagonal line + + int dy = (( endY - startY ) > 0 ) ? 1 : -1; + + // Calculate the pre-scaled values to be used in the for loop + int deltaX = ( x1 - x0 ) * 128 * dy; + int deltaY = ( y1 - y0 ) * 128; + int nextX = x0 * ( y1 - y0 ); + + // Figure out where the 1st row ends + switch ( dy ) { + case -1 : nextX += ( startY * 128 - y0 ) * ( x1 - x0 ); break; + case 1 : nextX += ( startY * 128 + 128 - y0 ) * ( x1 - x0 ); break; + } + + int lastX = nextX / deltaY; + UpdateRow ( lastX, startY ); + + // Now do the rest using integer math - each row is a delta Y of 128 + sBlockMapBounds *bound = &blockMapBounds [ startY ]; + sBlockMapBounds *endBound = &blockMapBounds [ endY ]; + if ( x0 < x1 ) { + for ( EVER ) { + // Do the next row + bound += dy; + if ( lastX < bound->lo ) bound->lo = lastX; + // Stop before we overshoot endX + if ( bound == endBound ) break; + nextX += deltaX; + lastX = nextX / deltaY; + if ( lastX > bound->hi ) bound->hi = lastX; + } + } else { + for ( EVER ) { + // Do the next row + bound += dy; + if ( lastX > bound->hi ) bound->hi = lastX; + // Stop before we overshoot endX + if ( bound == endBound ) break; + nextX += deltaX; + lastX = nextX / deltaY; + if ( lastX < bound->lo ) bound->lo = lastX; + } + } + } + + UpdateRow ( endX, endY ); + } +} + +void MarkBlockMap ( sWorldInfo *world ) +{ + FUNCTION_ENTRY ( NULL, "MarkBlockMap", true ); + + loRow = blockMap->noRows; + hiRow = -1; + + // Determine boundaries for the BLOCKMAP search + DrawBlockMapLine ( world->src->start, world->src->end ); + DrawBlockMapLine ( world->tgt->start, world->tgt->end ); + DrawBlockMapLine ( world->src->start, world->tgt->end ); + DrawBlockMapLine ( world->tgt->start, world->src->end ); +} + +bool FindInterveningLines ( sLineSet *set ) +{ + FUNCTION_ENTRY ( NULL, "FindInterveningLines", true ); + + // Reset the checked flag for GetLines + memset ( checkLine, true, checkLineSize ); + + // Mark all lines that have been bounded + int lineCount = 0; + + for ( int row = loRow; row <= hiRow; row++ ) { + sBlockMapBounds *bound = &blockMapBounds [ row ]; + for ( int col = bound->lo; col <= bound->hi; col++ ) { + sBlockMapArrayEntry *ptr = blockMapArray [ row ][ col ]; + if ( ptr != NULL ) do { + set->lines [ lineCount ] = ptr->line; + lineCount += ( *ptr->available == true ) ? 1 : 0; + *ptr->available = false; + } while ( (++ptr)->available ); + } + bound->lo = blockMap->noColumns; + bound->hi = -1; + } + + set->loIndex = 0; + set->hiIndex = lineCount - 1; + set->lines [ lineCount ] = NULL; + + return ( lineCount > 0 ) ? true : false; +} + +void GetBounds ( const sPoint *ss, const sPoint *se, const sPoint *ts, const sPoint *te, + long *loY, long *hiY, long *loX, long *hiX ) +{ + FUNCTION_ENTRY ( NULL, "GetBounds", true ); + + if ( ss->y < se->y ) { + if ( ts->y < te->y ) { + *loY = ( ss->y < ts->y ) ? ss->y : ts->y; + *hiY = ( se->y > te->y ) ? se->y : te->y; + } else { + *loY = ( ss->y < te->y ) ? ss->y : te->y; + *hiY = ( se->y > ts->y ) ? se->y : ts->y; + } + } else { + if ( ts->y < te->y ) { + *loY = ( se->y < ts->y ) ? se->y : ts->y; + *hiY = ( ss->y > te->y ) ? ss->y : te->y; + } else { + *loY = ( se->y < te->y ) ? se->y : te->y; + *hiY = ( ss->y > ts->y ) ? ss->y : ts->y; + } + } + if ( ss->x < se->x ) { + if ( ts->x < te->x ) { + *loX = ( ss->x < ts->x ) ? ss->x : ts->x; + *hiX = ( se->x > te->x ) ? se->x : te->x; + } else { + *loX = ( ss->x < te->x ) ? ss->x : te->x; + *hiX = ( se->x > ts->x ) ? se->x : ts->x; + } + } else { + if ( ts->x < te->x ) { + *loX = ( se->x < ts->x ) ? se->x : ts->x; + *hiX = ( ss->x > te->x ) ? ss->x : te->x; + } else { + *loX = ( se->x < te->x ) ? se->x : te->x; + *hiX = ( ss->x > ts->x ) ? ss->x : ts->x; + } + } +} + +bool TrimSetBounds ( sLineSet *set ) +{ + FUNCTION_ENTRY ( NULL, "TrimSetBounds", true ); + + if ( set->loIndex >= set->hiIndex ) return false; + + while ( set->lines [ set->loIndex ]->ignore == true ) { + set->loIndex++; + if ( set->loIndex >= set->hiIndex ) return false; + } + + while ( set->lines [ set->hiIndex ]->ignore == true ) { + set->hiIndex--; + } + + return true; +} + +inline void RotatePoint ( sPoint *p, int x, int y ) +{ + FUNCTION_ENTRY ( NULL, "RotatePoint", false ); + + p->x = DX * ( x - X ) + DY * ( y - Y ); + p->y = DX * ( y - Y ) - DY * ( x - X ); +} + +int TrimLines ( const sTransLine *src, const sTransLine *tgt, sLineSet *set ) +{ + FUNCTION_ENTRY ( NULL, "TrimLines", true ); + + long loY, hiY, loX, hiX; + GetBounds ( src->start, src->end, tgt->start, tgt->end, &loY, &hiY, &loX, &hiX ); + + // Set up globals used by RotatePoint + X = src->start->x; + Y = src->start->y; + DX = tgt->end->x - src->start->x; + DY = tgt->end->y - src->start->y; + + // Variables for a rotated bounding box + sPoint p1, p2, p3; + RotatePoint ( &p1, src->end->x, src->end->y ); + RotatePoint ( &p2, tgt->start->x, tgt->start->y ); + RotatePoint ( &p3, tgt->end->x, tgt->end->y ); + + long minX = ( p1.x < 0 ) ? 0 : p1.x; + long maxX = ( p2.x < p3.x ) ? p2.x : p3.x; + long minY = ( p1.y < p2.y ) ? p1.y : p2.y; + + int linesLeft = 0; + + bool checkBlock = (( minX <= maxX ) && (( DX != 0 ) || ( DY != 0 ))) ? true : false; + + for ( int i = set->loIndex; i <= set->hiIndex; i++ ) { + + sSolidLine *line = set->lines [i]; + + line->ignore = true; + + // Eliminate any lines completely outside the axis aligned bounding box + if ( line->start->y <= loY ) { + if ( line->end->y <= loY ) continue; + } else if ( line->start->y >= hiY ) { + if ( line->end->y >= hiY ) continue; + } + if ( line->start->x >= hiX ) { + if ( line->end->x >= hiX ) continue; + } else if ( line->start->x <= loX ) { + if ( line->end->x <= loX ) continue; + } + + // Stop if we find a single line that obstructs the view completely + if ( checkBlock == true ) { + sPoint start, end; + start.y = DX * ( line->start->y - Y ) - DY * ( line->start->x - X ); + if (( start.y >= 0 ) || ( start.y <= minY )) { + end.y = DX * ( line->end->y - Y ) - DY * ( line->end->x - X ); + if ((( end.y <= minY ) && ( start.y >= 0 )) || (( end.y >= 0 ) && ( start.y <= minY ))) { + start.x = DX * ( line->start->x - X ) + DY * ( line->start->y - Y ); + if (( start.x >= minX ) && ( start.x <= maxX )) { + end.x = DX * ( line->end->x - X ) + DY * ( line->end->y - Y ); + if (( end.x >= minX ) && ( end.x <= maxX )) { + return -1; + } + } + // Use the new information and see if line is outside the bounding box + } else if ((( end.y >= 0 ) && ( start.y >= 0 )) || (( end.y <= minY ) && ( start.y <= minY ))) { + continue; + } + } + } + + line->ignore = false; + linesLeft++; + + } + + if ( linesLeft == 0 ) return 0; + + if ((( src->DX != 0 ) && ( src->DY != 0 )) || (( tgt->DX != 0 ) && ( tgt->DY != 0 ))) { + + // Eliminate lines that touch the src/tgt lines but are not in view + for ( int i = set->loIndex; i <= set->hiIndex; i++ ) { + sSolidLine *line = set->lines [i]; + if ( line->ignore == true ) continue; + int y = 1; + if (( line->start == src->start ) || ( line->start == src->end )) { + y = src->DX * ( line->end->y - src->start->y ) - src->DY * ( line->end->x - src->start->x ); + } else if (( line->end == src->start ) || ( line->end == src->end )) { + y = src->DX * ( line->start->y - src->start->y ) - src->DY * ( line->start->x - src->start->x ); + } else if (( line->start == tgt->start ) || ( line->start == tgt->end )) { + y = tgt->DX * ( line->end->y - tgt->start->y ) - tgt->DY * ( line->end->x - tgt->start->x ); + } else if (( line->end == tgt->start ) || ( line->end == tgt->end )) { + y = tgt->DX * ( line->start->y - tgt->start->y ) - tgt->DY * ( line->start->x - tgt->start->x ); + } + if ( y <= 0 ) { + line->ignore = true; + linesLeft--; + } + } + } + + TrimSetBounds ( set ); + + return linesLeft; +} + +// +// Find out which side of the poly-line the line is on +// +// Return Values: +// 1 - above (not completely below) the poly-line +// 0 - intersects the poly-line +// -1 - below the poly-line (one or both end-points may touch the poly-line) +// -2 - can't tell start this segment +// + +int Intersects ( const sPoint *p1, const sPoint *p2, const sPoint *t1, const sPoint *t2 ) +{ + FUNCTION_ENTRY ( NULL, "Intersects", false ); + + long DX, DY, y1, y2; + + // Rotate & translate using p1->p2 as the +X-axis + DX = p2->x - p1->x; + DY = p2->y - p1->y; + + y1 = DX * ( t1->y - p1->y ) - DY * ( t1->x - p1->x ); + y2 = DX * ( t2->y - p1->y ) - DY * ( t2->x - p1->x ); + + // Eliminate the 2 easy cases (t1 & t2 both above or below the x-axis) + if (( y1 > 0 ) && ( y2 > 0 )) return 1; + if (( y1 <= 0 ) && ( y2 <= 0 )) return -1; + // t1->t2 crosses poly-Line segment (or one point touches it and the other is above it) + + // Rotate & translate using t1->t2 as the +X-axis + DX = t2->x - t1->x; + DY = t2->y - t1->y; + + y1 = DX * ( p1->y - t1->y ) - DY * ( p1->x - t1->x ); + y2 = DX * ( p2->y - t1->y ) - DY * ( p2->x - t1->x ); + + // Eliminate the 2 easy cases (p1 & p2 both above or below the x-axis) + if (( y1 > 0 ) && ( y2 > 0 )) return -2; + if (( y1 < 0 ) && ( y2 < 0 )) return -2; + + return 0; +} + +int FindSide ( sMapLine *line, sPolyLine *poly ) +{ + FUNCTION_ENTRY ( NULL, "FindSide", false ); + + bool completelyBelow = true; + for ( int i = 0; i < poly->noPoints - 1; i++ ) { + const sPoint *p1 = poly->point [i]; + const sPoint *p2 = poly->point [i+1]; + switch ( Intersects ( p1, p2, line->start, line->end )) { + case -1 : break; + case 0 : return 0; + case -2 : + case 1 : completelyBelow = false; + } + } + return completelyBelow ? -1 : 1; +} + +void AddToPolyLine ( sPolyLine *poly, sSolidLine *line ) +{ + FUNCTION_ENTRY ( NULL, "AddToPolyLine", true ); + + long DX, DY, y1, y2; + + y1 = 0; + + // Find new index start from the 'left' + int i; + for ( i = 0; i < poly->noPoints - 1; i++ ) { + const sPoint *p1 = poly->point [i]; + const sPoint *p2 = poly->point [i+1]; + DX = p2->x - p1->x; + DY = p2->y - p1->y; + + y1 = DX * ( line->start->y - p1->y ) - DY * ( line->start->x - p1->x ); + y2 = DX * ( line->end->y - p1->y ) - DY * ( line->end->x - p1->x ); + if (( y1 > 0 ) != ( y2 > 0 )) break; + } + i += 1; + + // Find new index start from the 'right' + int j; + for ( j = poly->noPoints - 1; j > i; j-- ) { + const sPoint *p1 = poly->point [j-1]; + const sPoint *p2 = poly->point [j]; + DX = p2->x - p1->x; + DY = p2->y - p1->y; + + long y1 = DX * ( line->start->y - p1->y ) - DY * ( line->start->x - p1->x ); + long y2 = DX * ( line->end->y - p1->y ) - DY * ( line->end->x - p1->x ); + if (( y1 > 0 ) != ( y2 > 0 )) break; + } + + int ptsRemoved = j - i; + int toCopy = poly->noPoints - j; + if ( toCopy > 0 ) memmove ( &poly->point [i+1], &poly->point [j], sizeof ( sPoint * ) * toCopy ); + poly->noPoints += 1 - ptsRemoved; + + poly->point [i] = ( y1 > 0 ) ? line->start : line->end; + poly->lastPoint = i; +} + +bool PolyLinesCross ( sPolyLine *upper, sPolyLine *lower ) +{ + bool foundAbove = false, ambiguous = false; + int last = 0, max = upper->noPoints - 1; + if ( upper->lastPoint != -1 ) { + max = 2; + last = upper->lastPoint - 1; + } + for ( int i = 0; i < max; i++ ) { + const sPoint *p1 = upper->point [ last + i ]; + const sPoint *p2 = upper->point [ last + i + 1 ]; + for ( int j = 0; j < lower->noPoints - 1; j++ ) { + const sPoint *p3 = lower->point [j]; + const sPoint *p4 = lower->point [j+1]; + switch ( Intersects ( p1, p2, p3, p4 )) { + case 1 : foundAbove = true; + break; + case 0 : return true; + case -2 : ambiguous = true; + break; + } + } + } + + if ( foundAbove == true ) return false; + + if ( ambiguous == true ) { + const sPoint *p1 = upper->point [0]; + const sPoint *p2 = upper->point [ upper->noPoints - 1 ]; + long DX = p2->x - p1->x; + long DY = p2->y - p1->y; + for ( int i = 1; i < lower->noPoints - 1; i++ ) { + const sPoint *testPoint = lower->point [i]; + if ( DX * ( testPoint->y - p1->y ) - DY * ( testPoint->x - p1->x ) < 0 ) return true; + } + } + + return false; +} + +bool CorrectForNewStart ( sPolyLine *poly ) +{ + FUNCTION_ENTRY ( NULL, "CorrectForNewStart", true ); + + const sPoint *p0 = poly->point [0]; + for ( int i = poly->noPoints - 1; i > 1; i-- ) { + const sPoint *p1 = poly->point [i]; + const sPoint *p2 = poly->point [i-1]; + long dx = p1->x - p0->x; + long dy = p1->y - p0->y; + long y = dx * ( p2->y - p0->y ) - dy * ( p2->x - p0->x ); + if ( y < 0 ) { + poly->point [i-1] = p0; + poly->point += i - 1; + poly->noPoints -= i - 1; + poly->lastPoint -= i - 1; + return true; + } + } + return false; +} + +bool CorrectForNewEnd ( sPolyLine *poly ) +{ + FUNCTION_ENTRY ( NULL, "CorrectForNewEnd", true ); + + const sPoint *p0 = poly->point [ poly->noPoints - 1 ]; + for ( int i = 0; i < poly->noPoints - 2; i++ ) { + const sPoint *p1 = poly->point [i]; + const sPoint *p2 = poly->point [i+1]; + long dx = p0->x - p1->x; + long dy = p0->y - p1->y; + long y = dx * ( p2->y - p1->y ) - dy * ( p2->x - p1->x ); + if ( y < 0 ) { + poly->point [i+1] = p0; + poly->noPoints -= poly->noPoints - i - 2; + return true; + } + } + return false; +} + +bool AdjustEndPoints ( sTransLine *left, sTransLine *right, sPolyLine *upper, sPolyLine *lower ) +{ + FUNCTION_ENTRY ( NULL, "AdjustEndPoints", true ); + + if ( upper->lastPoint == -1 ) return true; + const sPoint *test = upper->point [ upper->lastPoint ]; + + long dx, dy, y; + bool changed = false; + + dx = test->x - left->hiPoint->x; + dy = test->y - left->hiPoint->y; + y = dx * ( right->hiPoint->y - left->hiPoint->y ) - + dy * ( right->hiPoint->x - left->hiPoint->x ); + if ( y > 0 ) { + long num = ( right->start->y - left->hiPoint->y ) * dx - + ( right->start->x - left->hiPoint->x ) * dy; + long det = right->DX * dy - right->DY * dx; + REAL t = ( REAL ) num / ( REAL ) det; + if ( t <= right->lo ) return false; + if ( t < right->hi ) { + right->hi = t; + right->hiPoint->x = right->start->x + ( long ) ( t * right->DX ); + right->hiPoint->y = right->start->y + ( long ) ( t * right->DY ); + changed |= CorrectForNewStart ( upper ); + } + } + + dx = test->x - right->loPoint->x; + dy = test->y - right->loPoint->y; + y = dx * ( left->loPoint->y - right->loPoint->y ) - + dy * ( left->loPoint->x - right->loPoint->x ); + if ( y < 0 ) { + long num = ( left->start->y - right->loPoint->y ) * dx - + ( left->start->x - right->loPoint->x ) * dy; + long det = left->DX * dy - left->DY * dx; + REAL t = ( REAL ) num / ( REAL ) det; + if ( t >= left->hi ) return false; + if ( t > left->lo ) { + left->lo = t; + left->loPoint->x = left->start->x + ( long ) ( t * left->DX ); + left->loPoint->y = left->start->y + ( long ) ( t * left->DY ); + changed |= CorrectForNewEnd ( upper ); + } + } + + return (( changed == true ) && ( PolyLinesCross ( upper, lower ) == true )) ? false : true; +} + +bool FindPolyLines ( sWorldInfo *world ) +{ + FUNCTION_ENTRY ( NULL, "FindPolyLines", true ); + + sPolyLine *upperPoly = &world->upperPoly; + sPolyLine *lowerPoly = &world->lowerPoly; + + sLineSet *set = &world->solidSet; + + for ( EVER ) { + + bool done = true; + bool stray = false; + + for ( int i = set->loIndex; i <= set->hiIndex; i++ ) { + + sSolidLine *line = set->lines [ i ]; + if ( line->ignore == true ) continue; + + switch ( FindSide ( line, lowerPoly )) { + + case 1 : // Completely above the lower/right poly-Line + switch ( FindSide ( line, upperPoly )) { + + case 1 : // Line is between the two poly-lines + stray = true; + break; + + case 0 : // Intersects the upper/left poly-Line + if ( stray ) done = false; + AddToPolyLine ( upperPoly, line ); + if (( lowerPoly->noPoints > 2 ) && ( PolyLinesCross ( upperPoly, lowerPoly ) == true )) { + return false; + } + if ( AdjustEndPoints ( world->src, world->tgt, upperPoly, lowerPoly ) == false ) { + return false; + } + case -1 : // Completely above the upper/left poly-line + line->ignore = true; + break; + + } + break; + + case 0 : // Intersects the lower/right poly-Line + if ( stray == true ) done = false; + AddToPolyLine ( lowerPoly, line ); + if ( PolyLinesCross ( lowerPoly, upperPoly ) == true ) { + return false; + } + if ( AdjustEndPoints ( world->tgt, world->src, lowerPoly, upperPoly ) == false ) { + return false; + } + case -1 : // Completely below the lower/right poly-Line + line->ignore = true; + break; + + } + } + + if ( done == true ) break; + + TrimSetBounds ( set ); + } + + return true; +} + +bool FindObstacles ( sWorldInfo *world ) +{ + FUNCTION_ENTRY ( NULL, "FindObstacles", true ); + + if ( world->solidSet.hiIndex < world->solidSet.loIndex ) return false; + + // If we have an unbroken line between src & tgt there is a direct LOS + if ( world->upperPoly.noPoints == 2 ) return false; + if ( world->lowerPoly.noPoints == 2 ) return false; + + // To be absolutely correct, we should create a list of obstacles + // (ie: connected lineDefs completely enclosed by upperPoly & lowerPoly) + // and see if any of them completely block the LOS + + return false; +} + +void InitializeWorld ( sWorldInfo *world, sTransLine *src, sTransLine *tgt ) +{ + FUNCTION_ENTRY ( NULL, "InitializeWorld", true ); + + world->src = src; + world->tgt = tgt; + + world->solidSet.lines = testLines; + world->solidSet.loIndex = 0; + world->solidSet.hiIndex = -1; + + static sPoint p1, p2, p3, p4; + p1 = *src->start; + p2 = *src->end; + p3 = *tgt->start; + p4 = *tgt->end; + + src->loPoint = &p1; src->lo = 0.0; + src->hiPoint = &p2; src->hi = 1.0; + tgt->loPoint = &p3; tgt->lo = 0.0; + tgt->hiPoint = &p4; tgt->hi = 1.0; + + sPolyLine *lowerPoly = &world->lowerPoly; + lowerPoly->point = polyPoints; + lowerPoly->noPoints = 2; + lowerPoly->lastPoint = -1; + lowerPoly->point [0] = src->hiPoint; + lowerPoly->point [1] = tgt->loPoint; + + sPolyLine *upperPoly = &world->upperPoly; + upperPoly->point = &polyPoints [ noSolidLines + 2 ]; + upperPoly->noPoints = 2; + upperPoly->lastPoint = -1; + upperPoly->point [0] = tgt->hiPoint; + upperPoly->point [1] = src->loPoint; +} + +bool CheckLOS ( sTransLine *src, sTransLine *tgt ) +{ + FUNCTION_ENTRY ( NULL, "CheckLOS", true ); + + sWorldInfo myWorld; + InitializeWorld ( &myWorld, src, tgt ); + + MarkBlockMap ( &myWorld ); + + // See if there are any solid lines in the blockmap region between src & tgt + if ( FindInterveningLines ( &myWorld.solidSet ) == true ) { + + // If src & tgt touch, look for a quick way out - no lines passing through the common point + const sPoint *common = NULL; + if ((( common = src->end ) == tgt->start ) || (( common = src->start ) == tgt->end )) { + for ( int i = myWorld.solidSet.loIndex; i <= myWorld.solidSet.hiIndex; i++ ) { + sSolidLine *line = myWorld.solidSet.lines [i]; + if (( line->start == common ) || ( line->end == common )) goto more; + } + // The two lines touch and there are no lines blocking them + return true; + } + + more: + + // Do a more refined check to see if there are any lines + switch ( TrimLines ( myWorld.src, myWorld.tgt, &myWorld.solidSet )) { + + case -1 : // A single line completely blocks the view + return false; + + case 0 : // No intervening lines left - end check + break; + + default : + // Do an even more refined check + if ( FindPolyLines ( &myWorld ) == false ) return false; + // Now see if there are any obstacles that may block the LOS + if ( FindObstacles ( &myWorld ) == true ) return false; + } + } + + return true; +} + +bool DivideRegion ( sTransLine *src, sTransLine *tgt ) +{ + FUNCTION_ENTRY ( NULL, "DivideRegion", true ); + + // Find the point of intersection on src + long num = tgt->DX * ( src->start->y - tgt->start->y ) - tgt->DY * ( src->start->x - tgt->start->x ); + long det = src->DX * tgt->DY - src->DY * tgt->DX; + REAL t = ( REAL ) num / ( REAL ) det; + + sPoint crossPoint ( src->start->x + ( long ) ( t * src->DX ), src->start->y + ( long ) ( t * src->DY )); + + // See if we ran into an integer truncation problem (shortcut if we did) + if (( crossPoint == *src->start ) || ( crossPoint == *src->end )) { + return CheckLOS ( src, tgt ); + } + + sTransLine newSrc = *src; + + newSrc.end = &crossPoint; + + bool isVisible = CheckLOS ( &newSrc, tgt ); + + if ( isVisible == false ) { + newSrc.start = &crossPoint; + newSrc.end = src->end; + swap ( tgt->start, tgt->end ); + tgt->DX = -tgt->DX; + tgt->DY = -tgt->DY; + isVisible = CheckLOS ( &newSrc, tgt ); + } + + return isVisible; +} + +UINT8 GetLineVisibility ( const sTransLine *srcLine, const sTransLine *tgtLine ) +{ + FUNCTION_ENTRY ( NULL, "GetLineVisibility", true ); + + if ( srcLine == tgtLine ) return VIS_VISIBLE; + + int row = (( srcLine < tgtLine ) ? srcLine : tgtLine ) - transLines; + int col = (( srcLine < tgtLine ) ? tgtLine : srcLine ) - transLines; + + int offset = row * ( 2 * noTransLines - 1 - row ) / 2 + ( col - row - 1 ); + + UINT8 data = lineVisTable [ offset / 4 ]; + + return ( UINT8 ) ( 0x03 & ( data >> ( 2 * ( offset % 4 )))); +} + +void SetLineVisibility ( const sTransLine *srcLine, const sTransLine *tgtLine, UINT8 vis ) +{ + FUNCTION_ENTRY ( NULL, "SetLineVisibility", false ); + + if ( srcLine == tgtLine ) return; + + int row = (( srcLine < tgtLine ) ? srcLine : tgtLine ) - transLines; + int col = (( srcLine < tgtLine ) ? tgtLine : srcLine ) - transLines; + + int offset = row * ( 2 * noTransLines - 1 - row ) / 2 + ( col - row - 1 ); + + UINT8 data = lineVisTable [ offset / 4 ]; + + data &= ~ ( 0x03 << ( 2 * ( offset % 4 ))); + data |= vis << ( 2 * ( offset % 4 )); + + lineVisTable [ offset / 4 ] = data; +} + +bool DontBother ( const sTransLine *srcLine, const sTransLine *tgtLine ) +{ + FUNCTION_ENTRY ( NULL, "DontBother", true ); + + if (( rejectTable [ srcLine->leftSector ][ tgtLine->leftSector ] != VIS_UNKNOWN ) && + ( rejectTable [ srcLine->leftSector ][ tgtLine->rightSector ] != VIS_UNKNOWN ) && + ( rejectTable [ srcLine->rightSector ][ tgtLine->leftSector ] != VIS_UNKNOWN ) && + ( rejectTable [ srcLine->rightSector ][ tgtLine->rightSector ] != VIS_UNKNOWN )) { + return true; + } + + return false; +} + +int MapDistance ( const sPoint *p1, const sPoint *p2 ) +{ + FUNCTION_ENTRY ( NULL, "MapDistance", true ); + + int dx = p1->x - p2->x; + int dy = p1->y - p2->y; + + return dx * dx + dy * dy; +} + +bool PointTooFar ( const sPoint *p, const sTransLine *line ) +{ + FUNCTION_ENTRY ( NULL, "PointTooFar", true ); + + const sPoint *p1 = line->start; + const sPoint *p2 = line->end; + + int c1 = line->DX * ( p->x - p1->x ) + line->DY * ( p->y - p1->y ); + + if ( c1 <= 0 ) { + // 'p' is closest to the start of the line segment + return ( MapDistance ( p, p1 ) < maxMapDistance ) ? false : true; + } + + if ( c1 >= line->H ) { + // 'p' is closest to the end of the line segment + return ( MapDistance ( p, p2 ) < maxMapDistance ) ? false : true; + } + + int d = ( line->DX * ( p->y - p1->y ) - line->DY * ( p->x - p1->x )) / line->H; + + return ( d < maxMapDistance ) ? false : true; +} + +bool LinesTooFarApart ( const sTransLine *srcLine, const sTransLine *tgtLine ) +{ + FUNCTION_ENTRY ( NULL, "TestLinePair", true ); + + if (( maxMapDistance != INT_MAX ) && + ( PointTooFar ( srcLine->start, tgtLine ) == true ) && + ( PointTooFar ( srcLine->end, tgtLine ) == true ) && + ( PointTooFar ( tgtLine->start, srcLine ) == true ) && + ( PointTooFar ( tgtLine->end, srcLine ) == true )) { + STATUS ( "Lines " << srcLine->index << " and " << tgtLine->index << " are too far apart" ); + return true; + } + + return false; +} + +bool TestLinePair ( const sTransLine *srcLine, const sTransLine *tgtLine ) +{ + FUNCTION_ENTRY ( NULL, "TestLinePair", true ); + + UINT8 vis = GetLineVisibility ( srcLine, tgtLine ); + + if (( vis != VIS_UNKNOWN ) || ( DontBother ( srcLine, tgtLine ) == true )) { + return false; + } + + if ( LinesTooFarApart ( srcLine, tgtLine ) == true ) { + SetLineVisibility ( srcLine, tgtLine, VIS_HIDDEN ); + return false; + } + + sTransLine src = *srcLine; + sTransLine tgt = *tgtLine; + + bool isVisible = false; + + bool bisect = false; + if ( AdjustLinePair ( &src, &tgt, &bisect ) == true ) { + isVisible = ( bisect == true ) ? DivideRegion ( &src, &tgt ) : CheckLOS ( &src, &tgt ); + } + + SetLineVisibility ( srcLine, tgtLine, isVisible ? VIS_VISIBLE : VIS_HIDDEN ); + + return isVisible; +} + +void MarkPairVisible ( sTransLine *srcLine, sTransLine *tgtLine ) +{ + FUNCTION_ENTRY ( NULL, "MarkPairVisible", true ); + + // There is a direct LOS between the two lines - mark all affected sectors + MarkVisibility ( srcLine->leftSector, tgtLine->leftSector, VIS_VISIBLE ); + MarkVisibility ( srcLine->leftSector, tgtLine->rightSector, VIS_VISIBLE ); + MarkVisibility ( srcLine->rightSector, tgtLine->leftSector, VIS_VISIBLE ); + MarkVisibility ( srcLine->rightSector, tgtLine->rightSector, VIS_VISIBLE ); +} + +//---------------------------------------------------------------------------- +// Sort sectors so the the most critical articulation points are placed first +//---------------------------------------------------------------------------- +int SortSector ( const void *ptr1, const void *ptr2 ) +{ + FUNCTION_ENTRY ( NULL, "SortSector", false ); + + const sSector *sec1 = * ( const sSector ** ) ptr1; + const sSector *sec2 = * ( const sSector ** ) ptr2; + + // Favor the sector with the best metric (higher is better) + if ( sec1->metric != sec2->metric ) { + return sec2->metric - sec1->metric; + } + + // Favor the sector that is not part of a loop + int sec1Loop = ( sec1->loDFS < sec1->indexDFS ) ? 1 : 0; + int sec2Loop = ( sec2->loDFS < sec2->indexDFS ) ? 1 : 0; + + if ( sec1Loop != sec2Loop ) { + return sec1Loop - sec2Loop; + } + + // Favor the sector with the most neighbors + if ( sec1->noNeighbors != sec2->noNeighbors ) { + return sec2->noNeighbors - sec1->noNeighbors; + } + + // Favor the sector with the most visible lines + if ( sec1->noLines != sec2->noLines ) { + return sec2->noLines - sec1->noLines; + } + + // It's a tie - use the sector index - lower index favored + return sec1->index - sec2->index; +} + +//---------------------------------------------------------------------------- +// Create a mapping for see-thru lines that tries to put lines that affect +// the most sectors first. As more sectors are marked visible/hidden, the +// number of remaining line pairs that must be checked drops. By ordering +// the lines, we can speed things up quite a bit with just a little effort. +//---------------------------------------------------------------------------- +int SetupLineMap ( sTransLine **lineMap, sSector **sectorList, int maxSectors ) +{ + FUNCTION_ENTRY ( NULL, "SetupLineMap", true ); + + static bool inMap [0x10000]; + + memset ( inMap, 0, sizeof ( inMap )); + + int maxIndex = 0; + for ( int i = 0; i < maxSectors; i++ ) { + for ( int j = 0; j < sectorList [i]->noLines; j++ ) { + sTransLine *line = sectorList [i]->line [j]; + if ( inMap [line->index] == false ) { + inMap [line->index] = true; + lineMap [ maxIndex++ ] = line; + } + } + } + + return maxIndex; +} + +bool ShouldTest ( sTransLine *src, int key, sTransLine *tgt, int sector ) +{ + long y1 = src->DX * ( tgt->start->y - src->start->y ) - src->DY * ( tgt->start->x - src->start->x ); + long y2 = src->DX * ( tgt->end->y - src->start->y ) - src->DY * ( tgt->end->x - src->start->x ); + + if ( src->rightSector == key ) { + if (( y1 <= 0 ) && ( y2 <= 0 )) { + return false; + } + } else if (( y1 >= 0 ) && ( y2 >= 0 )) { + return false; + } + + long x1 = tgt->DX * ( src->start->y - tgt->start->y ) - tgt->DY * ( src->start->x - tgt->start->x ); + long x2 = tgt->DX * ( src->end->y - tgt->start->y ) - tgt->DY * ( src->end->x - tgt->start->x ); + + if ( tgt->rightSector == sector ) { + if (( x1 <= 0 ) && ( x2 <= 0 )) { + return false; + } + } else if (( x1 >= 0 ) && ( x2 >= 0 )) { + return false; + } + + return true; +} + +void ProcessSectorLines ( sSector *key, sSector *root, sSector *sector, sTransLine **lines ) +{ + FUNCTION_ENTRY ( NULL, "ProcessSectorLines", true ); + + bool isVisible = ( rejectTable [ key->index ][ sector->index ] == VIS_VISIBLE ) ? true : false; + bool isUnknown = ( rejectTable [ key->index ][ sector->index ] == VIS_UNKNOWN ) ? true : false; + + if ( isUnknown == true ) { + + sTransLine **ptr = lines; + + while ( *ptr != NULL ) { + + sTransLine *srcLine = *ptr++; + + for ( int i = 0; i < sector->noNeighbors; i++ ) { + sSector *child = sector->neighbor [i]; + // Test each line that may lead back to the key sector (can reach higher up in the graph) + if ( child->loDFS <= sector->indexDFS ) { + for ( int j = 0; j < sector->noLines; j++ ) { + sTransLine *tgtLine = sector->line [j]; + if (( tgtLine->leftSector == child->index ) || ( tgtLine->rightSector == child->index )) { + if ( ShouldTest ( srcLine, key->index, tgtLine, sector->index ) == true ) { + if ( TestLinePair ( srcLine, tgtLine )) { + MarkPairVisible ( srcLine, tgtLine ); + goto done; + } + } + } + } + } + } + } + } + + if ( isVisible == false ) { + + sGraph *graph = sector->graph; + + // See if we're in a loop + if ( sector->loDFS == sector->indexDFS ) { + + // Nope. Hide ourself and all our children from the other components + for ( int i = sector->indexDFS; i <= sector->hiDFS; i++ ) { + HideSectorFromComponents ( key, root, graph->sector [i] ); + } + + } else { + + // Yep. Hide ourself + HideSectorFromComponents ( key, root, sector ); + + for ( int i = 0; i < sector->noNeighbors; i++ ) { + sSector *child = sector->neighbor [i]; + if ( child->graphParent == sector ) { + // Hide any child components that aren't in the loop + if ( child->loDFS >= sector->indexDFS ) { + for ( int i = child->indexDFS; i <= child->hiDFS; i++ ) { + HideSectorFromComponents ( key, root, graph->sector [i] ); + } + } else { + ProcessSectorLines ( key, root, child, lines ); + } + } + } + } + + } else { + +done: + // Continue checking all of our children + for ( int i = 0; i < sector->noNeighbors; i++ ) { + sSector *child = sector->neighbor [i]; + if ( child->graphParent == sector ) { + ProcessSectorLines ( key, root, child, lines ); + } + } + } +} + +void ProcessSector ( sSector *sector ) +{ + FUNCTION_ENTRY ( NULL, "ProcessSector", true ); + + if ( sector->isArticulation == true ) { + + // For now, make sure this sector is at the top of the graph (keeps things simple) + QuickGraph ( sector ); + + sTransLine **lines = new sTransLine * [ sector->noLines + 1 ]; + + for ( int i = 0; i < sector->noNeighbors; i++ ) { + + sSector *child = sector->neighbor [i]; + + // Find each child that is the start of a component of this sector + if ( child->graphParent == sector ) { + + // Make a list of lines that connect this component + int index = 0; + for ( int j = 0; j < sector->noLines; j++ ) { + sTransLine *line = sector->line [j]; + if (( line->leftSector == child->index ) || ( line->rightSector == child->index )) { + lines [index++] = line; + } + } + + // If this child is part of a loop, add lines from all the other children in the loop too + if ( child->loDFS < child->indexDFS ) { + for ( int j = i + 1; j < sector->noNeighbors; j++ ) { + sSector *child2 = sector->neighbor [j]; + if ( child2->indexDFS <= child->hiDFS ) { + for ( int k = 0; k < sector->noLines; k++ ) { + sTransLine *line = sector->line [k]; + if (( line->leftSector == child2->index ) || ( line->rightSector == child2->index )) { + lines [index++] = line; + } + } + } + } + } + + lines [index] = NULL; + + ProcessSectorLines ( sector, child, child, lines ); + } + + } + + delete [] lines; + + } else { + + sGraph *graph = sector->baseGraph; + + UINT8 *rejectRow = rejectTable [ sector->index ]; + + for ( int i = 0; i < graph->noSectors; i++ ) { + + sSector *tgtSector = graph->sector [i]; + + if ( rejectRow [ tgtSector->index ] == VIS_UNKNOWN ) { + + for ( int j = 0; j < sector->noLines; j++ ) { + sTransLine *srcLine = sector->line [j]; + for ( int k = 0; k < tgtSector->noLines; k++ ) { + sTransLine *tgtLine = tgtSector->line [k]; + if ( TestLinePair ( srcLine, tgtLine ) == true ) { + MarkPairVisible ( srcLine, tgtLine ); + goto next; + } + } + } + + MarkVisibility ( sector->index, tgtSector->index, VIS_HIDDEN ); + + next: + + ; + } + } + } +} + +bool NeedDistances ( const sRejectOptionRMB *rmb ) +{ + FUNCTION_ENTRY ( NULL, "NeedDistances", true ); + + if ( rmb == NULL ) return false; + + // Look for any RMB option that requires distances + for ( int i = 0; rmb [i].Info != NULL; i++ ) { + switch ( rmb [i].Info->Type ) { + case OPTION_BLIND : + case OPTION_LENGTH : + case OPTION_SAFE : + case OPTION_REPORT : + return true; + default : + break; + } + } + + return false; +} + +void ApplyDistanceLimits ( const sRejectOptionRMB *rmb, int noSectors, int **distanceTable ) +{ + FUNCTION_ENTRY ( NULL, "ApplyDistanceLimits", true ); + + if ( rmb == NULL ) return; + + int maxLength = INT_MAX; + + for ( int i = 0; rmb [i].Info != NULL; i++ ) { + if ( rmb [i].Info->Type == OPTION_LENGTH ) { + // Remember the last LENGTH option seen + maxLength = rmb [i].Data [0]; + } + } + + // Did we find a LENGTH option? + if ( maxLength != INT_MAX ) { + for ( int x = 0; x < noSectors; x++ ) { + for ( int y = x + 1; y < noSectors; y++ ) { + if ( distanceTable [x][y] > maxLength ) { + rejectTable [x][y] = VIS_HIDDEN; + rejectTable [y][x] = VIS_HIDDEN; + } + } + } + } +} + +int FindMaxMapDistance ( const sRejectOptionRMB *rmb ) +{ + FUNCTION_ENTRY ( NULL, "FindMaxMapDistance", true ); + + int maxDistance = INT_MAX; + + if ( rmb != NULL ) { + for ( int i = 0; rmb [i].Info != NULL; i++ ) { + if ( rmb [i].Info->Type == OPTION_DISTANCE ) { + // Store the square of the distance (avoid floating point later on) + maxDistance = rmb [i].Data [0] * rmb [i].Data [0]; + } + } + } + + return maxDistance; +} + +void ProcessOptionsRMB ( const sRejectOptionRMB *rmb, int noSectors, int **distanceTable ) +{ + FUNCTION_ENTRY ( NULL, "ProcessOptionsRMB", true ); + + if ( rmb == NULL ) return; + + // Handle the options that rely on distance + if ( distanceTable != NULL ) { + + sSectorRMB *sectorList = new sSectorRMB [noSectors]; + memset ( sectorList, -1, sizeof ( sSectorRMB ) * noSectors ); + + // Populate sectorList with the BLIND/SAFE information + for ( int i = 0; rmb [i].Info != NULL; i++ ) { + if ( rmb [i].Info->Type == OPTION_BLIND ) { + if ( rmb [i].Banded == true ) { + for ( int j = 0; rmb [i].List [0][j] != -1; j++ ) { + sSectorRMB *sector = §orList [rmb [i].List [0][j]]; + sector->Blind = rmb [i].Inverted ? 7 : 4; + sector->BlindLo = rmb [i].Data [0]; + sector->BlindHi = rmb [i].Data [1]; + } + } else { + for ( int j = 0; rmb [i].List [0][j] != -1; j++ ) { + sSectorRMB *sector = §orList [rmb [i].List [0][j]]; + if ( sector->Blind < 4 ) { + if ( sector->Blind == -1 ) sector->Blind = 0; + sector->Blind |= rmb [i].Inverted ? 2 : 1; + ( rmb [i].Inverted ? sector->BlindHi : sector->BlindLo ) = rmb [i].Data [0]; + } + } + } + } + if ( rmb [i].Info->Type == OPTION_SAFE ) { + if ( rmb [i].Banded == true ) { + for ( int j = 0; rmb [i].List [0][j] != -1; j++ ) { + sSectorRMB *sector = §orList [rmb [i].List [0][j]]; + sector->Safe = rmb [i].Inverted ? 7 : 4; + sector->SafeLo = rmb [i].Data [0]; + sector->SafeHi = rmb [i].Data [1]; + } + } else { + for ( int j = 0; rmb [i].List [0][j] != -1; j++ ) { + sSectorRMB *sector = §orList [rmb [i].List [0][j]]; + if ( sector->Safe < 4 ) { + if ( sector->Safe == -1 ) sector->Safe = 0; + sector->Safe |= rmb [i].Inverted ? 2 : 1; + ( rmb [i].Inverted ? sector->SafeHi : sector->SafeLo ) = rmb [i].Data [0]; + } + } + } + } + } + + // Do the BLIND/SAFE thing + for ( int i = 0; i < noSectors; i++ ) { + sSectorRMB *sector = §orList [i]; + if ( sector->Blind > 0 ) { + if ( sector->Blind == 3 ) { + if ( sector->BlindLo > sector->BlindHi ) { + swap ( sector->BlindLo, sector->BlindHi ); + sector->Blind = 4; + } + } + for ( int j = 0; j < noSectors; j++ ) { + if ( sector->Blind & 1 ) { + // Handle normal BLIND + if ( distanceTable [i][j] >= sector->BlindLo ) { + rejectTable [i][j] |= VIS_RMB_HIDDEN; + } + } + if ( sector->Blind & 2 ) { + // Handle inverse BLIND + if ( distanceTable [i][j] < sector->BlindHi ) { + rejectTable [i][j] |= VIS_RMB_HIDDEN; + } + } + if ( sector->Blind == 4 ) { + // Handle normal BAND BLIND + if (( distanceTable [i][j] >= sector->BlindLo ) && + ( distanceTable [i][j] < sector->BlindHi )) { + rejectTable [i][j] |= VIS_RMB_HIDDEN; + } + } + } + } + if ( sector->Safe > 0 ) { + if ( sector->Safe == 3 ) { + if ( sector->SafeLo > sector->SafeHi ) { + swap ( sector->SafeLo, sector->SafeHi ); + sector->Safe = 4; + } + } + for ( int j = 0; j < noSectors; j++ ) { + if ( sector->Safe & 1 ) { + // Handle normal SAFE + if ( distanceTable [i][j] >= sector->SafeLo ) { + rejectTable [j][i] |= VIS_RMB_HIDDEN; + } + } + if ( sector->Safe & 2 ) { + // Handle inverse SAFE + if ( distanceTable [i][j] < sector->SafeHi ) { + rejectTable [j][i] |= VIS_RMB_HIDDEN; + } + } + if ( sector->Safe == 4 ) { + // Handle normal BAND SAFE + if (( distanceTable [i][j] >= sector->SafeLo ) && + ( distanceTable [i][j] < sector->SafeHi )) { + rejectTable [j][i] |= VIS_RMB_HIDDEN; + } + } + } + } + } + + delete [] sectorList; + } + + // INCLUDE is the 2nd highest priority option + for ( int i = 0; rmb [i].Info != NULL; i++ ) { + if ( rmb [i].Info->Type == OPTION_INCLUDE ) { + int *src = rmb [i].List [0]; + int *tgt = rmb [i].List [1]; + while ( *src != -1 ) { + int *next = tgt; + while ( *next != -1 ) { + rejectTable [ *src ][ *next++ ] |= VIS_RMB_VISIBLE; + } + src++; + } + } + } + + // EXCLUDE is the highest priority option + for ( int i = 0; rmb [i].Info != NULL; i++ ) { + if ( rmb [i].Info->Type == OPTION_EXCLUDE ) { + int *src = rmb [i].List [0]; + int *tgt = rmb [i].List [1]; + while ( *src != -1 ) { + int *next = tgt; + while ( *next != -1 ) { + rejectTable [ *src ][ *next++ ] = VIS_HIDDEN; + } + src++; + } + } + } +} + +bool CreateREJECT ( DoomLevel *level, const sRejectOptions &options ) +{ + FUNCTION_ENTRY ( NULL, "CreateREJECT", true ); + + if (( options.Force == false ) && ( FeaturesDetected ( level ) == true )) { + return true; + } + + int noSectors = level->SectorCount (); + if ( options.Empty ) { + level->NewReject ((( noSectors * noSectors ) + 7 ) / 8, GetREJECT ( level, true )); + return false; + } + + PrepareREJECT ( noSectors ); + CopyVertices ( level ); + + // Make sure we have something worth doing + if ( SetupLines ( level )) { + + // Set up a scaled BLOCKMAP type structure + PrepareBLOCKMAP ( level ); + + // Make a list of which sectors contain others and their boundary lines + sSector *sector = CreateSectorInfo ( level ); + + // Mark the easy ones visible to speed things up later + EliminateTrivialCases ( sector, noSectors ); + + bool bUseGraphs = options.UseGraphs; + + int **distanceTable = NULL; + + if ( NeedDistances ( options.rmb ) == true ) { + distanceTable = CreateDistanceTable ( sector, noSectors ); + ApplyDistanceLimits ( options.rmb, noSectors, distanceTable ); + } + + maxMapDistance = FindMaxMapDistance ( options.rmb ); + + // Initialize globals used to test line pairs + testLines = new sSolidLine * [ noSolidLines ]; + polyPoints = new const sPoint * [ 2 * ( noSolidLines + 2 )]; + + // Create a map that can be used to reorder lines more efficiently + sSector **sectorList = new sSector * [ noSectors ]; + for ( int i = 0; i < noSectors; i++ ) sectorList [i] = §or [i]; + + Status ( "Working..." ); + + if ( bUseGraphs == true ) { + + // Method 1: Use graphs to reduce things down + InitializeGraphs ( sector, noSectors ); + + // Try to order lines to maximize our chances of culling child sectors + qsort ( sectorList, noSectors, sizeof ( sSector * ), SortSector ); + + for ( int i = 0; i < noSectors; i++ ) { + UpdateProgress ( 1, 100.0 * ( double ) i / ( double ) noSectors ); + ProcessSector ( sectorList [i] ); + } + + delete [] graphTable.graph; + delete [] graphTable.sectorPool; + + } else { + + // Method 2: Down and dirty - check everything + + // Try to order lines to maximize our chances of culling child sectors + qsort ( sectorList, noSectors, sizeof ( sSector * ), SortSector ); + + sTransLine **lineMap = new sTransLine * [ noTransLines ]; + int lineMapSize = SetupLineMap ( lineMap, sectorList, noSectors ); + + int done = 0; + int total = noTransLines * ( noTransLines - 1 ) / 2; + double nextProgress = 0.0; + + // Now the tough part: check all lines against each other + for ( int i = 0; i < lineMapSize; i++ ) { + + sTransLine *srcLine = lineMap [ i ]; + for ( int j = lineMapSize - 1; j > i; j-- ) { + sTransLine *tgtLine = lineMap [ j ]; + if ( TestLinePair ( srcLine, tgtLine ) == true ) { + MarkPairVisible ( srcLine, tgtLine ); + } + } + + // Update the progress indicator to let the user know we're not hung + done += lineMapSize - ( i + 1 ); + double progress = ( 100.0 * done ) / total; + if ( progress >= nextProgress ) { + UpdateProgress ( 2, progress ); + nextProgress = progress + 0.1; + } + } + + delete [] lineMap; + } + + CleanUpBLOCKMAP (); + + // Clean up allocations we made + delete [] sectorList; + delete [] polyPoints; + delete [] testLines; + + // Apply special RMB rules (now that all physical LOS calculations are done) + ProcessOptionsRMB ( options.rmb, noSectors, distanceTable ); + + // Clean up allocations made by CreateDistanceTable + delete [] distanceTable; + distanceTable = NULL; + + // Clean up allocations made by CreateSectorInfo + delete [] neighborList; + delete [] sectorLines; + delete [] sector; + } + + level->NewReject ((( noSectors * noSectors ) + 7 ) / 8, GetREJECT ( level, false )); + + // Clean up allocations made by SetupLines + delete [] lineProcessed; + delete [] checkLine; + delete [] solidLines; + delete [] transLines; + delete [] indexToSolid; + delete [] lineVisTable; + + // Delete our local copy of the vertices + delete [] vertices; + + // Finally, release our reject table data + CleanUpREJECT ( noSectors ); + + return false; +} diff --git a/ZenNode/blockmap.cpp b/ZenNode/blockmap.cpp new file mode 100644 index 0000000..697352e --- /dev/null +++ b/ZenNode/blockmap.cpp @@ -0,0 +1,258 @@ +//---------------------------------------------------------------------------- +// +// File: blockmap.cpp +// Date: 14-Jul-1995 +// Programmer: Marc Rousseau +// +// Description: This module contains the logic for the BLOCKMAP builder. +// +// Copyright (c) 1994-2004 Marc Rousseau, All Rights Reserved. +// +// 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 2 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, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. +// +// Revision History: +// +//---------------------------------------------------------------------------- + +#include +#include +#include +#include +#include "common.hpp" +#include "level.hpp" +#include "ZenNode.hpp" +#include "console.hpp" + +void AddLineDef ( sBlockList *block, int line ) +{ + if (( block->count % 16 ) == 0 ) { + int size = ( block->count + 16 ) * sizeof ( int ); + block->line = ( int * ) realloc ( block->line, size ); + } + block->line [ block->count++ ] = line; +} + +sBlockMap *GenerateBLOCKMAP ( DoomLevel *level ) +{ + Status ( "Creating BLOCKMAP ... " ); + + const wVertex *vertex = level->GetVertices (); + const wLineDef *lineDef = level->GetLineDefs (); + + int xLeft, xRight, yTop, yBottom; + xRight = xLeft = vertex [0].x; + yTop = yBottom = vertex [0].y; + + for ( int i = 1; i < level->VertexCount (); i++ ) { + if ( vertex [i].x < xLeft ) xLeft = vertex [i].x; + if ( vertex [i].x > xRight ) xRight = vertex [i].x; + if ( vertex [i].y < yBottom ) yBottom = vertex [i].y; + if ( vertex [i].y > yTop ) yTop = vertex [i].y; + } + + xLeft -= 8; xRight += 8; + yBottom -= 8; yTop += 8; + + int noCols = ( xRight - xLeft ) / 128 + 1; + int noRows = ( yTop - yBottom ) / 128 + 1; + int totalSize = noCols * noRows; + + sBlockList *blockList = new sBlockList [ totalSize ]; + for ( int i = 0; i < totalSize; i++ ) { + blockList [i].firstIndex = i; + blockList [i].offset = 0; + blockList [i].count = 0; + blockList [i].line = NULL; + } + + for ( int i = 0; i < level->LineDefCount (); i++ ) { + + const wVertex *vertS = &vertex [ lineDef [i].start ]; + const wVertex *vertE = &vertex [ lineDef [i].end ]; + + long x0 = vertS->x - xLeft; + long y0 = vertS->y - yBottom; + long x1 = vertE->x - xLeft; + long y1 = vertE->y - yBottom; + + int startX = x0 / 128, startY = y0 / 128; + int endX = x1 / 128, endY = y1 / 128; + + int index = startX + startY * noCols; + + if ( startX == endX ) { + AddLineDef ( &blockList [ index ], i ); + if ( startY != endY ) { // vertical line + int dy = (( endY - startY ) > 0 ) ? 1 : -1; + do { + startY += dy; + index += dy * noCols; + AddLineDef ( &blockList [ index ], i ); + } while ( startY != endY ); + } + } else { + if ( startY == endY ) { // horizontal line + AddLineDef ( &blockList [ index ], i ); + int dx = (( endX - startX ) > 0 ) ? 1 : -1; + do { + startX += dx; + index += dx; + AddLineDef ( &blockList [ index ], i ); + } while ( startX != endX ); + } else { // diagonal line + + int dx = ( x1 - x0 ); + int dy = ( y1 - y0 ); + + int sx = ( dx < 0 ) ? -1 : 1; + int sy = ( dy < 0 ) ? -1 : 1; + + x1 *= dy; + int nextX = x0 * dy; + int deltaX = ( startY * 128 + 64 * ( 1 + sy ) - y0 ) * dx; + + bool done = false; + + do { + int thisX = nextX; + nextX += deltaX; + if (( sx * sy * nextX ) >= ( sx * sy * x1 )) nextX = x1, done = true; + + int lastIndex = index + nextX / dy / 128 - thisX / dy / 128; + + AddLineDef ( &blockList [ index ], i ); + while ( index != lastIndex ) { + index += sx; + AddLineDef ( &blockList [ index ], i ); + } + + index += sy * noCols; + deltaX = ( 128 * dx ) * sy; + + } while ( ! done ); + + int lastIndex = endX + endY * noCols; + if ( index != lastIndex + sy * noCols ) { + AddLineDef ( &blockList [ lastIndex ], i ); + } + } + } + } + + sBlockMap *blockMap = new sBlockMap; + blockMap->xOrigin = xLeft; + blockMap->yOrigin = yBottom; + blockMap->noColumns = noCols; + blockMap->noRows = noRows; + blockMap->data = blockList; + + return blockMap; +} + +int CreateBLOCKMAP ( DoomLevel *level, const sBlockMapOptions &options ) +{ + // Generate the data + sBlockMap *blockMap = GenerateBLOCKMAP ( level ); + + Status ( "Packing BLOCKMAP ... " ); + + sBlockList *blockList = blockMap->data; + + // Count unique blockList elements + int totalSize = blockMap->noColumns * blockMap->noRows; + int blockListSize = 0, savings = 0; + int zeroIndex = -1; + for ( int i = 0; i < totalSize; i++ ) { + if ( options.Compress ) { + if ( blockList [i].count == 0 ) { + if ( zeroIndex != -1 ) { + blockList [i].firstIndex = zeroIndex; + savings += 0 + 2; + continue; + } + zeroIndex = i; + } else { + // Only go back to the beginning of the previous row + int rowStart = ( i / blockMap->noColumns ) * blockMap->noColumns; + int lastStart = rowStart ? rowStart - blockMap->noColumns : 0; + int index = i - 1; + while ( index >= lastStart ) { + int count = blockList[i].count; + if (( blockList[index].count == count ) && + ( memcmp ( blockList[i].line, blockList[index].line, count * sizeof ( int )) == 0 )) { + blockList [i].firstIndex = index; + savings += count + 2; + break; + } + index--; + } + if ( index >= lastStart ) continue; + } + } + blockList [i].firstIndex = i; + blockListSize += 2 + blockList [i].count; + } + + Status ( "Saving BLOCKMAP ... " ); + + int blockSize = sizeof ( wBlockMap ) + + totalSize * sizeof ( INT16 ) + + blockListSize * sizeof ( INT16 ); + char *start = new char [ blockSize ]; + wBlockMap *map = ( wBlockMap * ) start; + map->xOrigin = ( INT16 ) blockMap->xOrigin; + map->yOrigin = ( INT16 ) blockMap->yOrigin; + map->noColumns = ( UINT16 ) blockMap->noColumns; + map->noRows = ( UINT16 ) blockMap->noRows; + + // Fill in data & offsets + UINT16 *offset = ( UINT16 * ) ( map + 1 ); + UINT16 *data = offset + totalSize; + for ( int i = 0; i < totalSize; i++ ) { + sBlockList *block = &blockList [i]; + if ( block->firstIndex == i ) { + block->offset = data - ( UINT16 * ) start; + *data++ = 0; + for ( int x = 0; x < block->count; x++ ) { + *data++ = ( UINT16 ) block->line [x]; + } + *data++ = ( UINT16 ) -1; + } else { + block->offset = blockList [ block->firstIndex ].offset; + } + } + + bool errors = false; + + for ( int i = 0; i < totalSize; i++ ) { + if ( blockList [i].offset > 0xFFFF ) { + errors = true; + } + offset [i] = ( UINT16 ) blockList [i].offset; + if ( blockList [i].line ) free ( blockList [i].line ); + } + + delete [] blockList; + delete blockMap; + + if ( errors == true ) { + delete [] start; + return -1; + } + + level->NewBlockMap ( blockSize, map ); + + return savings * sizeof ( INT16 ); +} diff --git a/ZenNode/bspdiff.cpp b/ZenNode/bspdiff.cpp new file mode 100644 index 0000000..1bfb862 --- /dev/null +++ b/ZenNode/bspdiff.cpp @@ -0,0 +1,462 @@ +//---------------------------------------------------------------------------- +// +// File: bspdiff.cpp +// Date: 27-Oct-2000 +// Programmer: Marc Rousseau +// +// Description: Compares two BSP structures and report any differences +// +// Copyright (c) 2000-2004 Marc Rousseau, All Rights Reserved. +// +// 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 2 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, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. +// +// Revision History: +// +//---------------------------------------------------------------------------- + +#include +#include +#include +#include +#include + +#if defined ( __OS2__ ) + #define INCL_DOS + #define INCL_SUB + #include + #include + #include +#elif defined ( __WIN32__ ) + #include + #include + #include + #include +#elif defined ( __GNUC__ ) +#else + #error This program must be compiled as a 32-bit app. +#endif + +#include "common.hpp" +#include "wad.hpp" +#include "level.hpp" +#include "console.hpp" + +#define VERSION "1.0" +#define MAX_LEVELS 50 + +#define UNSUPPORTED_FEATURE -1 +#define UNRECOGNIZED_PARAMETER -2 + +#if defined ( __GNUC__ ) + +#define stricmp strcasecmp +#define cprintf printf + +extern char *strupr ( char *ptr ); +extern int getch (); +extern bool kbhit (); + +#endif + +int GCD ( int A, int B ) +{ + if ( A < 0 ) A = -A; else if ( A == 0 ) return 1; + if ( B < 0 ) B = -B; else if ( B == 0 ) return 1; + + unsigned twos = 0; + while ((( A | B ) & 1 ) == 0 ) { + twos++; + A >>= 1; + B >>= 1; + } + + while (( A & 1 ) == 0 ) A >>= 1; // remove other powers of 2 + while (( B & 1 ) == 0 ) B >>= 1; // remove other powers of 2 + + // A and B both odd at this point! + while ( A != B ) { + while ( A > B ) { + A -= B; // subtractracting smaller odd number + // from larger odd number. ( A now even ) + while (( A & 1 ) == 0 ) A >>= 1; // remove powers of 2 + } + while ( B > A ) { + B -= A; // subtractracting smaller odd number + // from larger odd number. ( B now even ) + while (( B & 1 ) == 0 ) B >>= 1; // remove powers of 2 + } + } + + return ( A << twos ); // reapply original powers of two +} + +void printHelp () +{ + fprintf ( stderr, "Usage: nodediff {-options} filename1[.wad] filename2[.wad] [level{+level}]\n" ); + fprintf ( stderr, "\n" ); + fprintf ( stderr, " -x+ turn on option -x- turn off option %c = default\n", DEFAULT_CHAR ); + fprintf ( stderr, "\n" ); + fprintf ( stderr, " level - ExMy for DOOM / Heretic\n" ); + fprintf ( stderr, " MAPxx for DOOM II / HEXEN\n" ); +} + +int parseArgs ( int index, const char *argv[] ) +{ + bool errors = false; + while ( argv [ index ] ) { + + if ( argv [index][0] != '/' ) break; + + index++; + } + if ( errors ) fprintf ( stderr, "\n" ); + return index; +} + +int getLevels ( int argIndex, const char *argv[], char names [][MAX_LUMP_NAME], wadList *list ) +{ + int index = 0, errors = 0; + + char buffer [128]; + buffer [0] = '\0'; + if ( argv [argIndex] ) { + strcpy ( buffer, argv [argIndex] ); + strupr ( buffer ); + } + char *ptr = strtok ( buffer, "+" ); + + // See if the user requested specific levels + if ( WAD::IsMap ( ptr )) { + argIndex++; + while ( ptr ) { + if ( WAD::IsMap ( ptr )) + if ( list->FindWAD ( ptr )) + strcpy ( names [index++], ptr ); + else + fprintf ( stderr, " Could not find %s\n", ptr, errors++ ); + else + fprintf ( stderr, " %s is not a valid name for a level\n", ptr, errors++ ); + ptr = strtok ( NULL, "+" ); + } + } else { + int size = list->DirSize (); + const wadListDirEntry *dir = list->GetDir ( 0 ); + for ( int i = 0; i < size; i++ ) { + if ( dir->wad->IsMap ( dir->entry->name )) { + if ( index == MAX_LEVELS ) + fprintf ( stderr, "ERROR: Too many levels in WAD - ignoring %s!\n", dir->entry->name, errors++ ); + else + memcpy ( names [index++], dir->entry->name, MAX_LUMP_NAME ); + } + dir++; + } + } + memset ( names [index], 0, MAX_LUMP_NAME ); + + if ( errors ) fprintf ( stderr, "\n" ); + return argIndex; +} + +void EnsureExtension ( char *fileName, const char *ext ) +{ + size_t length = strlen ( fileName ); + char *ptr = strrchr ( fileName, '.' ); + if (( ptr && strchr ( ptr, '\\' )) || + ( ! ptr && stricmp ( &fileName[length-4], ext ))) + strcat ( fileName, ext ); +} + +const char *TypeName ( eWadType type ) +{ + switch ( type ) { + case wt_DOOM : return "DOOM"; + case wt_DOOM2 : return "DOOM2"; + case wt_HERETIC : return "Heretic"; + case wt_HEXEN : return "Hexen"; + default : break; + } + return ""; +} + +wadList *getInputFiles ( const char *cmdLine, char *wadFileName ) +{ + char *listNames = wadFileName; + wadList *myList = new wadList; + + if ( cmdLine == NULL ) return myList; + + char temp [ 256 ]; + strcpy ( temp, cmdLine ); + char *ptr = strtok ( temp, "+" ); + + int errors = 0; + while ( ptr && *ptr ) { + char wadName [ 256 ]; + strcpy ( wadName, ptr ); + EnsureExtension ( wadName, ".wad" ); + + WAD *wad = new WAD ( wadName ); + if ( wad->Status () != ws_OK ) { + const char *msg; + switch ( wad->Status ()) { + case ws_INVALID_FILE : msg = "The file %s does not exist\n"; break; + case ws_CANT_READ : msg = "Can't open the file %s for read access\n"; break; + case ws_INVALID_WAD : msg = "%s is not a valid WAD file\n"; break; + default : msg = "** Unexpected Error opening %s **\n"; break; + } + fprintf ( stderr, msg, wadName ); + delete wad; + } else { + if ( ! myList->IsEmpty ()) { + cprintf ( "Merging: %s with %s\r\n", wadName, listNames ); + *wadFileName++ = '+'; + } + if ( myList->Add ( wad ) == false ) { + errors++; + if ( myList->Type () != wt_UNKNOWN ) + fprintf ( stderr, "ERROR: %s is not a %s PWAD.\n", wadName, TypeName ( myList->Type ())); + else + fprintf ( stderr, "ERROR: %s is not the same type.\n", wadName ); + delete wad; + } else { + char *end = wadName + strlen ( wadName ) - 1; + while (( end > wadName ) && ( *end != '\\' )) end--; + if ( *end == '\\' ) end++; + wadFileName += sprintf ( wadFileName, "%s", end ); + } + } + ptr = strtok ( NULL, "+" ); + } + if ( wadFileName [-1] == '+' ) wadFileName [-1] = '\0'; + if ( myList->wadCount () > 1 ) cprintf ( "\r\n" ); + if ( errors ) fprintf ( stderr, "\n" ); + + return myList; +} + +template inline T sgn ( T val ) { return ( val > 0 ) ? 1 : ( val < 0 ) ? -1 : 0; } + +void NormalizeNODES ( wNode *node, int noNodes ) +{ + for ( int i = 0; i < noNodes; i++ ) { + int gcd = GCD ( node [i].dx, node [i].dy ); + node [i].dx /= gcd; + node [i].dy /= gcd; + if ( node [i].dx == 0 ) node [i].dy = sgn ( node [i].dy ); + if ( node [i].dy == 0 ) node [i].dx = sgn ( node [i].dx ); + if (( node [i].dx < 0 ) || (( node [i].dx == 0 ) && ( node [i].dy < 0 ))) { + node [i].dx = -node [i].dx; + node [i].dy = -node [i].dy; + swap ( node [i].side [0], node [i].side [1] ); + swap ( node [i].child [0], node [i].child [1] ); + } + } +} + +static int SortSegs ( const void *ptr1, const void *ptr2 ) +{ + int dif = (( wSegs * ) ptr1)->lineDef - (( wSegs * ) ptr2)->lineDef; + if ( dif ) return dif; + + return (( wSegs * ) ptr1)->flip - (( wSegs * ) ptr2)->flip ? + (( wSegs * ) ptr1)->flip - (( wSegs * ) ptr2)->flip : + (( wSegs * ) ptr1)->offset - (( wSegs * ) ptr2)->offset; +} + +bool LinesMatch ( const wNode &node1, const wNode &node2 ) +{ + if (( node1.dx != node2.dx ) || ( node1.dy != node2.dy )) return false; + + if ( node1.dx == 0 ) return ( node1.x == node2.x ) ? true : false; + if ( node1.dy == 0 ) return ( node1.y == node2.y ) ? true : false; + + double tx = ( double ) ( node2.x - node1.x ) / ( double ) node1.dx; + double ty = ( double ) ( node2.y - node1.y ) / ( double ) node1.dy; + + double delta = fabs ( tx - ty ); + + if ( delta < 0.001 ) return true; + + if ( delta < 1.000 ) printf ( "tx = %g ty = %g\n", tx, ty ); + + return false; +} + +char *locationString; + +void printNode ( const wNode &node, int index ) +{ + printf ( "\n%5d - (%5d,%5d) (%5d,%5d) L: %c %5d R: %c %5d", + index, node.x, node.y, node.dx, node.dy, + ( node.child [0] & 0x8000 ) ? 'S' : 'N', node.child [0] & 0x7FFF, ( node.child [1] & 0x8000 ) ? 'S' : 'N', node.child [1] & 0x7FFF ); +} + +static DoomLevel *srcLevel; +static DoomLevel *tgtLevel; + +int CompareSSECTOR ( int srcIndex, int tgtIndex ) +{ + srcIndex &= 0x7FFF; + tgtIndex &= 0x7FFF; + +// qsort ( segs, noSegs, sizeof ( SEG ), SortSegs ); + + return 0; +} + +int CompareNODE ( int srcIndex, int tgtIndex, char *location ) +{ + int length = location - locationString; + + if (( srcIndex & 0x8000 ) != ( tgtIndex & 0x8000 )) { + printf ( "\n%*.*s: Leaf != Node", length, length, locationString ); + return 1; + } + + if ( srcIndex & 0x8000 ) { + return CompareSSECTOR ( srcIndex, tgtIndex ); + } + + const wNode &src = srcLevel->GetNodes () [srcIndex]; + const wNode &tgt = tgtLevel->GetNodes () [tgtIndex]; + + if ( LinesMatch ( src, tgt ) == false ) { + int length = location - locationString; + printf ( "\n%*.*s:", length, length, locationString ); + printNode ( src, srcIndex ); + printNode ( tgt, tgtIndex ); + return 1; + } + + int count = 0; + + *location = 'R'; + count += CompareNODE ( src.child [0], tgt.child [0], location + 1 ); + + *location = 'L'; + count += CompareNODE ( src.child [1], tgt.child [1], location + 1 ); + + return count; +} + +int ProcessLevel ( char *name, wadList *myList1, wadList *myList2 ) +{ + cprintf ( "\r %-*.*s: ", MAX_LUMP_NAME, MAX_LUMP_NAME, name ); + GetXY ( &startX, &startY ); + + int mismatches = 0; + + srcLevel = NULL; + tgtLevel = NULL; + + const wadListDirEntry *dir = myList1->FindWAD ( name ); + srcLevel = new DoomLevel ( name, dir->wad ); + if ( srcLevel->IsValid ( true ) == false ) { + Status ( "This level is not valid... " ); + mismatches = -1; + goto done; + } + + dir = myList2->FindWAD ( name ); + tgtLevel = new DoomLevel ( name, dir->wad ); + if ( tgtLevel->IsValid ( true ) == false ) { + Status ( "This level is not valid... " ); + mismatches = -1; + goto done; + } + + { + NormalizeNODES (( wNode * ) srcLevel->GetNodes (), srcLevel->NodeCount ()); + NormalizeNODES (( wNode * ) tgtLevel->GetNodes (), tgtLevel->NodeCount ()); + + int noNodes = max ( srcLevel->NodeCount (), tgtLevel->NodeCount ()); + + locationString = new char [ noNodes ]; + + mismatches = CompareNODE ( srcLevel->NodeCount () - 1, tgtLevel->NodeCount () - 1, locationString ); + + delete [] locationString; + + if ( mismatches == 0 ) Status ( "NODES Match" ); + } + +done: + + cprintf ( "\r\n" ); + + delete tgtLevel; + delete srcLevel; + + return mismatches; +} + +int main ( int argc, const char *argv[] ) +{ + fprintf ( stderr, "Compare Version %s (c) 2003-2004 Marc Rousseau\n\n", VERSION ); + + if ( argc == 1 ) { + printHelp (); + return -1; + } + + SaveConsoleSettings (); + + int argIndex = 1, changes = 0; + + while ( KeyPressed ()) GetKey (); + + do { + + argIndex = parseArgs ( argIndex, argv ); + if ( argIndex < 0 ) break; + + char wadFileName1 [ 256 ]; + wadList *myList1 = getInputFiles ( argv [argIndex++], wadFileName1 ); + if ( myList1->IsEmpty ()) { changes = -1000; break; } + + char wadFileName2 [ 256 ]; + wadList *myList2 = getInputFiles ( argv [argIndex++], wadFileName2 ); + if ( myList2->IsEmpty ()) { changes = -1000; break; } + + cprintf ( "Comparing: %s and %s\r\n\n", wadFileName1, wadFileName2 ); + + char levelNames [MAX_LEVELS+1][MAX_LUMP_NAME]; + argIndex = getLevels ( argIndex, argv, levelNames, myList1 ); + + if ( levelNames [0][0] == '\0' ) { + fprintf ( stderr, "Unable to find any valid levels in %s\n", wadFileName1 ); + break; + } + + int noLevels = 0; + + do { + + changes += ProcessLevel ( levelNames [noLevels++], myList1, myList2 ); + if ( KeyPressed () && ( GetKey () == 0x1B )) break; + + } while ( levelNames [noLevels][0] ); + + cprintf ( "\r\n" ); + + delete myList1; + delete myList2; + + } while ( argv [argIndex] ); + + RestoreConsoleSettings (); + + return changes; +} diff --git a/ZenNode/bspinfo.cpp b/ZenNode/bspinfo.cpp new file mode 100644 index 0000000..62b15b6 --- /dev/null +++ b/ZenNode/bspinfo.cpp @@ -0,0 +1,439 @@ +//---------------------------------------------------------------------------- +// +// File: bspinfo.cpp +// Date: 11-Oct-1995 +// Programmer: Marc Rousseau +// +// Description: An application to analyze the contents of a BSP tree +// +// Copyright (c) 1995-2004 Marc Rousseau, All Rights Reserved. +// +// 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 2 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, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. +// +// Revision History: +// +//---------------------------------------------------------------------------- + +#include +#include +#include +#include +#include +#include "common.hpp" +#include "logger.hpp" +#include "wad.hpp" +#include "level.hpp" +#include "console.hpp" + +#if defined ( __OS2__ ) + #include + #include +#elif defined ( __WIN32__ ) + #include + #include +#elif defined ( __GNUC__ ) || defined ( __INTEL_COMPILER ) + #include +#else + #error This program must be compiled as a 32-bit app. +#endif + +DBG_REGISTER ( __FILE__ ); + +#define VERSION "1.3" +#define BANNER "BSPInfo Version " VERSION " (c) 1995-2004 Marc Rousseau" +#define MAX_LEVELS 99 + +#if defined ( __GNUC__ ) || defined ( __INTEL_COMPILER ) + +#define stricmp strcasecmp + +extern char *strupr ( char *ptr ); + +#endif + +struct { + bool Tree; +} flags; + +void printHelp () +{ + fprintf ( stderr, "Usage: bspInfo [-options] filename[.wad] [level[+level]]\n" ); + fprintf ( stderr, "\n" ); + fprintf ( stderr, " -x+ turn on option -x- turn off option %c = default\n", DEFAULT_CHAR ); + fprintf ( stderr, "\n" ); + fprintf ( stderr, " -t - Display NODE tree\n" ); + fprintf ( stderr, "\n" ); + fprintf ( stderr, " level - ExMy for DOOM / Heretic\n" ); + fprintf ( stderr, " MAPxx for DOOM II / HEXEN\n" ); +} + +int parseArgs ( int index, const char *argv [] ) +{ + FUNCTION_ENTRY ( NULL, "parseArgs", true ); + + bool errors = false; + while ( argv [ index ] ) { + + if ( argv [index][0] != '-' ) break; + + char *localCopy = strdup ( argv [ index ]); + char *ptr = localCopy + 1; + strupr ( localCopy ); + + bool localError = false; + while ( *ptr && ( localError == false )) { + int option = *ptr++; + bool setting = true; + if (( *ptr == '+' ) || ( *ptr == '-' )) { + setting = ( *ptr == '-' ) ? false : true; + ptr++; + } + switch ( option ) { + case 'T' : flags.Tree = setting; break; + default : localError = true; + } + } + if ( localError ) { + errors = true; + int offset = ptr - localCopy - 1; + size_t width = strlen ( ptr ) + 1; + fprintf ( stderr, "Unrecognized parameter '%*.*s'\n", width, width, argv [index] + offset ); + } + free ( localCopy ); + index++; + } + + if ( errors ) fprintf ( stderr, "\n" ); + + return index; +} + +int getLevels ( int argIndex, const char *argv [], char names [][MAX_LUMP_NAME], wadList *list ) +{ + FUNCTION_ENTRY ( NULL, "getLevels", true ); + + int index = 0, errors = 0; + + char buffer [128]; + buffer [0] = '\0'; + if ( argv [argIndex] ) { + strcpy ( buffer, argv [argIndex] ); + strupr ( buffer ); + } + char *ptr = strtok ( buffer, "+" ); + + // See if the user requested specific levels + if ( WAD::IsMap ( ptr )) { + argIndex++; + while ( ptr ) { + if ( WAD::IsMap ( ptr )) { + if ( list->FindWAD ( ptr )) { + strcpy ( names [index++], ptr ); + } else { + fprintf ( stderr, " Could not find %s\n", ptr, errors++ ); + } + } else { + fprintf ( stderr, " %s is not a valid name for a level\n", ptr, errors++ ); + } + ptr = strtok ( NULL, "+" ); + } + } else { + int size = list->DirSize (); + const wadListDirEntry *dir = list->GetDir ( 0 ); + for ( int i = 0; i < size; i++ ) { + if ( dir->wad->IsMap ( dir->entry->name )) { + // Make sure it's really a level + if ( strcmp ( dir[1].entry->name, "THINGS" ) == 0 ) { + if ( index == MAX_LEVELS ) { + fprintf ( stderr, "ERROR: Too many levels in WAD - ignoring %s!\n", dir->entry->name, errors++ ); + } else { + memcpy ( names [index++], dir->entry->name, MAX_LUMP_NAME ); + } + } + } + dir++; + } + } + memset ( names [index], 0, MAX_LUMP_NAME ); + + if ( errors ) fprintf ( stderr, "\n" ); + + return argIndex; +} + +void EnsureExtension ( char *fileName, const char *ext ) +{ + FUNCTION_ENTRY ( NULL, "EnsureExtension", true ); + + // See if the file exists first + FILE *file = fopen ( fileName, "rb" ); + if ( file != NULL ) { + fclose ( file ); + return; + } + + size_t length = strlen ( fileName ); + if ( stricmp ( &fileName [length-4], ext ) != 0 ) { + strcat ( fileName, ext ); + } +} + +const char *TypeName ( eWadType type ) +{ + FUNCTION_ENTRY ( NULL, "TypeName", true ); + + const char *name = NULL; + switch ( type ) { + case wt_DOOM : name = "DOOM"; break; + case wt_DOOM2 : name = "DOOM2"; break; + case wt_HERETIC : name = "Heretic"; break; + case wt_HEXEN : name = "Hexen"; break; + default : name = ""; break; + } + return name; +} + +wadList *getInputFiles ( const char *cmdLine, char *wadFileName ) +{ + FUNCTION_ENTRY ( NULL, "getInputFiles", true ); + + char *listNames = wadFileName; + wadList *myList = new wadList; + + if ( cmdLine == NULL ) return myList; + + char temp [ 256 ]; + strcpy ( temp, cmdLine ); + char *ptr = strtok ( temp, "+" ); + + int errors = 0; + + while ( ptr && *ptr ) { + char wadName [ 256 ]; + strcpy ( wadName, ptr ); + EnsureExtension ( wadName, ".wad" ); + + WAD *wad = new WAD ( wadName ); + if ( wad->Status () != ws_OK ) { + const char *msg; + switch ( wad->Status ()) { + case ws_INVALID_FILE : msg = "The file %s does not exist\n"; break; + case ws_CANT_READ : msg = "Can't open the file %s for read access\n"; break; + case ws_INVALID_WAD : msg = "%s is not a valid WAD file\n"; break; + default : msg = "** Unexpected Error opening %s **\n"; break; + } + fprintf ( stderr, msg, wadName ); + delete wad; + } else { + if ( ! myList->IsEmpty ()) { + cprintf ( "Merging: %s with %s\r\n", wadName, listNames ); + *wadFileName++ = '+'; + } + if ( myList->Add ( wad ) == false ) { + errors++; + if ( myList->Type () != wt_UNKNOWN ) { + fprintf ( stderr, "ERROR: %s is not a %s PWAD.\n", wadName, TypeName ( myList->Type ())); + } else { + fprintf ( stderr, "ERROR: %s is not the same type.\n", wadName ); + } + delete wad; + } else { + char *end = wadName + strlen ( wadName ) - 1; + while (( end > wadName ) && ( *end != SEPERATOR )) end--; + if ( *end == SEPERATOR ) end++; + wadFileName += sprintf ( wadFileName, "%s", end ); + } + } + ptr = strtok ( NULL, "+" ); + } + + if ( wadFileName [-1] == '+' ) wadFileName [-1] = '\0'; + if ( myList->wadCount () > 1 ) cprintf ( "\r\n" ); + if ( errors ) fprintf ( stderr, "\n" ); + + return myList; +} + +const wNode *nodes; +int totalDepth; + +int Traverse ( int index, int depth, int &diagonals, int &balance, int &lChildren, int &rChildren ) +{ + FUNCTION_ENTRY ( NULL, "Traverse", false ); + + const wNode *node = &nodes [ index ]; + + if ( flags.Tree ) printf ( "(%5d,%5d) [%5d,%5d]\n", node->x, node->y, node->dx, node->dy ); + + if (( node->dx != 0 ) && ( node->dy != 0 )) diagonals++; + + int lIndex = node->child [0]; + int rIndex = node->child [1]; + + if (( lIndex & 0x8000 ) == ( rIndex & 0x8000 )) balance++; + + int lDepth = 0, rDepth = 0; + + depth++; + + if ( flags.Tree ) printf ( "%5d %*.*sLeft - ", depth, depth*2, depth*2, "" ); + + if (( lIndex & 0x8000 ) == 0 ) { + int left = 0, right = 0; + lDepth = Traverse ( lIndex, depth, diagonals, balance, left, right ); + lChildren = 1 + left + right; + } else { + if ( flags.Tree ) printf ( "** NONE **\n" ); + lDepth = depth; + totalDepth += depth + 1; + } + + if ( flags.Tree ) printf ( "%5d %*.*sRight - ", depth, depth*2, depth*2, "" ); + + if (( rIndex & 0x8000 ) == 0 ) { + int left = 0, right = 0; + rDepth = Traverse ( rIndex, depth, diagonals, balance, left, right ); + rChildren = 1 + left + right; + } else { + if ( flags.Tree ) printf ( "** NONE **\n" ); + rDepth = depth; + totalDepth += depth + 1; + } + + return (( lDepth > rDepth ) ? lDepth : rDepth ); +} + +void AnalyzeBSP ( DoomLevel *curLevel ) +{ + FUNCTION_ENTRY ( NULL, "AnalyzeBSP", true ); + + if ( curLevel->IsValid ( true, true ) == false ) { + printf ( "******** Invalid level ********" ); + return; + } + + totalDepth = 0; + + nodes = curLevel->GetNodes (); + int balance = 0, diagonals = 0; + if ( flags.Tree ) printf ( "\n\nROOT: " ); + + int left = 0; + int right = 0; + int depth = Traverse ( curLevel->NodeCount () - 1, 0, diagonals, balance, left, right ); + + const wSegs *seg = curLevel->GetSegs (); + const wLineDef *lineDef = curLevel->GetLineDefs (); + + bool *lineUsed = new bool [ curLevel->LineDefCount ()]; + memset ( lineUsed, false, sizeof ( bool ) * curLevel->LineDefCount ()); + for ( int i = 0; i < curLevel->SegCount (); i++, seg++ ) { + lineUsed [ seg->lineDef ] = true; + } + + int sideDefs = 0; + for ( int i = 0; i < curLevel->LineDefCount (); i++ ) { + if ( lineUsed [i] == false ) continue; + if ( lineDef[i].sideDef[0] != NO_SIDEDEF ) sideDefs++; + if ( lineDef[i].sideDef[1] != NO_SIDEDEF ) sideDefs++; + } + + int splits = curLevel->SegCount () - sideDefs; + + int noLeafs = curLevel->SubSectorCount (); + int optDepth = ( int ) ceil ( log (( float ) noLeafs ) / log ( 2.0 )); + int maxLeafs = ( int ) pow ( 2, optDepth ); + int minDepth = noLeafs * ( optDepth + 1 ) - maxLeafs; + int maxDepth = noLeafs * (( noLeafs - 1 ) / 2 + 1 ) - 1; + + double minScore = ( double ) minDepth / ( double ) maxDepth; + double score = ( double ) minDepth / ( double ) totalDepth; + score = ( score - minScore ) / ( 1 - minScore ); + + if ( ! flags.Tree ) { + float avgDepth = noLeafs ? ( float ) totalDepth / ( float ) noLeafs : 0; + printf ( "%2d ", depth ); + printf ( "%4.1f ", avgDepth ); + printf ( "%5.3f ", score ); + printf ( "%5.3f ", ( left < right ) ? ( double ) left / ( double ) right : ( double ) right / ( double ) left ); + printf ( "%5d - %4.1f%% ", splits, 100.0 * splits / sideDefs ); + printf ( "%5d - %4.1f%% ", diagonals, 100.0 * diagonals / curLevel->NodeCount ()); + printf ( "%5d ", curLevel->NodeCount ()); + printf ( "%5d", curLevel->SegCount ()); + } +} + +int main ( int argc, const char *argv[] ) +{ + FUNCTION_ENTRY ( NULL, "main", true ); + + SaveConsoleSettings (); + + cprintf ( "%s\r\n\r\n", BANNER ); + if ( ! isatty ( fileno ( stdout ))) fprintf ( stdout, "%s\n\n", BANNER ); + if ( ! isatty ( fileno ( stderr ))) fprintf ( stderr, "%s\n\n", BANNER ); + + if ( argc == 1 ) { + printHelp (); + return -1; + } + + flags.Tree = false; + + int argIndex = 1; + do { + + argIndex = parseArgs ( argIndex, argv ); + if ( argIndex < 0 ) break; + + char wadFileName [ 256 ]; + wadList *myList = getInputFiles ( argv [argIndex++], wadFileName ); + if ( myList->IsEmpty ()) break; + printf ( "Analyzing: %s\n\n", wadFileName ); + + char levelNames [MAX_LEVELS+1][MAX_LUMP_NAME]; + argIndex = getLevels ( argIndex, argv, levelNames, myList ); + + if ( levelNames [0][0] == '\0' ) { + fprintf ( stderr, "Unable to find any valid levels in %s\n", wadFileName ); + break; + } + + if ( ! flags.Tree ) { + printf ( " Max Avg\n" ); + printf ( " Depth Depth FOM Balance Splits Diagonals Nodes Segs\n" ); + } + + int noLevels = 0; + + do { + + const wadListDirEntry *dir = myList->FindWAD ( levelNames [ noLevels ]); + DoomLevel *curLevel = new DoomLevel ( levelNames [ noLevels ], dir->wad ); + printf ( "%8.8s: ", levelNames [ noLevels++ ]); + AnalyzeBSP ( curLevel ); + printf ( "\n" ); + delete curLevel; + + } while ( levelNames [noLevels][0] ); + + printf ( "\n" ); + + delete myList; + + } while ( argv [argIndex] ); + + return 0; +} diff --git a/ZenNode/compare.cpp b/ZenNode/compare.cpp new file mode 100644 index 0000000..d3f27d3 --- /dev/null +++ b/ZenNode/compare.cpp @@ -0,0 +1,494 @@ +//---------------------------------------------------------------------------- +// +// File: compare.cpp +// Date: 16-Jan-1996 +// Programmer: Marc Rousseau +// +// Description: Compares two REJECT structures and report any differences +// +// Copyright (c) 1996-2004 Marc Rousseau, All Rights Reserved. +// +// 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 2 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, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. +// +// Revision History: +// +//---------------------------------------------------------------------------- + +#include +#include +#include +#include + +#if defined ( __OS2__ ) + #define INCL_DOS + #define INCL_SUB + #include + #include + #include + #include +#elif defined ( __WIN32__ ) + #include + #include + #include + #include + #include +#elif defined ( __GNUC__ ) || defined ( __INTEL_COMPILER ) + #include +#else + #error This program must be compiled as a 32-bit app. +#endif + +#include "common.hpp" +#include "logger.hpp" +#include "wad.hpp" +#include "level.hpp" +#include "console.hpp" + +DBG_REGISTER ( __FILE__ ); + +#define VERSION "1.3" +#define BANNER "compare Version " VERSION " (c) 1996-2004 Marc Rousseau" +#define MAX_LEVELS 99 + +#define UNSUPPORTED_FEATURE -1 +#define UNRECOGNIZED_PARAMETER -2 + +#if defined ( __BORLANDC__ ) + #pragma option -x -xf +#endif + +#if defined ( __GNUC__ ) || defined ( __INTEL_COMPILER ) + +#define stricmp strcasecmp + +extern char *strupr ( char *ptr ); + +#endif + +void printHelp () +{ + FUNCTION_ENTRY ( NULL, "printHelp", true ); + + fprintf ( stderr, "Usage: compare {-options} filename1[.wad] filename2[.wad] [level{+level}]\n" ); + fprintf ( stderr, "\n" ); + fprintf ( stderr, " -x+ turn on option -x- turn off option %c = default\n", DEFAULT_CHAR ); + fprintf ( stderr, "\n" ); + fprintf ( stderr, " level - ExMy for DOOM / Heretic\n" ); + fprintf ( stderr, " MAPxx for DOOM II / HEXEN\n" ); +} + +int parseArgs ( int index, const char *argv[] ) +{ + FUNCTION_ENTRY ( NULL, "parseArgs", true ); + + bool errors = false; + while ( argv [ index ] ) { + + if ( argv [index][0] != '-' ) break; + + char *localCopy = strdup ( argv [ index ]); + char *ptr = localCopy + 1; + strupr ( localCopy ); + + bool localError = false; + while ( *ptr && ( localError == false )) { + int option = *ptr++; + bool setting = true; + if (( *ptr == '+' ) || ( *ptr == '-' )) { + setting = ( *ptr == '-' ) ? false : true; + ptr++; + } + switch ( option ) { + case -1 : + default : localError = true; + } + } + if ( localError ) { + errors = true; + int offset = ptr - localCopy - 1; + size_t width = strlen ( ptr ) + 1; + fprintf ( stderr, "Unrecognized parameter '%*.*s'\n", width, width, argv [index] + offset ); + } + free ( localCopy ); + index++; + } + + if ( errors ) fprintf ( stderr, "\n" ); + + return index; +} + +int getLevels ( int argIndex, const char *argv[], char names [][MAX_LUMP_NAME], wadList *list1, wadList *list2 ) +{ + FUNCTION_ENTRY ( NULL, "getLevels", true ); + + int index = 0, errors = 0; + + char buffer [128]; + buffer [0] = '\0'; + if ( argv [argIndex] ) { + strcpy ( buffer, argv [argIndex] ); + strupr ( buffer ); + } + char *ptr = strtok ( buffer, "+" ); + + // See if the user requested specific levels + if ( WAD::IsMap ( ptr )) { + argIndex++; + while ( ptr ) { + if ( WAD::IsMap ( ptr )) { + if ( list1->FindWAD ( ptr )) { + strcpy ( names [index++], ptr ); + } else { + fprintf ( stderr, " Could not find %s\n", ptr, errors++ ); + } + } else { + fprintf ( stderr, " %s is not a valid name for a level\n", ptr, errors++ ); + } + ptr = strtok ( NULL, "+" ); + } + } else { + int size = list1->DirSize (); + const wadListDirEntry *dir = list1->GetDir ( 0 ); + for ( int i = 0; i < size; i++ ) { + if ( dir->wad->IsMap ( dir->entry->name )) { + if ( index == MAX_LEVELS ) { + fprintf ( stderr, "ERROR: Too many levels in WAD - ignoring %s!\n", dir->entry->name, errors++ ); + } else { + memcpy ( names [index++], dir->entry->name, MAX_LUMP_NAME ); + } + } + dir++; + } + } + memset ( names [index], 0, MAX_LUMP_NAME ); + + // Remove any maps that aren't in both files + for ( int i = 0; names [i][0]; i++ ) { + if ( list2->FindWAD ( names [i] ) == NULL ) { + memcpy ( names + i, names + i + 1, ( index - i ) * MAX_LUMP_NAME ); + i--; + } + } + + if ( errors ) fprintf ( stderr, "\n" ); + + return argIndex; +} + +void EnsureExtension ( char *fileName, const char *ext ) +{ + FUNCTION_ENTRY ( NULL, "EnsureExtension", true ); + + // See if the file exists first + FILE *file = fopen ( fileName, "rb" ); + if ( file != NULL ) { + fclose ( file ); + return; + } + + size_t length = strlen ( fileName ); + if ( stricmp ( &fileName [length-4], ext ) != 0 ) { + strcat ( fileName, ext ); + } +} + +const char *TypeName ( eWadType type ) +{ + FUNCTION_ENTRY ( NULL, "TypeName", true ); + + const char *name = NULL; + switch ( type ) { + case wt_DOOM : name = "DOOM"; break; + case wt_DOOM2 : name = "DOOM2"; break; + case wt_HERETIC : name = "Heretic"; break; + case wt_HEXEN : name = "Hexen"; break; + default : name = ""; break; + } + return name; +} + +wadList *getInputFiles ( const char *cmdLine, char *wadFileName ) +{ + FUNCTION_ENTRY ( NULL, "getInputFiles", true ); + + char *listNames = wadFileName; + wadList *myList = new wadList; + + if ( cmdLine == NULL ) return myList; + + char temp [ 256 ]; + strcpy ( temp, cmdLine ); + char *ptr = strtok ( temp, "+" ); + + int errors = 0; + + while ( ptr && *ptr ) { + char wadName [ 256 ]; + strcpy ( wadName, ptr ); + EnsureExtension ( wadName, ".wad" ); + + WAD *wad = new WAD ( wadName ); + if ( wad->Status () != ws_OK ) { + const char *msg; + switch ( wad->Status ()) { + case ws_INVALID_FILE : msg = "The file %s does not exist\n"; break; + case ws_CANT_READ : msg = "Can't open the file %s for read access\n"; break; + case ws_INVALID_WAD : msg = "%s is not a valid WAD file\n"; break; + default : msg = "** Unexpected Error opening %s **\n"; break; + } + fprintf ( stderr, msg, wadName ); + delete wad; + } else { + if ( ! myList->IsEmpty ()) { + cprintf ( "Merging: %s with %s\r\n", wadName, listNames ); + *wadFileName++ = '+'; + } + if ( myList->Add ( wad ) == false ) { + errors++; + if ( myList->Type () != wt_UNKNOWN ) { + fprintf ( stderr, "ERROR: %s is not a %s PWAD.\n", wadName, TypeName ( myList->Type ())); + } else { + fprintf ( stderr, "ERROR: %s is not the same type.\n", wadName ); + } + delete wad; + } else { + char *end = wadName + strlen ( wadName ) - 1; + while (( end > wadName ) && ( *end != SEPERATOR )) end--; + if ( *end == SEPERATOR ) end++; + wadFileName += sprintf ( wadFileName, "%s", end ); + } + } + ptr = strtok ( NULL, "+" ); + } + + if ( wadFileName [-1] == '+' ) wadFileName [-1] = '\0'; + if ( myList->wadCount () > 1 ) cprintf ( "\r\n" ); + if ( errors ) fprintf ( stderr, "\n" ); + + return myList; +} + +int CompareREJECT ( DoomLevel *srcLevel, DoomLevel *tgtLevel ) +{ + FUNCTION_ENTRY ( NULL, "CompareREJECT", true ); + + bool match = true; + int noSectors = srcLevel->SectorCount (); + char *srcPtr = ( char * ) srcLevel->GetReject (); + char *tgtPtr = ( char * ) tgtLevel->GetReject (); + + int **vis2hid = new int * [ noSectors ]; + int **hid2vis = new int * [ noSectors ]; + + int *v2hCount = new int [ noSectors ]; + int *h2vCount = new int [ noSectors ]; + + int bits = 8; + int srcVal = *srcPtr++; + int tgtVal = *tgtPtr++; + int dif = srcVal ^ tgtVal; + for ( int i = 0; i < noSectors; i++ ) { + vis2hid [i] = new int [ noSectors ]; + hid2vis [i] = new int [ noSectors ]; + memset ( vis2hid [i], 0, noSectors * sizeof ( int )); + memset ( hid2vis [i], 0, noSectors * sizeof ( int )); + v2hCount [i] = 0; + h2vCount [i] = 0; + for ( int j = 0; j < noSectors; j++ ) { + if ( dif & 1 ) { + if ( srcVal & 1 ) { + hid2vis [i][h2vCount [i]++] = j; + } else { + vis2hid [i][v2hCount [i]++] = j; + } + match = false; + } + if ( --bits == 0 ) { + bits = 8; + srcVal = *srcPtr++; + tgtVal = *tgtPtr++; + dif = srcVal ^ tgtVal; + } else { + srcVal >>= 1; + tgtVal >>= 1; + dif >>= 1; + } + } + } + + bool first = true; + for ( int i = 0; i < noSectors; i++ ) { + if (( v2hCount [i] == 0 ) && ( h2vCount [i] == 0 )) continue; + bool v2h = false; + for ( int j = 0; j < v2hCount [i]; j++ ) { + int index = vis2hid [i][j]; + if (( v2hCount [i] > v2hCount [index] ) || + (( v2hCount [i] == v2hCount [index] ) && ( i > index ))) { + v2h = true; + break; + } + } + bool h2v = false; + for ( int j = 0; j < h2vCount [i]; j++ ) { + int index = hid2vis [i][j]; + if (( h2vCount [i] > h2vCount [index] ) || + (( h2vCount [i] == h2vCount [index] ) && ( i > index ))) { + h2v = true; + break; + } + } + if ( v2h == true ) { + if ( first == false ) printf ( " " ); + printf ( "vis->hid %5d:", i ); + for ( int j = 0; j < v2hCount [i]; j++ ) { + printf ( " %d", vis2hid [i][j] ); + } + printf ( "\n" ); + first = false; + } + if ( h2v == true ) { + if ( first == false ) printf ( " " ); + printf ( "hid->vis %5d:", i ); + for ( int j = 0; j < h2vCount [i]; j++ ) { + printf ( " %d", hid2vis [i][j] ); + } + printf ( "\n" ); + first = false; + } + } + + if ( match == true ) printf ( "Perfect Match\n" ); + + for ( int i = 0; i < noSectors; i++ ) { + delete [] vis2hid [i]; + delete [] hid2vis [i]; + } + + delete [] vis2hid; + delete [] hid2vis; + + delete [] v2hCount; + delete [] h2vCount; + + return match ? 0 : 1; +} + +int ProcessLevel ( char *name, wadList *myList1, wadList *myList2 ) +{ + FUNCTION_ENTRY ( NULL, "ProcessLevel", true ); + + int change; + cprintf ( " %-*.*s: ", MAX_LUMP_NAME, MAX_LUMP_NAME, name ); + GetXY ( &startX, &startY ); + + DoomLevel *tgtLevel = NULL; + const wadListDirEntry *dir = myList1->FindWAD ( name ); + DoomLevel *srcLevel = new DoomLevel ( name, dir->wad ); + if ( srcLevel->IsValid ( true ) == false ) { + change = -1000; + cprintf ( "The source level is not valid... \r\n" ); + goto done; + } + + dir = myList2->FindWAD ( name ); + tgtLevel = new DoomLevel ( name, dir->wad ); + if ( tgtLevel->IsValid ( true ) == false ) { + change = -1000; + cprintf ( "The target level is not valid... \r\n" ); + goto done; + } + if ( srcLevel->RejectSize () != tgtLevel->RejectSize ()) { + change = -1000; + cprintf ( "The reject maps aren't the same size\r\n" ); + goto done; + } + + change = CompareREJECT ( srcLevel, tgtLevel ); + +done: + + delete tgtLevel; + delete srcLevel; + + return change; +} + +#if defined ( __BORLANDC__ ) + #include +#endif + +int main ( int argc, const char *argv[] ) +{ + FUNCTION_ENTRY ( NULL, "main", true ); + + SaveConsoleSettings (); + HideCursor (); + + cprintf ( "%s\r\n\r\n", BANNER ); + if ( ! isatty ( fileno ( stdout ))) fprintf ( stdout, "%s\n\n", BANNER ); + if ( ! isatty ( fileno ( stderr ))) fprintf ( stderr, "%s\n\n", BANNER ); + + if ( argc == 1 ) { + printHelp (); + return -1; + } + + int argIndex = 1, changes = 0; + + while ( KeyPressed ()) GetKey (); + do { + + argIndex = parseArgs ( argIndex, argv ); + if ( argIndex < 0 ) break; + + char wadFileName1 [ 256 ]; + wadList *myList1 = getInputFiles ( argv [argIndex++], wadFileName1 ); + if ( myList1->IsEmpty ()) { changes = -1000; break; } + + char wadFileName2 [ 256 ]; + wadList *myList2 = getInputFiles ( argv [argIndex++], wadFileName2 ); + if ( myList2->IsEmpty ()) { changes = -1000; break; } + + cprintf ( "Comparing: %s and %s\r\n\n", wadFileName1, wadFileName2 ); + + char levelNames [MAX_LEVELS+1][MAX_LUMP_NAME]; + argIndex = getLevels ( argIndex, argv, levelNames, myList1, myList2 ); + + if ( levelNames [0][0] == '\0' ) { + fprintf ( stderr, "Unable to find any valid levels in %s\n", wadFileName1 ); + break; + } + + int noLevels = 0; + + do { + + changes += ProcessLevel ( levelNames [noLevels++], myList1, myList2 ); + if ( KeyPressed () && ( GetKey () == 0x1B )) break; + + } while ( levelNames [noLevels][0] ); + + delete myList1; + delete myList2; + + } while ( argv [argIndex] ); + + cprintf ( "\r\n" ); + + RestoreConsoleSettings (); + + return changes; +} diff --git a/ZenNode/console.cpp b/ZenNode/console.cpp new file mode 100644 index 0000000..7721780 --- /dev/null +++ b/ZenNode/console.cpp @@ -0,0 +1,574 @@ +//---------------------------------------------------------------------------- +// +// File: console.cpp +// Date: 13-Aug-2000 +// Programmer: Marc Rousseau +// +// Description: Screen I/O routines for ZenNode +// +// Copyright (c) 2000-2004 Marc Rousseau, All Rights Reserved. +// +// 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 2 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, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. +// +// Revision History: +// +// 06-??-95 Added Win32 support +// 07-19-95 Updated command line & screen logic +// 11-19-95 Updated command line again +// 12-06-95 Add config & customization file support +// 11-??-98 Added Linux support +// 04-21-01 Modified Linux code to match Win32 console I/O behavior +// 04-26-01 Added SIGABRT to list of 'handled' signals +// +//---------------------------------------------------------------------------- + +#if defined ( __OS2__ ) + #include + #include + #include + #define INCL_DOS + #define INCL_SUB + #include +#elif defined ( __WIN32__ ) + #include + #include + #include +#elif defined ( __GNUC__ ) || defined ( __INTEL_COMPILER ) + #include + #include + #include + #include + #include + #include + #include + #include + #include "level.hpp" +#else + #error This program must be compiled as a 32-bit app. +#endif + +#include "common.hpp" +#include "console.hpp" + +static UINT32 curX = 0; +static UINT32 curY = 0; + +UINT32 startX, startY; +char progress [4] = { 0x7C, 0x2F, 0x2D, 0x5C }; +int progressIndex; + +#if defined ( __OS2__ ) + +static HVIO hVio = 0; +static VIOCURSORINFO vioco; +static int oldAttr; + +void SaveConsoleSettings () +{ + VioGetCurType ( &vioco, hVio ); + oldAttr = vioco.attr; + vioco.attr = 0xFFFF; + VioSetCurType ( &vioco, hVio ); +} + +void RestoreConsoleSettings () +{ + vioco.attr = oldAttr; + VioSetCurType ( &vioco, hVio ); +} + +void GetXY ( UINT32 *x, UINT32 *y ) +{ + VioGetCurPos ( y, x, hVio ); +} + +void GotoXY ( UINT32 x, UINT32 y ) +{ + curX = x; + curY = y; + VioSetCurPos ( y, x, hVio ); +} + +UINT32 CurrentTime () +{ + UINT32 time; + DosQuerySysInfo ( QSV_MS_COUNT, QSV_MS_COUNT, &time, 4 ); + return time; +} + +void Status ( char *message ) +{ + int len = strlen ( message ); + VioWrtCharStr (( BYTE * ) message, len, startY, startX, hVio ); + VioWrtNChar (( BYTE * ) " ", 80 - ( startX + len ), startY, startX + len, hVio ); + curY = startY; + curX = startX + len; +} + +void GoRight () +{ + VioWrtNChar (( BYTE * ) "R", 1, curY, curX++, hVio ); +} + +void GoLeft () +{ + VioWrtNChar (( BYTE * ) "L", 1, curY, curX - 1 , hVio ); +} + +void Backup () +{ + curX--; +} + +void ShowDone () +{ + VioWrtNChar (( BYTE * ) "*", 1, curY, curX, hVio ); +} + +void ShowProgress () +{ + VioWrtNChar (( BYTE * ) &progress [ progressIndex++ % SIZE ( progress )], 1, curY, curX, hVio ); +} + +void MoveUp ( int delta ) +{ + curY -= delta; + VioSetCurPos ( curY, 0, hVio ); +} + +void MoveDown ( int delta ) +{ + curY += delta; + VioSetCurPos ( curY, 0, hVio ); +} + +#elif defined ( __WIN32__ ) + +#if defined ( __GNUC__ ) || defined ( __INTEL_COMPILER ) +#define cprintf _cprintf +#endif + +static CONSOLE_SCREEN_BUFFER_INFO screenInfo; +static CONSOLE_CURSOR_INFO cursorInfo; +static BOOL oldVisible; + +static HANDLE hOutput; +static COORD currentPos; + +static LARGE_INTEGER timerFrequency; + +long WINAPI myHandler ( PEXCEPTION_POINTERS ) +{ + RestoreConsoleSettings (); + + return EXCEPTION_CONTINUE_SEARCH; +} + +void SignalHandler ( int ) +{ + RestoreConsoleSettings (); + cprintf ( "\r\n" ); + exit ( -1 ); +} + +void SaveConsoleSettings () +{ + hOutput = GetStdHandle ( STD_OUTPUT_HANDLE ); + GetConsoleCursorInfo ( hOutput, &cursorInfo ); + GetConsoleScreenBufferInfo ( hOutput, &screenInfo ); + oldVisible = cursorInfo.bVisible; + SetUnhandledExceptionFilter ( myHandler ); + signal ( SIGBREAK, SignalHandler ); + signal ( SIGINT, SignalHandler ); + atexit ( RestoreConsoleSettings ); + + QueryPerformanceFrequency ( &timerFrequency ); +} + +void RestoreConsoleSettings () +{ + cursorInfo.bVisible = oldVisible; + SetConsoleCursorInfo ( hOutput, &cursorInfo ); +} + +void HideCursor () +{ + CONSOLE_CURSOR_INFO info = cursorInfo; + info.bVisible = FALSE; + SetConsoleCursorInfo ( hOutput, &info ); +} + +void ShowCursor () +{ + CONSOLE_CURSOR_INFO info = cursorInfo; + info.bVisible = TRUE; + SetConsoleCursorInfo ( hOutput, &info ); +} + +int GetKey () +{ + int key = 0; + UCHAR *ptr = ( UCHAR * ) &key; + int ch = getch (); + + if ( ch == 0xE0 ) { + *ptr++ = 0x1B; + *ptr++ = 0x5B; + ch = getch (); + switch ( ch ) { + case 0x48 : ch = 0x41; break; + case 0x50 : ch = 0x42; break; + case 0x4D : ch = 0x43; break; + case 0x4B : ch = 0x44; break; + } + } + *ptr++ = ( UCHAR ) ch; + + return key; +} + +bool KeyPressed () +{ + return ( kbhit () != 0 ) ? true : false; +} + +UINT32 CurrentTime () +{ + LARGE_INTEGER time; + QueryPerformanceCounter ( &time ); + + return ( UINT32 ) ( 1000 * time.QuadPart / timerFrequency.QuadPart ); +} + +void ClearScreen () +{ + DWORD dwActual; + COORD origin; + origin.X = 0; + origin.Y = 0; + FillConsoleOutputCharacter ( hOutput, ' ', screenInfo.dwSize.X * screenInfo.dwSize.Y, origin, &dwActual ); + + screenInfo.dwCursorPosition.X = 0; + screenInfo.dwCursorPosition.Y = 0; +} + +void GetXY ( UINT32 *x, UINT32 *y ) +{ + GetConsoleScreenBufferInfo ( hOutput, &screenInfo ); + *x = screenInfo.dwCursorPosition.X; + *y = screenInfo.dwCursorPosition.Y; +} + +void GotoXY ( UINT32 x, UINT32 y ) +{ + curX = x; + curY = y; + + COORD cursor; + cursor.X = ( SHORT ) curX; + cursor.Y = ( SHORT ) curY; + SetConsoleCursorPosition ( hOutput, cursor ); +} + +void MoveUp ( int delta ) +{ + GetConsoleScreenBufferInfo ( hOutput, &screenInfo ); + COORD pos; + pos.X = ( SHORT ) 0; + pos.Y = ( SHORT ) ( screenInfo.dwCursorPosition.Y - delta ); + SetConsoleCursorPosition ( hOutput, pos ); +} + +void MoveDown ( int delta ) +{ + GetConsoleScreenBufferInfo ( hOutput, &screenInfo ); + COORD pos; + pos.X = ( SHORT ) 0; + pos.Y = ( SHORT ) ( screenInfo.dwCursorPosition.Y + delta ); + SetConsoleCursorPosition ( hOutput, pos ); +} + +void Status ( char *message ) +{ + DWORD count; + DWORD len = ( DWORD ) strlen ( message ); + currentPos.X = ( SHORT ) startX; + currentPos.Y = ( SHORT ) startY; + if ( len != 0 ) { + WriteConsoleOutputCharacter ( hOutput, message, len, currentPos, &count ); + currentPos.X = ( SHORT ) ( currentPos.X + len ); + } + FillConsoleOutputCharacter ( hOutput, ' ', screenInfo.dwSize.X - currentPos.X, currentPos, &count ); +} + +void GoRight () +{ + DWORD count; + WriteConsoleOutputCharacter ( hOutput, "R", 1, currentPos, &count ); + currentPos.X++; +} + +void GoLeft () +{ + DWORD count; + currentPos.X--; + WriteConsoleOutputCharacter ( hOutput, "L", 1, currentPos, &count ); + currentPos.X++; +} + +void Backup () +{ + currentPos.X--; +} + +void ShowDone () +{ + DWORD count; + WriteConsoleOutputCharacter ( hOutput, "*", 1, currentPos, &count ); +} + +void ShowProgress () +{ + DWORD count; + WriteConsoleOutputCharacter ( hOutput, &progress [ progressIndex++ % SIZE ( progress )], 1, currentPos, &count ); +} + +#elif defined ( __GNUC__ ) || defined ( __INTEL_COMPILER ) + +static FILE *console; +static termios stored; +static termios term_getch; +static termios term_kbhit; +static int lastChar; +static int keyhit; +static bool cursor_visible = true; + +int cprintf ( const char *fmt, ... ) +{ + va_list args; + va_start ( args, fmt ); + + int ret = vfprintf ( console, fmt, args ); + + fflush ( console ); + + va_end ( args ); + + return ret; +} + +void SignalHandler ( int signal ) +{ + struct sigaction sa; + memset ( &sa, 0, sizeof ( sa )); + sa.sa_handler = SIG_DFL; + switch ( signal ) { + case SIGABRT : + RestoreConsoleSettings (); + break; + case SIGSEGV : + fprintf ( stderr, "Segmentation fault" ); + case SIGINT : + RestoreConsoleSettings (); + printf ( "\r\n" ); + exit ( -1 ); + break; + case SIGTSTP : + RestoreConsoleSettings (); + printf ( "\r\n" ); + sigaction ( SIGTSTP, &sa, NULL ); + kill ( getpid (), SIGTSTP ); + break; + case SIGCONT : + SaveConsoleSettings (); + break; + } +} + +void SaveConsoleSettings () +{ + tcgetattr ( 0, &stored ); + memcpy ( &term_getch, &stored, sizeof ( struct termios )); + // Disable echo + term_getch.c_lflag &= ~ECHO; + // Disable canonical mode, and set buffer size to 1 byte + term_getch.c_lflag &= ~ICANON; + term_getch.c_cc[VMIN] = 1; + + memcpy ( &term_kbhit, &term_getch, sizeof ( struct termios )); + term_kbhit.c_cc[VTIME] = 0; + term_kbhit.c_cc[VMIN] = 0; + + // Create a 'console' device + console = fopen ( "/dev/tty", "w" ); + if ( console == NULL ) { + console = stdout; + } + + HideCursor (); + + struct sigaction sa; + memset ( &sa, 0, sizeof ( sa )); + sa.sa_handler = SignalHandler; + sigaction ( SIGINT, &sa, NULL ); + sigaction ( SIGABRT, &sa, NULL ); + sigaction ( SIGSEGV, &sa, NULL ); + sigaction ( SIGTSTP, &sa, NULL ); + sigaction ( SIGCONT, &sa, NULL ); + + atexit ( RestoreConsoleSettings ); +} + +void RestoreConsoleSettings () +{ + if ( console != stdout ) { + fclose ( console ); + console = stdout; + } + + struct sigaction sa; + memset ( &sa, 0, sizeof ( sa )); + sa.sa_handler = SIG_DFL; + sigaction ( SIGINT, &sa, NULL ); + sigaction ( SIGABRT, &sa, NULL ); + sigaction ( SIGSEGV, &sa, NULL ); + sigaction ( SIGTSTP, &sa, NULL ); + sigaction ( SIGCONT, &sa, NULL ); + + tcsetattr ( 0, TCSANOW, &stored ); + + ShowCursor (); +} + +void HideCursor () +{ + if ( cursor_visible == true ) { + printf ( "\033[?25l" ); + fflush ( stdout ); + cursor_visible = false; + } +} + +void ShowCursor () +{ + if ( cursor_visible == false ) { + printf ( "\033[?25h" ); + fflush ( stdout ); + cursor_visible = true; + } +} + +int GetKey () +{ + int retVal = lastChar; + lastChar = 0; + + if ( keyhit == 0 ) { + tcsetattr ( 0, TCSANOW, &term_getch ); + keyhit = read ( STDIN_FILENO, &retVal, sizeof ( retVal )); + } + + keyhit = 0; + + return retVal; +} + +bool KeyPressed () +{ + if ( keyhit == 0 ) { + tcsetattr ( 0, TCSANOW, &term_kbhit ); + keyhit = read ( STDIN_FILENO, &lastChar, sizeof ( lastChar )); + } + + return ( keyhit != 0 ) ? true : false; +} + +UINT32 CurrentTime () +{ + timeval time; + gettimeofday ( &time, NULL ); + return ( UINT32 ) (( time.tv_sec * 1000 ) + ( time.tv_usec / 1000 )); +} + +void ClearScreen () +{ + printf ( "\033[2J" ); + fflush ( stdout ); +} + +void GetXY ( UINT32 *x, UINT32 *y ) +{ + *x = MAX_LUMP_NAME + 5; + *y = 0; +} + +void GotoXY ( UINT32 x, UINT32 y ) +{ +// fprintf ( console, "\033[%d;%dH", y, x ); + fprintf ( console, "\033[%dG", x ); + fflush ( console ); + curX = x; + curY = y; +} + +void Status ( char *message ) +{ + fprintf ( console, "\033[%dG%s\033[K", startX, message ); + fflush ( console ); +} + +void GoRight () +{ + fprintf ( console, "R" ); + fflush ( console ); +} + +void GoLeft () +{ + fprintf ( console, "\033[DL" ); + fflush ( console ); +} + +void Backup () +{ + fprintf ( console, "\033[D" ); + fflush ( console ); +} + +void ShowDone () +{ + fprintf ( console, "*\033[D" ); + fflush ( console ); +} + +void ShowProgress () +{ + fprintf ( console, "%c\033[D", progress [ progressIndex++ % SIZE ( progress )] ); + fflush ( console ); +} + +void MoveUp ( int delta ) +{ + fprintf ( console, "\033[%dA", delta ); + fflush ( console ); +} + +void MoveDown ( int delta ) +{ + fprintf ( console, "\033[%dB", delta ); + fflush ( console ); +} + +#endif diff --git a/ZenNode/console.hpp b/ZenNode/console.hpp new file mode 100644 index 0000000..c2ff5ca --- /dev/null +++ b/ZenNode/console.hpp @@ -0,0 +1,78 @@ +//---------------------------------------------------------------------------- +// +// File: console.hpp +// Date: 01-Jun-2002 +// Programmer: Marc Rousseau +// +// Description: +// +// Copyright (c) 1998-2004 Marc Rousseau, All Rights Reserved. +// +// 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 2 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, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. +// +// Revision History: +// +//---------------------------------------------------------------------------- + +#ifndef CONSOLE_HPP_ +#define CONSOLE_HPP_ + +#if defined ( __OS2__ ) || defined ( __WIN32__ ) + +#if defined ( __GNUC__ ) || defined ( __INTEL_COMPILER ) +#define cprintf _cprintf +#endif + +#elif defined ( __GNUC__ ) || defined ( __INTEL_COMPILER ) + +#define stricmp strcasecmp + +extern int cprintf ( const char *, ... ); + +#endif + +void SaveConsoleSettings (); +void RestoreConsoleSettings (); + +void HideCursor (); +void ShowCursor (); + +int GetKey (); +bool KeyPressed (); + +UINT32 CurrentTime (); + +void ClearScreen (); + +extern UINT32 startX, startY; + +void GetXY ( UINT32 *x, UINT32 *y ); +void GotoXY ( UINT32 x, UINT32 y ); +void MoveUp ( int delta ); +void MoveDown ( int delta ); +void PutXY ( UINT32 x, UINT32 y, char *ptr, int length ); +void Put ( char *ptr, int length ); + +// ----- External Functions Required by ZenNode ----- + +void Status ( char *message ); +void GoRight (); +void GoLeft (); +void Backup (); +void ShowDone (); +void ShowProgress (); + +#endif + diff --git a/ZenNode/geometry.hpp b/ZenNode/geometry.hpp new file mode 100644 index 0000000..5f0cb5d --- /dev/null +++ b/ZenNode/geometry.hpp @@ -0,0 +1,91 @@ +//---------------------------------------------------------------------------- +// +// File: geometry.hpp +// Date: 26-October-1994 +// Programmer: Marc Rousseau +// +// Description: +// +// Copyright (c) 1994-2004 Marc Rousseau, All Rights Reserved. +// +// 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 2 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, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. +// +// Revision History: +// +//---------------------------------------------------------------------------- + +#ifndef GEOMETRY_HPP_ +#define GEOMETRY_HPP_ + +#if ! defined ( COMMON_HPP_ ) + #include "common.hpp" +#endif + +struct sPoint; +struct sRectangle; + +struct sPoint { + long x; + long y; + sPoint () { x = 0; y = 0; } + sPoint ( long _x, long _y ) { x = _x; y = _y; } + bool isInside ( const sRectangle & ) const; + bool operator == ( const sPoint &o ) const { return ( x == o.x ) && ( y == o.y ); } + bool operator != ( const sPoint &o ) const { return ( x != o.x ) || ( y != o.y ); } +}; + +struct sLine { + sPoint start; + sPoint end; + sLine () : start (), end () {} + sLine ( const sPoint &s, const sPoint &e ) { start = s; end = e; } + int rise () const { return end.y - start.y; } + int run () const { return end.x - start.x; } + float slope () const { return rise () / ( float ) run (); } + bool endMatches ( const sLine & ) const; + bool isInside ( const sRectangle & ) const; + bool intersects ( const sRectangle & ) const; + bool intersects ( const sLine & ) const; +}; + +struct sRectangle { + int xLeft, xRight; + int yTop, yBottom; + sRectangle (); + sRectangle ( long, long, long, long ); + sRectangle ( const sPoint &, const sPoint & ); + void include ( const sPoint & ); + void include ( const sRectangle & ); + long left () const { return xLeft; } + long right () const { return xRight; } + long top () const { return yTop; } + long bottom () const { return yBottom; } + long width () const { return xRight - xLeft; } + long height () const { return yTop - yBottom; } + sPoint center () const { return sPoint (( xLeft + xRight ) / 2, ( yTop + yBottom ) / 2 ); } + sPoint tl () const { return sPoint ( xLeft, yTop ); } + sPoint bl () const { return sPoint ( xLeft, yBottom ); } + sPoint tr () const { return sPoint ( xRight, yTop ); } + sPoint br () const { return sPoint ( xRight, yBottom ); } + bool isInside ( const sRectangle & ) const; + bool intersects ( const sRectangle & ) const; +}; + +inline sRectangle::sRectangle () +{ + xLeft = yTop = xRight = yBottom = 0; +} + +#endif diff --git a/ZenNode/makefile b/ZenNode/makefile new file mode 100644 index 0000000..0ba7cba --- /dev/null +++ b/ZenNode/makefile @@ -0,0 +1,139 @@ +# ZenNode makefile for Linux + +CC = g++ +CFLAGS += -g -O -fstrength-reduce -fno-rtti +CFLAGS += -fomit-frame-pointer -foptimize-sibling-calls +#CFLAGS += -fbranch-probabilities +#CFLAGS += -fprofile-arcs +WARNINGS = -Wall -Wno-format -Wstrict-prototypes -Wmissing-prototypes -Winline +#CC = /opt/intel/compiler60/ia32/bin/icc +#CFLAGS = -g -O3 +INCLUDES = -I../DOOM -I../common +TARGETS = ZenNode bspdiff bspinfo compare + +CFLAGS += -D__LINUX__ + +ifdef WIN32 +CFLAGS += -D__WIN32__ +endif + +ifdef DEBUG +CFLAGS += -DDEBUG +LOGGER = ../common/logger/logger.o ../common/logger/string.o +CFLAGS += -fexceptions +LOGGER += ../common/logger/linux-logger.o +LIBS += -lpthread -lrt +endif + +.cpp.o: + $(CC) -c $(CFLAGS) $(WARNINGS) $(INCLUDES) -o $@ $< + +.SUFFIXES: .cpp .o + +all: $(TARGETS) + +depend: clean + +clean: + rm -rf {.,../DOOM/,../common/logger}/{*.o,*~} $(TARGETS) + +ZenNode: ZenMain.o ZenNode.o ZenReject.o ZenRMB.o blockmap.o console.o ../DOOM/wad.o ../DOOM/level.o $(LOGGER) + $(CC) $(LIBS) -o $@ $^ + +bspdiff: bspdiff.o console.o ../DOOM/wad.o ../DOOM/level.o $(LOGGER) + $(CC) $(LIBS) -o $@ $^ + +bspinfo: bspinfo.o console.o ../DOOM/wad.o ../DOOM/level.o $(LOGGER) + $(CC) $(LIBS) -o $@ $^ + +compare: compare.o console.o ../DOOM/wad.o ../DOOM/level.o $(LOGGER) + $(CC) $(LIBS) -o $@ $^ + +ZenReject.o: \ + ZenReject.cpp \ + ZenReject-util.cpp \ + ../common/common.hpp \ + ../DOOM/level.hpp \ + ZenNode.hpp \ + console.hpp \ + geometry.hpp + +ZenNode.o: \ + ZenNode.cpp \ + ../common/common.hpp \ + ../common/logger.hpp \ + ../DOOM/level.hpp \ + ZenNode.hpp \ + console.hpp + +ZenMain.o: \ + ZenMain.cpp \ + ../common/common.hpp \ + ../common/logger.hpp \ + ../DOOM/wad.hpp \ + ../DOOM/level.hpp \ + console.hpp \ + ZenNode.hpp + +ZenReject.o: \ + ZenReject.cpp \ + ../common/common.hpp \ + ../DOOM/level.hpp \ + ZenNode.hpp \ + console.hpp \ + geometry.hpp + +ZenRMB.o: \ + ZenRMB.cpp \ + ../common/common.hpp \ + ../DOOM/level.hpp \ + ZenNode.hpp + +blockmap.o: \ + blockmap.cpp \ + ../common/common.hpp \ + ../DOOM/level.hpp \ + ZenNode.hpp + +console.o: \ + console.cpp \ + ../DOOM/level.hpp \ + ../common/common.hpp \ + console.hpp \ + +../DOOM/wad.o: \ + ../DOOM/wad.cpp \ + ../common/common.hpp \ + ../DOOM/wad.hpp \ + ../DOOM/level.hpp + +../DOOM/level.o: \ + ../DOOM/level.cpp \ + ../common/common.hpp \ + ../common/logger.hpp \ + ../DOOM/wad.hpp \ + ../DOOM/level.hpp + +bspdiff.o: \ + bspdiff.cpp \ + ../common/common.hpp \ + ../common/logger.hpp \ + ../DOOM/wad.hpp \ + ../DOOM/level.hpp \ + console.hpp + +bspinfo.o: \ + bspinfo.cpp \ + ../common/common.hpp \ + ../common/logger.hpp \ + ../DOOM/wad.hpp \ + ../DOOM/level.hpp \ + console.hpp + +compare.o: \ + compare.cpp \ + ../common/common.hpp \ + ../common/logger.hpp \ + ../DOOM/wad.hpp \ + ../DOOM/level.hpp \ + console.hpp diff --git a/common/common.hpp b/common/common.hpp new file mode 100644 index 0000000..24d6041 --- /dev/null +++ b/common/common.hpp @@ -0,0 +1,258 @@ +//---------------------------------------------------------------------------- +// +// File: common.hpp +// Date: +// Programmer: Marc Rousseau +// +// Description: +// +// Copyright (c) 1994-2004 Marc Rousseau, All Rights Reserved. +// +// 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 2 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, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. +// +// Revision History: +// +//---------------------------------------------------------------------------- + +#ifndef COMMON_HPP_ +#define COMMON_HPP_ + +//---------------------------------------------------------------------------- +// Generic definitions +//---------------------------------------------------------------------------- + +#ifdef UNREFERENCED_PARAMETER + #undef UNREFERENCED_PARAMETER +#endif + +#if defined ( _MSC_VER ) + #define UNREFERENCED_PARAMETER(x) x +#else + #define UNREFERENCED_PARAMETER(x) +#endif + +#define OFFSET_OF(t,x) (( size_t ) & (( t * ) 0 )->( x )) +#define SIZE(x) ( sizeof ( x ) / sizeof (( x )[0] )) +#define EVER ;; + +typedef signed char INT8; +typedef signed short INT16; +typedef signed int INT32; + +typedef unsigned char UINT8, UCHAR; +typedef unsigned short UINT16; +typedef unsigned int UINT32; + +#if defined ( __GNUC__ ) + + typedef long long INT64; + typedef unsigned long long UINT64; + +#else + + typedef __int64 INT64; + typedef unsigned __int64 UINT64; + +#endif + +#if defined ( __WIN32__ ) || defined ( __AMIGAOS__ ) + + // Undo any previos definitions + #define BIG_ENDIAN 1234 + #define LITTLE_ENDIAN 4321 + + #define BYTE_ORDER LITTLE_ENDIAN + +#else + + // Use the environments endian definitions + #undef __USE_BSD + #define __USE_BSD + #include + +#endif + +typedef int (*QSORT_FUNC) ( const void *, const void * ); + +#ifdef max + #undef max +#endif +#ifdef min + #undef min +#endif + +template < class T > inline void swap ( T &item1, T &item2 ) +{ + T temp = item1; + item1 = item2; + item2 = temp; +} + +//---------------------------------------------------------------------------- +// Platform specific definitions +//---------------------------------------------------------------------------- + +#if defined ( __OS2__ ) || defined ( __WIN32__ ) + +#define SEPERATOR '\\' +#define DEFAULT_CHAR 'û' + +#elif defined ( __GNUC__ ) || defined ( __INTEL_COMPILER ) + +#define SEPERATOR '/' +#define DEFAULT_CHAR '*' + +#endif + +//---------------------------------------------------------------------------- +// Compiler specific definitions +//---------------------------------------------------------------------------- + +#if defined ( __BORLANDC__ ) + + #if defined ( __BCPLUSPLUS__ ) || defined ( __TCPLUSPLUS__ ) + #undef NULL + #define NULL ( void * ) 0 + #endif + + #if (( __BORLANDC__ < 0x0500 ) || defined ( __OS2__ )) + + // Fake out ANSI definitions for deficient compilers + #define for if (0); else for + + enum { false, true }; + class bool { + int value; + public: + operator = ( int x ) { value = x ? true : false; } + bool operator ! () { return value; } + }; + + #endif + + template inline const T &min ( const T &t1, const T &t2 ) + { + return ( t1 > t2 ) ? t2 : t1; + } + + template inline const T &max ( const T &t1, const T &t2 ) + { + return ( t1 > t2 ) ? t1 : t2; + } + +#elif defined ( _MSC_VER ) + + #undef NULL + #define NULL 0L + + #define MAXPATH _MAX_PATH + #define MAXDRIVE _MAX_DRIVE + #define MAXDIR _MAX_DIR + #define MAXFNAME _MAX_FNAME + #define MAXEXT _MAX_EXT + + #if ( _MSC_VER < 1300 ) + + // Fake out ANSI definitions for deficient compilers + #define for if (0); else for + + #pragma warning ( disable: 4127 ) // C4127: conditional expression is constant + + #endif + + #pragma warning ( disable: 4244 ) // C4244: 'xyz' conversion from 'xxx' to 'yyy', possible loss of data + #pragma warning( disable : 4290 ) // C4290: C++ exception specification ignored... + #pragma warning ( disable: 4514 ) // C4514: 'xyz' unreferenced inline function has been removed + + template inline const T &min ( const T &t1, const T &t2 ) + { + return ( t1 > t2 ) ? t2 : t1; + } + + template inline const T &max ( const T &t1, const T &t2 ) + { + return ( t1 > t2 ) ? t1 : t2; + } + + #if ( _MSC_VER >= 1300 ) + extern char *strndup ( const char *string, size_t max ); + #endif + +#elif defined ( __WATCOMC__ ) + + #define M_PI 3.14159265358979323846 + + template inline const T &min ( const T &t1, const T &t2 ) + { + return ( t1 > t2 ) ? t2 : t1; + } + + template inline const T &max ( const T &t1, const T &t2 ) + { + return ( t1 > t2 ) ? t1 : t2; + } + +#elif defined ( __GNUC__ ) || defined ( __INTEL_COMPILER ) + + #define MAXPATH FILENAME_MAX + #define MAXDRIVE FILENAME_MAX + #define MAXDIR FILENAME_MAX + #define MAXFNAME FILENAME_MAX + #define MAXEXT FILENAME_MAX + + #define O_BINARY 0 + + #define stricmp strcasecmp + #define strnicmp strncasecmp + + template inline const T &min ( const T &t1, const T &t2 ) + { + return ( t1 > t2 ) ? t2 : t1; + } + + template inline const T &max ( const T &t1, const T &t2 ) + { + return ( t1 > t2 ) ? t1 : t2; + } + +#elif defined ( __AMIGAOS__ ) + + // Fake out ANSI definitions for deficient compilers + #define for if (0); else for + + enum { false, true }; + class bool { + int value; + public: + operator = ( int x ) { value = x ? true : false; } + bool operator ! () { return value; } + }; + + template inline const T &min ( const T &t1, const T &t2 ) + { + return ( t1 > t2 ) ? t2 : t1; + } + + template inline const T &max ( const T &t1, const T &t2 ) + { + return ( t1 > t2 ) ? t1 : t2; + } + + extern char *strdup ( const char *string ); + extern char *strndup ( const char *string, size_t max ); + +#endif + +#endif diff --git a/common/logger.hpp b/common/logger.hpp new file mode 100644 index 0000000..21f44c2 --- /dev/null +++ b/common/logger.hpp @@ -0,0 +1,185 @@ +//---------------------------------------------------------------------------- +// +// File: logger.hpp +// Date: 15-Apr-1998 +// Programmer: Marc Rousseau +// +// Description: Error Logger object header +// +// Copyright (c) 1998-2004 Marc Rousseau, All Rights Reserved. +// +// 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 2 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, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. +// +// Revision History: +// +//---------------------------------------------------------------------------- + +#ifndef LOGGER_HPP_ +#define LOGGER_HPP_ + +#ifdef ERROR + #undef ERROR +#endif +#ifdef ASSERT + #undef ASSERT +#endif + +#if defined ( DEBUG ) + + struct LOG_FLAGS { + bool FunctionEntry; + }; + + extern LOG_FLAGS g_LogFlags; + + #define FUNCTION_ENTRY(t,f,l) \ + void *l_pThis = ( void * ) t; \ + char *l_FnName = f; \ + bool l_log = ( g_LogFlags.FunctionEntry == l ) ? true : false; \ + if ( l_log && g_LogFlags.FunctionEntry ) { \ + DBG_MINOR_TRACE2 ( l_FnName, l_pThis, "Function entered" ); \ + } + + #define ASSERT(x) (( ! ( x )) ? dbg_Assert ( dbg_FileName, __LINE__, l_FnName, l_pThis, #x ), 1 : 0 ) + #define ASSERT_BOOL(x) ASSERT ( x ) + +#else + + #if defined ( _MSC_VER ) + #pragma warning ( disable: 4127 ) // C4127: conditional expression is constant + #endif + + #define FUNCTION_ENTRY(t,f,l) + #define ASSERT(x) + #define ASSERT_BOOL(x) 0 + +#endif + +#define TRACE(x) DBG_MAJOR_TRACE2 ( l_FnName, l_pThis, ( const char * ) x ) +#define EVENT(x) DBG_MINOR_EVENT2 ( l_FnName, l_pThis, ( const char * ) x ) +#define MINOR_EVENT(x) DBG_MINOR_EVENT2 ( l_FnName, l_pThis, ( const char * ) x ) +#define MAJOR_EVENT(x) DBG_MAJOR_EVENT2 ( l_FnName, l_pThis, ( const char * ) x ) +#define STATUS(x) DBG_STATUS2 ( l_FnName, l_pThis, ( const char * ) x ) +#define WARNING(x) DBG_WARNING2 ( l_FnName, l_pThis, ( const char * ) x ) +#define ERROR(x) DBG_ERROR2 ( l_FnName, l_pThis, ( const char * ) x ) +#define FATAL(x) DBG_FATAL2 ( l_FnName, l_pThis, ( const char * ) x ) + +#if ! defined ( DEBUG ) + + #define DBG_REGISTER(x) + #define DBG_STRING(x) "" + #define DBG_MINOR_TRACE(f,t,x) + #define DBG_MINOR_TRACE2(f,t,x) + #define DBG_MAJOR_TRACE(f,t,x) + #define DBG_MAJOR_TRACE2(f,t,x) + #define DBG_MINOR_EVENT(f,t,x) + #define DBG_MINOR_EVENT2(f,t,x) + #define DBG_MAJOR_EVENT(f,t,x) + #define DBG_MAJOR_EVENT2(f,t,x) + #define DBG_STATUS(f,t,x) + #define DBG_STATUS2(f,t,x) + #define DBG_WARNING(f,t,x) + #define DBG_WARNING2(f,t,x) + #define DBG_ERROR(f,t,x) + #define DBG_ERROR2(f,t,x) + #define DBG_FATAL(f,t,x) + #define DBG_FATAL2(f,t,x) + #define DBG_ASSERT(f,t,x) 0 + +#else + + #define DBG_REGISTER(x) static int dbg_FileName = dbg_RegisterFile ( x ); + #define DBG_STRING(x) x + #define DBG_MINOR_TRACE(f,t,x) dbg_Record ( _MINOR_TRACE_, dbg_FileName, __LINE__, f, t, x ) + #define DBG_MINOR_TRACE2(f,t,x) dbg_Record ( _MINOR_TRACE_, dbg_FileName, __LINE__, f, t, dbg_Stream () << x ), dbg_Stream ().flush () + #define DBG_MAJOR_TRACE(f,t,x) dbg_Record ( _MAJOR_TRACE_, dbg_FileName, __LINE__, f, t, x ) + #define DBG_MAJOR_TRACE2(f,t,x) dbg_Record ( _MAJOR_TRACE_, dbg_FileName, __LINE__, f, t, dbg_Stream () << x ), dbg_Stream ().flush () + #define DBG_MINOR_EVENT(f,t,x) dbg_Record ( _MINOR_EVENT_, dbg_FileName, __LINE__, f, t, x ) + #define DBG_MINOR_EVENT2(f,t,x) dbg_Record ( _MINOR_EVENT_, dbg_FileName, __LINE__, f, t, dbg_Stream () << x ), dbg_Stream ().flush () + #define DBG_MAJOR_EVENT(f,t,x) dbg_Record ( _MAJOR_EVENT_, dbg_FileName, __LINE__, f, t, x ) + #define DBG_MAJOR_EVENT2(f,t,x) dbg_Record ( _MAJOR_EVENT_, dbg_FileName, __LINE__, f, t, dbg_Stream () << x ), dbg_Stream ().flush () + #define DBG_STATUS(f,t,x) dbg_Record ( _STATUS_, dbg_FileName, __LINE__, f, t, x ) + #define DBG_STATUS2(f,t,x) dbg_Record ( _STATUS_, dbg_FileName, __LINE__, f, t, dbg_Stream () << x ), dbg_Stream ().flush () + #define DBG_WARNING(f,t,x) dbg_Record ( _WARNING_, dbg_FileName, __LINE__, f, t, x ) + #define DBG_WARNING2(f,t,x) dbg_Record ( _WARNING_, dbg_FileName, __LINE__, f, t, dbg_Stream () << x ), dbg_Stream ().flush () + #define DBG_ERROR(f,t,x) dbg_Record ( _ERROR_, dbg_FileName, __LINE__, f, t, x ) + #define DBG_ERROR2(f,t,x) dbg_Record ( _ERROR_, dbg_FileName, __LINE__, f, t, dbg_Stream () << x ), dbg_Stream ().flush () + #define DBG_FATAL(f,t,x) dbg_Record ( _FATAL_, dbg_FileName, __LINE__, f, t, x ) + #define DBG_FATAL2(f,t,x) dbg_Record ( _FATAL_, dbg_FileName, __LINE__, f, t, dbg_Stream () << x ), dbg_Stream ().flush () + #define DBG_ASSERT(f,t,x) (( ! ( x )) ? dbg_Assert ( dbg_FileName, __LINE__, f, t, #x ), 1 : 0 ) + +enum eLOG_TYPE { + _MINOR_TRACE_, + _MAJOR_TRACE_, + _MINOR_EVENT_, + _MAJOR_EVENT_, + _STATUS_, + _WARNING_, + _ERROR_, + _FATAL_, + LOG_TYPE_MAX +}; + +class dbgString { + + friend dbgString &hex ( dbgString & ); + friend dbgString &dec ( dbgString & ); + + int m_Base; + bool m_OwnBuffer; + char *m_Ptr; + char *m_Buffer; + +public: + + dbgString ( char * ); + + operator const char * (); + void flush (); + + dbgString &operator << ( const char * ); + dbgString &operator << ( char ); + dbgString &operator << ( unsigned char ); + dbgString &operator << ( short ); + dbgString &operator << ( unsigned short ); + dbgString &operator << ( int ); + dbgString &operator << ( unsigned int ); + dbgString &operator << ( long ); + dbgString &operator << ( unsigned long ); +/* + dbgString &operator << ( __int64 ); + dbgString &operator << ( unsigned __int64 ); +*/ + dbgString &operator << ( double ); + dbgString &operator << ( void * ); + dbgString &operator << ( dbgString &(*f) ( dbgString & )); + +}; + +dbgString &hex ( dbgString & ); +dbgString &dec ( dbgString & ); + +extern "C" { + + dbgString &dbg_Stream (); + int dbg_RegisterFile ( const char * ); + void dbg_Assert ( int, int, const char * , void *, const char * ); + void dbg_Record ( int, int, int, const char *, void *, const char * ); + +}; + +#endif + +#endif