- 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
actorid
nkorslund 16 years ago
parent 367b9754b2
commit 393d284a8a

@ -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<<getLevel());
// Merge the two data sets together into the output buffer.
float offset = heightOffset;
for(int y=0; y<vertRows; y++)
@ -160,21 +152,21 @@ struct MeshInfo
// 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;
offset += *cast(short*)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
hmap+=2; // Skip the height 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;
*vbuf++ = rowofs * 8;
// Normal vector.
*vbuf++ = *hmap++;
@ -185,10 +177,9 @@ struct MeshInfo
*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;
// Adjust the offset for the next vertex.
if(x < vertCols-1)
rowofs += *cast(short*)hmap;
}
}
}
@ -343,9 +334,20 @@ struct TerrainArchive
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.
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];
QuadInfo *res = &quadList[ind];
assert(res);

@ -29,7 +29,7 @@ public:
float h = (p.y + 2048)*2.0/CELL_WIDTH;
h *= h;
mNode->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);

@ -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()

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

@ -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<<shift);
// Get the height from the first cell
float rowheight;
if(sub[0].isEmpty())
rowheight = 0.0;
else
rowheight = sub[0].quad.meshes[0].info.heightOffset;
mi.worldWidth = 8192 << shift;
// This is also the offset for the entire mesh
mi.heightOffset = rowheight;
// Copy the mesh height from the top left mesh
mi.heightOffset = sub[0].getHeight();
// Just set bogus data for now
int[] tmp = cast(int[])mh.vertexBuffer[0..$-1];
tmp[] = 0x7f000000;
float scale = 8.0 * (1<<shift);
mi.minHeight = mi.maxHeight = mi.heightOffset * scale;
return;
// Output buffer
byte verts[] = mh.vertexBuffer;
byte *vertPtr = mh.vertexBuffer.ptr;
// Bytes per vertex
const int VSIZE = 5;
// First off, rewrite this to use indices instead of pointers. It's
// much safer and clearer.
// Used to calculate the max and min heights
float minh = 300000.0;
float maxh = -300000.0;
// Loop through each 'row' of submeshes
for(int subY=0; subY<2; subY++)
foreach(si, s; sub)
{
// Loop through each row of vertices
for(int row=0; row<65; row++)
int SX = si % 2;
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;
}
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 -------

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

@ -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
{

Loading…
Cancel
Save