- 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
This commit is contained in:
nkorslund 2009-07-04 16:51:20 +00:00
parent 367b9754b2
commit 393d284a8a
7 changed files with 297 additions and 139 deletions

View file

@ -144,14 +144,6 @@ struct MeshInfo
float *vbuf = vdest.ptr; float *vbuf = vdest.ptr;
assert(vdest.length == vertRows*vertCols*8); 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<<getLevel());
// Merge the two data sets together into the output buffer. // Merge the two data sets together into the output buffer.
float offset = heightOffset; float offset = heightOffset;
for(int y=0; y<vertRows; y++) for(int y=0; y<vertRows; y++)
@ -160,21 +152,21 @@ struct MeshInfo
// height value. All the values in a row gives the height // height value. All the values in a row gives the height
// relative to the previous value, and the first value in each // relative to the previous value, and the first value in each
// row is relative to the first value in the previous row. // row is relative to the first value in the previous row.
offset += *hmap; offset += *cast(short*)hmap;
// This is the 'sliding offset' for this row. It's adjusted // This is the 'sliding offset' for this row. It's adjusted
// for each vertex that's added, but only affects this row. // for each vertex that's added, but only affects this row.
float rowofs = offset; float rowofs = offset;
for(int x=0; x<vertCols; x++) for(int x=0; x<vertCols; x++)
{ {
hmap++; // Skip the byte we just read hmap+=2; // Skip the height we just read
// X and Y from the pregenerated buffer // X and Y from the pregenerated buffer
*vbuf++ = *gmap++; *vbuf++ = *gmap++;
*vbuf++ = *gmap++; *vbuf++ = *gmap++;
// The height is calculated from the current offset // The height is calculated from the current offset
*vbuf++ = rowofs * scale; *vbuf++ = rowofs * 8;
// Normal vector. // Normal vector.
*vbuf++ = *hmap++; *vbuf++ = *hmap++;
@ -185,10 +177,9 @@ struct MeshInfo
*vbuf++ = *gmap++; *vbuf++ = *gmap++;
*vbuf++ = *gmap++; *vbuf++ = *gmap++;
// Adjust the offset for the next vertex. On the last // Adjust the offset for the next vertex.
// iteration this will read past the current row, but if(x < vertCols-1)
// that's OK since rowofs is discarded afterwards. rowofs += *cast(short*)hmap;
rowofs += *hmap;
} }
} }
} }
@ -343,9 +334,20 @@ struct TerrainArchive
ifile.readArray(indexBufData); ifile.readArray(indexBufData);
} }
bool hasQuad(int X, int Y, int level)
{
if((level in quadMap) is null ||
(X in quadMap[level]) is null ||
(Y in quadMap[level][X]) is null)
return false;
return true;
}
// Get info about a given quad from the index. // Get info about a given quad from the index.
QuadInfo *getQuad(int X, int Y, int level) QuadInfo *getQuad(int X, int Y, int level)
{ {
assert(hasQuad(X,Y,level), format("Cannot find quad %s %s level %s",
X, Y, level));
int ind = quadMap[level][X][Y]; int ind = quadMap[level][X][Y];
QuadInfo *res = &quadList[ind]; QuadInfo *res = &quadList[ind];
assert(res); assert(res);

View file

@ -29,7 +29,7 @@ public:
float h = (p.y + 2048)*2.0/CELL_WIDTH; float h = (p.y + 2048)*2.0/CELL_WIDTH;
h *= h; h *= h;
mNode->setPosition(p.x, -p.z, -32 - h); mNode->setPosition(p.x, -p.z, -32 -h);
} }
private: private:
@ -55,16 +55,18 @@ private:
vd = mMeshDistance; vd = mMeshDistance;
mObject->position(-vd,vd,-2048); const int HEIGHT = -2048 - 10;
mObject->position(-vd,vd,HEIGHT);
mObject->textureCoord(0, 1); mObject->textureCoord(0, 1);
mObject->position(-vd,-vd,-2048); mObject->position(-vd,-vd,HEIGHT);
mObject->textureCoord(0, 0); mObject->textureCoord(0, 0);
mObject->position(vd,-vd,-2048); mObject->position(vd,-vd,HEIGHT);
mObject->textureCoord(1, 0); mObject->textureCoord(1, 0);
mObject->position(vd,vd,-2048); mObject->position(vd,vd,HEIGHT);
mObject->textureCoord(1, 1); mObject->textureCoord(1, 1);
mObject->quad(0,1,2,3); mObject->quad(0,1,2,3);

View file

