forked from mirror/openmw-tes3mp
Rewrote more of the terrain system in D
git-svn-id: https://openmw.svn.sourceforge.net/svnroot/openmw/trunk@121 ea6a568a-9f4f-0410-981a-c910a81bb256
This commit is contained in:
parent
2e1f97f3d5
commit
4f5b9d019a
15 changed files with 570 additions and 2543 deletions
|
@ -48,7 +48,6 @@ import sound.sfx;
|
||||||
import nif.nif;
|
import nif.nif;
|
||||||
|
|
||||||
import core.filefinder;
|
import core.filefinder;
|
||||||
//import core.config;
|
|
||||||
|
|
||||||
// These are handles for various resources. They may refer to a file
|
// These are handles for various resources. They may refer to a file
|
||||||
// in the file system, an entry in a BSA archive, or point to an
|
// in the file system, an entry in a BSA archive, or point to an
|
||||||
|
@ -255,6 +254,9 @@ struct ResourceManager
|
||||||
|
|
||||||
char[80] texBuffer;
|
char[80] texBuffer;
|
||||||
|
|
||||||
|
// Checks the BSAs / file system for a given texture name. Tries
|
||||||
|
// various Morrowind-specific hacks, like changing the extension to
|
||||||
|
// .dds and adding a 'textures\' prefix. Does not load the texture.
|
||||||
TextureIndex lookupTexture(char[] id)
|
TextureIndex lookupTexture(char[] id)
|
||||||
{
|
{
|
||||||
if(dummy) return null;
|
if(dummy) return null;
|
||||||
|
@ -318,15 +320,19 @@ struct ResourceManager
|
||||||
char[] tmp;
|
char[] tmp;
|
||||||
if(id.length < 70)
|
if(id.length < 70)
|
||||||
{
|
{
|
||||||
|
// Avoid memory allocations if possible.
|
||||||
tmp = texBuffer[0..9+id.length];
|
tmp = texBuffer[0..9+id.length];
|
||||||
tmp[9..$] = id;
|
tmp[9..$] = id;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
tmp = "textures\\" ~ id;
|
tmp = "textures\\" ~ id;
|
||||||
|
writefln("WARNING: Did an allocation on %s", tmp);
|
||||||
|
}
|
||||||
|
|
||||||
searchWithDDS(tmp);
|
searchWithDDS(tmp);
|
||||||
|
|
||||||
// Not found? If so, try without the 'texture\'
|
// Not found? Try without the 'texture\'
|
||||||
if(ti.bsaIndex == -1)
|
if(ti.bsaIndex == -1)
|
||||||
{
|
{
|
||||||
tmp = tmp[9..$];
|
tmp = tmp[9..$];
|
||||||
|
@ -469,18 +475,9 @@ struct TextureResource
|
||||||
{
|
{
|
||||||
return bsaIndex == -1;
|
return bsaIndex == -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*KILLME
|
|
||||||
// Returns true if resource is loaded
|
|
||||||
bool isLoaded()
|
|
||||||
{
|
|
||||||
return ml != null;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// OLD STUFF
|
// OLD STUFF
|
||||||
|
|
||||||
/+
|
/+
|
||||||
|
|
||||||
void initResourceManager()
|
void initResourceManager()
|
||||||
|
|
|
@ -26,6 +26,11 @@ module esm.loadcell;
|
||||||
import esm.imports;
|
import esm.imports;
|
||||||
import esm.loadregn;
|
import esm.loadregn;
|
||||||
|
|
||||||
|
import std.math : abs;
|
||||||
|
|
||||||
|
int max(int x, int y)
|
||||||
|
{ return x>=y?x:y; }
|
||||||
|
|
||||||
/* Cells can be seen as a collection of several records, and holds
|
/* Cells can be seen as a collection of several records, and holds
|
||||||
* data about objects, creatures, statics and landscape (for exterior
|
* data about objects, creatures, statics and landscape (for exterior
|
||||||
* cells). This data can be huge, and has to be loaded when we need
|
* cells). This data can be huge, and has to be loaded when we need
|
||||||
|
@ -88,9 +93,9 @@ struct ExteriorCell
|
||||||
|
|
||||||
Region* region;
|
Region* region;
|
||||||
|
|
||||||
// We currently don't load all cell data (these can be huge!),
|
// We currently don't load all cell data (these can be huge!), and
|
||||||
// anyway it makes sense to only load these when accessed. Use this
|
// it makes sense to only load these when accessed. Use this to
|
||||||
// to reopen the file later.
|
// reopen the file later.
|
||||||
TES3FileContext context;
|
TES3FileContext context;
|
||||||
|
|
||||||
// Landscape and path grid data
|
// Landscape and path grid data
|
||||||
|
@ -99,6 +104,12 @@ struct ExteriorCell
|
||||||
|
|
||||||
PathGrid paths;
|
PathGrid paths;
|
||||||
|
|
||||||
|
// Return true if we have land data
|
||||||
|
bool hasLand()
|
||||||
|
{
|
||||||
|
return land.state == LoadState.Loaded && land.hasData;
|
||||||
|
}
|
||||||
|
|
||||||
void load()
|
void load()
|
||||||
{with(esFile){
|
{with(esFile){
|
||||||
this.region = getHNOPtr!(Region)("RGNN", regions);
|
this.region = getHNOPtr!(Region)("RGNN", regions);
|
||||||
|
@ -118,6 +129,10 @@ struct ExteriorCell
|
||||||
// Land can also be here instead. In fact, it can be both
|
// Land can also be here instead. In fact, it can be both
|
||||||
// places. I have to figure out what it means.
|
// places. I have to figure out what it means.
|
||||||
if(isNextHRec("LAND")) land.load();
|
if(isNextHRec("LAND")) land.load();
|
||||||
|
|
||||||
|
if(land.state == LoadState.Loaded)
|
||||||
|
if(gridX != land.X || gridY != land.Y)
|
||||||
|
writefln("WARNING: Grid mismatch at %s,%s!", gridX, gridY);
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,6 +160,10 @@ class CellList : ListKeeper
|
||||||
HashTable!(char[], InteriorCell, ESMRegionAlloc) in_cells;
|
HashTable!(char[], InteriorCell, ESMRegionAlloc) in_cells;
|
||||||
HashTable!(uint, ExteriorCell, ESMRegionAlloc, ExtCellHash) ex_cells;
|
HashTable!(uint, ExteriorCell, ESMRegionAlloc, ExtCellHash) ex_cells;
|
||||||
|
|
||||||
|
// Store the maximum x or y coordinate (in absolute value). This is
|
||||||
|
// used in the landscape pregen process.
|
||||||
|
int maxXY;
|
||||||
|
|
||||||
this()
|
this()
|
||||||
{
|
{
|
||||||
in_cells = in_cells.init;
|
in_cells = in_cells.init;
|
||||||
|
@ -176,6 +195,12 @@ class CellList : ListKeeper
|
||||||
return ex_cells.getPtr(compound(x,y));
|
return ex_cells.getPtr(compound(x,y));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check whether we have a given exterior cell
|
||||||
|
bool hasExt(int x, int y)
|
||||||
|
{
|
||||||
|
return ex_cells.inList(compound(x,y));
|
||||||
|
}
|
||||||
|
|
||||||
void *lookup(char[] s)
|
void *lookup(char[] s)
|
||||||
{ assert(0); }
|
{ assert(0); }
|
||||||
|
|
||||||
|
@ -203,7 +228,6 @@ class CellList : ListKeeper
|
||||||
}
|
}
|
||||||
|
|
||||||
uint length() { return numInt() + numExt(); }
|
uint length() { return numInt() + numExt(); }
|
||||||
|
|
||||||
uint numInt() { return in_cells.length; }
|
uint numInt() { return in_cells.length; }
|
||||||
uint numExt() { return ex_cells.length; }
|
uint numExt() { return ex_cells.length; }
|
||||||
|
|
||||||
|
@ -223,7 +247,8 @@ class CellList : ListKeeper
|
||||||
{with(esFile){
|
{with(esFile){
|
||||||
char[] id = getHNString("NAME");
|
char[] id = getHNString("NAME");
|
||||||
|
|
||||||
// Just ignore this, don't know what it does.
|
// Just ignore this, don't know what it does. I assume it
|
||||||
|
// deletes the cell, but we can't handle that yet.
|
||||||
if(isNextSub("DELE")) getHInt();
|
if(isNextSub("DELE")) getHInt();
|
||||||
|
|
||||||
readHNExact(&data, data.sizeof, "DATA");
|
readHNExact(&data, data.sizeof, "DATA");
|
||||||
|
@ -244,7 +269,7 @@ class CellList : ListKeeper
|
||||||
// Overloading an existing cell
|
// Overloading an existing cell
|
||||||
{
|
{
|
||||||
if(p.state != LoadState.Previous)
|
if(p.state != LoadState.Previous)
|
||||||
fail("Duplicate internal cell " ~ id);
|
fail("Duplicate interior cell " ~ id);
|
||||||
|
|
||||||
assert(id == p.id);
|
assert(id == p.id);
|
||||||
p.load();
|
p.load();
|
||||||
|
@ -266,6 +291,9 @@ class CellList : ListKeeper
|
||||||
p.gridY = data.gridY;
|
p.gridY = data.gridY;
|
||||||
p.load();
|
p.load();
|
||||||
p.state = LoadState.Loaded;
|
p.state = LoadState.Loaded;
|
||||||
|
|
||||||
|
int mx = max(abs(p.gridX), abs(p.gridY));
|
||||||
|
maxXY = max(maxXY, mx);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -282,34 +310,46 @@ class CellList : ListKeeper
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Landscape data. The landscape is stored as a hight map, and that is
|
* Landscape data.
|
||||||
* pretty much all I know at the moment.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
struct Land
|
struct Land
|
||||||
{
|
{
|
||||||
LoadState state;
|
LoadState state;
|
||||||
|
|
||||||
uint flags; // ?? - only first four bits seem to be used?
|
uint flags; // ?? - only first four bits seem to be used
|
||||||
|
|
||||||
|
// Map coordinates.
|
||||||
|
int X, Y;
|
||||||
|
|
||||||
TES3FileContext context;
|
TES3FileContext context;
|
||||||
|
|
||||||
|
bool hasData = false;
|
||||||
|
|
||||||
void load()
|
void load()
|
||||||
{with(esFile){
|
{with(esFile){
|
||||||
getHNUlong("INTV"); // Grid location. See next line.
|
// Get the grid location
|
||||||
//writefln("x=%d, y=%d", *(cast(int*)&grid), *(cast(int*)&grid+1));
|
ulong grid = getHNUlong("INTV");
|
||||||
|
X = grid & uint.max;
|
||||||
|
Y = (grid >> 32);
|
||||||
|
|
||||||
flags = getHNInt("DATA");
|
flags = getHNInt("DATA");
|
||||||
|
|
||||||
// Save file position
|
// Save file position
|
||||||
getContext(context);
|
getContext(context);
|
||||||
|
|
||||||
|
int cnt;
|
||||||
|
|
||||||
// Skip these here. Load the actual data when the cell is loaded.
|
// Skip these here. Load the actual data when the cell is loaded.
|
||||||
if(isNextSub("VNML")) skipHSubSize(12675);
|
if(isNextSub("VNML")) {skipHSubSize(12675);cnt++;}
|
||||||
if(isNextSub("VHGT")) skipHSubSize(4232);
|
if(isNextSub("VHGT")) {skipHSubSize(4232);cnt++;}
|
||||||
if(isNextSub("WNAM")) skipHSubSize(81);
|
if(isNextSub("WNAM")) skipHSubSize(81);
|
||||||
if(isNextSub("VCLR")) skipHSubSize(12675);
|
if(isNextSub("VCLR")) skipHSubSize(12675);
|
||||||
if(isNextSub("VTEX")) skipHSubSize(512);
|
if(isNextSub("VTEX")) {skipHSubSize(512);cnt++;}
|
||||||
|
|
||||||
|
// We need all three of VNML, VHGT and VTEX in order to use the
|
||||||
|
// landscape.
|
||||||
|
hasData = (cnt == 3);
|
||||||
|
|
||||||
if(state == LoadState.Loaded)
|
if(state == LoadState.Loaded)
|
||||||
writefln("Duplicate landscape data in " ~ getFilename());
|
writefln("Duplicate landscape data in " ~ getFilename());
|
||||||
|
|
|
@ -50,7 +50,7 @@ class LandTextureList : ListKeeper
|
||||||
// Contains the list of land textures for each file, indexed by
|
// Contains the list of land textures for each file, indexed by
|
||||||
// file. TODO: Use some handle system here too instead of raw
|
// file. TODO: Use some handle system here too instead of raw
|
||||||
// filename?
|
// filename?
|
||||||
HashTable!(char[], TextureList, ESMRegionAlloc) files;
|
HashTable!(char[], TextureList, ESMRegionAlloc, CITextHash) files;
|
||||||
|
|
||||||
// The texture list for the current file
|
// The texture list for the current file
|
||||||
TextureList current;
|
TextureList current;
|
||||||
|
@ -62,11 +62,7 @@ class LandTextureList : ListKeeper
|
||||||
|
|
||||||
// The first file (Morrowind.esm) typically needs a little more
|
// The first file (Morrowind.esm) typically needs a little more
|
||||||
// than most others
|
// than most others
|
||||||
|
|
||||||
current = esmRegion.getBuffer!(TextureIndex)(0,120);
|
current = esmRegion.getBuffer!(TextureIndex)(0,120);
|
||||||
|
|
||||||
// Overkill, just leave it as it is
|
|
||||||
//files.rehash(resources.esm.length + resources.esp.length);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void load()
|
void load()
|
||||||
|
|
|
@ -192,21 +192,6 @@ class CellData
|
||||||
|
|
||||||
loadReferences();
|
loadReferences();
|
||||||
|
|
||||||
// TODO: Set up landscape system here.
|
|
||||||
/*
|
|
||||||
with(esFile)
|
|
||||||
{
|
|
||||||
restoreContext(exCell.land.context, reg);
|
|
||||||
|
|
||||||
// TODO: Not all of these will be present at all times
|
|
||||||
readHNExact(,12675, "VNML");
|
|
||||||
readHNExact(,4232, "VHGT");
|
|
||||||
readHNExact(,81, "WNAM");
|
|
||||||
readHNExact(,12675, "VCLR");
|
|
||||||
readHNExact(,512, "VTEX");
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
const float cellWidth = 8192;
|
const float cellWidth = 8192;
|
||||||
|
|
||||||
// TODO/FIXME: This is temporary
|
// TODO/FIXME: This is temporary
|
||||||
|
|
17
terrain/bindings.d
Normal file
17
terrain/bindings.d
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
module terrain.bindings;
|
||||||
|
|
||||||
|
alias void *SceneNode;
|
||||||
|
alias void *Bounds;
|
||||||
|
alias void *MeshObj;
|
||||||
|
|
||||||
|
extern(C):
|
||||||
|
|
||||||
|
SceneNode terr_createChildNode(float relX, float relY, SceneNode);
|
||||||
|
void terr_destroyNode(SceneNode);
|
||||||
|
Bounds terr_makeBounds(float minHeight, float maxHeight, float width);
|
||||||
|
float terr_getSqCamDist(Bounds);
|
||||||
|
MeshObj terr_makeMesh(int segment, SceneNode);
|
||||||
|
void terr_killMesh(MeshObj);
|
||||||
|
|
||||||
|
void terr_genData();
|
||||||
|
void terr_setupRendering();
|
|
@ -1,463 +0,0 @@
|
||||||
/*
|
|
||||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
|
||||||
Copyright (C) 2009 Nicolay Korslund
|
|
||||||
WWW: http://openmw.sourceforge.net/
|
|
||||||
|
|
||||||
This file (cpp_archive.cpp) is part of the OpenMW package.
|
|
||||||
|
|
||||||
OpenMW is distributed as free software: you can redistribute it
|
|
||||||
and/or modify it under the terms of the GNU General Public License
|
|
||||||
version 3, as published by the Free Software Foundation.
|
|
||||||
|
|
||||||
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
|
|
||||||
version 3 along with this program. If not, see
|
|
||||||
http://www.gnu.org/licenses/ .
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Info about the entire quad. TODO: Some of this (such as the texture
|
|
||||||
// scale and probably the width and radius) can be generated at
|
|
||||||
// loadtime and is common for all quads on the same level.
|
|
||||||
struct QuadInfo
|
|
||||||
{
|
|
||||||
// Basic info
|
|
||||||
int cellX, cellY;
|
|
||||||
int level;
|
|
||||||
|
|
||||||
// Bounding box info
|
|
||||||
float minHeight, maxHeight;
|
|
||||||
float worldWidth;
|
|
||||||
float boundingRadius;
|
|
||||||
|
|
||||||
// Texture scale for this quad
|
|
||||||
float texScale;
|
|
||||||
|
|
||||||
// True if we should make the given child
|
|
||||||
bool hasChild[4];
|
|
||||||
|
|
||||||
// Number of mesh segments in this quad
|
|
||||||
int meshNum;
|
|
||||||
|
|
||||||
// Location of this quad in the main archive file. The size includes
|
|
||||||
// everything related to this quad, including mesh data, alpha maps,
|
|
||||||
// etc.
|
|
||||||
size_t offset, size;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct MeshInfo;
|
|
||||||
|
|
||||||
const static int CACHE_MAGIC = 0x345AF815;
|
|
||||||
|
|
||||||
struct ArchiveHeader
|
|
||||||
{
|
|
||||||
// "Magic" number to make sure we're actually reading an archive
|
|
||||||
// file
|
|
||||||
int magic;
|
|
||||||
|
|
||||||
// Total number of quads in the archive
|
|
||||||
int quads;
|
|
||||||
|
|
||||||
// Level of the 'root' quad. There will only be one quad on this
|
|
||||||
// level.
|
|
||||||
int rootLevel;
|
|
||||||
|
|
||||||
// Size of the alpha maps, in pixels along one side.
|
|
||||||
int alphaSize;
|
|
||||||
|
|
||||||
// Number of strings in the string table
|
|
||||||
int stringNum;
|
|
||||||
|
|
||||||
// Size of the string buffer
|
|
||||||
size_t stringSize;
|
|
||||||
};
|
|
||||||
|
|
||||||
// This class handles the cached terrain data.
|
|
||||||
class TerrainArchive
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
MeshInfo *curMesh;
|
|
||||||
QuadInfo *curQuad;
|
|
||||||
|
|
||||||
QuadInfo *rootQuad;
|
|
||||||
|
|
||||||
TerrainArchive()
|
|
||||||
: mappedPtr(0),
|
|
||||||
mappedSize(0),
|
|
||||||
mmf(0),
|
|
||||||
curMesh(0),
|
|
||||||
curQuad(0),
|
|
||||||
rootQuad(0) {}
|
|
||||||
|
|
||||||
void openFile(const std::string &name)
|
|
||||||
{
|
|
||||||
mmf = new MmFile(name);
|
|
||||||
|
|
||||||
// Read the index file first
|
|
||||||
std::ifstream ifile((name+".index").c_str(), std::ios::binary);
|
|
||||||
|
|
||||||
ArchiveHeader head;
|
|
||||||
ifile.read((char*)&head, sizeof(head));
|
|
||||||
|
|
||||||
// Sanity check
|
|
||||||
assert(head.magic == CACHE_MAGIC);
|
|
||||||
assert(head.quads > 0 && head.quads < 8192);
|
|
||||||
|
|
||||||
// Store header info
|
|
||||||
alphaSize = head.alphaSize;
|
|
||||||
|
|
||||||
// Read all the quads
|
|
||||||
quadList = new QuadInfo[head.quads];
|
|
||||||
ifile.read((char*)quadList, sizeof(QuadInfo)*head.quads);
|
|
||||||
|
|
||||||
// Create an index of all the quads
|
|
||||||
for(int qn = 0; qn < head.quads; qn++)
|
|
||||||
{
|
|
||||||
int x = quadList[qn].cellX;
|
|
||||||
int y = quadList[qn].cellY;
|
|
||||||
int l = quadList[qn].level;
|
|
||||||
|
|
||||||
assert(l >= 1);
|
|
||||||
|
|
||||||
quadMap[l][x][y] = qn;
|
|
||||||
|
|
||||||
// Store the root quad
|
|
||||||
if(l == head.rootLevel)
|
|
||||||
{
|
|
||||||
assert(rootQuad == NULL);
|
|
||||||
rootQuad = &quadList[qn];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
assert(l < head.rootLevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure the root was set
|
|
||||||
assert(rootQuad != NULL);
|
|
||||||
|
|
||||||
// Next read the string table
|
|
||||||
stringBuf = new char[head.stringSize];
|
|
||||||
stringOffsets = new int[head.stringNum];
|
|
||||||
stringNum = head.stringNum;
|
|
||||||
|
|
||||||
// First read the main string buffer
|
|
||||||
ifile.read(stringBuf, head.stringSize);
|
|
||||||
|
|
||||||
// Then read the string offsets
|
|
||||||
ifile.read((char*)stringOffsets, stringNum*sizeof(int));
|
|
||||||
|
|
||||||
// Read the vertex buffer data
|
|
||||||
int bufNum = head.rootLevel;
|
|
||||||
assert(bufNum == 7);
|
|
||||||
vertBufData.resize(bufNum);
|
|
||||||
indexBufData.resize(bufNum);
|
|
||||||
|
|
||||||
// Fill the buffers. Start at level 1.
|
|
||||||
for(int i=1;i<bufNum;i++)
|
|
||||||
{
|
|
||||||
int size;
|
|
||||||
void *ptr;
|
|
||||||
|
|
||||||
// Vertex buffer
|
|
||||||
ifile.read((char*)size, sizeof(int));
|
|
||||||
ptr = malloc(size);
|
|
||||||
vertBufData[i] = ptr;
|
|
||||||
ifile.read((char*)ptr, size);
|
|
||||||
|
|
||||||
// Index buffer
|
|
||||||
ifile.read((char*)size, sizeof(int));
|
|
||||||
ptr = malloc(size);
|
|
||||||
indexBufData[i] = ptr;
|
|
||||||
ifile.read((char*)ptr, size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get info about a given quad from the index.
|
|
||||||
QuadInfo *getQuad(int X, int Y, int level)
|
|
||||||
{
|
|
||||||
// FIXME: First check if the quad exists. This function should
|
|
||||||
// FAIL if that is not the case.
|
|
||||||
int ind = quadMap[level][X][Y];
|
|
||||||
QuadInfo *res = &quadList[ind];
|
|
||||||
assert(res);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Maps the terrain and material info for a given quad into
|
|
||||||
// memory. This is typically called right before the meshes are
|
|
||||||
// created.
|
|
||||||
void mapQuad(QuadInfo *info)
|
|
||||||
{
|
|
||||||
assert(info);
|
|
||||||
|
|
||||||
// Store the quad for later
|
|
||||||
curQuad = info;
|
|
||||||
|
|
||||||
doMap(info->offset, info->size);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the info struct for a given segment. Remembers the MeshInfo
|
|
||||||
// for all later calls.
|
|
||||||
MeshInfo *getMeshInfo(int segNum);
|
|
||||||
|
|
||||||
float *getVertexBuffer(int level)
|
|
||||||
{
|
|
||||||
assert(level>=1 && level<vertBufData.size());
|
|
||||||
return (float*)vertBufData[level];
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned short *getIndexBuffer(int level, int &size)
|
|
||||||
{
|
|
||||||
assert(level>=1 && level<indexBufData.size());
|
|
||||||
return (unsigned short*)indexBufData[level];
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
// All quad headers (from the index) are stored in this list
|
|
||||||
QuadInfo *quadList;
|
|
||||||
|
|
||||||
// A map of all quads. Contain indices to the above array. Indexed
|
|
||||||
// by [level][X][Y].
|
|
||||||
std::map<int,std::map<int,std::map<int,int> > > quadMap;
|
|
||||||
|
|
||||||
// These contain pregenerated mesh data that is common for all
|
|
||||||
// meshes on a given level.
|
|
||||||
std::vector<void*> vertBufData;
|
|
||||||
std::vector<void*> indexBufData;
|
|
||||||
|
|
||||||
// Used for the mmapped file
|
|
||||||
MmFile *mmf;
|
|
||||||
uint8_t *mappedPtr;
|
|
||||||
size_t mappedSize;
|
|
||||||
|
|
||||||
// Stores the string table
|
|
||||||
char *stringBuf;
|
|
||||||
int *stringOffsets;
|
|
||||||
int stringNum;
|
|
||||||
|
|
||||||
// Texture size of the alpha maps
|
|
||||||
int alphaSize;
|
|
||||||
|
|
||||||
const char *getString(int index)
|
|
||||||
{
|
|
||||||
assert(index >= 0);
|
|
||||||
assert(index < stringNum);
|
|
||||||
|
|
||||||
return stringBuf + stringOffsets[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
void doMap(size_t offset, size_t size)
|
|
||||||
{
|
|
||||||
assert(mmf != NULL);
|
|
||||||
mappedPtr = (uint8_t*)mmf->map(offset, size);
|
|
||||||
mappedSize = size;
|
|
||||||
assert(mappedPtr != NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the pointer to a given buffer in the mapped window. The
|
|
||||||
// offset is relative to the start of the window, and the size must
|
|
||||||
// fit inside the window.
|
|
||||||
void *getRelPtr(size_t offset, size_t size)
|
|
||||||
{
|
|
||||||
assert(mappedPtr != NULL);
|
|
||||||
|
|
||||||
// Don't overstep the mapped data
|
|
||||||
assert(offset + size <= mappedSize);
|
|
||||||
|
|
||||||
return mappedPtr + offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy a given buffer from the file. The buffer might be a
|
|
||||||
// compressed stream, so it's important that the buffers are written
|
|
||||||
// the same as they are read. (Ie. you can't write a buffer as one
|
|
||||||
// operation and read it as two, or vice versa. Also, buffers cannot
|
|
||||||
// overlap.) The offset is relative to the current mapped file
|
|
||||||
// window.
|
|
||||||
void copy(void *dest, size_t offset, size_t inSize)
|
|
||||||
{
|
|
||||||
const void *source = getRelPtr(offset, inSize);
|
|
||||||
|
|
||||||
// Just copy it for now
|
|
||||||
memcpy(dest, source, inSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
friend struct MeshInfo;
|
|
||||||
friend struct AlphaInfo;
|
|
||||||
} g_archive;
|
|
||||||
|
|
||||||
// Info about an alpha map belonging to a mesh
|
|
||||||
struct AlphaInfo
|
|
||||||
{
|
|
||||||
size_t bufSize, bufOffset;
|
|
||||||
|
|
||||||
// The texture name for this layer. The actual string is stored in
|
|
||||||
// the archive's string buffer.
|
|
||||||
int texName;
|
|
||||||
int alphaName;
|
|
||||||
|
|
||||||
// Fill the alpha texture buffer
|
|
||||||
void fillAlphaBuffer(uint8_t *abuf) const
|
|
||||||
{
|
|
||||||
g_archive.copy(abuf, bufOffset, bufSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the texture for this alpha layer
|
|
||||||
const char *getTexName() const
|
|
||||||
{
|
|
||||||
return g_archive.getString(texName);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the material name to give the alpha texture
|
|
||||||
const char *getAlphaName() const
|
|
||||||
{
|
|
||||||
return g_archive.getString(alphaName);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Info about each submesh
|
|
||||||
struct MeshInfo
|
|
||||||
{
|
|
||||||
// Bounding box info
|
|
||||||
float minHeight, maxHeight;
|
|
||||||
float worldWidth;
|
|
||||||
|
|
||||||
// Vertex and index numbers
|
|
||||||
int vertRows, vertCols;
|
|
||||||
int indexCount;
|
|
||||||
|
|
||||||
// Scene node position (relative to the parent node)
|
|
||||||
float x, y;
|
|
||||||
|
|
||||||
// Height offset to apply to all vertices
|
|
||||||
float heightOffset;
|
|
||||||
|
|
||||||
// Size and offset of the vertex buffer
|
|
||||||
size_t vertBufSize, vertBufOffset;
|
|
||||||
|
|
||||||
// Number and offset of AlphaInfo blocks
|
|
||||||
int alphaNum;
|
|
||||||
size_t alphaOffset;
|
|
||||||
|
|
||||||
// Texture name. Index to the string table.
|
|
||||||
int texName;
|
|
||||||
|
|
||||||
// Fill the given vertex buffer
|
|
||||||
void fillVertexBuffer(float *vbuf) const
|
|
||||||
{
|
|
||||||
//g_archive.copy(vbuf, vertBufOffset, vertBufSize);
|
|
||||||
|
|
||||||
// The height map and normals from the archive
|
|
||||||
char *hmap = (char*)g_archive.getRelPtr(vertBufOffset, vertBufSize);
|
|
||||||
|
|
||||||
int level = getLevel();
|
|
||||||
|
|
||||||
// The generic part, containing the x,y coordinates and the uv
|
|
||||||
// maps.
|
|
||||||
float *gmap = g_archive.getVertexBuffer(level);
|
|
||||||
|
|
||||||
// Calculate the factor to multiply each height value with. The
|
|
||||||
// heights are very limited in range as they are stored in a
|
|
||||||
// single byte. Normal MW data uses a factor of 8, but we have to
|
|
||||||
// double this for each successive level since we're splicing
|
|
||||||
// several vertices together and need to allow larger differences
|
|
||||||
// for each vertex. The formula is 8*2^(level-1).
|
|
||||||
float scale = 4.0 * (1<<level);
|
|
||||||
|
|
||||||
// Merge the two data sets together into the output buffer.
|
|
||||||
float offset = heightOffset;
|
|
||||||
for(int y=0; y<vertRows; y++)
|
|
||||||
{
|
|
||||||
// The offset for the entire row is determined by the first
|
|
||||||
// height value. All the values in a row gives the height
|
|
||||||
// relative to the previous value, and the first value in each
|
|
||||||
// row is relative to the first value in the previous row.
|
|
||||||
offset += *hmap;
|
|
||||||
|
|
||||||
// This is the 'sliding offset' for this row. It's adjusted
|
|
||||||
// for each vertex that's added, but only affects this row.
|
|
||||||
float rowofs = offset;
|
|
||||||
for(int x=0; x<vertCols; x++)
|
|
||||||
{
|
|
||||||
hmap++; // Skip the byte we just read
|
|
||||||
|
|
||||||
// X and Y from the pregenerated buffer
|
|
||||||
*vbuf++ = *gmap++;
|
|
||||||
*vbuf++ = *gmap++;
|
|
||||||
|
|
||||||
// The height is calculated from the current offset
|
|
||||||
*vbuf++ = rowofs * scale;
|
|
||||||
|
|
||||||
// Normal vector.
|
|
||||||
// TODO: Normalize?
|
|
||||||
*vbuf++ = *hmap++;
|
|
||||||
*vbuf++ = *hmap++;
|
|
||||||
*vbuf++ = *hmap++;
|
|
||||||
|
|
||||||
// UV
|
|
||||||
*vbuf++ = *gmap++;
|
|
||||||
*vbuf++ = *gmap++;
|
|
||||||
|
|
||||||
// Adjust the offset for the next vertex. On the last
|
|
||||||
// iteration this will read past the current row, but
|
|
||||||
// that's OK since rowofs is discarded afterwards.
|
|
||||||
rowofs += *hmap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fill the index buffer
|
|
||||||
void fillIndexBuffer(unsigned short *ibuf) const
|
|
||||||
{
|
|
||||||
// The index buffer is pregenerated. It is identical for all
|
|
||||||
// meshes on the same level, so just copy it over.
|
|
||||||
int size;
|
|
||||||
unsigned short *generic = g_archive.getIndexBuffer(getLevel(), size);
|
|
||||||
memcpy(ibuf, generic, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
int getLevel() const
|
|
||||||
{
|
|
||||||
assert(g_archive.curQuad);
|
|
||||||
return g_archive.curQuad->level;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get an alpha map belonging to this mesh
|
|
||||||
AlphaInfo *getAlphaInfo(int num) const
|
|
||||||
{
|
|
||||||
assert(num < alphaNum && num >= 0);
|
|
||||||
assert(getLevel() == 1);
|
|
||||||
AlphaInfo *res = (AlphaInfo*)g_archive.getRelPtr
|
|
||||||
(alphaOffset, alphaNum*sizeof(AlphaInfo));
|
|
||||||
res += num;
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the size of the alpha textures (in pixels).
|
|
||||||
int getAlphaSize() const
|
|
||||||
{ return g_archive.alphaSize; }
|
|
||||||
|
|
||||||
// Get the texture and material name to use for this mesh.
|
|
||||||
const char *getTexName() const
|
|
||||||
{ return g_archive.getString(texName); }
|
|
||||||
|
|
||||||
float getTexScale() const
|
|
||||||
{ return g_archive.curQuad->texScale; }
|
|
||||||
|
|
||||||
const char *getBackgroundTex() const
|
|
||||||
{ return "_land_default.dds"; }
|
|
||||||
};
|
|
||||||
|
|
||||||
MeshInfo *TerrainArchive::getMeshInfo(int segNum)
|
|
||||||
{
|
|
||||||
assert(curQuad);
|
|
||||||
assert(segNum < curQuad->meshNum);
|
|
||||||
|
|
||||||
// The mesh headers are at the beginning of the mapped segment.
|
|
||||||
curMesh = (MeshInfo*) getRelPtr(0, sizeof(MeshInfo)*curQuad->meshNum);
|
|
||||||
curMesh += segNum;
|
|
||||||
|
|
||||||
return curMesh;
|
|
||||||
}
|
|
|
@ -1,167 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (c) Jacob Essex 2009
|
|
||||||
|
|
||||||
This file is part of MWLand.
|
|
||||||
|
|
||||||
MWLand is free software: you can redistribute it and/or modify it
|
|
||||||
under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
MWLand 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 MWLand. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
///generic subrecord
|
|
||||||
struct SubRecord
|
|
||||||
{
|
|
||||||
SubRecord(){}
|
|
||||||
SubRecord(const std::string& n, const std::string& d) : subName(n), subData(d){}
|
|
||||||
std::string subName;
|
|
||||||
std::string subData;
|
|
||||||
};
|
|
||||||
|
|
||||||
///generic record
|
|
||||||
class Record {
|
|
||||||
public:
|
|
||||||
Record(const std::string& type)
|
|
||||||
: mType(type) {}
|
|
||||||
|
|
||||||
inline void addSubRecord(const std::string& id,
|
|
||||||
const SubRecord& subRecord)
|
|
||||||
{mSubRecords[id] = subRecord; }
|
|
||||||
|
|
||||||
std::string getSubRecordData(const std::string& recordName)
|
|
||||||
{
|
|
||||||
SubRecordMapItr itr = mSubRecords.find(recordName );
|
|
||||||
if ( itr != mSubRecords.end() )
|
|
||||||
return itr->second.subData;
|
|
||||||
return std::string("");
|
|
||||||
}
|
|
||||||
|
|
||||||
bool hasSubRecord(const std::string& recordName)
|
|
||||||
{
|
|
||||||
if ( mSubRecords.find(recordName ) != mSubRecords.end() )
|
|
||||||
return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline const std::string& getID() { return mId; }
|
|
||||||
inline void setID( const std::string& id) { mId = id;}
|
|
||||||
const std::string& getType(){return mType;}
|
|
||||||
|
|
||||||
private:
|
|
||||||
typedef std::map<std::string, SubRecord> SubRecordMap;
|
|
||||||
typedef SubRecordMap::iterator SubRecordMapItr;
|
|
||||||
SubRecordMap mSubRecords;
|
|
||||||
|
|
||||||
std::string mType;
|
|
||||||
std::string mId;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef std::list<Record*> RecordList;
|
|
||||||
typedef RecordList::iterator RecordListItr;
|
|
||||||
|
|
||||||
typedef std::map<std::string, Record*> RecordMap;
|
|
||||||
typedef RecordMap::iterator RecordMapItr;
|
|
||||||
|
|
||||||
///top level class for loading and saving esp files.
|
|
||||||
class ESM
|
|
||||||
{
|
|
||||||
private:
|
|
||||||
/// types of records to load
|
|
||||||
std::map<std::string, std::string> mLoadTypes;
|
|
||||||
|
|
||||||
/// map<id, record> of the record
|
|
||||||
RecordMap mRecords;
|
|
||||||
|
|
||||||
///checks if the given type should be loaded
|
|
||||||
inline bool loadType(const std::string& t)
|
|
||||||
{
|
|
||||||
return ( mLoadTypes.find(t) != mLoadTypes.end() );
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
inline void addRecordType(const std::string& t,
|
|
||||||
const std::string& i = "NAME")
|
|
||||||
{ mLoadTypes[t] = i; }
|
|
||||||
|
|
||||||
bool loadFile(const std::string& file)
|
|
||||||
{
|
|
||||||
std::ifstream esp (file.c_str(), std::ios::in | std::ios::binary);
|
|
||||||
|
|
||||||
if ( !esp.is_open() ) return false; //check open
|
|
||||||
|
|
||||||
esp.seekg(4);
|
|
||||||
|
|
||||||
long hdrSize; //get offset for start of data
|
|
||||||
esp.read ((char *)&hdrSize, sizeof(long));
|
|
||||||
|
|
||||||
//get num records
|
|
||||||
esp.seekg(16 + 8 + 296);
|
|
||||||
long numRecords;
|
|
||||||
esp.read ((char *)&numRecords, sizeof(long));
|
|
||||||
|
|
||||||
esp.seekg(hdrSize + 16); //go to end of header
|
|
||||||
|
|
||||||
for ( long i = 0; i < numRecords; i++ ){
|
|
||||||
|
|
||||||
char type[5];
|
|
||||||
esp.get(type, 5);
|
|
||||||
type[4] = '\0';
|
|
||||||
|
|
||||||
long recordSize;
|
|
||||||
esp.read ((char *)&recordSize, 4);
|
|
||||||
esp.seekg(8, std::ofstream::cur);
|
|
||||||
long endPos = recordSize + esp.tellg();
|
|
||||||
|
|
||||||
if ( loadType(type) ) {
|
|
||||||
Record* record = new Record(type);
|
|
||||||
|
|
||||||
//load all subrecords
|
|
||||||
while ( esp.tellg() < endPos ) {
|
|
||||||
char subType[5];
|
|
||||||
esp.get(subType, 5);
|
|
||||||
|
|
||||||
long subRecLength;
|
|
||||||
esp.read ((char *)&subRecLength, 4);
|
|
||||||
|
|
||||||
long subRecEnd = subRecLength + esp.tellg();
|
|
||||||
char* subRecData = new char[subRecLength];
|
|
||||||
esp.read(subRecData, subRecLength);
|
|
||||||
|
|
||||||
record->addSubRecord(subType, SubRecord(subType,std::string(subRecData, subRecLength)));
|
|
||||||
delete [] subRecData;
|
|
||||||
|
|
||||||
assert(subRecEnd==esp.tellg());
|
|
||||||
}
|
|
||||||
record->setID(record->getSubRecordData(mLoadTypes[type]));
|
|
||||||
mRecords[record->getSubRecordData(mLoadTypes[type])] = record;
|
|
||||||
}else{
|
|
||||||
esp.seekg(endPos);
|
|
||||||
}
|
|
||||||
assert(endPos==esp.tellg());
|
|
||||||
|
|
||||||
}
|
|
||||||
esp.close();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline Record* getRecord(const std::string& id){ return mRecords[id]; }
|
|
||||||
|
|
||||||
RecordList* getRecordsByType(const std::string& t)
|
|
||||||
{
|
|
||||||
RecordList* r = new RecordList;
|
|
||||||
for ( RecordMapItr iter = mRecords.begin(); iter != mRecords.end(); ++iter)
|
|
||||||
if ( t == iter->second->getType() )
|
|
||||||
r->push_back(iter->second);
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
};
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,223 +0,0 @@
|
||||||
struct VHGT
|
|
||||||
{ ///height data
|
|
||||||
float heightOffset;
|
|
||||||
char heightData[LAND_NUM_VERTS];
|
|
||||||
short unknown1;
|
|
||||||
char unknown2;
|
|
||||||
};
|
|
||||||
|
|
||||||
class MWLand
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
MWLand()
|
|
||||||
{
|
|
||||||
mMaxX = mMaxY = mMinX = mMinY = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void addLandTextureData(Record* record, const std::string& source)
|
|
||||||
{
|
|
||||||
LandTexture l;
|
|
||||||
l.name = record->getSubRecordData("NAME");
|
|
||||||
l.data = record->getSubRecordData("DATA");
|
|
||||||
l.intv = *((short*) record->getSubRecordData("INTV").c_str());
|
|
||||||
mLTEXRecords[source][l.intv] = l;
|
|
||||||
}
|
|
||||||
|
|
||||||
void addLandData(Record* record, const std::string& source)
|
|
||||||
{
|
|
||||||
if ( !record->hasSubRecord("VHGT") || !record->hasSubRecord("VTEX") ) //ensure all records exist
|
|
||||||
return;
|
|
||||||
|
|
||||||
//copy these, else we end up with invliad data
|
|
||||||
LAND::INTV intv = *(LAND::INTV*)record->getSubRecordData("INTV").c_str();
|
|
||||||
VHGT *vhgt = (VHGT*) record->getSubRecordData("VHGT").c_str();
|
|
||||||
LAND::VNML vnml = *(LAND::VNML*)record->getSubRecordData("VNML").c_str();
|
|
||||||
LAND::VTEX vtex = *(LAND::VTEX*)record->getSubRecordData("VTEX").c_str();
|
|
||||||
|
|
||||||
// FIXME: What happens to the memory allocation of vhgt here?
|
|
||||||
// Doesn't matter much, we're killing this entire file soon
|
|
||||||
// anyway.
|
|
||||||
mLandRecords[intv.x][intv.y].heights = vhgt;
|
|
||||||
mLandRecords[intv.x][intv.y].normals = parseNormals(&vnml);
|
|
||||||
mLandRecords[intv.x][intv.y].textures = parseTextures(&vtex);
|
|
||||||
mLandRecords[intv.x][intv.y].source = source;
|
|
||||||
|
|
||||||
mMaxX = std::max<int>(mMaxX, intv.x);
|
|
||||||
mMaxY = std::max<int>(mMaxY, intv.y);
|
|
||||||
mMinX = std::min<int>(mMinX, intv.x);
|
|
||||||
mMinY = std::min<int>(mMinY, intv.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
///Maximum distance of a cell on the X plane from grid pos 0 in the positive direction
|
|
||||||
inline int getMaxX() const { return mMaxX; }
|
|
||||||
///Maximum distance of a cell on the Y plane from grid pos 0 in the positvie direction
|
|
||||||
inline int getMaxY() const { return mMaxY; }
|
|
||||||
///Maximum distance of a cell on the X plane from grid pos 0 in the negative direction
|
|
||||||
inline int getMinX() const { return mMinX; }
|
|
||||||
///see others
|
|
||||||
inline int getMinY() const { return mMinY; }
|
|
||||||
|
|
||||||
inline VHGT *getHeights(int x, int y)
|
|
||||||
{
|
|
||||||
if ( hasData(x,y) )
|
|
||||||
return mLandRecords[x][y].heights;
|
|
||||||
assert(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline std::vector<char>& getNormals(int x, int y)
|
|
||||||
{
|
|
||||||
if ( hasData(x,y) )
|
|
||||||
return mLandRecords[x][y].normals;
|
|
||||||
assert(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline const std::string& getSource(int x, int y)
|
|
||||||
{
|
|
||||||
assert(hasData(x,y));
|
|
||||||
return mLandRecords[x][y].source;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool hasData(int x, int y) const
|
|
||||||
{
|
|
||||||
std::map<int , std::map<int, LandData> >::const_iterator itr = mLandRecords.find(x);
|
|
||||||
if ( itr == mLandRecords.end() )
|
|
||||||
return false;
|
|
||||||
if ( itr->second.find(y) == itr->second.end() )
|
|
||||||
return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
inline std::string& getLTEXRecord(const std::string& source, short i)
|
|
||||||
{
|
|
||||||
return mLTEXRecords[source][i].data;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool hasLTEXRecord(const std::string& source, short index) const
|
|
||||||
{
|
|
||||||
std::map<std::string, std::map<short, LandTexture> >::const_iterator itr = mLTEXRecords.find(source);
|
|
||||||
if ( itr == mLTEXRecords.end() )
|
|
||||||
return false;
|
|
||||||
if ( itr->second.find(index) == itr->second.end() )
|
|
||||||
return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline short getLTEXIndex(int x, int y, int pos)
|
|
||||||
{
|
|
||||||
assert(hasData(x,y));
|
|
||||||
return mLandRecords[x][y].textures[pos];
|
|
||||||
}
|
|
||||||
|
|
||||||
inline short getLTEXIndex(int x1, int y1, int x2, int y2)
|
|
||||||
{
|
|
||||||
return getLTEXIndex(x1, y1, y2*LAND_LTEX_WIDTH+x2);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
///the min/max size of cells
|
|
||||||
int mMaxY, mMinY;
|
|
||||||
int mMaxX, mMinX;
|
|
||||||
|
|
||||||
// Land structure as held in the ESM
|
|
||||||
struct LAND {
|
|
||||||
struct INTV { /// x, y grid pos of the cell
|
|
||||||
long x;
|
|
||||||
long y;
|
|
||||||
};
|
|
||||||
struct VNML { ///vertex normal data
|
|
||||||
struct NORMAL {
|
|
||||||
char x;
|
|
||||||
char y;
|
|
||||||
char z;
|
|
||||||
};
|
|
||||||
NORMAL normals[LAND_NUM_VERTS];
|
|
||||||
};
|
|
||||||
struct VTEX { ///splat texture data
|
|
||||||
short index[LAND_NUM_LTEX];
|
|
||||||
};
|
|
||||||
INTV* intv;
|
|
||||||
VNML* vnml;
|
|
||||||
VHGT* vhgt;
|
|
||||||
VTEX* vtex;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
std::vector<float> parseHeights(LAND::VHGT* vhgt)
|
|
||||||
{
|
|
||||||
std::vector<float> ph;
|
|
||||||
ph.resize(LAND_NUM_VERTS, LAND_DEFAULT_HEIGHT);
|
|
||||||
|
|
||||||
// heightOffset offsets the entire cell
|
|
||||||
float offset = vhgt->heightOffset;
|
|
||||||
|
|
||||||
for (int y = 0; y < LAND_VERT_WIDTH; y++)
|
|
||||||
{
|
|
||||||
// The first vertex in each row gives the difference relative
|
|
||||||
// to the last row start
|
|
||||||
offset += vhgt->heightData[y*LAND_VERT_WIDTH];
|
|
||||||
ph[y*LAND_VERT_WIDTH] =+ (float)offset*8;
|
|
||||||
|
|
||||||
float pos = offset;
|
|
||||||
for (int x = 1; x < LAND_VERT_WIDTH; x++)
|
|
||||||
{
|
|
||||||
// And each vertex within a row gives the difference
|
|
||||||
// relative to the previous one
|
|
||||||
pos += vhgt->heightData[y*LAND_VERT_WIDTH+x];
|
|
||||||
ph[y*LAND_VERT_WIDTH+x] = pos*8; //flipped x
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ph;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
std::vector<char> parseNormals( LAND::VNML* vnml )
|
|
||||||
{
|
|
||||||
std::vector<char> n;
|
|
||||||
n.resize(LAND_NUM_VERTS*3,0);
|
|
||||||
for ( int y = 0; y < LAND_VERT_WIDTH; y++ ) { //this could just be cast.
|
|
||||||
for ( int x = 0; x < LAND_VERT_WIDTH; x++ ) { //as a vector is a continus segment of mem...
|
|
||||||
n[(y*LAND_VERT_WIDTH+x)*3] = vnml->normals[y*LAND_VERT_WIDTH+x].x;
|
|
||||||
n[(y*LAND_VERT_WIDTH+x)*3+1] = vnml->normals[y*LAND_VERT_WIDTH+x].y;
|
|
||||||
n[(y*LAND_VERT_WIDTH+x)*3+2] = vnml->normals[y*LAND_VERT_WIDTH+x].z;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<short> parseTextures( LAND::VTEX* vtex )
|
|
||||||
{
|
|
||||||
std::vector<short> t;
|
|
||||||
t.resize(LAND_NUM_LTEX,0);
|
|
||||||
|
|
||||||
//thanks to timeslip (MGE) for the code
|
|
||||||
int rpos = 0; //bit ugly, but it works
|
|
||||||
for ( int y1 = 0; y1 < 4; y1++ )
|
|
||||||
for ( int x1 = 0; x1 < 4; x1++ )
|
|
||||||
for ( int y2 = 0; y2 < 4; y2++)
|
|
||||||
for ( int x2 = 0; x2 < 4; x2++ )
|
|
||||||
t[(y1*4+y2)*16+(x1*4+x2)]=vtex->index[rpos++];
|
|
||||||
return t;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Holds the representation of a cell in the way that is most usefull to me
|
|
||||||
*/
|
|
||||||
struct LandData
|
|
||||||
{
|
|
||||||
std::string source; //data file the data is from
|
|
||||||
VHGT *heights;
|
|
||||||
std::vector<char> normals;
|
|
||||||
std::vector<short> textures;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct LandTexture
|
|
||||||
{
|
|
||||||
std::string name, data;
|
|
||||||
short intv;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::map<std::string, std::map<short, LandTexture> > mLTEXRecords;
|
|
||||||
std::map<int, std::map<int,LandData> > mLandRecords;
|
|
||||||
};
|
|
|
@ -1,302 +0,0 @@
|
||||||
/**
|
|
||||||
* defines an area of Landscape
|
|
||||||
*
|
|
||||||
* A quad can either hold a mesh, or 4 other sub quads The functions
|
|
||||||
* split and unsplit either break the current quad into smaller quads,
|
|
||||||
* or alternatively remove the lower quads and create the terrain mesh
|
|
||||||
* on the the current (now the lowest) level
|
|
||||||
*/
|
|
||||||
/* Previously for MeshInterface:
|
|
||||||
* Interface between the quad and the terrain renderble classes, to the
|
|
||||||
* quad it looks like this rendereds a single mesh for the quad. This
|
|
||||||
* may not be the case.
|
|
||||||
*
|
|
||||||
* It also could allow several optimizations (e.g. multiple splits)
|
|
||||||
*/
|
|
||||||
|
|
||||||
class Quad
|
|
||||||
{
|
|
||||||
typedef std::list<TerrainMesh*> MeshList;
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
Quad(int cellX=0, int cellY=0, Quad* parent = NULL)
|
|
||||||
: mCellX(cellX),
|
|
||||||
mCellY(cellY)
|
|
||||||
{
|
|
||||||
RTRACE("Quad");
|
|
||||||
|
|
||||||
memset(mChildren, NULL, sizeof(Quad*)*NUM_CHILDREN);
|
|
||||||
|
|
||||||
hasMesh = false;
|
|
||||||
hasChildren = false;
|
|
||||||
isStatic = false;
|
|
||||||
|
|
||||||
// Do we have a parent?
|
|
||||||
if(parent != NULL)
|
|
||||||
{
|
|
||||||
mLevel = parent->mLevel-1;
|
|
||||||
|
|
||||||
if(mLevel == 1)
|
|
||||||
{
|
|
||||||
// Create the terrain and leave it there.
|
|
||||||
buildTerrain();
|
|
||||||
isStatic = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Coordinates relative to our parent
|
|
||||||
const int relX = cellX - parent->mCellX;
|
|
||||||
const int relY = cellY - parent->mCellY;
|
|
||||||
|
|
||||||
// The coordinates give the top left corner of the quad, or our
|
|
||||||
// relative coordinates within that should always be positive.
|
|
||||||
assert(relX >= 0);
|
|
||||||
assert(relY >= 0);
|
|
||||||
|
|
||||||
// Create a child scene node. The scene node position is given in
|
|
||||||
// world units, ie. CELL_WIDTH units per cell.
|
|
||||||
const Ogre::Vector3 pos(relX * CELL_WIDTH,
|
|
||||||
relY * CELL_WIDTH,
|
|
||||||
0);
|
|
||||||
mSceneNode = parent->mSceneNode->createChildSceneNode(pos);
|
|
||||||
|
|
||||||
// Get the archive data for this quad.
|
|
||||||
mInfo = g_archive.getQuad(mCellX,mCellY,mLevel);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// No parent, this is the top-most quad. Get all the info from
|
|
||||||
// the archive.
|
|
||||||
mInfo = g_archive.rootQuad;
|
|
||||||
|
|
||||||
mLevel = mInfo->level;
|
|
||||||
cellX = mCellX = mInfo->cellX;
|
|
||||||
cellY = mCellY = mInfo->cellY;
|
|
||||||
|
|
||||||
const Ogre::Vector3 pos(cellX * CELL_WIDTH,
|
|
||||||
cellY * CELL_WIDTH,
|
|
||||||
0);
|
|
||||||
mSceneNode = g_rootTerrainNode->
|
|
||||||
createChildSceneNode(pos);
|
|
||||||
|
|
||||||
// Split up
|
|
||||||
split();
|
|
||||||
|
|
||||||
// The root can never be unsplit
|
|
||||||
isStatic = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(mLevel >= 1);
|
|
||||||
assert(mSceneNode != NULL);
|
|
||||||
|
|
||||||
// Set up the bounding box. Use MW coordinates all the way
|
|
||||||
mBounds.setExtents(0,0,mInfo->minHeight,
|
|
||||||
mInfo->worldWidth,mInfo->worldWidth,
|
|
||||||
mInfo->maxHeight);
|
|
||||||
|
|
||||||
// Transform the box to world coordinates, so it can be compared
|
|
||||||
// with the camera later.
|
|
||||||
mBounds.transformAffine(mSceneNode->_getFullTransform());
|
|
||||||
|
|
||||||
const float radius = mInfo->boundingRadius;
|
|
||||||
|
|
||||||
mSplitDistance = radius * SPLIT_FACTOR;
|
|
||||||
mUnsplitDistance = radius * UNSPLIT_FACTOR;
|
|
||||||
|
|
||||||
// Square the distances
|
|
||||||
mSplitDistance *= mSplitDistance;
|
|
||||||
mUnsplitDistance *= mUnsplitDistance;
|
|
||||||
|
|
||||||
// Update the terrain. This will create the mesh or children if
|
|
||||||
// necessary.
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
~Quad()
|
|
||||||
{
|
|
||||||
RTRACE("~Quad");
|
|
||||||
if(hasMesh)
|
|
||||||
destroyTerrain();
|
|
||||||
else if(hasChildren)
|
|
||||||
for (size_t i = 0; i < NUM_CHILDREN; i++)
|
|
||||||
delete mChildren[i];
|
|
||||||
|
|
||||||
mSceneNode->removeAndDestroyAllChildren();
|
|
||||||
mSceneMgr->destroySceneNode(mSceneNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the landscape for this quad, and create children.
|
|
||||||
void split()
|
|
||||||
{
|
|
||||||
RTRACE("split");
|
|
||||||
|
|
||||||
// Never split a static quad or a quad that already has children.
|
|
||||||
assert(!isStatic);
|
|
||||||
assert(!hasChildren);
|
|
||||||
assert(mLevel > 1);
|
|
||||||
|
|
||||||
if(hasMesh)
|
|
||||||
destroyTerrain();
|
|
||||||
|
|
||||||
// Find the cell width of our children
|
|
||||||
int cWidth = 1 << (mLevel-2);
|
|
||||||
|
|
||||||
// Create children
|
|
||||||
for ( size_t i = 0; i < NUM_CHILDREN; ++i )
|
|
||||||
{
|
|
||||||
if(!mInfo->hasChild[i])
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// The cell coordinates for this child quad
|
|
||||||
int x = (i%2)*cWidth + mCellX;
|
|
||||||
int y = (i/2)*cWidth + mCellY;
|
|
||||||
|
|
||||||
mChildren[i] = new Quad(x,y,this);
|
|
||||||
}
|
|
||||||
hasChildren = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Removes children and rebuilds terrain
|
|
||||||
void unsplit()
|
|
||||||
{
|
|
||||||
RTRACE("unsplit");
|
|
||||||
|
|
||||||
// Never unsplit the root quad
|
|
||||||
assert(mLevel < g_archive.rootQuad->level);
|
|
||||||
// Never unsplit a static or quad that isn't split.
|
|
||||||
assert(!isStatic);
|
|
||||||
assert(hasChildren);
|
|
||||||
assert(!hasMesh);
|
|
||||||
|
|
||||||
for( size_t i = 0; i < NUM_CHILDREN; i++ )
|
|
||||||
{
|
|
||||||
delete mChildren[i];
|
|
||||||
mChildren[i] = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
buildTerrain();
|
|
||||||
|
|
||||||
hasChildren = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determines whether to split or unsplit the quad, and immediately
|
|
||||||
// does it.
|
|
||||||
void update()
|
|
||||||
{
|
|
||||||
RTRACE("Quad::update");
|
|
||||||
|
|
||||||
// Static quads don't change
|
|
||||||
if(isStatic)
|
|
||||||
return;
|
|
||||||
|
|
||||||
assert(mUnsplitDistance > mSplitDistance);
|
|
||||||
|
|
||||||
// Get (squared) camera distance. TODO: shouldn't this just be a
|
|
||||||
// simple vector difference from the mesh center?
|
|
||||||
float camDist;
|
|
||||||
{
|
|
||||||
const Ogre::Vector3 cpos = mCamera->getDerivedPosition();
|
|
||||||
Ogre::Vector3 diff(0, 0, 0);
|
|
||||||
diff.makeFloor(cpos - mBounds.getMinimum() );
|
|
||||||
diff.makeCeil(cpos - mBounds.getMaximum() );
|
|
||||||
camDist = diff.squaredLength();
|
|
||||||
}
|
|
||||||
|
|
||||||
// No children?
|
|
||||||
if(!hasChildren)
|
|
||||||
{
|
|
||||||
// If we're close, split now.
|
|
||||||
if(camDist < mSplitDistance)
|
|
||||||
split();
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// We're not close, and don't have any children. Should we
|
|
||||||
// built terrain?
|
|
||||||
if(!hasMesh)
|
|
||||||
buildTerrain();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we get here, we either had children when we entered, or we
|
|
||||||
// just performed a split.
|
|
||||||
assert(!hasMesh);
|
|
||||||
assert(hasChildren);
|
|
||||||
|
|
||||||
// If the camera is too far away, kill the children.
|
|
||||||
if( camDist > mUnsplitDistance )
|
|
||||||
{
|
|
||||||
unsplit();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We have children and we're happy about it. Update them too.
|
|
||||||
for (size_t i = 0; i < NUM_CHILDREN; ++i)
|
|
||||||
{
|
|
||||||
Quad *q = mChildren[i];
|
|
||||||
if(q != NULL) q->update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the terrain for this quad
|
|
||||||
void buildTerrain()
|
|
||||||
{
|
|
||||||
RTRACE("buildTerrain");
|
|
||||||
assert(!hasMesh);
|
|
||||||
assert(!isStatic);
|
|
||||||
|
|
||||||
// Map the terrain data into memory.
|
|
||||||
g_archive.mapQuad(mInfo);
|
|
||||||
|
|
||||||
// Create one mesh for each segment in the quad. TerrainMesh takes
|
|
||||||
// care of the loading.
|
|
||||||
for(int i=0; i < mInfo->meshNum; i++)
|
|
||||||
mMeshList.push_back(new TerrainMesh(i, mSceneNode));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief destroys the terrain.
|
|
||||||
*/
|
|
||||||
void destroyTerrain()
|
|
||||||
{
|
|
||||||
RTRACE("destroyTerrain");
|
|
||||||
assert(hasMesh);
|
|
||||||
|
|
||||||
for ( MeshList::iterator itr = mMeshList.begin();
|
|
||||||
itr != mMeshList.end(); ++itr )
|
|
||||||
delete *itr;
|
|
||||||
|
|
||||||
mMeshList.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
// List of meshes, if any
|
|
||||||
MeshList mMeshList;
|
|
||||||
|
|
||||||
// Scene node. All child quads are added to this.
|
|
||||||
SceneNode* mSceneNode;
|
|
||||||
|
|
||||||
// Bounding box, transformed to world coordinates. Used to calculate
|
|
||||||
// camera distance.
|
|
||||||
Ogre::AxisAlignedBox mBounds;
|
|
||||||
|
|
||||||
Ogre::Real mSplitDistance,mUnsplitDistance;
|
|
||||||
|
|
||||||
static const size_t NUM_CHILDREN = 4;
|
|
||||||
|
|
||||||
Quad* mChildren[NUM_CHILDREN]; ///optionaly the children. Should be
|
|
||||||
///0 if not exist
|
|
||||||
|
|
||||||
// Contains the 'level' of this node. Level 1 is the closest and
|
|
||||||
// most detailed level
|
|
||||||
int mLevel;
|
|
||||||
int mCellX, mCellY;
|
|
||||||
|
|
||||||
QuadInfo *mInfo;
|
|
||||||
|
|
||||||
bool hasMesh;
|
|
||||||
bool hasChildren;
|
|
||||||
bool isStatic; // Static quads are never split or unsplit
|
|
||||||
};
|
|
|
@ -20,83 +20,9 @@
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
///no texture assigned
|
|
||||||
const int LAND_LTEX_NONE = 0;
|
|
||||||
|
|
||||||
///the default land height that it defaults to in the TESCS
|
|
||||||
const int LAND_DEFAULT_HEIGHT = -2048;
|
|
||||||
|
|
||||||
///how many verts wide (and long) the cell is
|
|
||||||
const int LAND_VERT_WIDTH = 65;
|
|
||||||
|
|
||||||
///Number of verts that make up a cell
|
|
||||||
const int LAND_NUM_VERTS = LAND_VERT_WIDTH*LAND_VERT_WIDTH;
|
|
||||||
|
|
||||||
const int LAND_LTEX_WIDTH = 16;
|
|
||||||
const int LAND_NUM_LTEX = LAND_LTEX_WIDTH*LAND_LTEX_WIDTH;
|
|
||||||
|
|
||||||
const int CELL_WIDTH = 8192;
|
|
||||||
|
|
||||||
// Scaling factor to apply to textures on once cell. A factor of 1/16
|
|
||||||
// gives one repetition per square, since there are 16x16 texture
|
|
||||||
// 'squares' in acell. For reference, Yacoby's scaling was equivalent
|
|
||||||
// to having 1.0/10 here, or 10 repititions per cell. TODO: This looks
|
|
||||||
// a little blocky. Compare with screenshots from TES-CS.
|
|
||||||
const float TEX_SCALE = 1.0/16;
|
|
||||||
|
|
||||||
// Multiplied with the size of the quad. If these are too close, a
|
|
||||||
// quad might end up splitting/unsplitting continuously, since the
|
|
||||||
// quad size changes when we split.
|
|
||||||
const float SPLIT_FACTOR = 0.5;
|
|
||||||
const float UNSPLIT_FACTOR = 2.0;
|
|
||||||
|
|
||||||
//stops it crashing, now it leaks.
|
|
||||||
#define ENABLED_CRASHING 0
|
|
||||||
|
|
||||||
class Quad;
|
|
||||||
class TerrainMesh;
|
|
||||||
class BaseLand;
|
|
||||||
|
|
||||||
// Cache directory and file
|
|
||||||
std::string g_cacheDir;
|
|
||||||
std::string g_cacheFile;
|
|
||||||
|
|
||||||
// Enable or disable tracing of runtime functions. Making RTRACE do a
|
|
||||||
// trace slows down the code significantly even when -debug is off, so
|
|
||||||
// lets disable it for normal use.
|
|
||||||
#define RTRACE(x)
|
|
||||||
//#define RTRACE TRACE
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
// Prerequisites
|
|
||||||
#include <vector>
|
|
||||||
#include <map>
|
|
||||||
#include <fstream>
|
|
||||||
#include <string>
|
|
||||||
#include <list>
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
// Located in ../util/
|
|
||||||
#include "mmfile.h"
|
|
||||||
#include "outbuffer.h"
|
|
||||||
|
|
||||||
// Reading and writing the cache files
|
|
||||||
#include "cpp_archive.cpp"
|
|
||||||
#include "cpp_cachewriter.cpp"
|
|
||||||
|
|
||||||
// For generation
|
|
||||||
#include "cpp_esm.cpp"
|
|
||||||
#include "cpp_landdata.cpp"
|
|
||||||
#include "cpp_generator.cpp"
|
|
||||||
|
|
||||||
// For rendering
|
|
||||||
Quad *g_rootQuad;
|
|
||||||
BaseLand *g_baseLand;
|
|
||||||
SceneNode *g_rootTerrainNode;
|
|
||||||
|
|
||||||
#include "cpp_baseland.cpp"
|
#include "cpp_baseland.cpp"
|
||||||
#include "cpp_mesh.cpp"
|
#include "cpp_mesh.cpp"
|
||||||
#include "cpp_quad.cpp"
|
|
||||||
|
|
||||||
class TerrainFrameListener : public FrameListener
|
class TerrainFrameListener : public FrameListener
|
||||||
{
|
{
|
||||||
|
@ -112,14 +38,31 @@ protected:
|
||||||
extern "C" void d_superman();
|
extern "C" void d_superman();
|
||||||
*/
|
*/
|
||||||
|
|
||||||
extern "C" void terr_setCacheDir(char *cacheDir)
|
extern "C"
|
||||||
{
|
{
|
||||||
g_cacheDir = cacheDir;
|
|
||||||
g_cacheFile = g_cacheDir + "terrain.cache";
|
SceneNode* terr_createChildNode(float relX, float relY,
|
||||||
}
|
SceneNode *parent)
|
||||||
|
{}
|
||||||
|
|
||||||
|
void terr_destroyNode(SceneNode *node)
|
||||||
|
{}
|
||||||
|
|
||||||
|
void *terr_makeBounds(float minHeight, float maxHeight,
|
||||||
|
float width)
|
||||||
|
{}
|
||||||
|
|
||||||
|
float terr_getSqCamDist(void*)
|
||||||
|
{}
|
||||||
|
|
||||||
|
void *terr_makeMesh(int segment, SceneNode*)
|
||||||
|
{}
|
||||||
|
|
||||||
|
void terr_killMesh(void*)
|
||||||
|
{}
|
||||||
|
|
||||||
// Set up the rendering system
|
// Set up the rendering system
|
||||||
extern "C" void terr_setupRendering()
|
void terr_setupRendering()
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
// Add the terrain directory
|
// Add the terrain directory
|
||||||
|
@ -147,32 +90,4 @@ extern "C" void terr_setupRendering()
|
||||||
d_superman();
|
d_superman();
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
// Generate all cached data.
|
|
||||||
extern "C" void terr_genData()
|
|
||||||
{
|
|
||||||
Ogre::Root::getSingleton().renderOneFrame();
|
|
||||||
|
|
||||||
Generator mhm;
|
|
||||||
{
|
|
||||||
ESM esm;
|
|
||||||
|
|
||||||
const std::string fn("data/Morrowind.esm");
|
|
||||||
|
|
||||||
esm.addRecordType("LAND", "INTV");
|
|
||||||
esm.addRecordType("LTEX", "INTV");
|
|
||||||
|
|
||||||
esm.loadFile(fn);
|
|
||||||
RecordList* land = esm.getRecordsByType("LAND");
|
|
||||||
for ( RecordListItr itr = land->begin(); itr != land->end(); ++itr )
|
|
||||||
mhm.addLandData(*itr, fn);
|
|
||||||
|
|
||||||
RecordList* ltex = esm.getRecordsByType("LTEX");
|
|
||||||
for ( RecordListItr itr = ltex->begin(); itr != ltex->end(); ++itr )
|
|
||||||
mhm.addLandTextureData(*itr, fn);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mhm.generate(g_cacheFile);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
149
terrain/esmland.d
Normal file
149
terrain/esmland.d
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
module terrain.esmland;
|
||||||
|
|
||||||
|
import esm.loadltex;
|
||||||
|
import esm.loadcell;
|
||||||
|
import util.regions;
|
||||||
|
import esm.filereader;
|
||||||
|
|
||||||
|
const int LAND_NUM_VERTS = 65*65;
|
||||||
|
|
||||||
|
MWLand mwland;
|
||||||
|
|
||||||
|
// Interface to the ESM landscape data
|
||||||
|
struct MWLand
|
||||||
|
{
|
||||||
|
RegionManager reg;
|
||||||
|
|
||||||
|
// These structs/types represent the way data is actually stored in
|
||||||
|
// the ESM files.
|
||||||
|
|
||||||
|
// Heightmap
|
||||||
|
align(1)
|
||||||
|
struct VHGT
|
||||||
|
{
|
||||||
|
float heightOffset;
|
||||||
|
byte heightData[LAND_NUM_VERTS];
|
||||||
|
short unknown1;
|
||||||
|
char unknown2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normals
|
||||||
|
typedef byte[LAND_NUM_VERTS*3] VNML;
|
||||||
|
|
||||||
|
// Land textures. This is organized in 4x4 buffers of 4x4 squares
|
||||||
|
// each. This is how the original engine splits up the cell meshes,
|
||||||
|
// and it's probably a good idea for us to do the same.
|
||||||
|
typedef short[4][4][4][4] VTEX;
|
||||||
|
|
||||||
|
static assert(VHGT.sizeof == 4232);
|
||||||
|
static assert(VNML.sizeof == 12675);
|
||||||
|
static assert(VTEX.sizeof == 512);
|
||||||
|
|
||||||
|
// Landscape data for one cell
|
||||||
|
struct LandData
|
||||||
|
{
|
||||||
|
VHGT vhgt;
|
||||||
|
VNML normals;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Texture data for one cell
|
||||||
|
struct LTEXData
|
||||||
|
{
|
||||||
|
// TODO: Store the source file here too, so we can get the list
|
||||||
|
// from the right file. The source file is the same as the one we
|
||||||
|
// load the landscape from in loadCell().
|
||||||
|
VTEX vtex;
|
||||||
|
|
||||||
|
// Get the texture x2,y2 from the sub map x1,x2
|
||||||
|
char[] getTexture(int x1, int y1, int x2, int y2)
|
||||||
|
{
|
||||||
|
// Get the texture index relative to the current esm/esp file
|
||||||
|
short texID = vtex[y1][x1][y2][x2];
|
||||||
|
|
||||||
|
// Hack, will only work for Morrowind.esm. Fix this later.
|
||||||
|
auto tl = landTextures.files["Morrowind.esm"];
|
||||||
|
|
||||||
|
// Return the 'new' texture name. This name has automatically
|
||||||
|
// been converted to .dds if the .tga file was not found.
|
||||||
|
return tl[texID].getNewName();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a texture from the 16x16 grid in one cell
|
||||||
|
char[] getTexture(int x, int y)
|
||||||
|
{
|
||||||
|
return getTexture(x/4,y/4,x%4,y%4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the maximum absolute coordinate value in any direction
|
||||||
|
int getMaxCoord()
|
||||||
|
{ return cells.maxXY; }
|
||||||
|
|
||||||
|
// Does the given cell exist and does it have land data?
|
||||||
|
bool hasData(int x, int y)
|
||||||
|
{
|
||||||
|
// Does the cell exist?
|
||||||
|
if(!cells.hasExt(x,y))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// And does it have terrain data?
|
||||||
|
auto ex = cells.getExt(x,y);
|
||||||
|
return ex.hasLand();
|
||||||
|
}
|
||||||
|
|
||||||
|
LandData *getLandData(int x, int y)
|
||||||
|
{
|
||||||
|
loadCell(x, y);
|
||||||
|
return ¤tLand;
|
||||||
|
}
|
||||||
|
|
||||||
|
LTEXData *getLTEXData(int x, int y)
|
||||||
|
{
|
||||||
|
loadCell(x, y);
|
||||||
|
return ¤tLtex;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
int currentX = -1234;
|
||||||
|
int currentY = 4321;
|
||||||
|
|
||||||
|
LandData currentLand;
|
||||||
|
LTEXData currentLtex;
|
||||||
|
|
||||||
|
// Make sure the given cell is loaded
|
||||||
|
void loadCell(int x, int y)
|
||||||
|
{
|
||||||
|
// If the right cell is already loaded, don't do anything
|
||||||
|
if(x == currentX && y == currentY)
|
||||||
|
return;
|
||||||
|
|
||||||
|
assert(hasData(x,y));
|
||||||
|
|
||||||
|
currentX = x;
|
||||||
|
currentY = y;
|
||||||
|
|
||||||
|
// Get the file context for the terrain data. This can be used to
|
||||||
|
// skip to the right part of the ESM file.
|
||||||
|
auto cont = cells.getExt(x,y).land.context;
|
||||||
|
|
||||||
|
// We should use an existing region later, or at least delete this
|
||||||
|
// once we're done with the gen process.
|
||||||
|
if(reg is null)
|
||||||
|
reg = new RegionManager();
|
||||||
|
|
||||||
|
// Open the ESM at this cell
|
||||||
|
esFile.restoreContext(cont, reg);
|
||||||
|
|
||||||
|
// Store the data
|
||||||
|
esFile.readHNExact(currentLand.normals.ptr,
|
||||||
|
currentLand.normals.length, "VNML");
|
||||||
|
esFile.readHNExact(¤tLand.vhgt, VHGT.sizeof, "VHGT");
|
||||||
|
|
||||||
|
// These aren't used yet
|
||||||
|
if(esFile.isNextSub("WNAM")) esFile.skipHSubSize(81);
|
||||||
|
if(esFile.isNextSub("VCLR")) esFile.skipHSubSize(12675);
|
||||||
|
|
||||||
|
esFile.readHNExact(¤tLtex.vtex, VTEX.sizeof, "VTEX");
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,16 +27,15 @@ module terrain.generator;
|
||||||
/+
|
/+
|
||||||
import std.stdio;
|
import std.stdio;
|
||||||
import std.string;
|
import std.string;
|
||||||
|
|
||||||
import terrain.cachewriter;
|
import terrain.cachewriter;
|
||||||
|
import terrain.esmland;
|
||||||
import util.cachefile;
|
import util.cachefile;
|
||||||
|
|
||||||
const float TEX_SCALE = 1.0/16;
|
const float TEX_SCALE = 1.0/16;
|
||||||
|
|
||||||
char[] cacheDir = "cache/terrain/";
|
char[] cacheDir = "cache/terrain/";
|
||||||
|
|
||||||
// Interface to the ESM landscape data
|
|
||||||
MWLand mwland;
|
|
||||||
|
|
||||||
int mCount;
|
int mCount;
|
||||||
|
|
||||||
// Texture sizes for the various levels. For the most detailed level
|
// Texture sizes for the various levels. For the most detailed level
|
||||||
|
@ -328,11 +327,11 @@ void genLevel1Meshes(ref GenLevelResult res)
|
||||||
mi.worldWidth = vertSep*intervals;
|
mi.worldWidth = vertSep*intervals;
|
||||||
assert(mi.worldWidth == 8192);
|
assert(mi.worldWidth == 8192);
|
||||||
|
|
||||||
auto land = mwland.getData(cellX, cellY);
|
auto land = mwland.getLandData(cellX, cellY);
|
||||||
|
|
||||||
byte[] heightData = land.heights;
|
byte[] heightData = land.vhgt.heights;
|
||||||
byte[] normals = land.normals;
|
byte[] normals = land.normals;
|
||||||
mi.heightOffset = land.heightOffset;
|
mi.heightOffset = land.vhgt.heightOffset;
|
||||||
|
|
||||||
float max=-1000000.0;
|
float max=-1000000.0;
|
||||||
float min=1000000.0;
|
float min=1000000.0;
|
||||||
|
|
295
terrain/quad.d
Normal file
295
terrain/quad.d
Normal file
|
@ -0,0 +1,295 @@
|
||||||
|
module terrain.quad;
|
||||||
|
|
||||||
|
import terrain.archive;
|
||||||
|
import terrain.bindings;
|
||||||
|
|
||||||
|
const int CELL_WIDTH = 8192;
|
||||||
|
const float SPLIT_FACTOR = 0.5;
|
||||||
|
const float UNSPLIT_FACTOR = 2.0;
|
||||||
|
|
||||||
|
class Quad
|
||||||
|
{
|
||||||
|
this(int cellX=0, int cellY=0, Quad parent = null)
|
||||||
|
{
|
||||||
|
mCellX = cellX;
|
||||||
|
mCellY = cellY;
|
||||||
|
|
||||||
|
// Do we have a parent?
|
||||||
|
if(parent !is null)
|
||||||
|
{
|
||||||
|
mLevel = parent.mLevel-1;
|
||||||
|
|
||||||
|
if(mLevel == 1)
|
||||||
|
{
|
||||||
|
// Create the terrain and leave it there.
|
||||||
|
buildTerrain();
|
||||||
|
isStatic = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Coordinates relative to our parent
|
||||||
|
int relX = cellX - parent.mCellX;
|
||||||
|
int relY = cellY - parent.mCellY;
|
||||||
|
|
||||||
|
// The coordinates give the top left corner of the quad, or our
|
||||||
|
// relative coordinates within that should always be positive.
|
||||||
|
assert(relX >= 0);
|
||||||
|
assert(relY >= 0);
|
||||||
|
|
||||||
|
// Create a child scene node. The scene node position is given in
|
||||||
|
// world units, ie. CELL_WIDTH units per cell.
|
||||||
|
mNode = terr_createChildNode(relX*CELL_WIDTH,
|
||||||
|
relY*CELL_WIDTH,
|
||||||
|
parent.mNode);
|
||||||
|
/*
|
||||||
|
Ogre::Vector3 pos(relX * CELL_WIDTH,
|
||||||
|
relY * CELL_WIDTH,
|
||||||
|
0);
|
||||||
|
mNode = parent.mNode.createChildSceneNode(pos);
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Get the archive data for this quad.
|
||||||
|
mInfo = g_archive.getQuad(mCellX,mCellY,mLevel);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// No parent, this is the top-most quad. Get all the info from
|
||||||
|
// the archive.
|
||||||
|
mInfo = g_archive.rootQuad;
|
||||||
|
|
||||||
|
mLevel = mInfo.level;
|
||||||
|
cellX = mCellX = mInfo.cellX;
|
||||||
|
cellY = mCellY = mInfo.cellY;
|
||||||
|
|
||||||
|
mNode = terr_createChildNode(cellX*CELL_WIDTH,
|
||||||
|
cellY*CELL_WIDTH,
|
||||||
|
null);
|
||||||
|
/*
|
||||||
|
mNode = g_rootTerrainNode.
|
||||||
|
createChildSceneNode(pos);
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Split up
|
||||||
|
split();
|
||||||
|
|
||||||
|
// The root can never be unsplit
|
||||||
|
isStatic = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(mLevel >= 1);
|
||||||
|
assert(mNode !is null);
|
||||||
|
|
||||||
|
// TODO: How do we store the C++ bounding box?
|
||||||
|
mBounds = terr_makeBounds(mInfo.minHeight,
|
||||||
|
mInfo.maxHeight,
|
||||||
|
mInfo.worldWidth);
|
||||||
|
/*
|
||||||
|
// Set up the bounding box. Use MW coordinates all the way
|
||||||
|
mBounds.setExtents(0,0,mInfo.minHeight,
|
||||||
|
mInfo.worldWidth,mInfo.worldWidth,
|
||||||
|
mInfo.maxHeight);
|
||||||
|
|
||||||
|
// Transform the box to world coordinates, so it can be compared
|
||||||
|
// with the camera later.
|
||||||
|
mBounds.transformAffine(mNode._getFullTransform());
|
||||||
|
*/
|
||||||
|
float radius = mInfo.boundingRadius;
|
||||||
|
|
||||||
|
mSplitDistance = radius * SPLIT_FACTOR;
|
||||||
|
mUnsplitDistance = radius * UNSPLIT_FACTOR;
|
||||||
|
|
||||||
|
// Square the distances
|
||||||
|
mSplitDistance *= mSplitDistance;
|
||||||
|
mUnsplitDistance *= mUnsplitDistance;
|
||||||
|
|
||||||
|
// Update the terrain. This will create the mesh or children if
|
||||||
|
// necessary.
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
~this()
|
||||||
|
{
|
||||||
|
// TODO: We might rewrite the code so that the quads are never
|
||||||
|
// actually destroyed, just 'inactivated' by hiding their scene
|
||||||
|
// node. We only call update on our children if we don't have a
|
||||||
|
// mesh ourselves.
|
||||||
|
if(hasMesh)
|
||||||
|
destroyTerrain();
|
||||||
|
else if(hasChildren)
|
||||||
|
for (size_t i = 0; i < 4; i++)
|
||||||
|
delete mChildren[i];
|
||||||
|
|
||||||
|
terr_destroyNode(mNode);
|
||||||
|
/*
|
||||||
|
mNode.removeAndDestroyAllChildren();
|
||||||
|
mSceneMgr.destroySceneNode(mNode);
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the landscape for this quad, and create children.
|
||||||
|
void split()
|
||||||
|
{
|
||||||
|
// Never split a static quad or a quad that already has children.
|
||||||
|
assert(!isStatic);
|
||||||
|
assert(!hasChildren);
|
||||||
|
assert(mLevel > 1);
|
||||||
|
|
||||||
|
if(hasMesh)
|
||||||
|
destroyTerrain();
|
||||||
|
|
||||||
|
// Find the cell width of our children
|
||||||
|
int cWidth = 1 << (mLevel-2);
|
||||||
|
|
||||||
|
// Create children
|
||||||
|
for ( size_t i = 0; i < 4; ++i )
|
||||||
|
{
|
||||||
|
if(!mInfo.hasChild[i])
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// The cell coordinates for this child quad
|
||||||
|
int x = (i%2)*cWidth + mCellX;
|
||||||
|
int y = (i/2)*cWidth + mCellY;
|
||||||
|
|
||||||
|
mChildren[i] = new Quad(x,y,this);
|
||||||
|
}
|
||||||
|
hasChildren = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Removes children and rebuilds terrain
|
||||||
|
void unsplit()
|
||||||
|
{
|
||||||
|
// Never unsplit the root quad
|
||||||
|
assert(mLevel < g_archive.rootQuad.level);
|
||||||
|
// Never unsplit a static or quad that isn't split.
|
||||||
|
assert(!isStatic);
|
||||||
|
assert(hasChildren);
|
||||||
|
assert(!hasMesh);
|
||||||
|
|
||||||
|
for( size_t i = 0; i < 4; i++ )
|
||||||
|
{
|
||||||
|
delete mChildren[i];
|
||||||
|
mChildren[i] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTerrain();
|
||||||
|
|
||||||
|
hasChildren = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determines whether to split or unsplit the quad, and immediately
|
||||||
|
// does it.
|
||||||
|
void update()
|
||||||
|
{
|
||||||
|
// Static quads don't change
|
||||||
|
if(!isStatic)
|
||||||
|
{
|
||||||
|
assert(mUnsplitDistance > mSplitDistance);
|
||||||
|
|
||||||
|
// Get (squared) camera distance. TODO: shouldn't this just
|
||||||
|
// be a simple vector difference from the mesh center?
|
||||||
|
float camDist = terr_getSqCamDist(mBounds);
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
Ogre::Vector3 cpos = mCamera.getDerivedPosition();
|
||||||
|
Ogre::Vector3 diff(0, 0, 0);
|
||||||
|
diff.makeFloor(cpos - mBounds.getMinimum() );
|
||||||
|
diff.makeCeil(cpos - mBounds.getMaximum() );
|
||||||
|
camDist = diff.squaredLength();
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// No children?
|
||||||
|
if(!hasChildren)
|
||||||
|
{
|
||||||
|
// If we're close, split now.
|
||||||
|
if(camDist < mSplitDistance)
|
||||||
|
split();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// We're not close, and don't have any children. Should we
|
||||||
|
// built terrain?
|
||||||
|
if(!hasMesh)
|
||||||
|
buildTerrain();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we get here, we either had children when we entered,
|
||||||
|
// or we just performed a split.
|
||||||
|
assert(!hasMesh);
|
||||||
|
assert(hasChildren);
|
||||||
|
|
||||||
|
// If the camera is too far away, kill the children.
|
||||||
|
if(camDist > mUnsplitDistance)
|
||||||
|
{
|
||||||
|
unsplit();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(!hasChildren)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// We have children and we're happy about it. Update them too.
|
||||||
|
for(int i; i < 4; ++i)
|
||||||
|
{
|
||||||
|
Quad q = mChildren[i];
|
||||||
|
if(q !is null) q.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the terrain for this quad
|
||||||
|
void buildTerrain()
|
||||||
|
{
|
||||||
|
assert(!hasMesh);
|
||||||
|
assert(!isStatic);
|
||||||
|
|
||||||
|
// Map the terrain data into memory.
|
||||||
|
g_archive.mapQuad(mInfo);
|
||||||
|
|
||||||
|
// Create one mesh for each segment in the quad. TerrainMesh takes
|
||||||
|
// care of the loading.
|
||||||
|
meshList.length = mInfo.meshNum;
|
||||||
|
foreach(i, ref m; meshList)
|
||||||
|
m = terr_makeMesh(i, mNode);
|
||||||
|
|
||||||
|
hasMesh = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void destroyTerrain()
|
||||||
|
{
|
||||||
|
assert(hasMesh);
|
||||||
|
|
||||||
|
foreach(m; meshList)
|
||||||
|
terr_killMesh(m);
|
||||||
|
|
||||||
|
meshList[] = null;
|
||||||
|
hasMesh = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
// List of meshes, if any. The meshes are C++ objects.
|
||||||
|
MeshObj meshList[];
|
||||||
|
|
||||||
|
// Scene node. All child quads are added to this.
|
||||||
|
SceneNode mNode;
|
||||||
|
|
||||||
|
// Bounding box, transformed to world coordinates. Used to calculate
|
||||||
|
// camera distance.
|
||||||
|
//Ogre::AxisAlignedBox mBounds;
|
||||||
|
Bounds mBounds;
|
||||||
|
|
||||||
|
float mSplitDistance,mUnsplitDistance;
|
||||||
|
|
||||||
|
Quad mChildren[4];
|
||||||
|
|
||||||
|
// Contains the 'level' of this node. Level 1 is the closest and
|
||||||
|
// most detailed level
|
||||||
|
int mLevel;
|
||||||
|
int mCellX, mCellY;
|
||||||
|
|
||||||
|
QuadInfo *mInfo;
|
||||||
|
|
||||||
|
bool hasMesh;
|
||||||
|
bool hasChildren;
|
||||||
|
bool isStatic; // Static quads are never split or unsplit
|
||||||
|
}
|
|
@ -24,6 +24,7 @@
|
||||||
module terrain.terrain;
|
module terrain.terrain;
|
||||||
|
|
||||||
import terrain.generator;
|
import terrain.generator;
|
||||||
|
import terrain.bindings;
|
||||||
|
|
||||||
void initTerrain(bool doGen)
|
void initTerrain(bool doGen)
|
||||||
{
|
{
|
||||||
|
@ -34,7 +35,3 @@ void initTerrain(bool doGen)
|
||||||
|
|
||||||
//terr_setupRendering();
|
//terr_setupRendering();
|
||||||
}
|
}
|
||||||
|
|
||||||
extern(C):
|
|
||||||
void terr_genData();
|
|
||||||
void terr_setupRendering();
|
|
||||||
|
|
Loading…
Reference in a new issue