mirror of
https://github.com/EQEmu/Server.git
synced 2025-12-11 21:01:29 +00:00
* [Strings] Add more number formatters # Notes - Adds `Strings::ToUnsignedInt` for `uint32` support. - Adds `Strings::ToBigInt` for `int64` support. - Adds `Strings::ToUnsignedBigInt` for `uint64` support. - Adds `Strings::ToFloat` for `float` support. - Replaces all `std::stoi` references with `Strings::ToInt`. - Replaces all `atoi` references with `Strings::ToInt`. - Replaces all `std::stoul` references with `Strings::ToUnsignedInt`. - Replaces all `atoul` references with `Strings::ToUnsignedInt`. - Replaces all `std::stoll` references with `Strings::ToBigInt`. - Replaces all `atoll` references with `Strings::ToBigInt`. - Replaces all `std::stoull` references with `Strings::ToUnsignedBigInt`. - Replaces all `atoull` references with `Strings::ToUnsignedBigInt`. - Replaces all `std::stof` references with `Strings::ToFloat`. * [Strings] Add more number formatters - Adds `Strings::ToUnsignedInt` for `uint32` support. - Adds `Strings::ToBigInt` for `int64` support. - Adds `Strings::ToUnsignedBigInt` for `uint64` support. - Adds `Strings::ToFloat` for `float` support. - Replaces all `std::stoi` references with `Strings::ToInt`. - Replaces all `atoi` references with `Strings::ToInt`. - Replaces all `std::stoul` references with `Strings::ToUnsignedInt`. - Replaces all `atoul` references with `Strings::ToUnsignedInt`. - Replaces all `std::stoll` references with `Strings::ToBigInt`. - Replaces all `atoll` references with `Strings::ToBigInt`. - Replaces all `std::stoull` references with `Strings::ToUnsignedBigInt`. - Replaces all `atoull` references with `Strings::ToUnsignedBigInt`. - Replaces all `std::stof` references with `Strings::ToFloat`. * Rebase cleanup * Changes/benchmarks/tests --------- Co-authored-by: Akkadius <akkadius1@gmail.com>
1622 lines
42 KiB
C++
1622 lines
42 KiB
C++
/*
|
|
|
|
Father Nitwit's Zone to map conversion program.
|
|
Copyright (C) 2004 Father Nitwit (eqemu@8ass.com)
|
|
|
|
This thing uses code from freaku, so whatever license that comes under
|
|
is relavent, if you care.
|
|
|
|
the rest of it is GPL, even though I hate the GPL.
|
|
|
|
|
|
Derision: 20/06/2008
|
|
Replaced S3D and EQG fileloaders with later OpenEQ based versions
|
|
Added placeable object support.
|
|
Altered order of vertices in polygons from EQG files so that map
|
|
files produced from S3D and EQG zones are consistent in the vertex
|
|
order and therefore surface normals.
|
|
|
|
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; version 2 of the License.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY except by those people which sell it, which
|
|
are required to give you total support for your newly bought product;
|
|
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
|
|
*/
|
|
|
|
typedef unsigned char BYTE;
|
|
typedef unsigned short WORD;
|
|
typedef short SHORT;
|
|
typedef unsigned long DWORD;
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <math.h>
|
|
#include <stdlib.h>
|
|
//#include "EQWldData.h"
|
|
#include "types.h"
|
|
#include "azone.h"
|
|
#include "wld.hpp"
|
|
|
|
#include "archive.hpp"
|
|
#include "pfs.hpp"
|
|
|
|
#include "file_loader.hpp"
|
|
#include "zon.hpp"
|
|
#include "ter.hpp"
|
|
#include "zonv4.hpp"
|
|
|
|
//TODO: I am trimming faces for which all vertices are above MAX_Z
|
|
// but I am not taking out the vertices to go with them.
|
|
|
|
|
|
//this un-commented works with my map.cpp code correctly.
|
|
//with both of my inverts there commented.
|
|
#define INVERSEXY 1
|
|
//#define DEBUG
|
|
#include <vector>
|
|
#include <map>
|
|
using namespace std;
|
|
|
|
//#define SPLIT_DEBUG
|
|
|
|
|
|
|
|
#ifndef MAX_QUADRENT_FACES
|
|
#ifndef MIN_QUADRENT_SIZE
|
|
#error Umm... your asking for trouble by turning off both stopping criteria
|
|
#endif
|
|
#endif
|
|
|
|
int main(int argc, char *argv[]) {
|
|
|
|
printf("AZONE2: EQEmu .MAP file generator with placeable object support.\n");
|
|
|
|
if(argc != 2) {
|
|
printf("Usage: %s (zone short name)\n", argv[0]);
|
|
return(1);
|
|
}
|
|
|
|
char bufm[250];
|
|
|
|
sprintf(bufm, "%s.map", argv[1]);
|
|
|
|
QTBuilder QT;
|
|
|
|
if(!QT.build(argv[1]))
|
|
return(1);
|
|
|
|
if(!QT.writeMap(bufm))
|
|
return(1);
|
|
|
|
return(0);
|
|
}
|
|
|
|
QTBuilder::QTBuilder() {
|
|
_root = nullptr;
|
|
|
|
faceCount = 0;
|
|
faceBlock = nullptr;
|
|
|
|
#ifdef COUNT_MACTHES
|
|
gEasyMatches = 0;
|
|
gEasyExcludes = 0;
|
|
gHardMatches = 0;
|
|
gHardExcludes = 0;
|
|
#endif
|
|
}
|
|
|
|
QTBuilder::~QTBuilder() {
|
|
if(_root != nullptr)
|
|
delete _root;
|
|
_root = nullptr;
|
|
if(faceBlock != nullptr)
|
|
delete [] faceBlock;
|
|
faceBlock = nullptr;
|
|
}
|
|
|
|
bool QTBuilder::build(const char *shortname) {
|
|
|
|
|
|
char bufs[96];
|
|
Archive *archive;
|
|
FileLoader *fileloader;
|
|
Zone_Model *zm;
|
|
FILE *fff;
|
|
EQFileType FileType = UNKNOWN;
|
|
|
|
bool V4Zone = false;
|
|
|
|
sprintf(bufs, "%s.s3d", shortname);
|
|
|
|
archive = new PFSLoader();
|
|
fff = fopen(bufs, "rb");
|
|
if(fff != nullptr)
|
|
FileType = S3D;
|
|
else {
|
|
sprintf(bufs, "%s.eqg", shortname);
|
|
fff = fopen(bufs, "rb");
|
|
if(fff != nullptr)
|
|
FileType = EQG;
|
|
}
|
|
|
|
if(FileType == UNKNOWN) {
|
|
printf("Unable to locate %s.s3d or %s.eqg\n", shortname, shortname);
|
|
return(false);
|
|
}
|
|
|
|
if(archive->Open(fff) == 0) {
|
|
printf("Unable to open container file '%s'\n", bufs);
|
|
return(false);
|
|
}
|
|
|
|
switch(FileType) {
|
|
case S3D:
|
|
fileloader = new WLDLoader();
|
|
if(fileloader->Open(nullptr, (char *) shortname, archive) == 0) {
|
|
printf("Error reading WLD from %s\n", bufs);
|
|
return(false);
|
|
}
|
|
break;
|
|
case EQG:
|
|
fileloader = new ZonLoader();
|
|
if(fileloader->Open(nullptr, (char *) shortname, archive) == 0) {
|
|
delete fileloader;
|
|
fileloader = new Zonv4Loader();
|
|
if(fileloader->Open(nullptr, (char *) shortname, archive) == 0) {
|
|
printf("Error reading ZON/TER from %s\n", bufs);
|
|
return(false);
|
|
}
|
|
V4Zone = true;
|
|
}
|
|
break;
|
|
case UNKNOWN:
|
|
// Just here to stop the compiler warning
|
|
break;
|
|
}
|
|
|
|
zm = fileloader->model_data.zone_model;
|
|
|
|
long i;
|
|
VERTEX v1, v2, v3;
|
|
for(i = 0; i < zm->poly_count; ++i) {
|
|
#ifdef INVERSEXY
|
|
v1.y = zm->verts[zm->polys[i]->v1]->x;
|
|
v1.x = zm->verts[zm->polys[i]->v1]->y;
|
|
#else
|
|
v1.x = zm->verts[zm->polys[i]->v1]->x;
|
|
v1.y = zm->verts[zm->polys[i]->v1]->y;
|
|
#endif
|
|
v1.z = zm->verts[zm->polys[i]->v1]->z;
|
|
#ifdef INVERSEXY
|
|
v2.y = zm->verts[zm->polys[i]->v2]->x;
|
|
v2.x = zm->verts[zm->polys[i]->v2]->y;
|
|
#else
|
|
v2.x = zm->verts[zm->polys[i]->v2]->x;
|
|
v2.y = zm->verts[zm->polys[i]->v2]->y;
|
|
#endif
|
|
v2.z = zm->verts[zm->polys[i]->v2]->z;
|
|
#ifdef INVERSEXY
|
|
v3.y = zm->verts[zm->polys[i]->v3]->x;
|
|
v3.x = zm->verts[zm->polys[i]->v3]->y;
|
|
#else
|
|
v3.x = zm->verts[zm->polys[i]->v3]->x;
|
|
v3.y = zm->verts[zm->polys[i]->v3]->y;
|
|
#endif
|
|
v3.z = zm->verts[zm->polys[i]->v3]->z;
|
|
|
|
AddFace(v1, v2, v3);
|
|
}
|
|
|
|
printf("There are %lu vertices and %lu faces.\n", _FaceList.size()*3, _FaceList.size());
|
|
|
|
if(fileloader->model_data.plac_count)
|
|
{
|
|
if(V4Zone)
|
|
{
|
|
vector<ObjectGroupEntry>::iterator Iterator;
|
|
|
|
Iterator = fileloader->model_data.ObjectGroups.begin();
|
|
AddPlaceableV4(fileloader, bufs, false);
|
|
}
|
|
else
|
|
AddPlaceable(fileloader, bufs, false);
|
|
}
|
|
else
|
|
printf("No placeable objects (or perhaps %s_obj.s3d not present).\n", shortname);
|
|
|
|
printf("After processing placeable objects, there are %lu vertices and %lu faces.\n", _FaceList.size()*3, _FaceList.size());
|
|
|
|
unsigned long r;
|
|
|
|
faceCount = _FaceList.size();
|
|
|
|
|
|
|
|
faceBlock = new FACE[faceCount];
|
|
//im not going to assume I know vectors are stored in contiguous blocks
|
|
for(r = 0; r < faceCount; r++) {
|
|
faceBlock[r] = _FaceList[r];
|
|
}
|
|
|
|
//build quad tree... prolly much slower than it needs to be.
|
|
float minx, miny, maxx, maxy;
|
|
minx = 1e12;
|
|
miny = 1e12;
|
|
maxx = -1e12;
|
|
maxy = -1e12;
|
|
|
|
//find our limits.
|
|
for(r = 0; r < faceCount; r++) {
|
|
//a bit of lazyness going on here...
|
|
{
|
|
VERTEX &v = faceBlock[r].a;
|
|
if(v.x > maxx)
|
|
maxx = v.x;
|
|
if(v.x < minx)
|
|
minx = v.x;
|
|
if(v.y > maxy)
|
|
maxy = v.y;
|
|
if(v.y < miny)
|
|
miny = v.y;
|
|
}
|
|
{
|
|
VERTEX &v = faceBlock[r].b;
|
|
if(v.x > maxx)
|
|
maxx = v.x;
|
|
if(v.x < minx)
|
|
minx = v.x;
|
|
if(v.y > maxy)
|
|
maxy = v.y;
|
|
if(v.y < miny)
|
|
miny = v.y;
|
|
}
|
|
{
|
|
VERTEX &v = faceBlock[r].c;
|
|
if(v.x > maxx)
|
|
maxx = v.x;
|
|
if(v.x < minx)
|
|
minx = v.x;
|
|
if(v.y > maxy)
|
|
maxy = v.y;
|
|
if(v.y < miny)
|
|
miny = v.y;
|
|
}
|
|
}
|
|
|
|
printf("Bounding box: %.2f < x < %.2f, %.2f < y < %.2f\n", minx, maxx, miny, maxy);
|
|
|
|
printf("Building quadtree.\n");
|
|
|
|
_root = new QTNode(this, minx, maxx, miny, maxy);
|
|
if(_root == nullptr) {
|
|
printf("Unable to allocate new QTNode.\n");
|
|
return(false);
|
|
}
|
|
|
|
//build our initial set of faces... all of them:
|
|
FACE *faceptr = faceBlock;
|
|
_root->faces.resize(faceCount);
|
|
for(r = 0; r < faceCount; r++) {
|
|
_root->faces[r].face = faceptr;
|
|
_root->faces[r].index = r;
|
|
faceptr++;
|
|
}
|
|
|
|
_root->divideYourself(0);
|
|
|
|
printf("Done building quad tree...\n");
|
|
|
|
#ifdef COUNT_MACTHES
|
|
printf("Match counters: %lu easy in, %lu easy out, %lu hard in, %lu hard out.\n", gEasyMatches, gEasyExcludes, gHardMatches, gHardExcludes);
|
|
#endif
|
|
|
|
fileloader->Close();
|
|
|
|
delete fileloader;
|
|
|
|
archive->Close();
|
|
|
|
delete archive;
|
|
|
|
return(true);
|
|
}
|
|
|
|
|
|
|
|
bool QTBuilder::writeMap(const char *file) {
|
|
if(_root == nullptr)
|
|
return(false);
|
|
|
|
printf("Writing map file.\n");
|
|
|
|
FILE *out = fopen(file, "wb");
|
|
if(out == nullptr) {
|
|
printf("Unable to open output file '%s'.\n", file);
|
|
return(1);
|
|
}
|
|
|
|
mapHeader head;
|
|
head.version = MAP_VERSION;
|
|
head.face_count = faceCount;
|
|
head.node_count = _root->countNodes();
|
|
head.facelist_count = _root->countFacelists();
|
|
|
|
if(fwrite(&head, sizeof(head), 1, out) != 1) {
|
|
printf("Error writing map file header.\n");
|
|
fclose(out);
|
|
return(1);
|
|
|
|
}
|
|
|
|
printf("Map header: Version: 0x%08lX. %lu faces, %u nodes, %lu facelists\n", head.version, head.face_count, head.node_count, head.facelist_count);
|
|
|
|
|
|
|
|
//write faceBlock
|
|
if(fwrite(faceBlock, sizeof(FACE), faceCount, out) != faceCount) {
|
|
printf("Error writing map file faces.\n");
|
|
fclose(out);
|
|
return(1);
|
|
}
|
|
|
|
//make our node blocks to write out...
|
|
nodeHeader *nodes = new nodeHeader[head.node_count];
|
|
unsigned long *facelist = new unsigned long[head.facelist_count];
|
|
if(nodes == nullptr || facelist == nullptr) {
|
|
printf("Error allocating temporary memory for output.\n");
|
|
fclose(out);
|
|
return(1); //no memory
|
|
}
|
|
|
|
unsigned long hindex = 0;
|
|
unsigned long findex = 0;
|
|
_root->fillBlocks(nodes, facelist, hindex, findex);
|
|
|
|
if(fwrite(nodes, sizeof(nodeHeader), head.node_count, out) != head.node_count) {
|
|
printf("Error writing map file nodes.\n");
|
|
fclose(out);
|
|
return(1);
|
|
}
|
|
if(fwrite(facelist, sizeof(unsigned long), head.facelist_count, out) != head.facelist_count) {
|
|
printf("Error writing map file face list.\n");
|
|
fclose(out);
|
|
return(1);
|
|
}
|
|
|
|
|
|
long MapFileSize = ftell(out);
|
|
fclose(out);
|
|
delete[] nodes;
|
|
delete[] facelist;
|
|
|
|
printf("Done writing map (%3.2fMB).\n", (float)MapFileSize/1048576);
|
|
|
|
return(0);
|
|
}
|
|
|
|
|
|
QTNode::QTNode(QTBuilder *b, float Tminx, float Tmaxx, float Tminy, float Tmaxy) {
|
|
node1 = nullptr;
|
|
node2 = nullptr;
|
|
node3 = nullptr;
|
|
node4 = nullptr;
|
|
minx = Tminx;
|
|
maxx = Tmaxx;
|
|
miny = Tminy;
|
|
maxy = Tmaxy;
|
|
final = false;
|
|
buildVertexes();
|
|
|
|
builder = b;
|
|
}
|
|
|
|
QTNode::~QTNode() {
|
|
clearNodes();
|
|
}
|
|
|
|
void QTNode::clearNodes() {
|
|
if(node1 != nullptr)
|
|
delete node1;
|
|
if(node2 != nullptr)
|
|
delete node2;
|
|
if(node3 != nullptr)
|
|
delete node3;
|
|
if(node4 != nullptr)
|
|
delete node4;
|
|
node1 = nullptr;
|
|
node2 = nullptr;
|
|
node3 = nullptr;
|
|
node4 = nullptr;
|
|
}
|
|
|
|
//assumes that both supplied arrays are big enough per countNodes/Facelists
|
|
void QTNode::fillBlocks(nodeHeader *heads, unsigned long *flist, unsigned long &hindex, unsigned long &findex) {
|
|
nodeHeader *head = &heads[hindex];
|
|
hindex++;
|
|
|
|
head->minx = minx;
|
|
head->maxx = maxx;
|
|
head->miny = miny;
|
|
head->maxy = maxy;
|
|
head->flags = 0;
|
|
if(final) {
|
|
head->flags |= nodeFinal;
|
|
head->faces.count = faces.size();
|
|
head->faces.offset = findex;
|
|
unsigned long r;
|
|
for(r = 0; r < head->faces.count; r++) {
|
|
flist[findex] = faces[r].index;
|
|
findex++;
|
|
}
|
|
// findex += head->faces.count;
|
|
} else {
|
|
//branch node.
|
|
head->flags = 0;
|
|
|
|
if(node1 != nullptr) {
|
|
head->nodes[0] = hindex;
|
|
node1->fillBlocks(heads, flist, hindex, findex);
|
|
} else {
|
|
head->nodes[0] = NODE_NONE;
|
|
}
|
|
if(node2 != nullptr) {
|
|
head->nodes[1] = hindex;
|
|
node2->fillBlocks(heads, flist, hindex, findex);
|
|
} else {
|
|
head->nodes[1] = NODE_NONE;
|
|
}
|
|
if(node3 != nullptr) {
|
|
head->nodes[2] = hindex;
|
|
node3->fillBlocks(heads, flist, hindex, findex);
|
|
} else {
|
|
head->nodes[2] = NODE_NONE;
|
|
}
|
|
if(node4 != nullptr) {
|
|
head->nodes[3] = hindex;
|
|
node4->fillBlocks(heads, flist, hindex, findex);
|
|
} else {
|
|
head->nodes[3] = NODE_NONE;
|
|
}
|
|
}
|
|
}
|
|
|
|
unsigned long QTNode::countNodes() const {
|
|
unsigned long c = 1;
|
|
if(node1 != nullptr)
|
|
c += node1->countNodes();
|
|
if(node2 != nullptr)
|
|
c += node2->countNodes();
|
|
if(node3 != nullptr)
|
|
c += node3->countNodes();
|
|
if(node4 != nullptr)
|
|
c += node4->countNodes();
|
|
return(c);
|
|
}
|
|
|
|
unsigned long QTNode::countFacelists() const {
|
|
unsigned long c = final? faces.size() : 0;
|
|
if(node1 != nullptr)
|
|
c += node1->countFacelists();
|
|
if(node2 != nullptr)
|
|
c += node2->countFacelists();
|
|
if(node3 != nullptr)
|
|
c += node3->countFacelists();
|
|
if(node4 != nullptr)
|
|
c += node4->countFacelists();
|
|
return(c);
|
|
}
|
|
|
|
/*
|
|
Map Format:
|
|
|
|
1x mapHeader (head)
|
|
head.face_count x FACE
|
|
head.node_count x nodeHeader
|
|
head.facelist_count x unsigned long (indexes into face array)
|
|
|
|
|
|
*/
|
|
|
|
void QTNode::divideYourself(int depth) {
|
|
// printf("Dividing in box (%.2f -> %.2f, %.2f -> %.2f) at depth %d with %d faces.\n",
|
|
// minx, maxx, miny, maxy, depth, faces.size());
|
|
|
|
unsigned long cc;
|
|
cc = faces.size();
|
|
#ifdef MAX_QUADRENT_FACES
|
|
if(cc <= MAX_QUADRENT_FACES) {
|
|
#ifdef SPLIT_DEBUG
|
|
printf("Stopping (facecount) on box (%.2f -> %.2f, %.2f -> %.2f) at depth %d with %d faces.\n",
|
|
minx, maxx, miny, maxy, depth, cc);
|
|
#endif
|
|
final = true;
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
#ifdef MIN_QUADRENT_SIZE
|
|
if((maxx - minx) < MIN_QUADRENT_SIZE || (maxy - miny) < MIN_QUADRENT_SIZE) {
|
|
#ifdef SPLIT_DEBUG
|
|
printf("Stopping on box (size) (%.2f -> %.2f, %.2f -> %.2f) at depth %d with %d faces.\n",
|
|
minx, maxx, miny, maxy, depth, cc);
|
|
#endif
|
|
final = true;
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
doSplit();
|
|
|
|
//get counts on our split
|
|
float c1, c2, c3, c4;
|
|
c1 = node1? node1->faces.size() : 0;
|
|
c2 = node2? node2->faces.size() : 0;
|
|
c3 = node3? node3->faces.size() : 0;
|
|
c4 = node4? node4->faces.size() : 0;
|
|
|
|
#ifdef MIN_QUADRENT_GAIN
|
|
int miss = 0;
|
|
float gain1 = 1.0 - c1 / cc;
|
|
float gain2 = 1.0 - c2 / cc;
|
|
float gain3 = 1.0 - c3 / cc;
|
|
float gain4 = 1.0 - c4 / cc;
|
|
|
|
//see how many missed the gain mark
|
|
if(gain1 < MIN_QUADRENT_GAIN)
|
|
miss++;
|
|
if(gain2 < MIN_QUADRENT_GAIN)
|
|
miss++;
|
|
if(gain3 < MIN_QUADRENT_GAIN)
|
|
miss++;
|
|
if(gain4 < MIN_QUADRENT_GAIN)
|
|
miss++;
|
|
|
|
if(miss > MAX_QUADRENT_MISSES) {
|
|
#ifdef SPLIT_DEBUG
|
|
printf("Stopping (gain) on box (%.2f -> %.2f, %.2f -> %.2f) at depth %d with %d faces.\n",
|
|
minx, maxx, miny, maxy, depth, cc);
|
|
#endif
|
|
final = true;
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
|
|
//if all faces pass through all quadrents, then we are done
|
|
//partially obsoleted by gain test.
|
|
if(c1 == c2 && c1 == c3 && c1 == c4) {
|
|
#ifdef SPLIT_DEBUG
|
|
printf("Stopping (empty) on box (%.2f -> %.2f, %.2f -> %.2f) at depth %d with %d faces.\n",
|
|
minx, maxx, miny, maxy, depth, cc);
|
|
#endif
|
|
final = true;
|
|
return;
|
|
}
|
|
|
|
//there are prolly some more intelligent stopping criteria...
|
|
|
|
depth++;
|
|
|
|
if(node1 != nullptr)
|
|
node1->divideYourself(depth);
|
|
if(node2 != nullptr)
|
|
node2->divideYourself(depth);
|
|
if(node3 != nullptr)
|
|
node3->divideYourself(depth);
|
|
if(node4 != nullptr)
|
|
node4->divideYourself(depth);
|
|
|
|
|
|
}
|
|
|
|
void QTNode::buildVertexes() {
|
|
|
|
v[0].x = v[1].x = v[2].x = v[3].x = minx;
|
|
v[4].x = v[5].x = v[6].x = v[7].x = maxx;
|
|
|
|
v[0].y = v[1].y = v[4].y = v[5].y = miny;
|
|
v[2].y = v[3].y = v[6].y = v[7].y = maxy;
|
|
|
|
v[0].z = v[3].z = v[4].z = v[7].z = -999999;
|
|
v[1].z = v[2].z = v[5].z = v[6].z = 9999999;
|
|
}
|
|
|
|
|
|
static const GVector gNormals[6] = {
|
|
GVector(-1.0, 0.0, 0.0),
|
|
GVector(0.0, 1.0, 0.0),
|
|
GVector(1.0, 0.0, 0.0),
|
|
GVector(0.0, -1.0, 0.0),
|
|
GVector(0.0, 0.0, 1.0),
|
|
GVector(0.0, 0.0, -1.0),
|
|
};
|
|
|
|
static const unsigned short gIntFaces[6][4] =
|
|
{
|
|
{0, 1, 2, 3},
|
|
{3, 2, 6, 7},
|
|
{7, 6, 5, 4},
|
|
{4, 5, 1, 0},
|
|
{5, 6, 2, 1},
|
|
{7, 4, 0, 3}
|
|
};
|
|
|
|
|
|
//stolen in haste from my fear pathing program, and untested in here...
|
|
bool edges_cross(GPoint *pt1, GPoint *pt2, const VERTEX *pt3, const VERTEX *pt4) {
|
|
//I love macros
|
|
#define IntersectDenom(p1, p2, p3, p4) \
|
|
((p4->y - p3->y)*(p2->x - p1->x) - (p4->x - p3->x)*(p2->y - p1->y))
|
|
#define IntersectNumerX(p1, p2, p3, p4) \
|
|
((p4->x - p3->x)*(p1->y - p3->y) - (p4->y - p3->y)*(p1->x - p3->x))
|
|
#define IntersectNumerY(p1, p2, p3, p4) \
|
|
((p2->x - p1->x)*(p1->y - p3->y) - (p2->y - p1->y)*(p1->x - p3->x))
|
|
|
|
#define IntersectX(p1, p2, p3, p4, denom) \
|
|
(p1->x + IntersectNumerX(p1, p2, p3, p4)*(p2->x - p1->x)/denom)
|
|
#define IntersectY(p1, p2, p3, p4, denom) \
|
|
(p1->y + IntersectNumerX(p1, p2, p3, p4)*(p2->y - p1->y)/denom)
|
|
|
|
#define CheckEqualXY(p1, p2) \
|
|
(p1->x == p2->x && p1->y == p2->y)
|
|
|
|
#define CoordOnLine(p1, p2, coord, dim) \
|
|
(p1->dim > p2->dim? (coord >= p2->dim && coord <= p1->dim) : (coord >= p1->dim && coord <= p2->dim))
|
|
|
|
#define IntersectZfromX(p1, p2, inter) \
|
|
(p2->x > p1->x? \
|
|
(p1->z + ((inter - p1->x)/(p2->x - p1->x) * (p2->z - p1->z))) \
|
|
:(p2->z + ((inter - p2->x)/(p1->x - p2->x) * (p1->z - p2->z))))
|
|
|
|
float denom = IntersectDenom(pt1, pt2, pt3, pt4);
|
|
if(denom != 0) {
|
|
|
|
//the lines intersect, check segments now
|
|
float xinter = IntersectX(pt1, pt2, pt3, pt4, denom);
|
|
float yinter = IntersectY(pt1, pt2, pt3, pt4, denom);
|
|
|
|
//now see if this point is on both segments
|
|
if( CoordOnLine(pt1, pt2, xinter, x)
|
|
&& CoordOnLine(pt1, pt2, yinter, y)
|
|
&& CoordOnLine(pt3, pt4, xinter, x)
|
|
&& CoordOnLine(pt3, pt4, yinter, y) ){
|
|
// printf("Line (%.3f,%.3f,%.3f) -> (%.3f,%.3f,%.3f) d=%.3f\n", pt1->x, pt1->y, pt1->z, pt2->x, pt2->y, pt2->z, pt1->Dist2(pt2));
|
|
// printf("Hits (%.3f,%.3f,%.3f) -> (%.3f,%.3f,%.3f) d=%.3f\n", pt3->x, pt3->y, pt3->z, pt4->x, pt4->y, pt4->z, pt3->Dist2(pt4));
|
|
// printf("At (%.3f, %.3f), which IS on both segments.\n", xinter, yinter);
|
|
|
|
return(true);
|
|
}
|
|
// printf("At (%.3f, %.3f), which is not on both segments.\n", xinter, yinter);
|
|
}
|
|
return(false);
|
|
}
|
|
|
|
|
|
#define MAX(x,y) ((x)<y?(y):(x))
|
|
#define MIN(x,y) ((x)<y?(x):(y))
|
|
|
|
bool PointInTriangle(VERTEX *polygon, float px, float py)
|
|
|
|
/*
|
|
This code is from http://local.wasp.uwa.edu.au/~pbourke/geometry/
|
|
Copyright Paul Bourke
|
|
*/
|
|
|
|
{
|
|
int counter = 0;
|
|
int i;
|
|
double xinters;
|
|
VERTEX p1,p2;
|
|
|
|
p1 = polygon[0];
|
|
for (i=1;i<=3;i++) {
|
|
p2 = polygon[i % 3];
|
|
if (py > MIN(p1.y,p2.y)) {
|
|
if (py <= MAX(p1.y,p2.y)) {
|
|
if (px <= MAX(p1.x,p2.x)) {
|
|
if (p1.y != p2.y) {
|
|
xinters = (py-p1.y)*(p2.x-p1.x)/(p2.y-p1.y)+p1.x;
|
|
if (p1.x == p2.x || px <= xinters)
|
|
counter++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
p1 = p2;
|
|
}
|
|
|
|
if (counter % 2 == 0)
|
|
return(false);
|
|
else
|
|
return(true);
|
|
}
|
|
|
|
//quick function which got too messy in the loop below.
|
|
bool QTBuilder::FaceInNode(const QTNode *q, const FACE *f) {
|
|
const VERTEX *v1 = &f->a;
|
|
const VERTEX *v2 = &f->b;
|
|
const VERTEX *v3 = &f->c;
|
|
|
|
#ifdef COUNT_MACTHES
|
|
gEasyMatches++;
|
|
#endif
|
|
|
|
//Easy matches, points are within the quadrant.
|
|
if( ! ( v1->x <= q->minx || v1->x > q->maxx
|
|
|| v1->y <= q->miny || v1->y > q->maxy ) ) {
|
|
return(true);
|
|
}
|
|
if( ! ( v2->x <= q->minx || v2->x > q->maxx
|
|
|| v2->y <= q->miny || v2->y > q->maxy ) ) {
|
|
return(true);
|
|
}
|
|
if( ! ( v3->x <= q->minx || v3->x > q->maxx
|
|
|| v3->y <= q->miny || v3->y > q->maxy ) ) {
|
|
return(true);
|
|
}
|
|
|
|
#ifdef COUNT_MACTHES
|
|
gEasyMatches--;
|
|
gEasyExcludes++;
|
|
#endif
|
|
|
|
//make sure it is even possible to insersect:
|
|
if( v1->x < q->minx && v2->x < q->minx && v3->x < q->minx )
|
|
return(false);
|
|
if( v1->x > q->maxx && v2->x > q->maxx && v3->x > q->maxx )
|
|
return(false);
|
|
if( v1->y < q->miny && v2->y < q->miny && v3->y < q->miny )
|
|
return(false);
|
|
if( v1->y > q->maxy && v2->y > q->maxy && v3->y > q->maxy )
|
|
return(false);
|
|
|
|
|
|
#ifdef COUNT_MACTHES
|
|
gEasyExcludes--;
|
|
#endif
|
|
|
|
#ifdef COUNT_MACTHES
|
|
gHardMatches++;
|
|
#endif
|
|
// return(true);
|
|
|
|
//harder: no points are in the cube
|
|
|
|
//4 points of this node
|
|
GPoint pt1(q->minx, q->miny, 0),
|
|
pt2(q->minx, q->maxy, 0),
|
|
pt3(q->maxx, q->miny, 0),
|
|
pt4(q->maxx, q->maxy, 0);
|
|
|
|
/*
|
|
//box lines:
|
|
pt1, pt2
|
|
pt3, pt4
|
|
pt1, pt3
|
|
pt2, pt4
|
|
|
|
//tri lines
|
|
v1, v2
|
|
v1, v3
|
|
v2, v3
|
|
*/
|
|
|
|
#define CheckIntersect(p1, p2, p3, p4) \
|
|
(((p4->y - p3->y)*(p2.x - p1.x) - (p4->x - p3->x)*(p2.y - p1.y)) != 0)
|
|
|
|
int finaltest =
|
|
(edges_cross(&pt1, &pt2, v1, v2)
|
|
|| edges_cross(&pt1, &pt2, v1, v3)
|
|
|| edges_cross(&pt1, &pt2, v2, v3)
|
|
|| edges_cross(&pt3, &pt4, v1, v2)
|
|
|| edges_cross(&pt3, &pt4, v1, v3)
|
|
|| edges_cross(&pt3, &pt4, v2, v3)
|
|
|| edges_cross(&pt1, &pt3, v1, v2)
|
|
|| edges_cross(&pt1, &pt3, v1, v3)
|
|
|| edges_cross(&pt1, &pt3, v2, v3)
|
|
|| edges_cross(&pt2, &pt4, v1, v2)
|
|
|| edges_cross(&pt2, &pt4, v1, v3)
|
|
|| edges_cross(&pt2, &pt4, v2, v3));
|
|
|
|
if(finaltest) return finaltest;
|
|
|
|
VERTEX Triangle[3];
|
|
Triangle[0]=*v1; Triangle[1]=*v2; Triangle[2] = *v3;
|
|
finaltest = PointInTriangle(Triangle, q->minx, q->miny) ||
|
|
PointInTriangle(Triangle, q->minx, q->maxy) ||
|
|
PointInTriangle(Triangle, q->maxx, q->maxy) ||
|
|
PointInTriangle(Triangle, q->maxx, q->miny);
|
|
|
|
return finaltest;
|
|
}
|
|
|
|
|
|
|
|
void QTNode::doSplit() {
|
|
|
|
|
|
//find midpoints...
|
|
float midx = minx + (maxx - minx) / 2.0;
|
|
float midy = miny + (maxy - miny) / 2.0;
|
|
|
|
//ordering following definitions in map.h
|
|
node1 = new QTNode(builder, midx, maxx, midy, maxy);
|
|
node2 = new QTNode(builder, minx, midx, midy, maxy);
|
|
node3 = new QTNode(builder, minx, midx, miny, midy);
|
|
node4 = new QTNode(builder, midx, maxx, miny, midy);
|
|
if(node1 == nullptr || node2 == nullptr || node3 == nullptr || node4 == nullptr) {
|
|
printf("Error: unable to allocate new QTNode, giving up.\n");
|
|
return;
|
|
}
|
|
|
|
unsigned long r,l;
|
|
l = faces.size();
|
|
for(r = 0; r < l; r++) {
|
|
FaceRecord &cur = faces[r];
|
|
if(builder->FaceInNode(node1, cur.face))
|
|
node1->faces.push_back(cur);
|
|
if(builder->FaceInNode(node2, cur.face))
|
|
node2->faces.push_back(cur);
|
|
if(builder->FaceInNode(node3, cur.face))
|
|
node3->faces.push_back(cur);
|
|
if(builder->FaceInNode(node4, cur.face))
|
|
node4->faces.push_back(cur);
|
|
}
|
|
|
|
//clean up empty sets.
|
|
if(node1->faces.size() == 0) {
|
|
delete node1;
|
|
node1 = nullptr;
|
|
}
|
|
if(node2->faces.size() == 0) {
|
|
delete node2;
|
|
node2 = nullptr;
|
|
}
|
|
if(node3->faces.size() == 0) {
|
|
delete node3;
|
|
node3 = nullptr;
|
|
}
|
|
if(node4->faces.size() == 0) {
|
|
delete node4;
|
|
node4 = nullptr;
|
|
}
|
|
|
|
}
|
|
|
|
void QTBuilder::AddFace(VERTEX &v1, VERTEX &v2, VERTEX &v3) {
|
|
FACE f;
|
|
|
|
#ifdef MAX_Z
|
|
if(v1.z > MAX_Z && v2.z > MAX_Z && v3.z > MAX_Z)
|
|
return;
|
|
#endif
|
|
|
|
|
|
//this still might not work
|
|
f.nx = (v2.y - v1.y)*(v3.z - v1.z) - (v2.z - v1.z)*(v3.y - v1.y);
|
|
f.ny = (v2.z - v1.z)*(v3.x - v1.x) - (v2.x - v1.x)*(v3.z - v1.z);
|
|
f.nz = (v2.x - v1.x)*(v3.y - v1.y) - (v2.y - v1.y)*(v3.x - v1.x);
|
|
NormalizeN(&f);
|
|
f.nd = - f.nx * v1.x - f.ny * v1.y - f.nz * v1.z;
|
|
|
|
f.a = v1;
|
|
f.b = v2;
|
|
f.c = v3;
|
|
|
|
_FaceList.push_back(f);
|
|
}
|
|
|
|
|
|
GPoint::GPoint() {
|
|
x = 0;
|
|
y = 0;
|
|
z = 0;
|
|
}
|
|
|
|
GPoint::GPoint(VERTEX &v) {
|
|
x = v.x;
|
|
y = v.y;
|
|
z = v.z;
|
|
}
|
|
|
|
GPoint::GPoint(float nx, float ny, float nz) {
|
|
x = nx;
|
|
y = ny;
|
|
z = nz;
|
|
}
|
|
|
|
//dot of x,y,z
|
|
float GPoint::dot3(const GPoint &them) const {
|
|
return((x * them.x) + (y * them.y) +
|
|
(z * them.z));
|
|
}
|
|
|
|
//cross product
|
|
GPoint GPoint::cross(const GPoint &them) const {
|
|
return(GPoint(y * them.z - z * them.y,
|
|
z * them.x - x * them.z,
|
|
x * them.y - y * them.x));
|
|
}
|
|
|
|
GPoint operator-(const GPoint &v1, const GPoint &v2) {
|
|
return(GPoint(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z));
|
|
}
|
|
|
|
|
|
GVector::GVector() : GPoint() {
|
|
W = 0;
|
|
}
|
|
|
|
GVector::GVector(const GPoint &them) : GPoint(them) {
|
|
W = 1.0f;
|
|
}
|
|
|
|
GVector::GVector(float x, float y, float z, float w) : GPoint(x, y, z) {
|
|
W = w;
|
|
}
|
|
|
|
//dot product of x,y,z,w
|
|
float GVector::dot4(const GVector &them) const {
|
|
return((x * them.x) + (y * them.y) +
|
|
(z * them.z) + (W * them.W));
|
|
}
|
|
|
|
//dot product of x,y,z+w
|
|
float GVector::dot4(const GPoint &them) const {
|
|
return((x * them.x) + (y * them.y) +
|
|
(z * them.z) + W);
|
|
}
|
|
|
|
float GVector::length() {
|
|
return(sqrt((x * x) + (y * y) + (z * z)));
|
|
}
|
|
|
|
void GVector::normalize() {
|
|
float len = length(); //stupid square roots take forever
|
|
x /= len;
|
|
y /= len;
|
|
z /= len;
|
|
}
|
|
|
|
//stolen from: http://gamecode.tripod.com/tut/tut04.htm
|
|
int QTBuilder::ClipPolygon(POLYGON *poly, GVector *plane) {
|
|
/* Plan: cycle through the vertices, considering pairs of them.
|
|
If both vertices are visible, add them to the new array.
|
|
If both vertices are invisible, add neither to the new array.
|
|
If one vertex is visible and the other is not, move the one
|
|
that's not, and then add both vertices to the new array.
|
|
*/
|
|
float dist1, dist2; // distances of points to plane
|
|
float distratio; // fraction of distance between two points
|
|
// new vertices might be created. Don't change
|
|
// polygon's vertices because we might still need
|
|
// them. Instead, use tempvtx array and update vertices
|
|
// array at the end. Create tempvtx array once only.
|
|
int i, ii, j=0;
|
|
|
|
/* Check if plane is a valid plane */
|
|
if (!(plane->x || plane->y || plane->z)) return -1;
|
|
// if not valid plane, don't change polygon and return an error;
|
|
|
|
/* The vertices should, as for all functions, be arranged in cyclic
|
|
order. That is, if a line was drawn from each vertex to the next
|
|
it would form the correct outline of the polygon in 3D space.
|
|
This routine might create new vertices because of the clipping,
|
|
but the cyclic order will be preserved.
|
|
*/
|
|
|
|
for (i=0; i<poly->count; i++)
|
|
{
|
|
ii = (i+1)%poly->count;
|
|
dist1 = plane->x * poly->c[i].x + plane->y * poly->c[i].y +
|
|
plane->z * poly->c[i].z + plane->W;
|
|
dist2 = plane->x * poly->c[ii].x + plane->y * poly->c[ii].y +
|
|
plane->z * poly->c[ii].z + plane->W;
|
|
if (dist1<0 && dist2<0) // line unclipped and invisible
|
|
continue;
|
|
if (dist1>0 && dist2>0) // line unclipped and visible
|
|
tempvtx[j++]=poly->c[i];
|
|
else // line partially visible
|
|
if (dist1>0) // first vertex is visible
|
|
{
|
|
distratio = dist1/(dist1-dist2);
|
|
tempvtx[j] = poly->c[i];
|
|
j++; // Copied 1st vertex
|
|
tempvtx[j].x = poly->c[i].x +
|
|
(poly->c[ii].x - poly->c[i].x) * distratio;
|
|
tempvtx[j].y = poly->c[i].y +
|
|
(poly->c[ii].y - poly->c[i].y) * distratio;
|
|
tempvtx[j].z = poly->c[i].z +
|
|
(poly->c[ii].z - poly->c[i].z) * distratio;
|
|
j++; // Copied second vertex
|
|
}
|
|
else // second vertex is visible
|
|
{
|
|
distratio = dist2/(dist2-dist1);
|
|
tempvtx[j].x = poly->c[ii].x +
|
|
(poly->c[i].x - poly->c[ii].x) * distratio;
|
|
tempvtx[j].y = poly->c[ii].y +
|
|
(poly->c[i].y - poly->c[ii].y) * distratio;
|
|
tempvtx[j].z = poly->c[ii].z +
|
|
(poly->c[i].z - poly->c[ii].z) * distratio;
|
|
j++; // Copy only first vertex. 2nd vertex will be copied
|
|
// in next iteration of loop
|
|
}
|
|
}
|
|
|
|
for (i=0; i<j; i++)
|
|
poly->c[i] = tempvtx[i]; // Update the vertices in polygon
|
|
poly->count = j; // Update the vertex count
|
|
return j;
|
|
}
|
|
|
|
|
|
void QTBuilder::NormalizeN(FACE *p) {
|
|
float len = sqrt(p->nx*p->nx + p->ny*p->ny + p->nz*p->nz);
|
|
p->nx /= len;
|
|
p->ny /= len;
|
|
p->nz /= len;
|
|
}
|
|
|
|
void QTBuilder::AddPlaceable(FileLoader *fileloader, char *ZoneFileName, bool ListPlaceable) {
|
|
Polygon *poly;
|
|
Vertex *verts[3];
|
|
float XOffset, YOffset, ZOffset;
|
|
float RotX, RotY, RotZ;
|
|
float XScale, YScale, ZScale;
|
|
VERTEX v1, v2, v3, tmpv;
|
|
|
|
|
|
// Ghetto ini file parser to see which models to include
|
|
// The format of each line in azone.ini is:
|
|
//
|
|
// shortname.[eqg|s3d],model number, model numner, ...
|
|
// E.g.
|
|
// tox.s3d,1,17,34
|
|
// anguish.eqg,25,69,70
|
|
//
|
|
|
|
const int IniBufferSize = 255;
|
|
enum ReadingState { ReadingZoneName, ReadingModelNumbers };
|
|
ReadingState State = ReadingZoneName;
|
|
bool INIEntryFound = false;
|
|
int INIModelCount = 0;
|
|
char IniBuffer[IniBufferSize], ch;
|
|
vector<int> ModelNumbers;
|
|
int StrIndex = 0;
|
|
int ModelNumber;
|
|
|
|
FILE *IniFile = fopen("azone.ini", "rb");
|
|
|
|
if(!IniFile) {
|
|
printf("azone.ini not found in current directory. Not processing placeable models.\n");
|
|
return;
|
|
}
|
|
printf("Processing azone.ini for placeable models.\n");
|
|
|
|
while(!feof(IniFile)) {
|
|
ch = fgetc(IniFile);
|
|
if((ch=='#')&&(StrIndex==0)) { // Discard comment lines beginning with a hash
|
|
while((ch!=EOF)&&(ch!='\n'))
|
|
ch = fgetc(IniFile);
|
|
|
|
continue;
|
|
}
|
|
if((ch=='\n') && (State==ReadingZoneName)) {
|
|
StrIndex = 0;
|
|
continue;
|
|
}
|
|
if(ch=='\r') continue;
|
|
if((ch==EOF)||(ch=='\n')) {
|
|
IniBuffer[StrIndex] = '\0';
|
|
if(State == ReadingModelNumbers) {
|
|
ModelNumber = Strings::ToInt(IniBuffer);
|
|
if((ModelNumber >= 0) && (ModelNumber < fileloader->model_data.model_count))
|
|
{
|
|
fileloader->model_data.models[ModelNumber]->IncludeInMap = true;
|
|
INIModelCount++;
|
|
}
|
|
else
|
|
printf("ERROR: Specified model %s invalid, must be in range 0 to %i\n", IniBuffer, fileloader->model_data.model_count - 1);
|
|
}
|
|
break;
|
|
}
|
|
if(ch==',') {
|
|
IniBuffer[StrIndex]='\0';
|
|
StrIndex = 0;
|
|
if(State == ReadingZoneName) {
|
|
if(strcmp(ZoneFileName, IniBuffer)) {
|
|
StrIndex = 0;
|
|
// Not our zone, skip to next line
|
|
while((ch!=EOF)&&(ch!='\n'))
|
|
ch = fgetc(IniFile);
|
|
continue;
|
|
}
|
|
else {
|
|
State = ReadingModelNumbers;
|
|
INIEntryFound = true;
|
|
}
|
|
}
|
|
else {
|
|
ModelNumber = Strings::ToInt(IniBuffer);
|
|
if((ModelNumber >= 0) && (ModelNumber < fileloader->model_data.model_count))
|
|
{
|
|
fileloader->model_data.models[ModelNumber]->IncludeInMap = true;
|
|
INIModelCount++;
|
|
}
|
|
else
|
|
printf("ERROR: Specified model %s invalid, must be in range 0 to %i\n", IniBuffer, fileloader->model_data.model_count - 1);
|
|
}
|
|
continue;
|
|
}
|
|
IniBuffer[StrIndex++] = tolower(ch);
|
|
}
|
|
fclose(IniFile);
|
|
|
|
if(INIEntryFound) {
|
|
printf("azone.ini entry found for this zone. ");
|
|
if(INIModelCount > 0)
|
|
printf("Including %d models.\n", INIModelCount);
|
|
else
|
|
printf("No valid model numbers specified.\n");
|
|
}
|
|
else {
|
|
printf("No azone.ini entry found for zone %s\n", ZoneFileName);
|
|
}
|
|
|
|
|
|
|
|
for(int i = 0; i < fileloader->model_data.plac_count; ++i) {
|
|
if(fileloader->model_data.placeable[i]->model==-1) continue;
|
|
// The model pointer should only really be nullptr for the zone model, as we process that separately.
|
|
if(fileloader->model_data.models[fileloader->model_data.placeable[i]->model] == nullptr) continue;
|
|
if(ListPlaceable)
|
|
printf("Placeable Object %4d @ (%9.2f, %9.2f, %9.2f uses model %4d %s\n",i,
|
|
fileloader->model_data.placeable[i]->y,
|
|
fileloader->model_data.placeable[i]->x,
|
|
fileloader->model_data.placeable[i]->z,
|
|
fileloader->model_data.placeable[i]->model,
|
|
fileloader->model_data.models[fileloader->model_data.placeable[i]->model]->name);
|
|
|
|
|
|
if(!fileloader->model_data.models[fileloader->model_data.placeable[i]->model]->IncludeInMap)
|
|
continue;
|
|
printf("Including Placeable Object %4d using model %4d (%s).\n", i,
|
|
fileloader->model_data.placeable[i]->model,
|
|
fileloader->model_data.models[fileloader->model_data.placeable[i]->model]->name);
|
|
|
|
if(fileloader->model_data.placeable[i]->model>fileloader->model_data.model_count) continue;
|
|
|
|
XOffset = fileloader->model_data.placeable[i]->x;
|
|
YOffset = fileloader->model_data.placeable[i]->y;
|
|
ZOffset = fileloader->model_data.placeable[i]->z;
|
|
|
|
RotX = fileloader->model_data.placeable[i]->rx * 3.14159 / 180; // Convert from degrees to radians
|
|
RotY = fileloader->model_data.placeable[i]->ry * 3.14159 / 180;
|
|
RotZ = fileloader->model_data.placeable[i]->rz * 3.14159 / 180;
|
|
|
|
XScale = fileloader->model_data.placeable[i]->scale[0];
|
|
YScale = fileloader->model_data.placeable[i]->scale[1];
|
|
ZScale = fileloader->model_data.placeable[i]->scale[2];
|
|
|
|
|
|
Model *model = fileloader->model_data.models[fileloader->model_data.placeable[i]->model];
|
|
|
|
|
|
for(int j = 0; j < model->poly_count; ++j) {
|
|
|
|
poly = model->polys[j];
|
|
|
|
verts[0] = model->verts[poly->v1];
|
|
verts[1] = model->verts[poly->v2];
|
|
verts[2] = model->verts[poly->v3];
|
|
|
|
v1.x = verts[0]->x; v1.y = verts[0]->y; v1.z = verts[0]->z;
|
|
v2.x = verts[1]->x; v2.y = verts[1]->y; v2.z = verts[1]->z;
|
|
v3.x = verts[2]->x; v3.y = verts[2]->y; v3.z = verts[2]->z;
|
|
|
|
|
|
RotateVertex(v1, RotX, RotY, RotZ);
|
|
RotateVertex(v2, RotX, RotY, RotZ);
|
|
RotateVertex(v3, RotX, RotY, RotZ);
|
|
|
|
ScaleVertex(v1, XScale, YScale, ZScale);
|
|
ScaleVertex(v2, XScale, YScale, ZScale);
|
|
ScaleVertex(v3, XScale, YScale, ZScale);
|
|
|
|
TranslateVertex(v1, XOffset, YOffset, ZOffset);
|
|
TranslateVertex(v2, XOffset, YOffset, ZOffset);
|
|
TranslateVertex(v3, XOffset, YOffset, ZOffset);
|
|
|
|
|
|
// Swap X & Y
|
|
//
|
|
tmpv = v1; v1.x = tmpv.y; v1.y = tmpv.x;
|
|
tmpv = v2; v2.x = tmpv.y; v2.y = tmpv.x;
|
|
tmpv = v3; v3.x = tmpv.y; v3.y = tmpv.x;
|
|
|
|
AddFace(v1, v2, v3);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void QTBuilder::AddPlaceableV4(FileLoader *fileloader, char *ZoneFileName, bool ListPlaceable) {
|
|
Polygon *poly;
|
|
Vertex *verts[3];
|
|
float XOffset, YOffset, ZOffset;
|
|
float RotX, RotY, RotZ;
|
|
float XScale, YScale, ZScale;
|
|
VERTEX v1, v2, v3, tmpv;
|
|
|
|
//return;
|
|
|
|
printf("EQG V4 Placeable Zone Support\n");
|
|
printf("ObjectGroupCount = %lu\n", fileloader->model_data.ObjectGroups.size());
|
|
|
|
vector<ObjectGroupEntry>::iterator Iterator;
|
|
|
|
int OGNum = 0;
|
|
|
|
bool BailOut = false;
|
|
|
|
// Ghetto ini file parser to see which models to include
|
|
// The format of each line in azone.ini is:
|
|
//
|
|
// shortname.[eqg|s3d],model number, model numner, ...
|
|
// E.g.
|
|
// tox.s3d,1,17,34
|
|
// anguish.eqg,25,69,70
|
|
//
|
|
|
|
const int IniBufferSize = 255;
|
|
enum ReadingState { ReadingZoneName, ReadingModelNumbers };
|
|
ReadingState State = ReadingZoneName;
|
|
bool INIEntryFound = false;
|
|
int INIModelCount = 0;
|
|
char IniBuffer[IniBufferSize], ch;
|
|
vector<int> ModelNumbers;
|
|
int StrIndex = 0;
|
|
int ModelNumber;
|
|
|
|
FILE *IniFile = fopen("azone.ini", "rb");
|
|
|
|
if(!IniFile)
|
|
printf("azone.ini not found in current directory. Including default models.\n");
|
|
else
|
|
{
|
|
printf("Processing azone.ini for placeable models.\n");
|
|
|
|
while(!feof(IniFile)) {
|
|
ch = fgetc(IniFile);
|
|
if((ch=='#')&&(StrIndex==0)) { // Discard comment lines beginning with a hash
|
|
while((ch!=EOF)&&(ch!='\n'))
|
|
ch = fgetc(IniFile);
|
|
|
|
continue;
|
|
}
|
|
if((ch=='\n') && (State==ReadingZoneName)) {
|
|
StrIndex = 0;
|
|
continue;
|
|
}
|
|
if(ch=='\r') continue;
|
|
if((ch==EOF)||(ch=='\n')) {
|
|
IniBuffer[StrIndex] = '\0';
|
|
if(State == ReadingModelNumbers) {
|
|
bool Exclude = false;
|
|
bool Group = false;
|
|
if(IniBuffer[0]=='-')
|
|
{
|
|
Exclude = true;
|
|
strcpy(IniBuffer, IniBuffer+1);
|
|
}
|
|
if(IniBuffer[0]=='g')
|
|
{
|
|
Group = true;
|
|
strcpy(IniBuffer, IniBuffer+1);
|
|
}
|
|
ModelNumber = Strings::ToInt(IniBuffer);
|
|
if(!Group)
|
|
{
|
|
if((ModelNumber >= 0) && (ModelNumber < fileloader->model_data.model_count))
|
|
fileloader->model_data.models[ModelNumber]->IncludeInMap = Exclude ? false : true;
|
|
else
|
|
printf("ERROR: Specified model %s invalid, must be in range 0 to %i\n", IniBuffer, fileloader->model_data.model_count - 1);
|
|
}
|
|
else
|
|
{
|
|
if((ModelNumber >= 0) && ((unsigned int)ModelNumber < fileloader->model_data.ObjectGroups.size()))
|
|
fileloader->model_data.ObjectGroups[ModelNumber].IncludeInMap = Exclude ? false : true;
|
|
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
if(ch==',') {
|
|
IniBuffer[StrIndex]='\0';
|
|
StrIndex = 0;
|
|
if(State == ReadingZoneName) {
|
|
if(strcmp(ZoneFileName, IniBuffer)) {
|
|
StrIndex = 0;
|
|
// Not our zone, skip to next line
|
|
while((ch!=EOF)&&(ch!='\n'))
|
|
ch = fgetc(IniFile);
|
|
continue;
|
|
}
|
|
else {
|
|
State = ReadingModelNumbers;
|
|
INIEntryFound = true;
|
|
}
|
|
}
|
|
else {
|
|
bool Exclude = false;
|
|
bool Group = false;
|
|
if(IniBuffer[0]=='-')
|
|
{
|
|
Exclude = true;
|
|
strcpy(IniBuffer, IniBuffer+1);
|
|
}
|
|
if(IniBuffer[0]=='g')
|
|
{
|
|
Group = true;
|
|
strcpy(IniBuffer, IniBuffer+1);
|
|
}
|
|
ModelNumber = Strings::ToInt(IniBuffer);
|
|
if(!Group)
|
|
{
|
|
if((ModelNumber >= 0) && (ModelNumber < fileloader->model_data.model_count))
|
|
fileloader->model_data.models[ModelNumber]->IncludeInMap = Exclude ? false : true;
|
|
else
|
|
printf("ERROR: Specified model %s invalid, must be in range 0 to %i\n", IniBuffer, fileloader->model_data.model_count - 1);
|
|
}
|
|
else
|
|
{
|
|
if((ModelNumber >= 0) && ((unsigned int)ModelNumber < fileloader->model_data.ObjectGroups.size()))
|
|
fileloader->model_data.ObjectGroups[ModelNumber].IncludeInMap = Exclude ? false : true;
|
|
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
IniBuffer[StrIndex++] = tolower(ch);
|
|
}
|
|
fclose(IniFile);
|
|
|
|
if(INIEntryFound)
|
|
printf("azone.ini entry found for this zone. ");
|
|
else
|
|
printf("No azone.ini entry found for zone %s\n", ZoneFileName);
|
|
}
|
|
|
|
Iterator = fileloader->model_data.ObjectGroups.begin();
|
|
|
|
while(Iterator != fileloader->model_data.ObjectGroups.end())
|
|
{
|
|
if(!(*Iterator).IncludeInMap)
|
|
{
|
|
++OGNum;
|
|
++Iterator;
|
|
continue;
|
|
}
|
|
|
|
|
|
#ifdef DEBUG
|
|
printf("ObjectGroup Number: %i\n", OGNum++);
|
|
|
|
printf("ObjectGroup Coordinates: %8.3f, %8.3f, %8.3f\n",
|
|
(*Iterator).x, (*Iterator).y, (*Iterator).z);
|
|
|
|
printf("Tile: %8.3f, %8.3f, %8.3f\n",
|
|
(*Iterator).TileX, (*Iterator).TileY, (*Iterator).TileZ);
|
|
|
|
printf("ObjectGroup Rotations : %8.3f, %8.3f, %8.3f\n",
|
|
(*Iterator).RotX, (*Iterator).RotY, (*Iterator).RotZ);
|
|
|
|
printf("ObjectGroup Scale : %8.3f, %8.3f, %8.3f\n",
|
|
(*Iterator).ScaleX, (*Iterator).ScaleY, (*Iterator).ScaleZ);
|
|
#endif
|
|
|
|
list<int>::iterator ModelIterator;
|
|
|
|
ModelIterator = (*Iterator).SubObjects.begin();
|
|
|
|
while(ModelIterator != (*Iterator).SubObjects.end())
|
|
{
|
|
int SubModel = (*ModelIterator);
|
|
|
|
#ifdef DEBUG
|
|
printf(" SubModel: %i\n", (*ModelIterator));
|
|
#endif
|
|
|
|
XOffset = fileloader->model_data.placeable[SubModel]->x;
|
|
YOffset = fileloader->model_data.placeable[SubModel]->y;
|
|
ZOffset = fileloader->model_data.placeable[SubModel]->z;
|
|
#ifdef DEBUG
|
|
printf(" Translations: %8.3f, %8.3f, %8.3f\n", XOffset, YOffset, ZOffset);
|
|
printf(" Rotations : %8.3f, %8.3f, %8.3f\n", fileloader->model_data.placeable[SubModel]->rx,
|
|
fileloader->model_data.placeable[SubModel]->ry, fileloader->model_data.placeable[SubModel]->rz);
|
|
printf(" Scale : %8.3f, %8.3f, %8.3f\n", fileloader->model_data.placeable[SubModel]->scale[0],
|
|
fileloader->model_data.placeable[SubModel]->scale[1], fileloader->model_data.placeable[SubModel]->scale[2]);
|
|
#endif
|
|
|
|
RotX = fileloader->model_data.placeable[SubModel]->rx * 3.14159 / 180; // Convert from degrees to radians
|
|
RotY = fileloader->model_data.placeable[SubModel]->ry * 3.14159 / 180;
|
|
RotZ = fileloader->model_data.placeable[SubModel]->rz * 3.14159 / 180;
|
|
|
|
XScale = fileloader->model_data.placeable[SubModel]->scale[0];
|
|
YScale = fileloader->model_data.placeable[SubModel]->scale[1];
|
|
ZScale = fileloader->model_data.placeable[SubModel]->scale[2];
|
|
|
|
Model *model = fileloader->model_data.models[fileloader->model_data.placeable[SubModel]->model];
|
|
|
|
#ifdef DEBUG
|
|
printf("Model %i, name is %s\n", fileloader->model_data.placeable[SubModel]->model, model->name);
|
|
#endif
|
|
if(!model->IncludeInMap)
|
|
{
|
|
++ModelIterator;
|
|
#ifdef DEBUG
|
|
printf("Not including in .map\n");
|
|
#endif
|
|
continue;
|
|
}
|
|
for(int j = 0; j < model->poly_count; ++j) {
|
|
|
|
poly = model->polys[j];
|
|
|
|
verts[0] = model->verts[poly->v1];
|
|
verts[1] = model->verts[poly->v2];
|
|
verts[2] = model->verts[poly->v3];
|
|
|
|
v1.x = verts[0]->x; v1.y = verts[0]->y; v1.z = verts[0]->z;
|
|
v2.x = verts[1]->x; v2.y = verts[1]->y; v2.z = verts[1]->z;
|
|
v3.x = verts[2]->x; v3.y = verts[2]->y; v3.z = verts[2]->z;
|
|
|
|
// Scale by the values specified for the individual object.
|
|
//
|
|
ScaleVertex(v1, XScale, YScale, ZScale);
|
|
ScaleVertex(v2, XScale, YScale, ZScale);
|
|
ScaleVertex(v3, XScale, YScale, ZScale);
|
|
// Translate by the values specified for the individual object
|
|
//
|
|
TranslateVertex(v1, XOffset, YOffset, ZOffset);
|
|
TranslateVertex(v2, XOffset, YOffset, ZOffset);
|
|
TranslateVertex(v3, XOffset, YOffset, ZOffset);
|
|
// Rotate by the values specified for the group
|
|
// TODO: The X/Y rotations can be combined
|
|
//
|
|
RotateVertex(v1, (*Iterator).RotX * 3.14159 / 180.0f, 0, 0);
|
|
RotateVertex(v2, (*Iterator).RotX * 3.14159 / 180.0f, 0, 0);
|
|
RotateVertex(v3, (*Iterator).RotX * 3.14159 / 180.0f, 0, 0);
|
|
|
|
RotateVertex(v1, 0, (*Iterator).RotY * 3.14159 / 180.0f, 0);
|
|
RotateVertex(v2, 0, (*Iterator).RotY * 3.14159 / 180.0f, 0);
|
|
RotateVertex(v3, 0, (*Iterator).RotY * 3.14159 / 180.0f, 0);
|
|
|
|
// To make things align properly, we need to translate the object back to the origin
|
|
// before applying the model Z rotation. This is what the Correction VERTEX is for.
|
|
//
|
|
VERTEX Correction(XOffset, YOffset, ZOffset);
|
|
|
|
RotateVertex(Correction, (*Iterator).RotX * 3.14159 / 180.0f, 0, 0);
|
|
|
|
TranslateVertex(v1, -Correction.x, -Correction.y, -Correction.z);
|
|
TranslateVertex(v2, -Correction.x, -Correction.y, -Correction.z);
|
|
TranslateVertex(v3, -Correction.x, -Correction.y, -Correction.z);
|
|
// Rotate by model Z rotation
|
|
//
|
|
//
|
|
RotateVertex(v1, RotX, 0, 0);
|
|
RotateVertex(v2, RotX, 0, 0);
|
|
RotateVertex(v3, RotX, 0, 0);
|
|
|
|
// Don't know why the Y rotation needs to be negative
|
|
//
|
|
RotateVertex(v1, 0, -RotY, 0);
|
|
RotateVertex(v2, 0, -RotY, 0);
|
|
RotateVertex(v3, 0, -RotY, 0);
|
|
|
|
RotateVertex(v1, 0, 0, RotZ);
|
|
RotateVertex(v2, 0, 0, RotZ);
|
|
RotateVertex(v3, 0, 0, RotZ);
|
|
// Now we have applied the individual model rotations, translate back to where we were.
|
|
//
|
|
TranslateVertex(v1, Correction.x, Correction.y, Correction.z);
|
|
TranslateVertex(v2, Correction.x, Correction.y, Correction.z);
|
|
TranslateVertex(v3, Correction.x, Correction.y, Correction.z);
|
|
// Rotate by the Z rotation value specified for the object group
|
|
//
|
|
RotateVertex(v1, 0, 0, (*Iterator).RotZ * 3.14159 / 180.0f);
|
|
RotateVertex(v2, 0, 0, (*Iterator).RotZ * 3.14159 / 180.0f);
|
|
RotateVertex(v3, 0, 0, (*Iterator).RotZ * 3.14159 / 180.0f);
|
|
// Scale by the object group values
|
|
//
|
|
ScaleVertex(v1, (*Iterator).ScaleX, (*Iterator).ScaleY, (*Iterator).ScaleZ);
|
|
ScaleVertex(v2, (*Iterator).ScaleX, (*Iterator).ScaleY, (*Iterator).ScaleZ);
|
|
ScaleVertex(v3, (*Iterator).ScaleX, (*Iterator).ScaleY, (*Iterator).ScaleZ);
|
|
// Translate to the relevant tile starting co-ordinates
|
|
//
|
|
TranslateVertex(v1, (*Iterator).TileX, (*Iterator).TileY, (*Iterator).TileZ); // Y+14, Z + 68
|
|
TranslateVertex(v2, (*Iterator).TileX, (*Iterator).TileY, (*Iterator).TileZ);
|
|
TranslateVertex(v3, (*Iterator).TileX, (*Iterator).TileY, (*Iterator).TileZ);
|
|
// Translate to the position within the tile for this object group
|
|
//
|
|
TranslateVertex(v1, (*Iterator).x, (*Iterator).y, (*Iterator).z);
|
|
TranslateVertex(v2, (*Iterator).x, (*Iterator).y, (*Iterator).z);
|
|
TranslateVertex(v3, (*Iterator).x, (*Iterator).y, (*Iterator).z);
|
|
// Swap X & Y
|
|
//
|
|
tmpv = v1; v1.x = tmpv.y; v1.y = tmpv.x;
|
|
tmpv = v2; v2.x = tmpv.y; v2.y = tmpv.x;
|
|
tmpv = v3; v3.x = tmpv.y; v3.y = tmpv.x;
|
|
|
|
AddFace(v1, v2, v3);
|
|
|
|
}
|
|
++ModelIterator;
|
|
}
|
|
|
|
++Iterator;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
void QTBuilder::RotateVertex(VERTEX &v, float XRotation, float YRotation, float ZRotation) {
|
|
|
|
VERTEX nv;
|
|
|
|
nv = v;
|
|
|
|
// Rotate about the X axis
|
|
//
|
|
nv.y = (cos(XRotation) * v.y) - (sin(XRotation) * v.z);
|
|
nv.z = (sin(XRotation) * v.y) + (cos(XRotation) * v.z);
|
|
|
|
v = nv;
|
|
|
|
|
|
// Rotate about the Y axis
|
|
//
|
|
nv.x = (cos(YRotation) * v.x) + (sin(YRotation) * v.z) ;
|
|
nv.z = -(sin(YRotation) * v.x) + (cos(YRotation) * v.z);
|
|
|
|
v = nv;
|
|
|
|
// Rotate about the Z axis
|
|
//
|
|
nv.x = (cos(ZRotation) * v.x) - (sin(ZRotation) * v.y) ;
|
|
nv.y = (sin(ZRotation) * v.x) + (cos(ZRotation) * v.y) ;
|
|
|
|
v = nv;
|
|
}
|
|
|
|
void QTBuilder::ScaleVertex(VERTEX &v, float XScale, float YScale, float ZScale) {
|
|
|
|
v.x = v.x * XScale;
|
|
v.y = v.y * YScale;
|
|
v.z = v.z * ZScale;
|
|
}
|
|
|
|
|
|
void QTBuilder::TranslateVertex(VERTEX &v, float XOffset, float YOffset, float ZOffset) {
|
|
|
|
v.x = v.x + XOffset;
|
|
v.y = v.y + YOffset;
|
|
v.z = v.z + ZOffset;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|