@ -84,14 +84,26 @@ public:
// Finally, create the material // Finally, create the material
const std::string texName = info.getTexName(); const std::string texName = info.getTexName();
// Create or retrieve the material // Set up the scene node.
mMaterial = MaterialManager::getSingleton().createOrRetrieve mNode = parent->createChildSceneNode();
(texName, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME).first; 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* pass = mMaterial->getTechnique(0)->getPass(0);
pass->setLightingEnabled(false); pass->setLightingEnabled(false);
if(level != 1) if(level > 1)
{ {
// This material just has a normal texture // This material just has a normal texture
pass->createTextureUnitState(texName) pass->createTextureUnitState(texName)
@ -100,6 +112,8 @@ public:
} }
else else
{ {
assert(level == 1);
// Get the background texture. TODO: We should get this from // Get the background texture. TODO: We should get this from
// somewhere, no file names should be hard coded. The texture // somewhere, no file names should be hard coded. The texture
// might exist as a .tga in earlier versions of the game, and // might exist as a .tga in earlier versions of the game, and
@ -173,10 +187,6 @@ public:
tus->setTextureScale(scale, scale); tus->setTextureScale(scale, scale);
} }
} }
// Finally, set up the scene node.
mNode = parent->createChildSceneNode();
mNode->attachObject(this);
} }
~TerrainMesh() ~TerrainMesh()

View file

@ -275,7 +275,7 @@ extern "C"
addResourceLocation("cache/terrain/", "FileSystem", "General"); addResourceLocation("cache/terrain/", "FileSystem", "General");
// Enter superman mode // 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); //ogre_setFog(0.7, 0.7, 0.7, 200, 32*CELL_WIDTH);
d_terr_superman(); d_terr_superman();

View file

