mirror of
				https://github.com/OpenMW/openmw.git
				synced 2025-10-31 20:26:43 +00:00 
			
		
		
		
	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 core.filefinder; | ||||
| //import core.config;
 | ||||
| 
 | ||||
| // 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
 | ||||
|  | @ -255,6 +254,9 @@ struct ResourceManager | |||
| 
 | ||||
|   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) | ||||
|   { | ||||
|     if(dummy) return null; | ||||
|  | @ -318,15 +320,19 @@ struct ResourceManager | |||
|     char[] tmp; | ||||
|     if(id.length < 70) | ||||
|       { | ||||
|         // Avoid memory allocations if possible.
 | ||||
|         tmp = texBuffer[0..9+id.length]; | ||||
|         tmp[9..$] = id; | ||||
|       } | ||||
|     else  | ||||
|       tmp = "textures\\" ~ id; | ||||
|     else | ||||
|       { | ||||
|         tmp = "textures\\" ~ id; | ||||
|         writefln("WARNING: Did an allocation on %s", tmp); | ||||
|       } | ||||
| 
 | ||||
|     searchWithDDS(tmp); | ||||
| 
 | ||||
|     // Not found? If so, try without the 'texture\'
 | ||||
|     // Not found? Try without the 'texture\'
 | ||||
|     if(ti.bsaIndex == -1) | ||||
|       { | ||||
|         tmp = tmp[9..$]; | ||||
|  | @ -469,18 +475,9 @@ struct TextureResource | |||
|   { | ||||
|     return bsaIndex == -1; | ||||
|   } | ||||
| 
 | ||||
|   /*KILLME | ||||
|   // Returns true if resource is loaded
 | ||||
|   bool isLoaded() | ||||
|   { | ||||
|     return ml != null; | ||||
|   } | ||||
|   */ | ||||
| } | ||||
| 
 | ||||
| // OLD STUFF
 | ||||
| 
 | ||||
| /+ | ||||
| 
 | ||||
|   void initResourceManager() | ||||
|  |  | |||
|  | @ -26,6 +26,11 @@ module esm.loadcell; | |||
| import esm.imports; | ||||
| 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 | ||||
|  * data about objects, creatures, statics and landscape (for exterior | ||||
|  * cells). This data can be huge, and has to be loaded when we need | ||||
|  | @ -88,9 +93,9 @@ struct ExteriorCell | |||
| 
 | ||||
|   Region* region; | ||||
| 
 | ||||
|   // We currently don't load all cell data (these can be huge!),
 | ||||
|   // anyway it makes sense to only load these when accessed. Use this
 | ||||
|   // to reopen the file later.
 | ||||
|   // We currently don't load all cell data (these can be huge!), and
 | ||||
|   // it makes sense to only load these when accessed. Use this to
 | ||||
|   // reopen the file later.
 | ||||
|   TES3FileContext context; | ||||
| 
 | ||||
|   // Landscape and path grid data
 | ||||
|  | @ -99,6 +104,12 @@ struct ExteriorCell | |||
| 
 | ||||
|   PathGrid paths; | ||||
| 
 | ||||
|   // Return true if we have land data
 | ||||
|   bool hasLand() | ||||
|   { | ||||
|     return land.state == LoadState.Loaded && land.hasData; | ||||
|   } | ||||
| 
 | ||||
|   void load() | ||||
|     {with(esFile){ | ||||
|       this.region = getHNOPtr!(Region)("RGNN", regions); | ||||
|  | @ -118,6 +129,10 @@ struct ExteriorCell | |||
|       // Land can also be here instead. In fact, it can be both
 | ||||
|       // places. I have to figure out what it means.
 | ||||
|       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!(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() | ||||
|     { | ||||
|       in_cells = in_cells.init; | ||||
|  | @ -176,6 +195,12 @@ class CellList : ListKeeper | |||
|       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) | ||||
|     { assert(0); } | ||||
| 
 | ||||
|  | @ -203,7 +228,6 @@ class CellList : ListKeeper | |||
|     } | ||||
| 
 | ||||
|   uint length() { return numInt() + numExt(); } | ||||
| 
 | ||||
|   uint numInt() { return in_cells.length; } | ||||
|   uint numExt() { return ex_cells.length; } | ||||
| 
 | ||||
|  | @ -223,7 +247,8 @@ class CellList : ListKeeper | |||
|     {with(esFile){ | ||||
|       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(); | ||||
| 
 | ||||
|       readHNExact(&data, data.sizeof, "DATA"); | ||||
|  | @ -244,7 +269,7 @@ class CellList : ListKeeper | |||
| 	    // Overloading an existing cell
 | ||||
| 	    { | ||||
| 	      if(p.state != LoadState.Previous) | ||||
| 		fail("Duplicate internal cell " ~ id); | ||||
| 		fail("Duplicate interior cell " ~ id); | ||||
| 
 | ||||
| 	      assert(id == p.id); | ||||
| 	      p.load(); | ||||
|  | @ -266,6 +291,9 @@ class CellList : ListKeeper | |||
| 	      p.gridY = data.gridY; | ||||
| 	      p.load(); | ||||
| 	      p.state = LoadState.Loaded; | ||||
| 
 | ||||
|               int mx = max(abs(p.gridX), abs(p.gridY)); | ||||
|               maxXY = max(maxXY, mx); | ||||
| 	    } | ||||
| 	  else | ||||
| 	    { | ||||
|  | @ -282,34 +310,46 @@ class CellList : ListKeeper | |||
| } | ||||
| 
 | ||||
| /* | ||||
|  * Landscape data. The landscape is stored as a hight map, and that is | ||||
|  * pretty much all I know at the moment. | ||||
|  * Landscape data. | ||||
|  */ | ||||
| 
 | ||||
| struct Land | ||||
| { | ||||
|   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; | ||||
| 
 | ||||
|   bool hasData = false; | ||||
| 
 | ||||
|   void load() | ||||
|   {with(esFile){ | ||||
|     getHNUlong("INTV"); // Grid location. See next line.
 | ||||
|     //writefln("x=%d, y=%d", *(cast(int*)&grid), *(cast(int*)&grid+1));
 | ||||
|     // Get the grid location
 | ||||
|     ulong grid = getHNUlong("INTV"); | ||||
|     X = grid & uint.max; | ||||
|     Y = (grid >> 32); | ||||
| 
 | ||||
|     flags = getHNInt("DATA"); | ||||
| 
 | ||||
|     // Save file position
 | ||||
|     getContext(context); | ||||
| 
 | ||||
|     int cnt; | ||||
| 
 | ||||
|     // Skip these here. Load the actual data when the cell is loaded.
 | ||||
|     if(isNextSub("VNML")) skipHSubSize(12675); | ||||
|     if(isNextSub("VHGT")) skipHSubSize(4232); | ||||
|     if(isNextSub("VNML")) {skipHSubSize(12675);cnt++;} | ||||
|     if(isNextSub("VHGT")) {skipHSubSize(4232);cnt++;} | ||||
|     if(isNextSub("WNAM")) skipHSubSize(81); | ||||
|     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) | ||||
|       writefln("Duplicate landscape data in " ~ getFilename()); | ||||
|  |  | |||
|  | @ -50,7 +50,7 @@ class LandTextureList : ListKeeper | |||
|   // Contains the list of land textures for each file, indexed by
 | ||||
|   // file. TODO: Use some handle system here too instead of raw
 | ||||
|   // filename?
 | ||||
|   HashTable!(char[], TextureList, ESMRegionAlloc) files; | ||||
|   HashTable!(char[], TextureList, ESMRegionAlloc, CITextHash) files; | ||||
| 
 | ||||
|   // The texture list for the current file
 | ||||
|   TextureList current; | ||||
|  | @ -62,11 +62,7 @@ class LandTextureList : ListKeeper | |||
| 
 | ||||
|       // The first file (Morrowind.esm) typically needs a little more
 | ||||
|       // than most others
 | ||||
| 
 | ||||
|       current = esmRegion.getBuffer!(TextureIndex)(0,120); | ||||
| 
 | ||||
|       // Overkill, just leave it as it is
 | ||||
|       //files.rehash(resources.esm.length + resources.esp.length);
 | ||||
|     } | ||||
| 
 | ||||
|   void load() | ||||
|  |  | |||
|  | @ -192,21 +192,6 @@ class CellData | |||
| 
 | ||||
|       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; | ||||
| 
 | ||||
|       // 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_mesh.cpp" | ||||
| #include "cpp_quad.cpp" | ||||
| 
 | ||||
| class TerrainFrameListener : public FrameListener | ||||
| { | ||||
|  | @ -112,67 +38,56 @@ protected: | |||
| extern "C" void d_superman(); | ||||
| */ | ||||
| 
 | ||||
| extern "C" void terr_setCacheDir(char *cacheDir) | ||||
| extern "C" | ||||
| { | ||||
|   g_cacheDir = cacheDir; | ||||
|   g_cacheFile = g_cacheDir + "terrain.cache"; | ||||
| } | ||||
| 
 | ||||
| // Set up the rendering system
 | ||||
| extern "C" void terr_setupRendering() | ||||
| { | ||||
|   /*
 | ||||
|   // Add the terrain directory
 | ||||
|   ResourceGroupManager::getSingleton(). | ||||
|     addResourceLocation(g_cacheDir, "FileSystem", "General"); | ||||
|   SceneNode* terr_createChildNode(float relX, float relY, | ||||
|                                   SceneNode *parent) | ||||
|   {} | ||||
| 
 | ||||
|   // Create a root scene node first. The 'root' node is rotated to
 | ||||
|   // match the MW coordinate system
 | ||||
|   g_rootTerrainNode = root->createChildSceneNode("TERRAIN_ROOT"); | ||||
|   void terr_destroyNode(SceneNode *node) | ||||
|   {} | ||||
| 
 | ||||
|   // Open the archive file
 | ||||
|   g_archive.openFile(g_cacheFile); | ||||
|   void *terr_makeBounds(float minHeight, float maxHeight, | ||||
|                         float width) | ||||
|   {} | ||||
| 
 | ||||
|   // Create the root quad.
 | ||||
|   g_rootQuad = new Quad(); | ||||
|   float terr_getSqCamDist(void*) | ||||
|   {} | ||||
| 
 | ||||
|   g_baseLand = new BaseLand(g_rootTerrainNode); | ||||
|   void *terr_makeMesh(int segment, SceneNode*) | ||||
|   {} | ||||
| 
 | ||||
|   // Add the frame listener
 | ||||
|   mRoot->addFrameListener(new TerrainFrameListener); | ||||
|   void terr_killMesh(void*) | ||||
|   {} | ||||
| 
 | ||||
|   // Enter superman mode
 | ||||
|   mCamera->setFarClipDistance(32*CELL_WIDTH); | ||||
|   //ogre_setFog(0.7, 0.7, 0.7, 200, 32*CELL_WIDTH);
 | ||||
|   d_superman(); | ||||
|   */ | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
| // Generate all cached data.
 | ||||
| extern "C" void terr_genData() | ||||
| {  | ||||
|   Ogre::Root::getSingleton().renderOneFrame(); | ||||
| 
 | ||||
|   Generator mhm; | ||||
|   // Set up the rendering system
 | ||||
|   void terr_setupRendering() | ||||
|   { | ||||
|     ESM esm; | ||||
|     /*
 | ||||
|     // Add the terrain directory
 | ||||
|     ResourceGroupManager::getSingleton(). | ||||
|       addResourceLocation(g_cacheDir, "FileSystem", "General"); | ||||
| 
 | ||||
|     const std::string fn("data/Morrowind.esm"); | ||||
|     // Create a root scene node first. The 'root' node is rotated to
 | ||||
|     // match the MW coordinate system
 | ||||
|     g_rootTerrainNode = root->createChildSceneNode("TERRAIN_ROOT"); | ||||
| 
 | ||||
|     esm.addRecordType("LAND", "INTV"); | ||||
|     esm.addRecordType("LTEX", "INTV"); | ||||
|     // Open the archive file
 | ||||
|     g_archive.openFile(g_cacheFile); | ||||
| 
 | ||||
|     esm.loadFile(fn); | ||||
|     RecordList* land = esm.getRecordsByType("LAND"); | ||||
|     for ( RecordListItr itr = land->begin(); itr != land->end(); ++itr ) | ||||
|       mhm.addLandData(*itr, fn); | ||||
|     // Create the root quad.
 | ||||
|     g_rootQuad = new Quad(); | ||||
| 
 | ||||
|     RecordList* ltex = esm.getRecordsByType("LTEX"); | ||||
|     for ( RecordListItr itr = ltex->begin(); itr != ltex->end(); ++itr ) | ||||
|       mhm.addLandTextureData(*itr, fn); | ||||
|     g_baseLand = new BaseLand(g_rootTerrainNode); | ||||
| 
 | ||||
|     // Add the frame listener
 | ||||
|     mRoot->addFrameListener(new TerrainFrameListener); | ||||
| 
 | ||||
|     // Enter superman mode
 | ||||
|     mCamera->setFarClipDistance(32*CELL_WIDTH); | ||||
|     //ogre_setFog(0.7, 0.7, 0.7, 200, 32*CELL_WIDTH);
 | ||||
|     d_superman(); | ||||
|     */ | ||||
|   } | ||||
| 
 | ||||
|   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.string; | ||||
| 
 | ||||
| import terrain.cachewriter; | ||||
| import terrain.esmland; | ||||
| import util.cachefile; | ||||
| 
 | ||||
| const float TEX_SCALE = 1.0/16; | ||||
| 
 | ||||
| char[] cacheDir = "cache/terrain/"; | ||||
| 
 | ||||
| // Interface to the ESM landscape data
 | ||||
| MWLand mwland; | ||||
| 
 | ||||
| int mCount; | ||||
| 
 | ||||
| // Texture sizes for the various levels. For the most detailed level
 | ||||
|  | @ -328,11 +327,11 @@ void genLevel1Meshes(ref GenLevelResult res) | |||
|   mi.worldWidth = vertSep*intervals; | ||||
|   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; | ||||
|   mi.heightOffset = land.heightOffset; | ||||
|   mi.heightOffset = land.vhgt.heightOffset; | ||||
| 
 | ||||
|   float max=-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; | ||||
| 
 | ||||
| import terrain.generator; | ||||
| import terrain.bindings; | ||||
| 
 | ||||
| void initTerrain(bool doGen) | ||||
| { | ||||
|  | @ -34,7 +35,3 @@ void initTerrain(bool doGen) | |||
| 
 | ||||
|   //terr_setupRendering();
 | ||||
| } | ||||
| 
 | ||||
| extern(C): | ||||
| void terr_genData(); | ||||
| void terr_setupRendering(); | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue