mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-03-03 13:19:40 +00:00
- 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:
parent
367b9754b2
commit
393d284a8a
7 changed files with 297 additions and 139 deletions
|
@ -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);
|
||||
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;
|
||||
// Copy the mesh height from the top left mesh
|
||||
mi.heightOffset = sub[0].getHeight();
|
||||
|
||||
// This is also the offset for the entire mesh
|
||||
mi.heightOffset = rowheight;
|
||||
// Output buffer
|
||||
byte verts[] = mh.vertexBuffer;
|
||||
|
||||
// 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;
|
||||
// Bytes per vertex
|
||||
const int VSIZE = 5;
|
||||
|
||||
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
|
||||
// much safer and clearer.
|
||||
|
||||
// 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.
|
||||
|
||||
// Loop through both sub meshes, left and right
|
||||
for(int subX=0; subX<2; subX++)
|
||||
{
|
||||
GenLevelResult *s = &sub[subX+2*subY];
|
||||
|
||||
// Check if we have any data
|
||||
if(!s.isEmpty() && 0)
|
||||
{
|
||||
MeshHolder *smh = &s.quad.meshes[0];
|
||||
byte* inPtr = smh.vertexBuffer.ptr;
|
||||
|
||||
// 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++;
|
||||
assert(val >= short.min && val <= short.max);
|
||||
*(cast(short*)&verts[dest]) = val;
|
||||
dest += 2;
|
||||
}
|
||||
// Store the last one here. It _should_ be the
|
||||
// same as the first in the next section, if
|
||||
// present.
|
||||
|
||||
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 all the vertices in the mesh
|
||||
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)
|
||||
{
|
||||
src += 65*VSIZE;
|
||||
dest += 65*VSIZE;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
// Skip the rest of the row.
|
||||
src += 64*VSIZE + 3;
|
||||
|
||||
// Add the second height
|
||||
height += getValue();
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
// No data in this mesh. Just write zeros.
|
||||
for(int v=0; v<32; v++)
|
||||
minh = min(minh, -2048);
|
||||
maxh = max(maxh, -2048);
|
||||
|
||||
// Set all the vertices to zero.
|
||||
for(int y=0;y<33;y++)
|
||||
{
|
||||
// Height
|
||||
*vertPtr++ = 0;
|
||||
if(y==0 && SY != 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
assert(vertPtr == mh.vertexBuffer.ptr + mh.vertexBuffer.length - 1);
|
||||
|
||||
// Set max and min values here
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mi.minHeight = minh;
|
||||
mi.maxHeight = maxh;
|
||||
assert(minh <= maxh);
|
||||
}
|
||||
|
||||
// ------- OLD CODE - use these snippets later -------
|
||||
|
|
69
terrain/myfile.d
Normal file
69
terrain/myfile.d
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
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);
|
||||
auto m = terr_makeMesh(node, mi, info.level, TEX_SCALE);
|
||||
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…
Reference in a new issue