@ -26,7 +26,7 @@ module terrain.generator;
import std.stdio; import std.stdio;
import std.string; import std.string;
import std.math2;
import std.c.string; import std.c.string;
import terrain.cachewriter; import terrain.cachewriter;
@ -80,7 +80,7 @@ void generate(char[] filename)
texSizes[5] = 512; texSizes[5] = 512;
texSizes[4] = 256; texSizes[4] = 256;
texSizes[3] = 256; texSizes[3] = 256;
texSizes[2] = 256; texSizes[2] = 512;
texSizes[1] = 64; texSizes[1] = 64;
// Set some general parameters for the runtime // 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]; 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() bool isEmpty()
{ {
return (data.length == 0) && !hasMesh; return (data.length == 0) && !hasMesh;
@ -265,10 +279,8 @@ struct GenLevelResult
mi.vertRows = size; mi.vertRows = size;
mi.vertCols = size; mi.vertCols = size;
// 1 height + 3 normal components = 4 bytes per vertex. The reader // 2 height bytes + 3 normal components = 5 bytes per vertex.
// algorithm (fillVertexBuffer) needs 1 extra byte so add that mh.vertexBuffer = new byte[5*size*size];
// too.
mh.vertexBuffer = new byte[4*size*size+1];
hasMesh = true; hasMesh = true;
} }
@ -304,20 +316,7 @@ void genIndexData()
{ {
scope auto _trc = new MTrace("genIndexData"); scope auto _trc = new MTrace("genIndexData");
// Generate mesh data for each level. // FIXME: Do this at runtime.
/*
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.
*/
for(int lev=1; lev<=6; lev++) for(int lev=1; lev<=6; lev++)
{ {
// Make a new buffer to store the data // Make a new buffer to store the data
@ -353,7 +352,7 @@ void genIndexData()
cache.addVertexBuffer(lev,vertList); cache.addVertexBuffer(lev,vertList);
} }
// Next up, triangle indices // Pregenerate triangle indices
int size = 64*64*6; int size = 64*64*6;
auto indList = new ushort[size]; auto indList = new ushort[size];
int index = 0; int index = 0;
@ -531,8 +530,9 @@ void genLevel1Meshes(ref GenLevelResult res)
// The vertex data from the ESM // The vertex data from the ESM
byte data = heightData[offs]; byte data = heightData[offs];
// Write the height byte // Write the height value as a short (2 bytes)
verts[index++] = data; *(cast(short*)&verts[index]) = data;
index+=2;
// Calculate the height here, even though we don't store // Calculate the height here, even though we don't store
// it. We use it to find the min and max values. // 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 // Make sure we wrote exactly the right amount of data
assert(index == verts.length-1); assert(index == verts.length);
// Store the min/max values // Store the min/max values
mi.minHeight = min * 8; mi.minHeight = min * 8;
@ -864,111 +864,160 @@ void mergeMesh(GenLevelResult[] sub, ref GenLevelResult res)
// level above the cell level. // level above the cell level.
int shift = res.quad.info.level - 1; int shift = res.quad.info.level - 1;
assert(shift >= 1); assert(shift >= 1);
assert(sub.length == 4);
// Constants
int intervals = 64;
int vertNum = intervals+1;
int vertSep = 128 << shift;
// Allocate the result buffer // Allocate the result buffer
res.allocMesh(vertNum); res.allocMesh(65);
MeshHolder *mh = &res.quad.meshes[0]; MeshHolder *mh = &res.quad.meshes[0];
MeshInfo *mi = &mh.info; MeshInfo *mi = &mh.info;
// Basic info // Basic info
mi.worldWidth = vertSep*intervals; mi.worldWidth = 8192 << shift;
assert(mi.worldWidth == 8192<<shift);
// Get the height from the first cell // Copy the mesh height from the top left mesh
float rowheight; mi.heightOffset = sub[0].getHeight();
if(sub[0].isEmpty())
rowheight = 0.0;
else
rowheight = sub[0].quad.meshes[0].info.heightOffset;
// This is also the offset for the entire mesh // Output buffer
mi.heightOffset = rowheight; byte verts[] = mh.vertexBuffer;
// Just set bogus data for now // Bytes per vertex
int[] tmp = cast(int[])mh.vertexBuffer[0..$-1]; const int VSIZE = 5;
tmp[] = 0x7f000000;
float scale = 8.0 * (1<<shift);
mi.minHeight = mi.maxHeight = mi.heightOffset * scale;
return;
byte *vertPtr = mh.vertexBuffer.ptr; // Used to calculate the max and min heights
float minh = 300000.0;
float maxh = -300000.0;
// First off, rewrite this to use indices instead of pointers. It's foreach(si, s; sub)
// much safer and clearer.
// Loop through each 'row' of submeshes
for(int subY=0; subY<2; subY++)
{ {
// Loop through each row of vertices int SX = si % 2;
for(int row=0; row<65; row++) int SY = si / 2;
// Find the offset in the destination buffer
int dest = SX*32 + SY*65*32;
dest *= VSIZE;
void putValue(int val)
{ {
// FIXME: Ok, we have to skip each other row as well, of course. assert(val >= short.min && val <= short.max);
*(cast(short*)&verts[dest]) = val;
dest += 2;
}
// Loop through both sub meshes, left and right if(s.hasMesh)
for(int subX=0; subX<2; subX++) {
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()
{ {
GenLevelResult *s = &sub[subX+2*subY]; int s = *(cast(short*)&source[src]);
src += 2;
return s;
}
// Check if we have any data // Loop through all the vertices in the mesh
if(!s.isEmpty() && 0) for(int y=0;y<33;y++)
{
// 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)
{ {
MeshHolder *smh = &s.quad.meshes[0]; src += 65*VSIZE;
byte* inPtr = smh.vertexBuffer.ptr; dest += 65*VSIZE;
continue;
// 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
// Count the height from the two next vertices
int data = *inPtr++;
inPtr++;inPtr++;inPtr++; // Skip the first normal
data += *inPtr++;
// 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);
*vertPtr++ = data;
// 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.
} }
else
// Handle the first vertex of the row outside the
// loop.
int height = getValue();
// If this isn't the very first row, sum up two row
// heights and skip the first row.
if(y!=0)
{ {
// No data in this mesh. Just write zeros. // Skip the rest of the row.
for(int v=0; v<32; v++) src += 64*VSIZE + 3;
{
// Height
*vertPtr++ = 0;
// Normal, pointing straight upwards // Add the second height
*vertPtr++ = 0; height += getValue();
*vertPtr++ = 0;
*vertPtr++ = 0x7f;
}
} }
putValue(height);
// Copy the normal
verts[dest++] = source[src++];
verts[dest++] = source[src++];
verts[dest++] = source[src++];
// Loop through the remaining 64 vertices in this row,
// processing two at a time.
for(int x=0;x<32;x++)
{
height = getValue();
// 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++];
}
// 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)
{
dest += 65*VSIZE;
continue;
}
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 ------- // ------- OLD CODE - use these snippets later -------

69
terrain/myfile.d Normal file
View file

@ -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);
}
}

View file

@ -31,7 +31,8 @@ import std.file, std.stdio;
char[] cacheDir = "cache/terrain/"; 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; //debug=singleMesh;
void initTerrain(bool doGen) void initTerrain(bool doGen)
@ -55,12 +56,37 @@ void initTerrain(bool doGen)
debug(singleMesh) debug(singleMesh)
{ {
// Used for debugging single terrain meshes int X = 22;
auto node = terr_createChildNode(20000,-60000,null); int Y = 0;
auto info = g_archive.getQuad(0,0,1); bool next = false;
g_archive.mapQuad(info);
auto mi = g_archive.getMeshInfo(0); void doQuad(int x, int y, int lev)
auto m = terr_makeMesh(node, mi, info.level, TEX_SCALE); {
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 else
{ {