Yes.... And now, technical inner workings of the engine will become known
It's not a secret how this stuff works
We use the proven region/chunks "format". World is divided to regions, each regions have chunks.
Each region is separate
RawVolume<VT>. VT is defined as:
Code:
struct VT // Region Voxel Type
{
uint16_t material; // Material index
uint8_t light; // Light, still unused
uint32_t color; // Color for building blocks, still unused for smooth terrain
};
This structure is then serialized as is. This is the file format. We do not store density as we do not need it. Materials can be smooth, cubic, custom geometry and game entities.
When player navigstes into the region, chunks generate based on this data.
Each chunk uses triple for loop to make a copy from the region data.
Copy is done to a
RawVolume<DVT> field with applying a lowpass filter and material filtering in-place. DVT is defined as:
Code:
struct DVT //used only in chunks to generate the final mesh
{
uint16_t material; // Material
uint8_t light; // Light
uint32_t color; // Color
uint16_t density; // Density for Marching Cubes
DVT();
DVT(const VT& src);
DVT(const DVT& src);
VT toVT();
};
Low-pass filter is done with custom implementation, and only applied for materials that are smooth, e.g. if crystal's material is NOT smooth, no low pass will be applied to the voxel.
If a voxel is marked as smooth, following code is applied
Code:
static const int matLookups[] = {
0, 0, -1,
0, 0, 1,
0, 1, 0,
0, -1, 0,
1, 0, 0,
-1, 0, 0,
-1, -1, -1,
-1, -1, 0,
-1, -1, 1,
-1, 0, -1,
-1, 0, 1,
-1, 1, -1,
-1, 1, 0,
-1, 1, 1,
0, -1, -1,
0, -1, 1,
0, 1, -1,
0, 1, 1,
1, -1, -1,
1, -1, 0,
1, -1, 1,
1, 0, -1,
1, 0, 1,
1, 1, -1,
1, 1, 0,
1, 1, 1
};
...
Code:
float d = 0;
for(int ptidx=0; ptidx <26*3; ptidx+=3)
{
float dval = 0;
int mid = c->rgn->voxdata->getVoxelAt(cx+matLookups[ptidx],cy+matLookups[ptidx+1],cz+matLookups[ptidx+2]).material;
if(c->rgn->volume->materials.count(mid) && c->rgn->volume->materials[mid]->type != air) dval = 255.f;
d += dval;
}
d+=255.f; //self is always solid?
d /= 27.f;
uint8_t density = floor(d+.5f);
v2.density = density;
Then,
MarchingCubesSurfaceExtractor is executed to this volume twice. First pass uses
MarchingCubesController that ignores fluids and uses the density as is, and second pass uses a controller that only extracts fluids and never uses density (e.g. convertToDensity is 255 IF material is fluid, else it's zero).
Materials are defined as:
Code:
enum MaterialType
{
air=0, smooth, ore, crystal, liquid, cubic, build
};
struct material
{
material();
int id; //must be mapped by this field as well!
std::string name;
std::string filename; //texture file name
Ogre::TexturePtr texture; //Smooth: texture
MaterialType type;
bool isFluid, isSmooth;
// ...
void deserialize(json::object o);
//materials cannot be serialized so no code to serialize one...
};
During the custom copy loop, object spawning also occurs and creation of custom meshes also occurs at this time.
However, I have a lot more work to do before showing custom meshes from this data pass.
The custom copy pass also counts all materials encountered in a std map.
After this pass completes and meshes are generated, the count of rendering pass is determined, second for() x/y/z pass starts and generates the 3D Textures to be used for texture splatting.
Each 3D texture contains only 5 possible values:
Code:
Ogre::Vector4 col = Ogre::Vector4(0,0,0,0);
switch(colorid)
{
case 0: col = Ogre::Vector4(1,0,0,0); break;
case 1: col = Ogre::Vector4(0,1,0,0); break;
case 2: col = Ogre::Vector4(0,0,1,0); break;
case 3: col = Ogre::Vector4(0,0,0,1); break;
}
If we have 6 materials in the chunk, first 3D texture will contains first 4 materials and (0,0,0,0) for air and 5th and 6th material, while second 3D texture will contain (0,0,0,0) for everything except the 5th and 6th material. Thus, we have 2 splatting passes. This whole mesh generation process happens in a chunk worker thread, in parallel to the main FPS thread. After these mentioned steps complete, main thread is notified for mesh updation.
Ogre material is created on the fly with needed count of passes and textures set-up.
First pass renders the geometry pitch-black.
Second pass renders first 4 materials splat with additive blending and if material ids sum is zero, the discard command is used to optimize unneeded texture fetches (13 texture fetches per pixel per pass is not accepted! Discard here helps a lot).
Third pass renders next 4 materials splat, 4th pass next and so on... in our 6-material example, we have 3 passes - pitch black, add splat - first 4 materials and add splat - last 3 materials.
Lighting comes in appropriate pass. Light contribution is multiplied by pixel's alpha so we do not need additional passing.
The algorithm is quite slow in debig (2000 msec) but in release it's 50 to 90 ms for 32x32x32 chunks which is accepted solution.
REgion's voxel data overlaps with 2 voxels border, and chunks are copied with same 2 voxels border so we get valid normals.
3D textures are generated with 1 cell border so we guarantee no seams between chunks exist.
The whole thing is thread-proof.