You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1154 lines
28 KiB
D
1154 lines
28 KiB
D
/*
|
|
OpenMW - The completely unofficial reimplementation of Morrowind
|
|
Copyright (C) 2008-2009 Nicolay Korslund
|
|
Email: < korslund@gmail.com >
|
|
WWW: http://openmw.snaptoad.com/
|
|
|
|
This file (generator.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/ .
|
|
|
|
*/
|
|
|
|
// This module is responsible for generating the cache files.
|
|
module terrain.generator;
|
|
|
|
import std.stdio;
|
|
import std.string;
|
|
import std.math2;
|
|
import std.c.string;
|
|
|
|
import terrain.cachewriter;
|
|
import terrain.archive;
|
|
import terrain.esmland;
|
|
import terrain.terrain;
|
|
import terrain.bindings;
|
|
|
|
import util.cachefile;
|
|
|
|
import monster.util.aa;
|
|
import monster.util.string;
|
|
import monster.vm.dbg;
|
|
|
|
int mCount;
|
|
|
|
// Texture sizes for the various levels. For the most detailed level
|
|
// (level 1), this give the size of the alpha splatting textures rather
|
|
// than a final texture.
|
|
int[] texSizes;
|
|
|
|
CacheWriter cache;
|
|
|
|
void generate(char[] filename)
|
|
{
|
|
makePath(cacheDir);
|
|
|
|
cache.openFile(filename);
|
|
|
|
// Find the maxiumum distance from (0,0) in any direction
|
|
int max = mwland.getMaxCoord();
|
|
|
|
// Round up to nearest power of 2
|
|
int depth=1;
|
|
while(max)
|
|
{
|
|
max >>= 1;
|
|
depth++;
|
|
assert(depth <= 8);
|
|
}
|
|
max = 1 << depth-1;
|
|
|
|
// We already know the answers
|
|
assert(max == 32);
|
|
assert(depth == 6);
|
|
|
|
// Set the texture sizes. TODO: These should be config options,
|
|
// perhaps - or maybe a result of some higher-level detail setting.
|
|
texSizes.length = depth+1;
|
|
texSizes[6] = 1024;
|
|
texSizes[5] = 512;
|
|
texSizes[4] = 256;
|
|
texSizes[3] = 256;
|
|
texSizes[2] = 512;
|
|
texSizes[1] = 64;
|
|
|
|
// Set some general parameters for the runtime
|
|
cache.setParams(depth+1, texSizes[1]);
|
|
|
|
// Create some common data first
|
|
writefln("Generating common data");
|
|
genDefaults();
|
|
genIndexData();
|
|
|
|
writefln("Generating quad data");
|
|
GenLevelResult gen;
|
|
|
|
// Start at one level above the top, but don't generate data for it
|
|
genLevel(depth+1, -max, -max, gen, false);
|
|
|
|
writefln("Writing index file");
|
|
cache.finish();
|
|
writefln("Pregeneration done. Results written to ", filename);
|
|
}
|
|
|
|
struct GenLevelResult
|
|
{
|
|
QuadHolder quad;
|
|
|
|
bool hasMesh;
|
|
bool isAlpha;
|
|
|
|
int width;
|
|
|
|
ubyte[] data;
|
|
|
|
void allocImage(int _width)
|
|
{
|
|
assert(isEmpty());
|
|
|
|
width = _width;
|
|
data.length = width*width*3;
|
|
quad.meshes.length = 1;
|
|
|
|
assert(!hasAlpha());
|
|
}
|
|
|
|
void allocAlphas(int _width, int texNum)
|
|
{
|
|
assert(isEmpty() || hasMesh);
|
|
|
|
width = _width;
|
|
data.length = width*width*texNum;
|
|
isAlpha = true;
|
|
|
|
// Set up the alpha images. TODO: We have to split these over
|
|
// several meshes, but for now pretend that we're using only
|
|
// one.
|
|
assert(quad.meshes.length == 1);
|
|
quad.meshes[0].alphas.length = texNum;
|
|
assert(alphaNum() == texNum);
|
|
|
|
int s = width*width;
|
|
for(int i=0;i<texNum;i++)
|
|
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;
|
|
}
|
|
|
|
bool hasAlpha()
|
|
{
|
|
assert(isAlpha == (quad.info.level==1));
|
|
return isAlpha;
|
|
}
|
|
|
|
int alphaNum()
|
|
{
|
|
assert(hasAlpha());
|
|
assert(quad.meshes.length == 1);
|
|
return quad.meshes[0].alphas.length;
|
|
}
|
|
|
|
int getAlphaTex(int alpha)
|
|
{
|
|
return quad.meshes[0].alphas[alpha].info.texName;
|
|
}
|
|
|
|
void setAlphaTex(int alpha, int index)
|
|
{
|
|
quad.meshes[0].alphas[alpha].info.texName = index;
|
|
|
|
// Create the alpha material name.
|
|
char[] aname =
|
|
"ALPHA_" ~ .toString(quad.info.cellX)
|
|
~ "_" ~ .toString(quad.info.cellY)
|
|
~ "_" ~ .toString(alpha)
|
|
~ "_" ~ .toString(index);
|
|
|
|
quad.meshes[0].alphas[alpha].info.alphaName = cache.addString(aname);
|
|
}
|
|
|
|
void setTexName(char[] tex)
|
|
{
|
|
assert(!isEmpty());
|
|
assert(quad.meshes.length == 1);
|
|
quad.meshes[0].info.texName = cache.addString(tex);
|
|
}
|
|
|
|
void save(char[] tex)
|
|
{
|
|
assert(!isEmpty());
|
|
assert(!hasAlpha());
|
|
|
|
// Store the filename in the cache, so the runtime can find the
|
|
// file
|
|
setTexName(tex);
|
|
|
|
tex = cacheDir ~ tex;
|
|
|
|
writefln(" Creating ", tex);
|
|
|
|
terr_saveImage(data.ptr, width, toStringz(tex));
|
|
}
|
|
|
|
// Calculate the texture name for this quad
|
|
char[] getPNGName(bool isDefault)
|
|
{
|
|
int X = quad.info.cellX;
|
|
int Y = quad.info.cellY;
|
|
int level = quad.info.level;
|
|
|
|
char[] outname = toString(level) ~ "_";
|
|
|
|
if(isDefault)
|
|
outname ~= "default";
|
|
else
|
|
outname ~= toString(X) ~ "_" ~ toString(Y);
|
|
|
|
outname ~= ".png";
|
|
|
|
return outname;
|
|
}
|
|
|
|
// Resize the image
|
|
void resize(int toSize)
|
|
{
|
|
assert(!isEmpty());
|
|
assert(!hasAlpha());
|
|
|
|
// Make sure we're scaling down, since we never gain anything by
|
|
// scaling up.
|
|
assert(toSize < width);
|
|
|
|
ubyte newBuf[] = new ubyte[toSize*toSize*3];
|
|
|
|
// Resize
|
|
terr_resize(data.ptr, newBuf.ptr, width, toSize);
|
|
|
|
// Replace the old buffer
|
|
delete data;
|
|
width = toSize;
|
|
data = newBuf;
|
|
}
|
|
|
|
void kill()
|
|
{
|
|
if(!isEmpty())
|
|
{
|
|
// This takes care of both normal image data and alpha maps.
|
|
delete data;
|
|
|
|
if(hasMesh)
|
|
delete quad.meshes[0].vertexBuffer;
|
|
}
|
|
}
|
|
|
|
void allocMesh(int size)
|
|
{
|
|
quad.meshes.length = 1;
|
|
|
|
MeshHolder *mh = &quad.meshes[0];
|
|
MeshInfo *mi = &mh.info;
|
|
|
|
mi.vertRows = size;
|
|
mi.vertCols = size;
|
|
// 2 height bytes + 3 normal components = 5 bytes per vertex.
|
|
mh.vertexBuffer = new byte[5*size*size];
|
|
|
|
hasMesh = true;
|
|
}
|
|
}
|
|
|
|
// Default textures
|
|
GenLevelResult[] defaults;
|
|
|
|
// Generates the default texture images "2_default.png" etc
|
|
void genDefaults()
|
|
{
|
|
scope auto _trc = new MTrace("genDefaults");
|
|
|
|
int size = texSizes.length-1;
|
|
defaults.length = size;
|
|
|
|
for(int i=1; i<size; i++)
|
|
defaults[i].quad.info.level = i;
|
|
|
|
// Sending null as the first parameter tells the function to only
|
|
// render the default background.
|
|
assert(size > 2);
|
|
genLevel2Map(null, defaults[2]);
|
|
|
|
for(int i=3; i<size; i++)
|
|
mergeMaps(null, defaults[i]);
|
|
}
|
|
|
|
// Generates common mesh information that's stored in the .index
|
|
// file. This includes the x/y coordinates of meshes on each level,
|
|
// the u/v texture coordinates, and the triangle index data.
|
|
void genIndexData()
|
|
{
|
|
scope auto _trc = new MTrace("genIndexData");
|
|
|
|
// FIXME: Do this at runtime.
|
|
for(int lev=1; lev<=6; lev++)
|
|
{
|
|
// Make a new buffer to store the data
|
|
int size = 65*65*4;
|
|
auto vertList = new float[size];
|
|
int index = 0;
|
|
|
|
// Find the vertex separation for this level. The vertices are
|
|
// 128 units apart in each cell (level 1), and for each level
|
|
// above that we double the distance. This gives 128 * 2^(lev-1)
|
|
// = 64*2^lev.
|
|
int vertSep = 64 << lev;
|
|
|
|
// Loop over all the vertices in the mesh.
|
|
for(int y=0; y<65; y++)
|
|
for(int x=0; x<65; x++)
|
|
{
|
|
// X and Y
|
|
vertList[index++] = x*vertSep;
|
|
vertList[index++] = y*vertSep;
|
|
|
|
// U and V (texture coordinates)
|
|
float u = x/64.0;
|
|
float v = y/64.0;
|
|
assert(u>=0&&v>=0);
|
|
assert(u<=1&&v<=1);
|
|
|
|
vertList[index++] = u;
|
|
vertList[index++] = v;
|
|
}
|
|
assert(index == vertList.length);
|
|
// Store the buffer
|
|
cache.addVertexBuffer(lev,vertList);
|
|
}
|
|
|
|
// Pregenerate triangle indices
|
|
int size = 64*64*6;
|
|
auto indList = new ushort[size];
|
|
int index = 0;
|
|
|
|
bool flag = false;
|
|
for ( int y = 0; y < 64; y++ )
|
|
{
|
|
for ( int x = 0; x < 64; x++ )
|
|
{
|
|
int line1 = y*65 + x;
|
|
int line2 = (y+1)*65 + x;
|
|
|
|
if ( flag )
|
|
{
|
|
indList[index++] = line1;
|
|
indList[index++] = line1 + 1;
|
|
indList[index++] = line2;
|
|
|
|
indList[index++] = line2;
|
|
indList[index++] = line1 + 1;
|
|
indList[index++] = line2 + 1;
|
|
}
|
|
else
|
|
{
|
|
indList[index++] = line1;
|
|
indList[index++] = line2 + 1;
|
|
indList[index++] = line2;
|
|
|
|
indList[index++] = line1;
|
|
indList[index++] = line1 + 1;
|
|
indList[index++] = line2 + 1;
|
|
}
|
|
flag = !flag; //flip tris for next time
|
|
}
|
|
flag = !flag; //flip tries for next row
|
|
}
|
|
assert(index == indList.length);
|
|
|
|
cache.setIndexBuffer(indList);
|
|
}
|
|
|
|
void genLevel(int level, int X, int Y, ref GenLevelResult result,
|
|
bool makeData = true)
|
|
{
|
|
scope auto _trc = new MTrace(format("genLevel(%s,%s,%s)",level,X,Y));
|
|
result.quad.info.cellX = X;
|
|
result.quad.info.cellY = Y;
|
|
result.quad.info.level = level;
|
|
result.quad.info.worldWidth = 8192 << (level-1);
|
|
|
|
assert(result.isEmpty);
|
|
|
|
// Level 1 (most detailed) is handled differently from the
|
|
// other leves.
|
|
if(level == 1)
|
|
{
|
|
assert(makeData);
|
|
|
|
if(!mwland.hasData(X,Y))
|
|
// Oops, there's no data for this cell. Skip it.
|
|
return;
|
|
|
|
// The mesh is generated in pieces rather than as one part.
|
|
genLevel1Meshes(result);
|
|
|
|
// We also generate alpha maps instead of the actual textures.
|
|
genCellAlpha(result);
|
|
|
|
if(!result.isEmpty())
|
|
{
|
|
// Store the information we just created
|
|
assert(result.hasAlpha());
|
|
cache.writeQuad(result.quad);
|
|
}
|
|
|
|
return;
|
|
}
|
|
assert(level > 1);
|
|
|
|
// Number of cells along one side in each sub-quad (not in this
|
|
// quad)
|
|
int cells = 1 << (level-2);
|
|
|
|
// Call the sub-levels and store the result
|
|
GenLevelResult sub[4];
|
|
genLevel(level-1, X, Y, sub[0]); // NW
|
|
genLevel(level-1, X+cells, Y, sub[1]); // NE
|
|
genLevel(level-1, X, Y+cells, sub[2]); // SW
|
|
genLevel(level-1, X+cells, Y+cells, sub[3]); // SE
|
|
|
|
// Make sure we deallocate everything when the function exists
|
|
scope(exit)
|
|
{
|
|
foreach(ref s; sub)
|
|
s.kill();
|
|
}
|
|
|
|
// Mark the sub-quads that have data
|
|
bool anyUsed = false;
|
|
for(int i=0;i<4;i++)
|
|
{
|
|
bool used = !sub[i].isEmpty();
|
|
result.quad.info.hasChild[i] = used;
|
|
anyUsed = anyUsed || used;
|
|
}
|
|
|
|
if(!anyUsed)
|
|
{
|
|
// If our children are empty, then we are also empty.
|
|
assert(result.isEmpty());
|
|
return;
|
|
}
|
|
|
|
if(makeData)
|
|
{
|
|
if(level == 2)
|
|
// For level==2, generate a new texture from the alpha maps.
|
|
genLevel2Map(sub, result);
|
|
else
|
|
// Level 3+, merge the images from the previous levels
|
|
mergeMaps(sub, result);
|
|
|
|
// Create the landscape mesh by merging the result from the
|
|
// children.
|
|
mergeMesh(sub, result);
|
|
}
|
|
|
|
// Store the result in the cache file
|
|
cache.writeQuad(result.quad);
|
|
}
|
|
|
|
// Generate mesh data for one cell
|
|
void genLevel1Meshes(ref GenLevelResult res)
|
|
{
|
|
// Constants
|
|
int intervals = 64;
|
|
int vertNum = intervals+1;
|
|
int vertSep = 128;
|
|
|
|
// Allocate the mesh buffer
|
|
res.allocMesh(vertNum);
|
|
|
|
int cellX = res.quad.info.cellX;
|
|
int cellY = res.quad.info.cellY;
|
|
assert(res.quad.info.level==1);
|
|
|
|
MeshHolder *mh = &res.quad.meshes[0];
|
|
MeshInfo *mi = &mh.info;
|
|
|
|
// Set some basic data
|
|
mi.worldWidth = vertSep*intervals;
|
|
assert(mi.worldWidth == 8192);
|
|
|
|
auto land = mwland.getLandData(cellX, cellY);
|
|
|
|
byte[] heightData = land.vhgt.heightData;
|
|
byte[] normals = land.normals;
|
|
mi.heightOffset = land.vhgt.heightOffset;
|
|
|
|
float max=-1000000.0;
|
|
float min=1000000.0;
|
|
|
|
byte[] verts = mh.vertexBuffer;
|
|
int index = 0;
|
|
|
|
// Loop over all the vertices in the mesh
|
|
float rowheight = mi.heightOffset;
|
|
float height;
|
|
for(int y=0; y<65; y++)
|
|
for(int x=0; x<65; x++)
|
|
{
|
|
// Offset of this vertex within the source buffer
|
|
int offs=y*65+x;
|
|
|
|
// The vertex data from the ESM
|
|
byte data = heightData[offs];
|
|
|
|
// 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.
|
|
if(x == 0)
|
|
{
|
|
// Set the height to the row height
|
|
height = rowheight;
|
|
|
|
// First value in each row adjusts the row height
|
|
rowheight += data;
|
|
}
|
|
// Adjust the accumulated height with the new data.
|
|
height += data;
|
|
|
|
// Calculate the min and max
|
|
max = height > max ? height : max;
|
|
min = height < min ? height : min;
|
|
|
|
// Store the normals
|
|
for(int k=0; k<3; k++)
|
|
verts[index++] = normals[offs*3+k];
|
|
}
|
|
|
|
// Make sure we wrote exactly the right amount of data
|
|
assert(index == verts.length);
|
|
|
|
// Store the min/max values
|
|
mi.minHeight = min * 8;
|
|
mi.maxHeight = max * 8;
|
|
}
|
|
|
|
// Generate the alpha splatting bitmap for one cell.
|
|
void genCellAlpha(ref GenLevelResult res)
|
|
{
|
|
scope auto _trc = new MTrace("genCellAlpha");
|
|
|
|
int cellX = res.quad.info.cellX;
|
|
int cellY = res.quad.info.cellY;
|
|
assert(res.quad.info.level == 1);
|
|
|
|
// Set the texture name - it's used internally as the material name
|
|
// at runtime.
|
|
assert(res.quad.meshes.length == 1);
|
|
res.setTexName("AMAT_"~toString(cellX)~"_"~toString(cellY));
|
|
|
|
// List of texture indices for this cell. A cell has 16x16 texture
|
|
// squares.
|
|
int ltex[16][16];
|
|
|
|
auto ltexData = mwland.getLTEXData(cellX, cellY);
|
|
|
|
// A map from the global texture index to the local index for this
|
|
// cell.
|
|
int[int] textureMap;
|
|
|
|
int texNum = 0; // Number of local indices
|
|
|
|
// Loop through all the textures in the cell and get the indices
|
|
bool isDef = true;
|
|
for(int ty = 0; ty < 16; ty++)
|
|
for(int tx = 0; tx < 16; tx++)
|
|
{
|
|
// Get the texture in a given cell
|
|
char[] textureName = ltexData.getTexture(tx,ty);
|
|
|
|
// If the default texture is used, skip it. The background
|
|
// texture covers it (for now - we might change that later.)
|
|
if(textureName == "")
|
|
{
|
|
ltex[ty][tx] = -1;
|
|
continue;
|
|
}
|
|
|
|
isDef = false;
|
|
|
|
// Store the global index
|
|
int index = cache.addTexture(textureName);
|
|
ltex[ty][tx] = index;
|
|
|
|
// Add the index to the map
|
|
if(!(index in textureMap))
|
|
textureMap[index] = texNum++;
|
|
}
|
|
|
|
assert(texNum == textureMap.length);
|
|
|
|
// If we only found default textures, exit now.
|
|
if(isDef)
|
|
return;
|
|
|
|
int imageRes = texSizes[1];
|
|
int dataSize = imageRes*imageRes;
|
|
|
|
// Number of alpha pixels per texture square
|
|
int pps = imageRes/16;
|
|
|
|
// Make sure there are at least as many alpha pixels as there are
|
|
// textures
|
|
assert(imageRes >= 16);
|
|
assert(imageRes%16 == 0);
|
|
assert(pps >= 1);
|
|
assert(texNum >= 1);
|
|
|
|
// Allocate the alpha images
|
|
res.allocAlphas(imageRes, texNum);
|
|
assert(res.hasAlpha() && !res.isEmpty());
|
|
|
|
// Write the indices to the result list
|
|
foreach(int global, int local; textureMap)
|
|
res.setAlphaTex(local, global);
|
|
|
|
ubyte *uptr = res.data.ptr;
|
|
|
|
// Loop over all textures again. This time, do alpha splatting.
|
|
for(int ty = 0; ty < 16; ty++)
|
|
for(int tx = 0; tx < 16; tx++)
|
|
{
|
|
// Get the global texture index for this square, if any.
|
|
int index = ltex[ty][tx];
|
|
if(index == -1)
|
|
continue;
|
|
|
|
// Get the local index
|
|
index = textureMap[index];
|
|
|
|
// Get the offset of this square
|
|
long offs = index*dataSize + pps*(ty*imageRes + tx);
|
|
|
|
// FIXME: Make real splatting later. This is just
|
|
// placeholder code.
|
|
|
|
// Set alphas to full for this square
|
|
for(int y=0; y<pps; y++)
|
|
for(int x=0; x<pps; x++)
|
|
{
|
|
long toffs = offs + imageRes*y + x;
|
|
assert(toffs < dataSize*texNum);
|
|
uptr[toffs] = 255;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Generate a texture for level 2 from four alpha maps generated in
|
|
// level 1.
|
|
void genLevel2Map(GenLevelResult maps[], ref GenLevelResult res)
|
|
{
|
|
int fromSize = texSizes[1];
|
|
int toSize = texSizes[2];
|
|
|
|
struct LtexList
|
|
{
|
|
int[4] inds;
|
|
}
|
|
|
|
// Create an overview of which texture is used where. The 'key' is
|
|
// the global texture index, the 'value' is the corresponding
|
|
// local indices in each of the four submaps.
|
|
HashTable!(int, LtexList) lmap;
|
|
|
|
if(maps.length) // An empty list means use the default texture
|
|
for(int mi=0;mi<4;mi++)
|
|
{
|
|
if(maps[mi].isEmpty())
|
|
continue;
|
|
|
|
assert(maps[mi].hasAlpha());
|
|
assert(maps[mi].width == fromSize);
|
|
|
|
for(int ltex=0;ltex<maps[mi].alphaNum();ltex++)
|
|
{
|
|
// Global index for this texture
|
|
int gIndex = maps[mi].getAlphaTex(ltex);
|
|
|
|
// Store it in the map.
|
|
LtexList *v;
|
|
if(lmap.insertEdit(gIndex, v))
|
|
// If a new value was inserted, set all the values to -1
|
|
v.inds[] = -1;
|
|
|
|
v.inds[mi] = ltex;
|
|
}
|
|
}
|
|
|
|
float scale = TEX_SCALE/2;
|
|
|
|
char[] materialName = "MAT" ~ toString(mCount++);
|
|
|
|
terr_makeLandMaterial(toStringz(materialName),scale);
|
|
|
|
// Loop through all our textures
|
|
if(maps.length)
|
|
foreach(int gIndex, LtexList inds; lmap)
|
|
{
|
|
char[] name = cache.getString(gIndex);
|
|
|
|
// Skip default image, if present
|
|
if ( name.iBegins("_land_default.") )
|
|
continue;
|
|
|
|
// Create a new alpha texture and get a pointer to the pixel
|
|
// data
|
|
char *alphaName = toStringz(materialName ~ "_A_" ~ name);
|
|
auto pDest = terr_makeAlphaLayer(alphaName, 2*fromSize);
|
|
|
|
// Fill in the alpha values. TODO: Do all this with slices instead.
|
|
memset(pDest, 0, 4*fromSize*fromSize);
|
|
for(int i=0;i<4;i++)
|
|
{
|
|
// Does this sub-image have this texture?
|
|
int index = inds.inds[i];
|
|
if(index == -1) continue;
|
|
|
|
assert(!maps[i].isEmpty());
|
|
|
|
// Find the right sub-texture in the alpha map
|
|
ubyte *from = maps[i].data.ptr +
|
|
(fromSize*fromSize)*index;
|
|
|
|
// Find the right destination pointer
|
|
int x = i%2;
|
|
int y = i/2;
|
|
ubyte *to = pDest + x*fromSize + y*fromSize*fromSize*2;
|
|
|
|
// Copy the rows one by one
|
|
for(int row = 0; row < fromSize; row++)
|
|
{
|
|
assert(to+fromSize <= pDest + 4*fromSize*fromSize);
|
|
memcpy(to, from, fromSize);
|
|
to += 2*fromSize;
|
|
from += fromSize;
|
|
}
|
|
}
|
|
|
|
terr_closeAlpha(alphaName, toStringz(name), scale);
|
|
}
|
|
|
|
// Create the result buffer
|
|
res.allocImage(toSize);
|
|
|
|
// Texture file name
|
|
char[] outname = res.getPNGName(maps.length == 0);
|
|
|
|
terr_cleanupAlpha(toStringz(outname), res.data.ptr, toSize);
|
|
res.save(outname);
|
|
}
|
|
|
|
void mergeMaps(GenLevelResult[] maps, ref GenLevelResult res)
|
|
{
|
|
int level = res.quad.info.level;
|
|
|
|
assert(texSizes.length > level);
|
|
assert(level > 2);
|
|
int fromSize = texSizes[level-1];
|
|
int toSize = texSizes[level];
|
|
|
|
// Create a new image buffer large enough to hold the four
|
|
// sub textures
|
|
res.allocImage(fromSize*2);
|
|
|
|
// Add the four sub-textures
|
|
for(int mi=0;mi<4;mi++)
|
|
{
|
|
ubyte[] src;
|
|
|
|
// Use default texture if no source is present
|
|
if(maps.length == 0 || maps[mi].isEmpty())
|
|
src = defaults[level-1].data;
|
|
else
|
|
src = maps[mi].data;
|
|
|
|
assert(src.length == 3*fromSize*fromSize);
|
|
|
|
// Find the sub-part of the destination buffer to write to
|
|
int x = (mi%2) * fromSize;
|
|
int y = (mi/2) * fromSize;
|
|
|
|
// Copy the image into the new buffer
|
|
copyBox(src, res.data, fromSize, fromSize*2, x, y, 3);
|
|
}
|
|
|
|
// Resize image if necessary
|
|
if(toSize != 2*fromSize)
|
|
res.resize(toSize);
|
|
|
|
// Texture file name
|
|
char[] outname = res.getPNGName(maps.length == 0);
|
|
|
|
// Save the image
|
|
res.save(outname);
|
|
}
|
|
|
|
// Copy from one buffer into a sub-region of another buffer
|
|
void copyBox(ubyte[] src, ubyte[] dst,
|
|
int srcWidth, int dstWidth,
|
|
int dstX, int dstY, int pixSize)
|
|
{
|
|
int fskip = srcWidth * pixSize;
|
|
int tskip = dstWidth * pixSize;
|
|
int rows = srcWidth;
|
|
int rowSize = srcWidth*pixSize;
|
|
|
|
assert(src.length == pixSize*srcWidth*srcWidth);
|
|
assert(dst.length == pixSize*dstWidth*dstWidth);
|
|
assert(srcWidth <= dstWidth);
|
|
assert(dstX <= dstWidth-srcWidth && dstY <= dstWidth-srcWidth);
|
|
|
|
// Source and destination pointers
|
|
ubyte *from = src.ptr;
|
|
ubyte *to = dst.ptr + dstY*tskip + dstX*pixSize;
|
|
|
|
for(;rows>0;rows--)
|
|
{
|
|
memcpy(to, from, rowSize);
|
|
to += tskip;
|
|
from += fskip;
|
|
}
|
|
}
|
|
|
|
// Create the mesh for this level, by merging the meshes from the
|
|
// previous levels.
|
|
void mergeMesh(GenLevelResult[] sub, ref GenLevelResult res)
|
|
{
|
|
// How much to shift various numbers to the left at this level
|
|
// (ie. multiply by 2^shift). The height at each vertex is
|
|
// multiplied by 8 in each cell to get the final value. However,
|
|
// when we're merging two cells (in each direction), we have to
|
|
// scale down all the height values by 2 in order to fit the result
|
|
// in one byte. This means multiplying the factor by 2 for each
|
|
// level above the cell level.
|
|
int shift = res.quad.info.level - 1;
|
|
assert(shift >= 1);
|
|
assert(sub.length == 4);
|
|
|
|
// Allocate the result buffer
|
|
res.allocMesh(65);
|
|
|
|
MeshHolder *mh = &res.quad.meshes[0];
|
|
MeshInfo *mi = &mh.info;
|
|
|
|
// Basic info
|
|
mi.worldWidth = 8192 << shift;
|
|
|
|
// Copy the mesh height from the top left mesh
|
|
mi.heightOffset = sub[0].getHeight();
|
|
|
|
// Output buffer
|
|
byte verts[] = mh.vertexBuffer;
|
|
|
|
// Bytes per vertex
|
|
const int VSIZE = 5;
|
|
|
|
// Used to calculate the max and min heights
|
|
float minh = 300000.0;
|
|
float maxh = -300000.0;
|
|
|
|
foreach(si, s; sub)
|
|
{
|
|
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)
|
|
{
|
|
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 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
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
mi.minHeight = minh;
|
|
mi.maxHeight = maxh;
|
|
assert(minh <= maxh);
|
|
}
|
|
|
|
// ------- OLD CODE - use these snippets later -------
|
|
|
|
// About segments:
|
|
/* NOTES for the gen-phase: Was:
|
|
// This is pretty messy. Btw: 128*16 == 2048 ==
|
|
// CELL_WIDTH/4
|
|
// 65 points across one cell means 64 intervals, and 17 points
|
|
|
|
// means 16=64/4 intervals. So IOW the number of verts when
|
|
// dividing by D is (65-1)/D + 1 = 64/D+1, which means that D
|
|
// should divide 64, that is, be a power of two < 64.
|
|
|
|
addNewObject(Ogre::Vector3(x*16*128, 0, y*16*128), //pos
|
|
17, //size
|
|
false, //skirts
|
|
0.25f, float(x)/4.0f, float(y)/4.0f);//quad seg location
|
|
*/
|
|
|
|
/* This was also declared in the original code, you'll need it
|
|
when creating the cache data
|
|
|
|
size_t vw = mWidth; // mWidth is 17 or 65
|
|
if ( mUseSkirts ) vw += 2; // skirts are used for level 2 and up
|
|
vertCount=vw*vw;
|
|
*/
|
|
|
|
/**
|
|
* @brief fills the vertex buffer with data
|
|
* @todo I don't think tex co-ords are right
|
|
void calculateVertexValues()
|
|
{
|
|
int start = 0;
|
|
int end = mWidth;
|
|
|
|
if ( mUseSkirts )
|
|
{
|
|
--start;
|
|
++end;
|
|
}
|
|
|
|
for ( int y = start; y < end; y++ )
|
|
for ( int x = start; x < end; x++ )
|
|
{
|
|
if ( y < 0 || y > (mWidth-1) || x < 0 || x > (mWidth-1) )
|
|
{
|
|
// These are the skirt vertices. 'Skirts' are simply a
|
|
// wall at the edges of the mesh that goes straight down,
|
|
// cutting off the posibility that you might see 'gaps'
|
|
// between the meshes. Or at least I think that's the
|
|
// intention.
|
|
|
|
assert(mUseSkirts);
|
|
|
|
// 1st coordinate
|
|
if ( x < 0 )
|
|
*verts++ = 0;
|
|
else if ( x > (mWidth-1) )
|
|
*verts++ = (mWidth-1)*getVertexSeperation();
|
|
else
|
|
*verts++ = x*getVertexSeperation();
|
|
|
|
// 2nd coordinate
|
|
*verts++ = -4096; //2048 below base sea floor height
|
|
|
|
// 3rd coordinate
|
|
if ( y < 0 )
|
|
*verts++ = 0;
|
|
else if ( y > (mWidth-1) )
|
|
*verts++ = (mWidth-1)*getVertexSeperation();
|
|
else
|
|
*verts++ = y*getVertexSeperation();
|
|
|
|
// No normals
|
|
for ( Ogre::uint i = 0; i < 3; i++ )
|
|
*verts++ = 0;
|
|
|
|
// It shouldn't matter if these go over 1
|
|
float u = (float)(x) / (mWidth-1);
|
|
float v = (float)(y) / (mWidth-1);
|
|
*verts++ = u;
|
|
*verts++ = v;
|
|
}
|
|
else // Covered already
|
|
|
|
void calculateIndexValues()
|
|
{
|
|
size_t vw = mWidth-1;
|
|
if ( mUseSkirts ) vw += 2;
|
|
|
|
const size_t indexCount = (vw)*(vw)*6;
|
|
|
|
//need to manage allocation if not null
|
|
assert(mIndices==0);
|
|
|
|
// buffer was created here
|
|
|
|
bool flag = false;
|
|
Ogre::uint indNum = 0;
|
|
for ( Ogre::uint y = 0; y < (vw); y+=1 ) {
|
|
for ( Ogre::uint x = 0; x < (vw); x+=1 ) {
|
|
|
|
const int line1 = y * (vw+1) + x;
|
|
const int line2 = (y + 1) * (vw+1) + x;
|
|
|
|
if ( flag ) {
|
|
*indices++ = line1;
|
|
*indices++ = line2;
|
|
*indices++ = line1 + 1;
|
|
|
|
*indices++ = line1 + 1;
|
|
*indices++ = line2;
|
|
*indices++ = line2 + 1;
|
|
} else {
|
|
*indices++ = line1;
|
|
*indices++ = line2;
|
|
*indices++ = line2 + 1;
|
|
|
|
*indices++ = line1;
|
|
*indices++ = line2 + 1;
|
|
*indices++ = line1 + 1;
|
|
}
|
|
flag = !flag; //flip tris for next time
|
|
|
|
indNum+=6;
|
|
}
|
|
flag = !flag; //flip tries for next row
|
|
}
|
|
assert(indNum==indexCount);
|
|
//return mIndices;
|
|
}
|
|
*/
|