From 393d284a8aa402eb0d29e73ef89d1bb957b8a981 Mon Sep 17 00:00:00 2001 From: nkorslund Date: Sat, 4 Jul 2009 16:51:20 +0000 Subject: [PATCH] - very early version of the terrain engine, WORKING! git-svn-id: https://openmw.svn.sourceforge.net/svnroot/openmw/trunk@125 ea6a568a-9f4f-0410-981a-c910a81bb256 --- terrain/archive.d | 32 +++--- terrain/cpp_baseland.cpp | 12 +- terrain/cpp_mesh.cpp | 26 +++-- terrain/cpp_terrain.cpp | 2 +- terrain/generator.d | 243 +++++++++++++++++++++++---------------- terrain/myfile.d | 69 +++++++++++ terrain/terrain.d | 40 +++++-- 7 files changed, 291 insertions(+), 133 deletions(-) create mode 100644 terrain/myfile.d diff --git a/terrain/archive.d b/terrain/archive.d index 3911080f7..9ccf1b4c7 100644 --- a/terrain/archive.d +++ b/terrain/archive.d @@ -144,14 +144,6 @@ struct MeshInfo float *vbuf = vdest.ptr; assert(vdest.length == vertRows*vertCols*8); - // 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<setPosition(p.x, -p.z, -32 - h); + mNode->setPosition(p.x, -p.z, -32 -h); } private: @@ -55,16 +55,18 @@ private: vd = mMeshDistance; - mObject->position(-vd,vd,-2048); + const int HEIGHT = -2048 - 10; + + mObject->position(-vd,vd,HEIGHT); mObject->textureCoord(0, 1); - mObject->position(-vd,-vd,-2048); + mObject->position(-vd,-vd,HEIGHT); mObject->textureCoord(0, 0); - mObject->position(vd,-vd,-2048); + mObject->position(vd,-vd,HEIGHT); mObject->textureCoord(1, 0); - mObject->position(vd,vd,-2048); + mObject->position(vd,vd,HEIGHT); mObject->textureCoord(1, 1); mObject->quad(0,1,2,3); diff --git a/terrain/cpp_mesh.cpp b/terrain/cpp_mesh.cpp index e39dc1399..f5f6a26f5 100644 --- a/terrain/cpp_mesh.cpp +++ b/terrain/cpp_mesh.cpp @@ -84,14 +84,26 @@ public: // Finally, create the material const std::string texName = info.getTexName(); - // Create or retrieve the material - mMaterial = MaterialManager::getSingleton().createOrRetrieve - (texName, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME).first; + // Set up the scene node. + mNode = parent->createChildSceneNode(); + mNode->attachObject(this); + + // Finally, create or retrieve the material + if(MaterialManager::getSingleton().resourceExists(texName)) + { + mMaterial = MaterialManager::getSingleton().getByName + (texName); + return; + } + + // No existing material. Create a new one. + mMaterial = MaterialManager::getSingleton().create + (texName, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); Pass* pass = mMaterial->getTechnique(0)->getPass(0); pass->setLightingEnabled(false); - if(level != 1) + if(level > 1) { // This material just has a normal texture pass->createTextureUnitState(texName) @@ -100,6 +112,8 @@ public: } else { + assert(level == 1); + // Get the background texture. TODO: We should get this from // somewhere, no file names should be hard coded. The texture // might exist as a .tga in earlier versions of the game, and @@ -173,10 +187,6 @@ public: tus->setTextureScale(scale, scale); } } - - // Finally, set up the scene node. - mNode = parent->createChildSceneNode(); - mNode->attachObject(this); } ~TerrainMesh() diff --git a/terrain/cpp_terrain.cpp b/terrain/cpp_terrain.cpp index 65b2243a2..295301b06 100644 --- a/terrain/cpp_terrain.cpp +++ b/terrain/cpp_terrain.cpp @@ -275,7 +275,7 @@ extern "C" addResourceLocation("cache/terrain/", "FileSystem", "General"); // Enter superman mode - mCamera->setFarClipDistance(32*CELL_WIDTH); + mCamera->setFarClipDistance(40*CELL_WIDTH); //ogre_setFog(0.7, 0.7, 0.7, 200, 32*CELL_WIDTH); d_terr_superman(); diff --git a/terrain/generator.d b/terrain/generator.d index a795b4363..fac7b1fd5 100644 --- a/terrain/generator.d +++ b/terrain/generator.d @@ -26,7 +26,7 @@ module terrain.generator; import std.stdio; import std.string; - +import std.math2; import std.c.string; import terrain.cachewriter; @@ -80,7 +80,7 @@ void generate(char[] filename) texSizes[5] = 512; texSizes[4] = 256; texSizes[3] = 256; - texSizes[2] = 256; + texSizes[2] = 512; texSizes[1] = 64; // Set some general parameters for the runtime @@ -144,6 +144,20 @@ struct GenLevelResult quad.meshes[0].alphas[i].buffer = data[i*s..(i+1)*s]; } + // Get the height offset + float getHeight() + { + if(hasMesh) + { + assert(quad.meshes.length == 1); + return quad.meshes[0].info.heightOffset; + } + else + // The default mesh starts at 2048 = 256*8 units below water + // level. + return -256; + } + bool isEmpty() { return (data.length == 0) && !hasMesh; @@ -265,10 +279,8 @@ struct GenLevelResult mi.vertRows = size; mi.vertCols = size; - // 1 height + 3 normal components = 4 bytes per vertex. The reader - // algorithm (fillVertexBuffer) needs 1 extra byte so add that - // too. - mh.vertexBuffer = new byte[4*size*size+1]; + // 2 height bytes + 3 normal components = 5 bytes per vertex. + mh.vertexBuffer = new byte[5*size*size]; hasMesh = true; } @@ -304,20 +316,7 @@ void genIndexData() { scope auto _trc = new MTrace("genIndexData"); - // Generate mesh data for each level. - - /* - TODO: The mesh data is very easy to generate, and we haven't - really tested whether it's worth it to pregenerate it rather than - to just calculate it at runtime. Unlike the index buffer below - (which is just a memcpy at runtime, and definitely worth - pregenerating), we have to loop through all the vertices at - runtime anyway in order to splice this with the height data. It's - possible that the additional memory use, pluss the slowdown - (CPU-cache-wise) of reading from two buffers instead of one, makes - it worthwhile to generate this data at runtime instead. However I - guess the differences will be very small either way. - */ + // FIXME: Do this at runtime. for(int lev=1; lev<=6; lev++) { // Make a new buffer to store the data @@ -353,7 +352,7 @@ void genIndexData() cache.addVertexBuffer(lev,vertList); } - // Next up, triangle indices + // Pregenerate triangle indices int size = 64*64*6; auto indList = new ushort[size]; int index = 0; @@ -531,8 +530,9 @@ void genLevel1Meshes(ref GenLevelResult res) // The vertex data from the ESM byte data = heightData[offs]; - // Write the height byte - verts[index++] = data; + // Write the height value as a short (2 bytes) + *(cast(short*)&verts[index]) = data; + index+=2; // Calculate the height here, even though we don't store // it. We use it to find the min and max values. @@ -557,7 +557,7 @@ void genLevel1Meshes(ref GenLevelResult res) } // Make sure we wrote exactly the right amount of data - assert(index == verts.length-1); + assert(index == verts.length); // Store the min/max values mi.minHeight = min * 8; @@ -864,111 +864,160 @@ void mergeMesh(GenLevelResult[] sub, ref GenLevelResult res) // level above the cell level. int shift = res.quad.info.level - 1; assert(shift >= 1); - - // Constants - int intervals = 64; - int vertNum = intervals+1; - int vertSep = 128 << shift; + assert(sub.length == 4); // Allocate the result buffer - res.allocMesh(vertNum); + res.allocMesh(65); MeshHolder *mh = &res.quad.meshes[0]; MeshInfo *mi = &mh.info; // Basic info - mi.worldWidth = vertSep*intervals; - assert(mi.worldWidth == 8192<= short.min && val <= short.max); + *(cast(short*)&verts[dest]) = val; + dest += 2; + } + + if(s.hasMesh) + { + auto m = &s.quad.meshes[0]; + auto i = &m.info; + + minh = min(minh, i.minHeight); + maxh = max(maxh, i.maxHeight); + + byte[] source = m.vertexBuffer; + int src = 0; + + int getValue() + { + int s = *(cast(short*)&source[src]); + src += 2; + return s; + } - // Loop through both sub meshes, left and right - for(int subX=0; subX<2; subX++) + // Loop through all the vertices in the mesh + for(int y=0;y<33;y++) { - GenLevelResult *s = &sub[subX+2*subY]; + // Skip the first row in the mesh if there was a mesh + // above us. We assume that the previously written row + // already has the correct information. + if(y==0 && SY != 0) + { + src += 65*VSIZE; + dest += 65*VSIZE; + continue; + } + + // Handle the first vertex of the row outside the + // loop. + int height = getValue(); - // Check if we have any data - if(!s.isEmpty() && 0) + // If this isn't the very first row, sum up two row + // heights and skip the first row. + if(y!=0) { - MeshHolder *smh = &s.quad.meshes[0]; - byte* inPtr = smh.vertexBuffer.ptr; + // Skip the rest of the row. + src += 64*VSIZE + 3; - // Loop through each vertex in this mesh. We skip two - // at a time. - for(int v=0; v<64; v+=2) - { - // Handle the v=0 case + // Add the second height + height += getValue(); + } - // Count the height from the two next vertices - int data = *inPtr++; - inPtr++;inPtr++;inPtr++; // Skip the first normal - data += *inPtr++; + putValue(height); - // Divide by two, since the result needs to fit in - // one byte. We compensate for this when we regen - // the mesh at runtime. - data >>= 1; - assert(data < 128 && data >= -128); + // Copy the normal + verts[dest++] = source[src++]; + verts[dest++] = source[src++]; + verts[dest++] = source[src++]; - *vertPtr++ = data; + // Loop through the remaining 64 vertices in this row, + // processing two at a time. + for(int x=0;x<32;x++) + { + height = getValue(); - // Copy over the normal - *vertPtr++ = *inPtr++; - *vertPtr++ = *inPtr++; - *vertPtr++ = *inPtr++; - } - // Store the last one here. It _should_ be the - // same as the first in the next section, if - // present. + // Sum up the next two heights + src += 3; // Skip normal + height += getValue(); + + // Set the height + putValue(height); + + // Copy the normal + verts[dest++] = source[src++]; + verts[dest++] = source[src++]; + verts[dest++] = source[src++]; } - else + // Skip to the next row + dest += 32*VSIZE; + } + assert(src == source.length); + } + else + { + minh = min(minh, -2048); + maxh = max(maxh, -2048); + + // Set all the vertices to zero. + for(int y=0;y<33;y++) + { + if(y==0 && SY != 0) { - // No data in this mesh. Just write zeros. - for(int v=0; v<32; v++) - { - // Height - *vertPtr++ = 0; + dest += 65*VSIZE; + continue; + } - // Normal, pointing straight upwards - *vertPtr++ = 0; - *vertPtr++ = 0; - *vertPtr++ = 0x7f; + for(int x=0;x<33;x++) + { + if(x==0 && SX != 0) + { + dest += VSIZE; + continue; } + + // Zero height and vertical normal + verts[dest++] = 0; + verts[dest++] = 0; + verts[dest++] = 0; + verts[dest++] = 0; + verts[dest++] = 0x7f; } + // Skip to the next row + dest += 32*VSIZE; } } } - assert(vertPtr == mh.vertexBuffer.ptr + mh.vertexBuffer.length - 1); - // Set max and min values here + mi.minHeight = minh; + mi.maxHeight = maxh; + assert(minh <= maxh); } // ------- OLD CODE - use these snippets later ------- diff --git a/terrain/myfile.d b/terrain/myfile.d new file mode 100644 index 000000000..575151ed9 --- /dev/null +++ b/terrain/myfile.d @@ -0,0 +1,69 @@ +/* + OpenMW - The completely unofficial reimplementation of Morrowind + Copyright (C) 2009 Nicolay Korslund + WWW: http://openmw.sourceforge.net/ + + This file (archive.d) 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/ . + +*/ + +import std.stream; +import std.stdio; + +// Add a couple of helper functions to the file stream +class MyFile : File +{ + this(string filename, FileMode mode = FileMode.In) + { + super(filename, mode); + } + + void fill(T)(ref T t) + { + readExact(&t, T.sizeof); + } + + void dump(T)(ref T t) + { + writeExact(&t, T.sizeof); + } + + void fillArray(T)(T[] t) + { + readExact(t.ptr, t.length*T.sizeof); + } + + void dumpArray(T)(T[] t) + { + writeExact(t.ptr, t.length*T.sizeof); + } + + void readArray(T)(ref T[] arr) + { + int size; + read(size); + assert(size < 1024*1024 && size > 0); + arr = new T[size]; + fillArray!(T)(arr); + } + + void writeArray(T)(T[] t) + { + int size = t.length; + write(size); + dumpArray!(T)(t); + } +} diff --git a/terrain/terrain.d b/terrain/terrain.d index f080ae067..e9a168470 100644 --- a/terrain/terrain.d +++ b/terrain/terrain.d @@ -31,7 +31,8 @@ import std.file, std.stdio; char[] cacheDir = "cache/terrain/"; -// Enable this to render one single terrain mesh +// Enable this to render single terrain meshes instead of the entire +// data set //debug=singleMesh; void initTerrain(bool doGen) @@ -55,12 +56,37 @@ void initTerrain(bool doGen) debug(singleMesh) { - // Used for debugging single terrain meshes - auto node = terr_createChildNode(20000,-60000,null); - auto info = g_archive.getQuad(0,0,1); - g_archive.mapQuad(info); - auto mi = g_archive.getMeshInfo(0); - auto m = terr_makeMesh(node, mi, info.level, TEX_SCALE); + int X = 22; + int Y = 0; + bool next = false; + + void doQuad(int x, int y, int lev) + { + if(!g_archive.hasQuad(x,y,lev)) + return; + + int diffx = x-X; + int diffy = y-Y; + + diffx *= 8192; + diffy *= 8192; + + if(diffx == 0 && lev == 2) + diffx = 8192 * 2; + + auto node = terr_createChildNode(20000+diffx,-60000+diffy,null); + auto info = g_archive.getQuad(x,y,lev); + g_archive.mapQuad(info); + auto mi = g_archive.getMeshInfo(0); + terr_makeMesh(node, mi, info.level, TEX_SCALE); + } + + doQuad(X,Y,1); + doQuad(X+1,Y,1); + doQuad(X,Y+1,1); + doQuad(X+1,Y+1,1); + + doQuad(X + (next?2:0),Y,2); } else {