Merge remote-tracking branch 'zini/master' into physics
commit
a993af53e7
@ -0,0 +1,6 @@
|
||||
#include "textslotmsgbox.hpp"
|
||||
|
||||
void TextSlotMsgBox::setTextSlot(const QString& string)
|
||||
{
|
||||
setText(string);
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
#ifndef TEXT_SLOT_MSG_BOX
|
||||
#define TEXT_SLOT_MSG_BOX
|
||||
|
||||
#include <QMessageBox>
|
||||
|
||||
class TextSlotMsgBox : public QMessageBox
|
||||
{
|
||||
Q_OBJECT
|
||||
public slots:
|
||||
void setTextSlot(const QString& string);
|
||||
};
|
||||
|
||||
#endif
|
@ -0,0 +1,487 @@
|
||||
#include "unshieldthread.hpp"
|
||||
|
||||
#include <fstream>
|
||||
#include <components/misc/stringops.hpp>
|
||||
|
||||
namespace bfs = boost::filesystem;
|
||||
|
||||
namespace
|
||||
{
|
||||
static bool make_sure_directory_exists(bfs::path directory)
|
||||
{
|
||||
|
||||
if(!bfs::exists(directory))
|
||||
{
|
||||
bfs::create_directories(directory);
|
||||
}
|
||||
|
||||
return bfs::exists(directory);
|
||||
}
|
||||
|
||||
void fill_path(bfs::path& path, const std::string& name)
|
||||
{
|
||||
size_t start = 0;
|
||||
|
||||
size_t i;
|
||||
for(i = 0; i < name.length(); i++)
|
||||
{
|
||||
switch(name[i])
|
||||
{
|
||||
case '\\':
|
||||
path /= name.substr(start, i-start);
|
||||
start = i+1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
path /= name.substr(start, i-start);
|
||||
}
|
||||
|
||||
std::string get_setting(const std::string& category, const std::string& setting, const std::string& inx)
|
||||
{
|
||||
size_t start = inx.find(category);
|
||||
start = inx.find(setting, start) + setting.length() + 3;
|
||||
|
||||
size_t end = inx.find("!", start);
|
||||
|
||||
return inx.substr(start, end-start);
|
||||
}
|
||||
|
||||
std::string read_to_string(const bfs::path& path)
|
||||
{
|
||||
std::ifstream strstream(path.c_str(), std::ios::in | std::ios::binary);
|
||||
std::string str;
|
||||
|
||||
strstream.seekg(0, std::ios::end);
|
||||
str.resize(strstream.tellg());
|
||||
strstream.seekg(0, std::ios::beg);
|
||||
strstream.read(&str[0], str.size());
|
||||
strstream.close();
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
void add_setting(const std::string& category, const std::string& setting, const std::string& val, std::string& ini)
|
||||
{
|
||||
size_t loc;
|
||||
loc = ini.find("[" + category + "]");
|
||||
|
||||
// If category is not found, create it
|
||||
if(loc == std::string::npos)
|
||||
{
|
||||
loc = ini.size() + 2;
|
||||
ini += ("\r\n[" + category + "]\r\n");
|
||||
}
|
||||
|
||||
loc += category.length() +2 +2;
|
||||
ini.insert(loc, setting + "=" + val + "\r\n");
|
||||
}
|
||||
|
||||
void bloodmoon_fix_ini(std::string& ini, const bfs::path inxPath)
|
||||
{
|
||||
std::string inx = read_to_string(inxPath);
|
||||
|
||||
// Remove this one setting (the only one actually changed by bloodmoon, as opposed to just adding new ones)
|
||||
size_t start = ini.find("[Weather Blight]");
|
||||
start = ini.find("Ambient Loop Sound ID", start);
|
||||
size_t end = ini.find("\r\n", start) +2;
|
||||
ini.erase(start, end-start);
|
||||
|
||||
std::string category;
|
||||
std::string setting;
|
||||
|
||||
category = "General";
|
||||
{
|
||||
setting = "Werewolf FOV"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
}
|
||||
category = "Moons";
|
||||
{
|
||||
setting = "Script Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
}
|
||||
category = "Weather";
|
||||
{
|
||||
setting = "Snow Ripples"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Snow Ripple Radius"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Snow Ripples Per Flake"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Snow Ripple Scale"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Snow Ripple Speed"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Snow Gravity Scale"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Snow High Kill"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Snow Low Kill"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
}
|
||||
category = "Weather Blight";
|
||||
{
|
||||
setting = "Ambient Loop Sound ID"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
}
|
||||
category = "Weather Snow";
|
||||
{
|
||||
setting = "Sky Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Sky Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Sky Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Sky Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Fog Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Fog Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Fog Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Fog Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Ambient Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Ambient Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Ambient Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Ambient Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Sun Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Sun Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Sun Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Sun Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Sun Disc Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Transition Delta"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Land Fog Day Depth"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Land Fog Night Depth"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Clouds Maximum Percent"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Wind Speed"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Cloud Speed"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Glare View"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Cloud Texture"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Ambient Loop Sound ID"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Snow Threshold"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Snow Diameter"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Snow Height Min"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Snow Height Max"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Snow Entrance Speed"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Max Snowflakes"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
}
|
||||
category = "Weather Blizzard";
|
||||
{
|
||||
setting = "Sky Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Sky Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Sky Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Sky Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Fog Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Fog Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Fog Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Fog Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Ambient Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Ambient Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Ambient Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Ambient Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Sun Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Sun Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Sun Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Sun Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Sun Disc Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Transition Delta"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Land Fog Day Depth"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Land Fog Night Depth"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Clouds Maximum Percent"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Wind Speed"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Cloud Speed"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Glare View"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Cloud Texture"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Ambient Loop Sound ID"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
setting = "Storm Threshold"; add_setting(category, setting, get_setting(category, setting, inx), ini);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void fix_ini(const bfs::path& output_dir, bfs::path cdPath, bool tribunal, bool bloodmoon)
|
||||
{
|
||||
bfs::path ini_path = output_dir;
|
||||
ini_path /= "Morrowind.ini";
|
||||
|
||||
std::string ini = read_to_string(ini_path.string());
|
||||
|
||||
if(tribunal)
|
||||
{
|
||||
add_setting("Game Files", "GameFile1", "Tribunal.esm", ini);
|
||||
add_setting("Archives", "Archive 0", "Tribunal.bsa", ini);
|
||||
}
|
||||
if(bloodmoon)
|
||||
{
|
||||
bloodmoon_fix_ini(ini, cdPath / "setup.inx");
|
||||
add_setting("Game Files", "GameFile2", "Bloodmoon.esm", ini);
|
||||
add_setting("Archives", "Archive 1", "Bloodmoon.bsa", ini);
|
||||
}
|
||||
|
||||
std::ofstream inistream(ini_path.c_str());
|
||||
inistream << ini;
|
||||
inistream.close();
|
||||
}
|
||||
|
||||
void installToPath(const bfs::path& from, const bfs::path& to, bool copy = false)
|
||||
{
|
||||
make_sure_directory_exists(to);
|
||||
|
||||
for ( bfs::directory_iterator end, dir(from); dir != end; ++dir )
|
||||
{
|
||||
if(bfs::is_directory(dir->path()))
|
||||
installToPath(dir->path(), to / dir->path().filename(), copy);
|
||||
else
|
||||
{
|
||||
if(copy)
|
||||
bfs::copy_file(dir->path(), to / dir->path().filename());
|
||||
else
|
||||
bfs::rename(dir->path(), to / dir->path().filename());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bfs::path findFile(const bfs::path& in, std::string filename, bool recursive = true)
|
||||
{
|
||||
if(recursive)
|
||||
{
|
||||
for ( bfs::recursive_directory_iterator end, dir(in); dir != end; ++dir )
|
||||
{
|
||||
if(Misc::StringUtils::lowerCase(dir->path().filename().string()) == filename)
|
||||
return dir->path();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for ( bfs::directory_iterator end, dir(in); dir != end; ++dir )
|
||||
{
|
||||
if(Misc::StringUtils::lowerCase(dir->path().filename().string()) == filename)
|
||||
return dir->path();
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
bool contains(const bfs::path& in, std::string filename)
|
||||
{
|
||||
for(bfs::directory_iterator end, dir(in); dir != end; ++dir)
|
||||
{
|
||||
if(Misc::StringUtils::lowerCase(dir->path().filename().string()) == filename)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
time_t getTime(const char* time)
|
||||
{
|
||||
struct tm tms;
|
||||
memset(&tms, 0, sizeof(struct tm));
|
||||
strptime(time, "%d %B %Y", &tms);
|
||||
return mktime(&tms);
|
||||
}
|
||||
}
|
||||
|
||||
bool UnshieldThread::SetMorrowindPath(const std::string& path)
|
||||
{
|
||||
mMorrowindPath = path;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UnshieldThread::SetTribunalPath(const std::string& path)
|
||||
{
|
||||
mTribunalPath = path;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UnshieldThread::SetBloodmoonPath(const std::string& path)
|
||||
{
|
||||
mBloodmoonPath = path;
|
||||
return true;
|
||||
}
|
||||
|
||||
void UnshieldThread::SetOutputPath(const std::string& path)
|
||||
{
|
||||
mOutputPath = path;
|
||||
}
|
||||
|
||||
bool UnshieldThread::extract_file(Unshield* unshield, bfs::path output_dir, const char* prefix, int index)
|
||||
{
|
||||
bool success;
|
||||
bfs::path dirname;
|
||||
bfs::path filename;
|
||||
int directory = unshield_file_directory(unshield, index);
|
||||
|
||||
dirname = output_dir;
|
||||
|
||||
if (prefix && prefix[0])
|
||||
dirname /= prefix;
|
||||
|
||||
if (directory >= 0)
|
||||
{
|
||||
const char* tmp = unshield_directory_name(unshield, directory);
|
||||
if (tmp && tmp[0])
|
||||
fill_path(dirname, tmp);
|
||||
}
|
||||
|
||||
make_sure_directory_exists(dirname);
|
||||
|
||||
filename = dirname;
|
||||
filename /= unshield_file_name(unshield, index);
|
||||
|
||||
emit signalGUI(QString("Extracting: ") + QString(filename.c_str()));
|
||||
|
||||
success = unshield_file_save(unshield, index, filename.c_str());
|
||||
|
||||
if (!success)
|
||||
bfs::remove(filename);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
void UnshieldThread::extract_cab(const bfs::path& cab, const bfs::path& output_dir, bool extract_ini)
|
||||
{
|
||||
Unshield * unshield;
|
||||
unshield = unshield_open(cab.c_str());
|
||||
|
||||
int i;
|
||||
for (i = 0; i < unshield_file_group_count(unshield); i++)
|
||||
{
|
||||
UnshieldFileGroup* file_group = unshield_file_group_get(unshield, i);
|
||||
|
||||
for (size_t j = file_group->first_file; j <= file_group->last_file; j++)
|
||||
{
|
||||
if (unshield_file_is_valid(unshield, j))
|
||||
extract_file(unshield, output_dir, file_group->name, j);
|
||||
}
|
||||
}
|
||||
unshield_close(unshield);
|
||||
}
|
||||
|
||||
|
||||
bool UnshieldThread::extract()
|
||||
{
|
||||
bfs::path outputDataFilesDir = mOutputPath;
|
||||
outputDataFilesDir /= "Data Files";
|
||||
bfs::path extractPath = mOutputPath;
|
||||
extractPath /= "extract-temp";
|
||||
|
||||
if(!mMorrowindDone && mMorrowindPath.string().length() > 0)
|
||||
{
|
||||
mMorrowindDone = true;
|
||||
|
||||
bfs::path mwExtractPath = extractPath / "morrowind";
|
||||
extract_cab(mMorrowindPath, mwExtractPath, true);
|
||||
|
||||
bfs::path dFilesDir = findFile(mwExtractPath, "morrowind.esm").parent_path();
|
||||
|
||||
installToPath(dFilesDir, outputDataFilesDir);
|
||||
|
||||
// Videos are often kept uncompressed on the cd
|
||||
bfs::path videosPath = findFile(mMorrowindPath.parent_path(), "video", false);
|
||||
if(videosPath.string() != "")
|
||||
{
|
||||
emit signalGUI(QString("Installing Videos..."));
|
||||
installToPath(videosPath, outputDataFilesDir / "Video", true);
|
||||
}
|
||||
|
||||
bfs::path cdDFiles = findFile(mMorrowindPath.parent_path(), "data files", false);
|
||||
if(cdDFiles.string() != "")
|
||||
{
|
||||
emit signalGUI(QString("Installing Uncompressed Data files from CD..."));
|
||||
installToPath(cdDFiles, outputDataFilesDir, true);
|
||||
}
|
||||
|
||||
|
||||
bfs::rename(findFile(mwExtractPath, "morrowind.ini"), outputDataFilesDir / "Morrowind.ini");
|
||||
|
||||
mTribunalDone = contains(outputDataFilesDir, "tribunal.esm");
|
||||
mBloodmoonDone = contains(outputDataFilesDir, "bloodmoon.esm");
|
||||
|
||||
}
|
||||
|
||||
else if(!mTribunalDone && mTribunalPath.string().length() > 0)
|
||||
{
|
||||
mTribunalDone = true;
|
||||
|
||||
bfs::path tbExtractPath = extractPath / "tribunal";
|
||||
extract_cab(mTribunalPath, tbExtractPath, true);
|
||||
|
||||
bfs::path dFilesDir = findFile(tbExtractPath, "tribunal.esm").parent_path();
|
||||
|
||||
installToPath(dFilesDir, outputDataFilesDir);
|
||||
|
||||
// Mt GOTY CD has Sounds in a seperate folder from the rest of the data files
|
||||
bfs::path soundsPath = findFile(tbExtractPath, "sounds", false);
|
||||
if(soundsPath.string() != "")
|
||||
installToPath(soundsPath, outputDataFilesDir / "Sounds");
|
||||
|
||||
bfs::path cdDFiles = findFile(mTribunalPath.parent_path(), "data files", false);
|
||||
if(cdDFiles.string() != "")
|
||||
{
|
||||
emit signalGUI(QString("Installing Uncompressed Data files from CD..."));
|
||||
installToPath(cdDFiles, outputDataFilesDir, true);
|
||||
}
|
||||
|
||||
mBloodmoonDone = contains(outputDataFilesDir, "bloodmoon.esm");
|
||||
|
||||
fix_ini(outputDataFilesDir, bfs::path(mTribunalPath).parent_path(), mTribunalDone, mBloodmoonDone);
|
||||
}
|
||||
|
||||
else if(!mBloodmoonDone && mBloodmoonPath.string().length() > 0)
|
||||
{
|
||||
mBloodmoonDone = true;
|
||||
|
||||
bfs::path bmExtractPath = extractPath / "bloodmoon";
|
||||
extract_cab(mBloodmoonPath, bmExtractPath, true);
|
||||
|
||||
bfs::path dFilesDir = findFile(bmExtractPath, "bloodmoon.esm").parent_path();
|
||||
|
||||
installToPath(dFilesDir, outputDataFilesDir);
|
||||
|
||||
// My GOTY CD contains a folder within cab files called Tribunal patch,
|
||||
// which contains Tribunal.esm
|
||||
bfs::path tbPatchPath = findFile(bmExtractPath, "tribunal.esm");
|
||||
if(tbPatchPath.string() != "")
|
||||
bfs::rename(tbPatchPath, outputDataFilesDir / "Tribunal.esm");
|
||||
|
||||
bfs::path cdDFiles = findFile(mBloodmoonPath.parent_path(), "data files", false);
|
||||
if(cdDFiles.string() != "")
|
||||
{
|
||||
emit signalGUI(QString("Installing Uncompressed Data files from CD..."));
|
||||
installToPath(cdDFiles, outputDataFilesDir, true);
|
||||
}
|
||||
|
||||
fix_ini(outputDataFilesDir, bfs::path(mBloodmoonPath).parent_path(), false, mBloodmoonDone);
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void UnshieldThread::Done()
|
||||
{
|
||||
// Get rid of unnecessary files
|
||||
bfs::remove_all(mOutputPath / "extract-temp");
|
||||
|
||||
// Set modified time to release dates, to preserve load order
|
||||
if(mMorrowindDone)
|
||||
bfs::last_write_time(findFile(mOutputPath, "morrowind.esm"), getTime("1 May 2002"));
|
||||
|
||||
if(mTribunalDone)
|
||||
bfs::last_write_time(findFile(mOutputPath, "tribunal.esm"), getTime("6 November 2002"));
|
||||
|
||||
if(mBloodmoonDone)
|
||||
bfs::last_write_time(findFile(mOutputPath, "bloodmoon.esm"), getTime("3 June 2003"));
|
||||
}
|
||||
|
||||
std::string UnshieldThread::GetMWEsmPath()
|
||||
{
|
||||
return findFile(mOutputPath / "Data Files", "morrowind.esm").string();
|
||||
}
|
||||
|
||||
bool UnshieldThread::TribunalDone()
|
||||
{
|
||||
return mTribunalDone;
|
||||
}
|
||||
|
||||
bool UnshieldThread::BloodmoonDone()
|
||||
{
|
||||
return mBloodmoonDone;
|
||||
}
|
||||
|
||||
void UnshieldThread::run()
|
||||
{
|
||||
extract();
|
||||
emit close();
|
||||
}
|
||||
|
||||
UnshieldThread::UnshieldThread()
|
||||
{
|
||||
mMorrowindDone = false;
|
||||
mTribunalDone = false;
|
||||
mBloodmoonDone = false;
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
#ifndef UNSHIELD_THREAD_H
|
||||
#define UNSHIELD_THREAD_H
|
||||
|
||||
#include <QThread>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include <libunshield.h>
|
||||
|
||||
|
||||
class UnshieldThread : public QThread
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
bool SetMorrowindPath(const std::string& path);
|
||||
bool SetTribunalPath(const std::string& path);
|
||||
bool SetBloodmoonPath(const std::string& path);
|
||||
|
||||
void SetOutputPath(const std::string& path);
|
||||
|
||||
bool extract();
|
||||
|
||||
bool TribunalDone();
|
||||
bool BloodmoonDone();
|
||||
|
||||
void Done();
|
||||
|
||||
std::string GetMWEsmPath();
|
||||
|
||||
UnshieldThread();
|
||||
|
||||
private:
|
||||
|
||||
void extract_cab(const boost::filesystem::path& cab, const boost::filesystem::path& output_dir, bool extract_ini = false);
|
||||
bool extract_file(Unshield* unshield, boost::filesystem::path output_dir, const char* prefix, int index);
|
||||
|
||||
boost::filesystem::path mMorrowindPath;
|
||||
boost::filesystem::path mTribunalPath;
|
||||
boost::filesystem::path mBloodmoonPath;
|
||||
|
||||
bool mMorrowindDone;
|
||||
bool mTribunalDone;
|
||||
bool mBloodmoonDone;
|
||||
|
||||
boost::filesystem::path mOutputPath;
|
||||
|
||||
|
||||
protected:
|
||||
virtual void run();
|
||||
|
||||
signals:
|
||||
void signalGUI(QString);
|
||||
void close();
|
||||
};
|
||||
|
||||
#endif
|
@ -1,531 +0,0 @@
|
||||
#include <boost/lexical_cast.hpp>
|
||||
|
||||
#include <OgreTerrain.h>
|
||||
#include <OgreTerrainGroup.h>
|
||||
#include <OgreHardwarePixelBuffer.h>
|
||||
#include <OgreRoot.h>
|
||||
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
|
||||
#include <components/settings/settings.hpp>
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
|
||||
#include "terrainmaterial.hpp"
|
||||
#include "terrain.hpp"
|
||||
#include "renderconst.hpp"
|
||||
#include "shadows.hpp"
|
||||
#include "renderingmanager.hpp"
|
||||
|
||||
using namespace Ogre;
|
||||
|
||||
namespace MWRender
|
||||
{
|
||||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
TerrainManager::TerrainManager(Ogre::SceneManager* mgr, RenderingManager* rend) :
|
||||
mTerrainGroup(TerrainGroup(mgr, Terrain::ALIGN_X_Y, mLandSize, mWorldSize)), mRendering(rend)
|
||||
{
|
||||
mTerrainGlobals = OGRE_NEW TerrainGlobalOptions();
|
||||
|
||||
TerrainMaterialGeneratorPtr matGen;
|
||||
TerrainMaterial* matGenP = new TerrainMaterial();
|
||||
matGen.bind(matGenP);
|
||||
mTerrainGlobals->setDefaultMaterialGenerator(matGen);
|
||||
|
||||
TerrainMaterialGenerator::Profile* const activeProfile =
|
||||
mTerrainGlobals->getDefaultMaterialGenerator()
|
||||
->getActiveProfile();
|
||||
mActiveProfile = static_cast<TerrainMaterial::Profile*>(activeProfile);
|
||||
|
||||
// We don't want any pixel error at all. Really, LOD makes no sense here - morrowind uses 65x65 verts in one cell,
|
||||
// so applying LOD is most certainly slower than doing no LOD at all.
|
||||
// Setting this to 0 seems to cause glitches though. :/
|
||||
mTerrainGlobals->setMaxPixelError(1);
|
||||
|
||||
mTerrainGlobals->setLayerBlendMapSize(ESM::Land::LAND_TEXTURE_SIZE/2 + 1);
|
||||
|
||||
//10 (default) didn't seem to be quite enough
|
||||
mTerrainGlobals->setSkirtSize(128);
|
||||
|
||||
//due to the sudden flick between composite and non composite textures,
|
||||
//this seemed the distance where it wasn't too noticeable
|
||||
mTerrainGlobals->setCompositeMapDistance(mWorldSize*2);
|
||||
|
||||
mTerrainGroup.setOrigin(Vector3(mWorldSize/2,
|
||||
mWorldSize/2,
|
||||
0));
|
||||
|
||||
Terrain::ImportData& importSettings = mTerrainGroup.getDefaultImportSettings();
|
||||
|
||||
importSettings.inputBias = 0;
|
||||
importSettings.terrainSize = mLandSize;
|
||||
importSettings.worldSize = mWorldSize;
|
||||
importSettings.minBatchSize = 9;
|
||||
importSettings.maxBatchSize = mLandSize;
|
||||
|
||||
importSettings.deleteInputData = true;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
float TerrainManager::getTerrainHeightAt(Vector3 worldPos)
|
||||
{
|
||||
Ogre::Terrain* terrain = NULL;
|
||||
float height = mTerrainGroup.getHeightAtWorldPosition(worldPos, &terrain);
|
||||
if (terrain == NULL)
|
||||
return std::numeric_limits<int>().min();
|
||||
return height;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
TerrainManager::~TerrainManager()
|
||||
{
|
||||
OGRE_DELETE mTerrainGlobals;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
void TerrainManager::setDiffuse(const ColourValue& diffuse)
|
||||
{
|
||||
mTerrainGlobals->setCompositeMapDiffuse(diffuse);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
void TerrainManager::setAmbient(const ColourValue& ambient)
|
||||
{
|
||||
mTerrainGlobals->setCompositeMapAmbient(ambient);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
void TerrainManager::cellAdded(MWWorld::Ptr::CellStore *store)
|
||||
{
|
||||
const int cellX = store->mCell->getGridX();
|
||||
const int cellY = store->mCell->getGridY();
|
||||
|
||||
ESM::Land* land =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Land>().search(cellX, cellY);
|
||||
if (land == NULL) // no land data means we're not going to create any terrain.
|
||||
return;
|
||||
|
||||
int dataRequired = ESM::Land::DATA_VHGT | ESM::Land::DATA_VCLR;
|
||||
if (!land->isDataLoaded(dataRequired))
|
||||
{
|
||||
land->loadData(dataRequired);
|
||||
}
|
||||
|
||||
//split the cell terrain into four segments
|
||||
const int numTextures = ESM::Land::LAND_TEXTURE_SIZE/2;
|
||||
|
||||
for ( int x = 0; x < 2; x++ )
|
||||
{
|
||||
for ( int y = 0; y < 2; y++ )
|
||||
{
|
||||
Terrain::ImportData terrainData =
|
||||
mTerrainGroup.getDefaultImportSettings();
|
||||
|
||||
const int terrainX = cellX * 2 + x;
|
||||
const int terrainY = cellY * 2 + y;
|
||||
|
||||
//it makes far more sense to reallocate the memory here,
|
||||
//and let Ogre deal with it due to the issues with deleting
|
||||
//it at the wrong time if using threads (Which Terrain does)
|
||||
terrainData.inputFloat = OGRE_ALLOC_T(float,
|
||||
mLandSize*mLandSize,
|
||||
MEMCATEGORY_GEOMETRY);
|
||||
|
||||
//copy the height data row by row
|
||||
for ( int terrainCopyY = 0; terrainCopyY < mLandSize; terrainCopyY++ )
|
||||
{
|
||||
//the offset of the current segment
|
||||
const size_t yOffset = y * (mLandSize-1) * ESM::Land::LAND_SIZE +
|
||||
//offset of the row
|
||||
terrainCopyY * ESM::Land::LAND_SIZE;
|
||||
const size_t xOffset = x * (mLandSize-1);
|
||||
|
||||
memcpy(&terrainData.inputFloat[terrainCopyY*mLandSize],
|
||||
&land->mLandData->mHeights[yOffset + xOffset],
|
||||
mLandSize*sizeof(float));
|
||||
}
|
||||
|
||||
std::map<uint16_t, int> indexes;
|
||||
initTerrainTextures(&terrainData, cellX, cellY,
|
||||
x * numTextures, y * numTextures,
|
||||
numTextures, indexes, land->mPlugin);
|
||||
|
||||
if (mTerrainGroup.getTerrain(terrainX, terrainY) == NULL)
|
||||
{
|
||||
mTerrainGroup.defineTerrain(terrainX, terrainY, &terrainData);
|
||||
|
||||
mTerrainGroup.loadTerrain(terrainX, terrainY, true);
|
||||
|
||||
Terrain* terrain = mTerrainGroup.getTerrain(terrainX, terrainY);
|
||||
initTerrainBlendMaps(terrain,
|
||||
cellX, cellY,
|
||||
x * numTextures, y * numTextures,
|
||||
numTextures,
|
||||
indexes);
|
||||
terrain->setVisibilityFlags(RV_Terrain);
|
||||
terrain->setRenderQueueGroup(RQG_Main);
|
||||
|
||||
// disable or enable global colour map (depends on available vertex colours)
|
||||
if ( land->mLandData->mUsingColours )
|
||||
{
|
||||
TexturePtr vertex = getVertexColours(land,
|
||||
cellX, cellY,
|
||||
x*(mLandSize-1),
|
||||
y*(mLandSize-1),
|
||||
mLandSize);
|
||||
|
||||
mActiveProfile->setGlobalColourMapEnabled(true);
|
||||
mActiveProfile->setGlobalColourMap (terrain, vertex->getName());
|
||||
}
|
||||
else
|
||||
mActiveProfile->setGlobalColourMapEnabled (false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// when loading from a heightmap, Ogre::Terrain does not update the derived data (normal map, LOD)
|
||||
// synchronously, even if we supply synchronous = true parameter to loadTerrain.
|
||||
// the following to be the only way to make sure derived data is ready when rendering the next frame.
|
||||
while (mTerrainGroup.isDerivedDataUpdateInProgress())
|
||||
{
|
||||
// we need to wait for this to finish
|
||||
OGRE_THREAD_SLEEP(5);
|
||||
Root::getSingleton().getWorkQueue()->processResponses();
|
||||
}
|
||||
|
||||
mTerrainGroup.freeTemporaryResources();
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
void TerrainManager::cellRemoved(MWWorld::Ptr::CellStore *store)
|
||||
{
|
||||
for ( int x = 0; x < 2; x++ )
|
||||
{
|
||||
for ( int y = 0; y < 2; y++ )
|
||||
{
|
||||
int terrainX = store->mCell->getGridX() * 2 + x;
|
||||
int terrainY = store->mCell->getGridY() * 2 + y;
|
||||
if (mTerrainGroup.getTerrain(terrainX, terrainY) != NULL)
|
||||
mTerrainGroup.unloadTerrain(terrainX, terrainY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
void TerrainManager::initTerrainTextures(Terrain::ImportData* terrainData,
|
||||
int cellX, int cellY,
|
||||
int fromX, int fromY, int size,
|
||||
std::map<uint16_t, int>& indexes, size_t plugin)
|
||||
{
|
||||
// FIXME: In a multiple esm configuration, we have multiple palettes. Since this code
|
||||
// crosses cell boundaries, we no longer have a unique terrain palette. Instead, we need
|
||||
// to adopt the following code for a dynamic palette. And this is evil - the current design
|
||||
// does not work well for this task...
|
||||
|
||||
assert(terrainData != NULL && "Must have valid terrain data");
|
||||
assert(fromX >= 0 && fromY >= 0 &&
|
||||
"Can't get a terrain texture on terrain outside the current cell");
|
||||
assert(fromX+size <= ESM::Land::LAND_TEXTURE_SIZE &&
|
||||
fromY+size <= ESM::Land::LAND_TEXTURE_SIZE &&
|
||||
"Can't get a terrain texture on terrain outside the current cell");
|
||||
|
||||
//this ensures that the ltex indexes are sorted (or retrived as sorted
|
||||
//which simplifies shading between cells).
|
||||
//
|
||||
//If we don't sort the ltex indexes, the splatting order may differ between
|
||||
//cells which may lead to inconsistent results when shading between cells
|
||||
int num = MWBase::Environment::get().getWorld()->getStore().get<ESM::LandTexture>().getSize(plugin);
|
||||
std::set<uint16_t> ltexIndexes;
|
||||
for ( int y = fromY; y < fromY + size + 1; y++ )
|
||||
{
|
||||
for ( int x = fromX - 1; x < fromX + size; x++ ) // NB we wrap X from the other side because Y is reversed
|
||||
{
|
||||
int idx = getLtexIndexAt(cellX, cellY, x, y);
|
||||
// This is a quick hack to prevent the program from trying to fetch textures
|
||||
// from a neighboring cell, which might originate from a different plugin,
|
||||
// and use a separate texture palette. Right now, we simply cast it to the
|
||||
// default texture (i.e. 0).
|
||||
if (idx > num)
|
||||
idx = 0;
|
||||
ltexIndexes.insert(idx);
|
||||
}
|
||||
}
|
||||
|
||||
//there is one texture that we want to use as a base (i.e. it won't have
|
||||
//a blend map). This holds the ltex index of that base texture so that
|
||||
//we know not to include it in the output map
|
||||
int baseTexture = -1;
|
||||
for ( std::set<uint16_t>::iterator iter = ltexIndexes.begin();
|
||||
iter != ltexIndexes.end();
|
||||
++iter )
|
||||
{
|
||||
uint16_t ltexIndex = *iter;
|
||||
//this is the base texture, so we can ignore this at present
|
||||
if ( ltexIndex == baseTexture )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::map<uint16_t, int>::const_iterator it = indexes.find(ltexIndex);
|
||||
|
||||
if ( it == indexes.end() )
|
||||
{
|
||||
//NB: All vtex ids are +1 compared to the ltex ids
|
||||
|
||||
const MWWorld::Store<ESM::LandTexture> <exStore =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::LandTexture>();
|
||||
|
||||
// NOTE: using the quick hack above, we should no longer end up with textures indices
|
||||
// that are out of bounds. However, I haven't updated the test to a multi-palette
|
||||
// system yet. We probably need more work here, so we skip it for now.
|
||||
//assert( (int)ltexStore.getSize() >= (int)ltexIndex - 1 &&
|
||||
//"LAND.VTEX must be within the bounds of the LTEX array");
|
||||
|
||||
std::string texture;
|
||||
if ( ltexIndex == 0 )
|
||||
{
|
||||
texture = "_land_default.dds";
|
||||
}
|
||||
else
|
||||
{
|
||||
texture = ltexStore.search(ltexIndex-1, plugin)->mTexture;
|
||||
//TODO this is needed due to MWs messed up texture handling
|
||||
texture = texture.substr(0, texture.rfind(".")) + ".dds";
|
||||
}
|
||||
|
||||
const size_t position = terrainData->layerList.size();
|
||||
terrainData->layerList.push_back(Terrain::LayerInstance());
|
||||
|
||||
terrainData->layerList[position].worldSize = 256;
|
||||
terrainData->layerList[position].textureNames.push_back("textures\\" + texture);
|
||||
|
||||
if ( baseTexture == -1 )
|
||||
{
|
||||
baseTexture = ltexIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
indexes[ltexIndex] = position;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
void TerrainManager::initTerrainBlendMaps(Terrain* terrain,
|
||||
int cellX, int cellY,
|
||||
int fromX, int fromY, int size,
|
||||
const std::map<uint16_t, int>& indexes)
|
||||
{
|
||||
assert(terrain != NULL && "Must have valid terrain");
|
||||
assert(fromX >= 0 && fromY >= 0 &&
|
||||
"Can't get a terrain texture on terrain outside the current cell");
|
||||
assert(fromX+size <= ESM::Land::LAND_TEXTURE_SIZE &&
|
||||
fromY+size <= ESM::Land::LAND_TEXTURE_SIZE &&
|
||||
"Can't get a terrain texture on terrain outside the current cell");
|
||||
|
||||
//size must be a power of 2 as we do divisions with a power of 2 number
|
||||
//that need to result in an integer for correct splatting
|
||||
assert( (size & (size - 1)) == 0 && "Size must be a power of 2");
|
||||
|
||||
const int blendMapSize = terrain->getLayerBlendMapSize();
|
||||
|
||||
//zero out every map
|
||||
std::map<uint16_t, int>::const_iterator iter;
|
||||
for ( iter = indexes.begin(); iter != indexes.end(); ++iter )
|
||||
{
|
||||
float* pBlend = terrain->getLayerBlendMap(iter->second)
|
||||
->getBlendPointer();
|
||||
memset(pBlend, 0, sizeof(float) * blendMapSize * blendMapSize);
|
||||
}
|
||||
|
||||
//covert the ltex data into a set of blend maps
|
||||
for ( int texY = fromY; texY < fromY + size + 1; texY++ )
|
||||
{
|
||||
for ( int texX = fromX - 1; texX < fromX + size; texX++ ) // NB we wrap X from the other side because Y is reversed
|
||||
{
|
||||
const uint16_t ltexIndex = getLtexIndexAt(cellX, cellY, texX, texY);
|
||||
|
||||
//check if it is the base texture (which isn't in the map) and
|
||||
//if it is don't bother altering the blend map for it
|
||||
if ( indexes.find(ltexIndex) == indexes.end() )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
//while texX is the splat index relative to the entire cell,
|
||||
//relX is relative to the current segment we are splatting
|
||||
const int relX = texX - fromX + 1;
|
||||
const int relY = texY - fromY;
|
||||
|
||||
const int layerIndex = indexes.find(ltexIndex)->second;
|
||||
|
||||
float* const pBlend = terrain->getLayerBlendMap(layerIndex)
|
||||
->getBlendPointer();
|
||||
|
||||
//Note: Y is reversed
|
||||
const int splatY = blendMapSize - relY - 1;
|
||||
const int splatX = relX;
|
||||
|
||||
assert(splatX >= 0 && splatX < blendMapSize);
|
||||
assert(splatY >= 0 && splatY < blendMapSize);
|
||||
|
||||
const int index = (splatY)*blendMapSize + splatX;
|
||||
pBlend[index] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
for ( int i = 1; i < terrain->getLayerCount(); i++ )
|
||||
{
|
||||
TerrainLayerBlendMap* blend = terrain->getLayerBlendMap(i);
|
||||
blend->dirty();
|
||||
blend->update();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
int TerrainManager::getLtexIndexAt(int cellX, int cellY,
|
||||
int x, int y)
|
||||
{
|
||||
//check texture index falls within the 9 cell bounds
|
||||
//as this function can't cope with anything above that
|
||||
assert(x >= -ESM::Land::LAND_TEXTURE_SIZE &&
|
||||
y >= -ESM::Land::LAND_TEXTURE_SIZE &&
|
||||
"Trying to get land textures that are out of bounds");
|
||||
|
||||
assert(x < 2*ESM::Land::LAND_TEXTURE_SIZE &&
|
||||
y < 2*ESM::Land::LAND_TEXTURE_SIZE &&
|
||||
"Trying to get land textures that are out of bounds");
|
||||
|
||||
if ( x < 0 )
|
||||
{
|
||||
cellX--;
|
||||
x += ESM::Land::LAND_TEXTURE_SIZE;
|
||||
}
|
||||
else if ( x >= ESM::Land::LAND_TEXTURE_SIZE )
|
||||
{
|
||||
cellX++;
|
||||
x -= ESM::Land::LAND_TEXTURE_SIZE;
|
||||
}
|
||||
|
||||
if ( y < 0 )
|
||||
{
|
||||
cellY--;
|
||||
y += ESM::Land::LAND_TEXTURE_SIZE;
|
||||
}
|
||||
else if ( y >= ESM::Land::LAND_TEXTURE_SIZE )
|
||||
{
|
||||
cellY++;
|
||||
y -= ESM::Land::LAND_TEXTURE_SIZE;
|
||||
}
|
||||
|
||||
|
||||
ESM::Land* land =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Land>().search(cellX, cellY);
|
||||
if ( land != NULL )
|
||||
{
|
||||
if (!land->isDataLoaded(ESM::Land::DATA_VTEX))
|
||||
{
|
||||
land->loadData(ESM::Land::DATA_VTEX);
|
||||
}
|
||||
|
||||
return land->mLandData
|
||||
->mTextures[y * ESM::Land::LAND_TEXTURE_SIZE + x];
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
TexturePtr TerrainManager::getVertexColours(ESM::Land* land,
|
||||
int cellX, int cellY,
|
||||
int fromX, int fromY, int size)
|
||||
{
|
||||
TextureManager* const texMgr = TextureManager::getSingletonPtr();
|
||||
|
||||
const std::string colourTextureName = "VtexColours_" +
|
||||
boost::lexical_cast<std::string>(cellX) +
|
||||
"_" +
|
||||
boost::lexical_cast<std::string>(cellY) +
|
||||
"_" +
|
||||
boost::lexical_cast<std::string>(fromX) +
|
||||
"_" +
|
||||
boost::lexical_cast<std::string>(fromY);
|
||||
|
||||
TexturePtr tex = texMgr->getByName(colourTextureName);
|
||||
if ( !tex.isNull() )
|
||||
{
|
||||
return tex;
|
||||
}
|
||||
|
||||
tex = texMgr->createManual(colourTextureName,
|
||||
ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
|
||||
TEX_TYPE_2D, size, size, 0, PF_BYTE_BGR);
|
||||
|
||||
HardwarePixelBufferSharedPtr pixelBuffer = tex->getBuffer();
|
||||
|
||||
pixelBuffer->lock(HardwareBuffer::HBL_DISCARD);
|
||||
const PixelBox& pixelBox = pixelBuffer->getCurrentLock();
|
||||
|
||||
uint8* pDest = static_cast<uint8*>(pixelBox.data);
|
||||
|
||||
if ( land != NULL )
|
||||
{
|
||||
const char* const colours = land->mLandData->mColours;
|
||||
for ( int y = 0; y < size; y++ )
|
||||
{
|
||||
for ( int x = 0; x < size; x++ )
|
||||
{
|
||||
const size_t colourOffset = (y+fromY)*3*65 + (x+fromX)*3;
|
||||
|
||||
assert( colourOffset < 65*65*3 &&
|
||||
"Colour offset is out of the expected bounds of record" );
|
||||
|
||||
const unsigned char r = colours[colourOffset + 0];
|
||||
const unsigned char g = colours[colourOffset + 1];
|
||||
const unsigned char b = colours[colourOffset + 2];
|
||||
|
||||
//as is the case elsewhere we need to flip the y
|
||||
const size_t imageOffset = (size - 1 - y)*size*4 + x*4;
|
||||
pDest[imageOffset + 0] = b;
|
||||
pDest[imageOffset + 1] = g;
|
||||
pDest[imageOffset + 2] = r;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for ( int y = 0; y < size; y++ )
|
||||
{
|
||||
for ( int x = 0; x < size; x++ )
|
||||
{
|
||||
for ( int k = 0; k < 3; k++ )
|
||||
{
|
||||
*pDest++ = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pixelBuffer->unlock();
|
||||
|
||||
return tex;
|
||||
}
|
||||
|
||||
}
|
@ -1,128 +0,0 @@
|
||||
#ifndef _GAME_RENDER_TERRAIN_H
|
||||
#define _GAME_RENDER_TERRAIN_H
|
||||
|
||||
#include <OgreTerrain.h>
|
||||
#include <OgreTerrainGroup.h>
|
||||
|
||||
#include <components/esm/loadland.hpp>
|
||||
|
||||
#include "terrainmaterial.hpp"
|
||||
|
||||
namespace Ogre{
|
||||
class SceneManager;
|
||||
class TerrainGroup;
|
||||
class TerrainGlobalOptions;
|
||||
class Terrain;
|
||||
}
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
class CellStore;
|
||||
}
|
||||
|
||||
namespace MWRender{
|
||||
|
||||
class RenderingManager;
|
||||
|
||||
/**
|
||||
* Implements the Morrowind terrain using the Ogre Terrain Component
|
||||
*
|
||||
* Each terrain cell is split into four blocks as this leads to an increase
|
||||
* in performance and means we don't hit splat limits quite as much
|
||||
*/
|
||||
class TerrainManager{
|
||||
public:
|
||||
TerrainManager(Ogre::SceneManager* mgr, RenderingManager* rend);
|
||||
virtual ~TerrainManager();
|
||||
|
||||
void setDiffuse(const Ogre::ColourValue& diffuse);
|
||||
void setAmbient(const Ogre::ColourValue& ambient);
|
||||
|
||||
void cellAdded(MWWorld::CellStore* store);
|
||||
void cellRemoved(MWWorld::CellStore* store);
|
||||
|
||||
float getTerrainHeightAt (Ogre::Vector3 worldPos);
|
||||
|
||||
private:
|
||||
Ogre::TerrainGlobalOptions* mTerrainGlobals;
|
||||
Ogre::TerrainGroup mTerrainGroup;
|
||||
|
||||
RenderingManager* mRendering;
|
||||
|
||||
TerrainMaterial::Profile* mActiveProfile;
|
||||
|
||||
/**
|
||||
* The length in verticies of a single terrain block.
|
||||
*/
|
||||
static const int mLandSize = (ESM::Land::LAND_SIZE - 1)/2 + 1;
|
||||
|
||||
/**
|
||||
* The length in game units of a single terrain block.
|
||||
*/
|
||||
static const int mWorldSize = ESM::Land::REAL_SIZE/2;
|
||||
|
||||
/**
|
||||
* Setups up the list of textures for part of a cell, using indexes as
|
||||
* an output to create a mapping of MW LtexIndex to the relevant terrain
|
||||
* layer
|
||||
*
|
||||
* @param terrainData the terrain data to setup the textures for
|
||||
* @param cellX the coord of the cell
|
||||
* @param cellY the coord of the cell
|
||||
* @param fromX the ltex index in the current cell to start making the texture from
|
||||
* @param fromY the ltex index in the current cell to start making the texture from
|
||||
* @param size the size (number of splats) to get
|
||||
* @param indexes a mapping of ltex index to the terrain texture layer that
|
||||
* can be used by initTerrainBlendMaps
|
||||
*/
|
||||
void initTerrainTextures(Ogre::Terrain::ImportData* terrainData,
|
||||
int cellX, int cellY,
|
||||
int fromX, int fromY, int size,
|
||||
std::map<uint16_t, int>& indexes, size_t plugin);
|
||||
|
||||
/**
|
||||
* Creates the blend (splatting maps) for the given terrain from the ltex data.
|
||||
*
|
||||
* @param terrain the terrain object for the current cell
|
||||
* @param cellX the coord of the cell
|
||||
* @param cellY the coord of the cell
|
||||
* @param fromX the ltex index in the current cell to start making the texture from
|
||||
* @param fromY the ltex index in the current cell to start making the texture from
|
||||
* @param size the size (number of splats) to get
|
||||
* @param indexes the mapping of ltex to blend map produced by initTerrainTextures
|
||||
*/
|
||||
void initTerrainBlendMaps(Ogre::Terrain* terrain,
|
||||
int cellX, int cellY,
|
||||
int fromX, int fromY, int size,
|
||||
const std::map<uint16_t, int>& indexes);
|
||||
|
||||
/**
|
||||
* Gets a LTEX index at the given point, assuming the current cell
|
||||
* starts at (0,0). This supports getting values from the surrounding
|
||||
* cells so negative x, y is acceptable
|
||||
*
|
||||
* @param cellX the coord of the cell
|
||||
* @param cellY the coord of the cell
|
||||
* @param x, y the splat position of the ltex index to get relative to the
|
||||
* first splat of the current cell
|
||||
*/
|
||||
int getLtexIndexAt(int cellX, int cellY, int x, int y);
|
||||
|
||||
/**
|
||||
* Due to the fact that Ogre terrain doesn't support vertex colours
|
||||
* we have to generate them manually
|
||||
*
|
||||
* @param cellX the coord of the cell
|
||||
* @param cellY the coord of the cell
|
||||
* @param fromX the *vertex* index in the current cell to start making texture from
|
||||
* @param fromY the *vertex* index in the current cell to start making the texture from
|
||||
* @param size the size (number of vertexes) to get
|
||||
*/
|
||||
Ogre::TexturePtr getVertexColours(ESM::Land* land,
|
||||
int cellX, int cellY,
|
||||
int fromX, int fromY, int size);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // _GAME_RENDER_TERRAIN_H
|
@ -1,246 +0,0 @@
|
||||
#include "terrainmaterial.hpp"
|
||||
|
||||
#include <OgreTerrain.h>
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include <extern/shiny/Main/Factory.hpp>
|
||||
|
||||
namespace
|
||||
{
|
||||
Ogre::String getComponent (int num)
|
||||
{
|
||||
if (num == 0)
|
||||
return "x";
|
||||
else if (num == 1)
|
||||
return "y";
|
||||
else if (num == 2)
|
||||
return "z";
|
||||
else
|
||||
return "w";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
namespace MWRender
|
||||
{
|
||||
|
||||
TerrainMaterial::TerrainMaterial()
|
||||
{
|
||||
mLayerDecl.samplers.push_back(Ogre::TerrainLayerSampler("albedo_specular", Ogre::PF_BYTE_RGBA));
|
||||
//mLayerDecl.samplers.push_back(Ogre::TerrainLayerSampler("normal_height", Ogre::PF_BYTE_RGBA));
|
||||
|
||||
mLayerDecl.elements.push_back(
|
||||
Ogre::TerrainLayerSamplerElement(0, Ogre::TLSS_ALBEDO, 0, 3));
|
||||
//mLayerDecl.elements.push_back(
|
||||
// Ogre::TerrainLayerSamplerElement(0, Ogre::TLSS_SPECULAR, 3, 1));
|
||||
//mLayerDecl.elements.push_back(
|
||||
// Ogre::TerrainLayerSamplerElement(1, Ogre::TLSS_NORMAL, 0, 3));
|
||||
//mLayerDecl.elements.push_back(
|
||||
// Ogre::TerrainLayerSamplerElement(1, Ogre::TLSS_HEIGHT, 3, 1));
|
||||
|
||||
|
||||
mProfiles.push_back(OGRE_NEW Profile(this, "SM2", "Profile for rendering on Shader Model 2 capable cards"));
|
||||
setActiveProfile("SM2");
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
TerrainMaterial::Profile::Profile(Ogre::TerrainMaterialGenerator* parent, const Ogre::String& name, const Ogre::String& desc)
|
||||
: Ogre::TerrainMaterialGenerator::Profile(parent, name, desc)
|
||||
, mGlobalColourMap(false)
|
||||
, mMaterial(0)
|
||||
{
|
||||
}
|
||||
|
||||
TerrainMaterial::Profile::~Profile()
|
||||
{
|
||||
if (mMaterial)
|
||||
sh::Factory::getInstance().destroyMaterialInstance(mMaterial->getName());
|
||||
}
|
||||
|
||||
Ogre::MaterialPtr TerrainMaterial::Profile::generate(const Ogre::Terrain* terrain)
|
||||
{
|
||||
const Ogre::String& matName = terrain->getMaterialName();
|
||||
|
||||
sh::Factory::getInstance().destroyMaterialInstance (matName);
|
||||
|
||||
Ogre::MaterialPtr mat = Ogre::MaterialManager::getSingleton().getByName(matName);
|
||||
if (!mat.isNull())
|
||||
Ogre::MaterialManager::getSingleton().remove(matName);
|
||||
|
||||
mMaterial = sh::Factory::getInstance().createMaterialInstance (matName);
|
||||
mMaterial->setProperty ("allow_fixed_function", sh::makeProperty<sh::BooleanValue>(new sh::BooleanValue(false)));
|
||||
|
||||
int numPasses = getRequiredPasses(terrain);
|
||||
int maxLayersInOnePass = getMaxLayersPerPass(terrain);
|
||||
|
||||
for (int pass=0; pass<numPasses; ++pass)
|
||||
{
|
||||
int layerOffset = maxLayersInOnePass * pass;
|
||||
int blendmapOffset = (pass == 0) ? 1 : 0; // the first layer of the first pass is the base layer and does not need a blend map
|
||||
|
||||
sh::MaterialInstancePass* p = mMaterial->createPass ();
|
||||
|
||||
p->setProperty ("vertex_program", sh::makeProperty<sh::StringValue>(new sh::StringValue("terrain_vertex")));
|
||||
p->setProperty ("fragment_program", sh::makeProperty<sh::StringValue>(new sh::StringValue("terrain_fragment")));
|
||||
if (pass != 0)
|
||||
{
|
||||
p->setProperty ("scene_blend", sh::makeProperty(new sh::StringValue("alpha_blend")));
|
||||
// Only write if depth is equal to the depth value written by the previous pass.
|
||||
p->setProperty ("depth_func", sh::makeProperty(new sh::StringValue("equal")));
|
||||
}
|
||||
|
||||
p->mShaderProperties.setProperty ("colour_map", sh::makeProperty(new sh::BooleanValue(mGlobalColourMap)));
|
||||
p->mShaderProperties.setProperty ("is_first_pass", sh::makeProperty(new sh::BooleanValue(pass == 0)));
|
||||
|
||||
// global colour map
|
||||
sh::MaterialInstanceTextureUnit* colourMap = p->createTextureUnit ("colourMap");
|
||||
colourMap->setProperty ("texture_alias", sh::makeProperty<sh::StringValue> (new sh::StringValue(mMaterial->getName() + "_colourMap")));
|
||||
colourMap->setProperty ("tex_address_mode", sh::makeProperty<sh::StringValue> (new sh::StringValue("clamp")));
|
||||
|
||||
// global normal map
|
||||
sh::MaterialInstanceTextureUnit* normalMap = p->createTextureUnit ("normalMap");
|
||||
normalMap->setProperty ("direct_texture", sh::makeProperty<sh::StringValue> (new sh::StringValue(terrain->getTerrainNormalMap ()->getName())));
|
||||
normalMap->setProperty ("tex_address_mode", sh::makeProperty<sh::StringValue> (new sh::StringValue("clamp")));
|
||||
|
||||
Ogre::uint numLayersInThisPass = std::min(maxLayersInOnePass, terrain->getLayerCount()-layerOffset);
|
||||
|
||||
// HACK: Terrain::getLayerBlendTextureIndex should be const, but it is not.
|
||||
// Remove this once ogre got fixed.
|
||||
Ogre::Terrain* nonconstTerrain = const_cast<Ogre::Terrain*>(terrain);
|
||||
|
||||
// a blend map might be shared between two passes
|
||||
// so we can't just use terrain->getBlendTextureCount()
|
||||
Ogre::uint numBlendTextures=0;
|
||||
std::vector<std::string> blendTextures;
|
||||
for (unsigned int layer=blendmapOffset; layer<numLayersInThisPass; ++layer)
|
||||
{
|
||||
std::string blendTextureName = terrain->getBlendTextureName(nonconstTerrain->getLayerBlendTextureIndex(
|
||||
static_cast<Ogre::uint8>(layerOffset+layer)).first);
|
||||
if (std::find(blendTextures.begin(), blendTextures.end(), blendTextureName) == blendTextures.end())
|
||||
{
|
||||
blendTextures.push_back(blendTextureName);
|
||||
++numBlendTextures;
|
||||
}
|
||||
}
|
||||
|
||||
p->mShaderProperties.setProperty ("num_layers", sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(numLayersInThisPass))));
|
||||
p->mShaderProperties.setProperty ("num_blendmaps", sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(numBlendTextures))));
|
||||
|
||||
// blend maps
|
||||
// the index of the first blend map used in this pass
|
||||
int blendmapStart;
|
||||
if (terrain->getLayerCount() == 1) // special case. if there's only one layer, we don't need blend maps at all
|
||||
blendmapStart = 0;
|
||||
else
|
||||
blendmapStart = nonconstTerrain->getLayerBlendTextureIndex(static_cast<Ogre::uint8>(layerOffset+blendmapOffset)).first;
|
||||
for (Ogre::uint i = 0; i < numBlendTextures; ++i)
|
||||
{
|
||||
sh::MaterialInstanceTextureUnit* blendTex = p->createTextureUnit ("blendMap" + Ogre::StringConverter::toString(i));
|
||||
blendTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(terrain->getBlendTextureName(blendmapStart+i))));
|
||||
blendTex->setProperty ("tex_address_mode", sh::makeProperty (new sh::StringValue("clamp")));
|
||||
}
|
||||
|
||||
// layer maps
|
||||
for (Ogre::uint i = 0; i < numLayersInThisPass; ++i)
|
||||
{
|
||||
sh::MaterialInstanceTextureUnit* diffuseTex = p->createTextureUnit ("diffuseMap" + Ogre::StringConverter::toString(i));
|
||||
diffuseTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(terrain->getLayerTextureName(layerOffset+i, 0))));
|
||||
|
||||
if (i+layerOffset > 0)
|
||||
{
|
||||
int blendTextureIndex = nonconstTerrain->getLayerBlendTextureIndex(static_cast<Ogre::uint8>(layerOffset+i)).first;
|
||||
int blendTextureComponent = nonconstTerrain->getLayerBlendTextureIndex(static_cast<Ogre::uint8>(layerOffset+i)).second;
|
||||
p->mShaderProperties.setProperty ("blendmap_component_" + Ogre::StringConverter::toString(i),
|
||||
sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(blendTextureIndex-blendmapStart) + "." + getComponent(blendTextureComponent))));
|
||||
}
|
||||
else
|
||||
{
|
||||
// just to make it shut up about blendmap_component_0 not existing in the first pass.
|
||||
// it might be retrieved, but will never survive the preprocessing step.
|
||||
p->mShaderProperties.setProperty ("blendmap_component_" + Ogre::StringConverter::toString(i),
|
||||
sh::makeProperty (new sh::StringValue("")));
|
||||
}
|
||||
}
|
||||
|
||||
// shadow
|
||||
for (Ogre::uint i = 0; i < 3; ++i)
|
||||
{
|
||||
sh::MaterialInstanceTextureUnit* shadowTex = p->createTextureUnit ("shadowMap" + Ogre::StringConverter::toString(i));
|
||||
shadowTex->setProperty ("content_type", sh::makeProperty<sh::StringValue> (new sh::StringValue("shadow")));
|
||||
}
|
||||
|
||||
p->mShaderProperties.setProperty ("shadowtexture_offset", sh::makeProperty (new sh::StringValue(
|
||||
Ogre::StringConverter::toString(numBlendTextures + numLayersInThisPass + 2))));
|
||||
|
||||
// make sure the pass index is fed to the permutation handler, because blendmap components may be different
|
||||
p->mShaderProperties.setProperty ("pass_index", sh::makeProperty(new sh::IntValue(pass)));
|
||||
}
|
||||
|
||||
return Ogre::MaterialManager::getSingleton().getByName(matName);
|
||||
}
|
||||
|
||||
void TerrainMaterial::Profile::setGlobalColourMapEnabled (bool enabled)
|
||||
{
|
||||
mGlobalColourMap = enabled;
|
||||
mParent->_markChanged();
|
||||
}
|
||||
|
||||
void TerrainMaterial::Profile::setGlobalColourMap (Ogre::Terrain* terrain, const std::string& name)
|
||||
{
|
||||
sh::Factory::getInstance ().setTextureAlias (terrain->getMaterialName () + "_colourMap", name);
|
||||
}
|
||||
|
||||
Ogre::MaterialPtr TerrainMaterial::Profile::generateForCompositeMap(const Ogre::Terrain* terrain)
|
||||
{
|
||||
throw std::runtime_error ("composite map not supported");
|
||||
}
|
||||
|
||||
Ogre::uint8 TerrainMaterial::Profile::getMaxLayers(const Ogre::Terrain* terrain) const
|
||||
{
|
||||
return 255;
|
||||
}
|
||||
|
||||
int TerrainMaterial::Profile::getMaxLayersPerPass (const Ogre::Terrain* terrain)
|
||||
{
|
||||
// count the texture units free
|
||||
Ogre::uint8 freeTextureUnits = 16;
|
||||
// normalmap
|
||||
--freeTextureUnits;
|
||||
// colourmap
|
||||
--freeTextureUnits;
|
||||
// shadow
|
||||
--freeTextureUnits;
|
||||
--freeTextureUnits;
|
||||
--freeTextureUnits;
|
||||
|
||||
// each layer needs 1.25 units (1xdiffusespec, 0.25xblend)
|
||||
return static_cast<Ogre::uint8>(freeTextureUnits / (1.25f));
|
||||
}
|
||||
|
||||
int TerrainMaterial::Profile::getRequiredPasses (const Ogre::Terrain* terrain)
|
||||
{
|
||||
int maxLayersPerPass = getMaxLayersPerPass(terrain);
|
||||
assert(terrain->getLayerCount());
|
||||
assert(maxLayersPerPass);
|
||||
return std::ceil(static_cast<float>(terrain->getLayerCount()) / maxLayersPerPass);
|
||||
}
|
||||
|
||||
void TerrainMaterial::Profile::updateParams(const Ogre::MaterialPtr& mat, const Ogre::Terrain* terrain)
|
||||
{
|
||||
}
|
||||
|
||||
void TerrainMaterial::Profile::updateParamsForCompositeMap(const Ogre::MaterialPtr& mat, const Ogre::Terrain* terrain)
|
||||
{
|
||||
}
|
||||
|
||||
void TerrainMaterial::Profile::requestOptions(Ogre::Terrain* terrain)
|
||||
{
|
||||
terrain->_setMorphRequired(true);
|
||||
terrain->_setNormalMapRequired(true); // global normal map
|
||||
terrain->_setLightMapRequired(false);
|
||||
terrain->_setCompositeMapRequired(false);
|
||||
}
|
||||
|
||||
}
|
@ -1,88 +0,0 @@
|
||||
/*
|
||||
-----------------------------------------------------------------------------
|
||||
This source file is part of OGRE
|
||||
(Object-oriented Graphics Rendering Engine)
|
||||
For the latest info, see http://www.ogre3d.org/
|
||||
|
||||
Copyright (c) 2000-2011 Torus Knot Software Ltd
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
-----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#ifndef MWRENDER_TERRAINMATERIAL_H
|
||||
#define MWRENDER_TERRAINMATERIAL_H
|
||||
|
||||
#include "OgreTerrainPrerequisites.h"
|
||||
#include "OgreTerrainMaterialGenerator.h"
|
||||
#include "OgreGpuProgramParams.h"
|
||||
|
||||
namespace sh
|
||||
{
|
||||
class MaterialInstance;
|
||||
}
|
||||
|
||||
namespace MWRender
|
||||
{
|
||||
|
||||
class TerrainMaterial : public Ogre::TerrainMaterialGenerator
|
||||
{
|
||||
public:
|
||||
|
||||
class Profile : public Ogre::TerrainMaterialGenerator::Profile
|
||||
{
|
||||
public:
|
||||
Profile(Ogre::TerrainMaterialGenerator* parent, const Ogre::String& name, const Ogre::String& desc);
|
||||
virtual ~Profile();
|
||||
|
||||
virtual bool isVertexCompressionSupported() const { return false; }
|
||||
|
||||
virtual Ogre::MaterialPtr generate(const Ogre::Terrain* terrain);
|
||||
|
||||
virtual Ogre::MaterialPtr generateForCompositeMap(const Ogre::Terrain* terrain);
|
||||
|
||||
virtual Ogre::uint8 getMaxLayers(const Ogre::Terrain* terrain) const;
|
||||
|
||||
virtual void updateParams(const Ogre::MaterialPtr& mat, const Ogre::Terrain* terrain);
|
||||
|
||||
virtual void updateParamsForCompositeMap(const Ogre::MaterialPtr& mat, const Ogre::Terrain* terrain);
|
||||
|
||||
virtual void requestOptions(Ogre::Terrain* terrain);
|
||||
|
||||
void setGlobalColourMapEnabled(bool enabled);
|
||||
void setGlobalColourMap (Ogre::Terrain* terrain, const std::string& name);
|
||||
virtual void setLightmapEnabled(bool) {}
|
||||
|
||||
private:
|
||||
sh::MaterialInstance* mMaterial;
|
||||
|
||||
int getRequiredPasses (const Ogre::Terrain* terrain);
|
||||
int getMaxLayersPerPass (const Ogre::Terrain* terrain);
|
||||
|
||||
bool mGlobalColourMap;
|
||||
|
||||
};
|
||||
|
||||
TerrainMaterial();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif
|
@ -0,0 +1,56 @@
|
||||
#include "terrainstorage.hpp"
|
||||
|
||||
#include "../mwbase/world.hpp"
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
|
||||
namespace MWRender
|
||||
{
|
||||
|
||||
Ogre::AxisAlignedBox TerrainStorage::getBounds()
|
||||
{
|
||||
int minX = 0, minY = 0, maxX = 0, maxY = 0;
|
||||
|
||||
const MWWorld::ESMStore &esmStore =
|
||||
MWBase::Environment::get().getWorld()->getStore();
|
||||
|
||||
MWWorld::Store<ESM::Cell>::iterator it = esmStore.get<ESM::Cell>().extBegin();
|
||||
for (; it != esmStore.get<ESM::Cell>().extEnd(); ++it)
|
||||
{
|
||||
if (it->getGridX() < minX)
|
||||
minX = it->getGridX();
|
||||
if (it->getGridX() > maxX)
|
||||
maxX = it->getGridX();
|
||||
if (it->getGridY() < minY)
|
||||
minY = it->getGridY();
|
||||
if (it->getGridY() > maxY)
|
||||
maxY = it->getGridY();
|
||||
}
|
||||
|
||||
// since grid coords are at cell origin, we need to add 1 cell
|
||||
maxX += 1;
|
||||
maxY += 1;
|
||||
|
||||
return Ogre::AxisAlignedBox(minX, minY, 0, maxX, maxY, 0);
|
||||
}
|
||||
|
||||
ESM::Land* TerrainStorage::getLand(int cellX, int cellY)
|
||||
{
|
||||
const MWWorld::ESMStore &esmStore =
|
||||
MWBase::Environment::get().getWorld()->getStore();
|
||||
ESM::Land* land = esmStore.get<ESM::Land>().search(cellX, cellY);
|
||||
// Load the data we are definitely going to need
|
||||
int mask = ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | ESM::Land::DATA_VCLR | ESM::Land::DATA_VTEX;
|
||||
if (land && !land->isDataLoaded(mask))
|
||||
land->loadData(mask);
|
||||
return land;
|
||||
}
|
||||
|
||||
const ESM::LandTexture* TerrainStorage::getLandTexture(int index, short plugin)
|
||||
{
|
||||
const MWWorld::ESMStore &esmStore =
|
||||
MWBase::Environment::get().getWorld()->getStore();
|
||||
return esmStore.get<ESM::LandTexture>().find(index, plugin);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
#ifndef MWRENDER_TERRAINSTORAGE_H
|
||||
#define MWRENDER_TERRAINSTORAGE_H
|
||||
|
||||
#include <components/terrain/storage.hpp>
|
||||
|
||||
namespace MWRender
|
||||
{
|
||||
|
||||
class TerrainStorage : public Terrain::Storage
|
||||
{
|
||||
private:
|
||||
virtual ESM::Land* getLand (int cellX, int cellY);
|
||||
virtual const ESM::LandTexture* getLandTexture(int index, short plugin);
|
||||
public:
|
||||
virtual Ogre::AxisAlignedBox getBounds();
|
||||
///< Get bounds in cell units
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif
|
@ -0,0 +1,48 @@
|
||||
# Locate LIBUNSHIELD
|
||||
# This module defines
|
||||
# LIBUNSHIELD_LIBRARY
|
||||
# LIBUNSHIELD_FOUND, if false, do not try to link to LibUnshield
|
||||
# LIBUNSHIELD_INCLUDE_DIR, where to find the headers
|
||||
#
|
||||
# Created by Tom Mason (wheybags) for OpenMW (http://openmw.com), based on FindMPG123.cmake
|
||||
#
|
||||
# Ripped off from other sources. In fact, this file is so generic (I
|
||||
# just did a search and replace on another file) that I wonder why the
|
||||
# CMake guys haven't wrapped this entire thing in a single
|
||||
# function. Do we really need to repeat this stuff for every single
|
||||
# library when they all work the same? </today's rant>
|
||||
|
||||
FIND_PATH(LIBUNSHIELD_INCLUDE_DIR libunshield.h
|
||||
HINTS
|
||||
PATHS
|
||||
~/Library/Frameworks
|
||||
/Library/Frameworks
|
||||
/usr/local
|
||||
/usr
|
||||
/sw # Fink
|
||||
/opt/local # DarwinPorts
|
||||
/opt/csw # Blastwave
|
||||
/opt
|
||||
/usr/include
|
||||
)
|
||||
|
||||
FIND_LIBRARY(LIBUNSHIELD_LIBRARY
|
||||
unshield
|
||||
HINTS
|
||||
# PATH_SUFFIXES lib64 lib libs64 libs libs/Win32 libs/Win64
|
||||
PATHS
|
||||
~/Library/Frameworks
|
||||
/Library/Frameworks
|
||||
/usr/local
|
||||
/usr
|
||||
/sw
|
||||
/opt/local
|
||||
/opt/csw
|
||||
/opt
|
||||
/usr/lib
|
||||
)
|
||||
|
||||
IF(LIBUNSHIELD_LIBRARY AND LIBUNSHIELD_INCLUDE_DIR)
|
||||
SET(LIBUNSHIELD_FOUND "YES")
|
||||
ENDIF(LIBUNSHIELD_LIBRARY AND LIBUNSHIELD_INCLUDE_DIR)
|
||||
|
@ -0,0 +1,169 @@
|
||||
#include "chunk.hpp"
|
||||
|
||||
#include <OgreSceneNode.h>
|
||||
#include <OgreHardwareBufferManager.h>
|
||||
|
||||
#include "quadtreenode.hpp"
|
||||
#include "terrain.hpp"
|
||||
#include "storage.hpp"
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
|
||||
Chunk::Chunk(QuadTreeNode* node, short lodLevel)
|
||||
: mNode(node)
|
||||
, mVertexLod(lodLevel)
|
||||
, mAdditionalLod(0)
|
||||
{
|
||||
mVertexData = OGRE_NEW Ogre::VertexData;
|
||||
mVertexData->vertexStart = 0;
|
||||
|
||||
// Set the total number of vertices
|
||||
size_t numVertsOneSide = mNode->getSize() * (ESM::Land::LAND_SIZE-1);
|
||||
numVertsOneSide /= std::pow(2, lodLevel);
|
||||
numVertsOneSide += 1;
|
||||
assert((int)numVertsOneSide == ESM::Land::LAND_SIZE);
|
||||
mVertexData->vertexCount = numVertsOneSide * numVertsOneSide;
|
||||
|
||||
// Set up the vertex declaration, which specifies the info for each vertex (normals, colors, UVs, etc)
|
||||
Ogre::VertexDeclaration* vertexDecl = mVertexData->vertexDeclaration;
|
||||
|
||||
Ogre::HardwareBufferManager* mgr = Ogre::HardwareBufferManager::getSingletonPtr();
|
||||
size_t nextBuffer = 0;
|
||||
|
||||
// Positions
|
||||
vertexDecl->addElement(nextBuffer++, 0, Ogre::VET_FLOAT3, Ogre::VES_POSITION);
|
||||
mVertexBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3),
|
||||
mVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC);
|
||||
// Normals
|
||||
vertexDecl->addElement(nextBuffer++, 0, Ogre::VET_FLOAT3, Ogre::VES_NORMAL);
|
||||
mNormalBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3),
|
||||
mVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC);
|
||||
|
||||
// UV texture coordinates
|
||||
vertexDecl->addElement(nextBuffer++, 0, Ogre::VET_FLOAT2,
|
||||
Ogre::VES_TEXTURE_COORDINATES, 0);
|
||||
Ogre::HardwareVertexBufferSharedPtr uvBuf = mNode->getTerrain()->getVertexBuffer(numVertsOneSide);
|
||||
|
||||
// Colours
|
||||
vertexDecl->addElement(nextBuffer++, 0, Ogre::VET_COLOUR, Ogre::VES_DIFFUSE);
|
||||
mColourBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_COLOUR),
|
||||
mVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC);
|
||||
|
||||
mNode->getTerrain()->getStorage()->fillVertexBuffers(lodLevel, mNode->getSize(), mNode->getCenter(),
|
||||
mVertexBuffer, mNormalBuffer, mColourBuffer);
|
||||
|
||||
mVertexData->vertexBufferBinding->setBinding(0, mVertexBuffer);
|
||||
mVertexData->vertexBufferBinding->setBinding(1, mNormalBuffer);
|
||||
mVertexData->vertexBufferBinding->setBinding(2, uvBuf);
|
||||
mVertexData->vertexBufferBinding->setBinding(3, mColourBuffer);
|
||||
|
||||
mIndexData = OGRE_NEW Ogre::IndexData();
|
||||
mIndexData->indexStart = 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Chunk::updateIndexBuffer()
|
||||
{
|
||||
// Fetch a suitable index buffer (which may be shared)
|
||||
size_t ourLod = mVertexLod + mAdditionalLod;
|
||||
|
||||
int flags = 0;
|
||||
|
||||
for (int i=0; i<4; ++i)
|
||||
{
|
||||
QuadTreeNode* neighbour = mNode->getNeighbour((Direction)i);
|
||||
|
||||
// If the neighbour isn't currently rendering itself,
|
||||
// go up until we find one. NOTE: We don't need to go down,
|
||||
// because in that case neighbour's detail would be higher than
|
||||
// our detail and the neighbour would handle stitching by itself.
|
||||
while (neighbour && !neighbour->hasChunk())
|
||||
neighbour = neighbour->getParent();
|
||||
|
||||
size_t lod = 0;
|
||||
if (neighbour)
|
||||
lod = neighbour->getActualLodLevel();
|
||||
|
||||
if (lod <= ourLod) // We only need to worry about neighbours less detailed than we are -
|
||||
lod = 0; // neighbours with more detail will do the stitching themselves
|
||||
|
||||
// Use 4 bits for each LOD delta
|
||||
if (lod > 0)
|
||||
{
|
||||
assert (lod - ourLod < std::pow(2,4));
|
||||
flags |= int(lod - ourLod) << (4*i);
|
||||
}
|
||||
}
|
||||
|
||||
flags |= ((int)mAdditionalLod) << (4*4);
|
||||
|
||||
size_t numIndices;
|
||||
mIndexBuffer = mNode->getTerrain()->getIndexBuffer(flags, numIndices);
|
||||
mIndexData->indexCount = numIndices;
|
||||
mIndexData->indexBuffer = mIndexBuffer;
|
||||
}
|
||||
|
||||
Chunk::~Chunk()
|
||||
{
|
||||
OGRE_DELETE mVertexData;
|
||||
OGRE_DELETE mIndexData;
|
||||
}
|
||||
|
||||
void Chunk::setMaterial(const Ogre::MaterialPtr &material)
|
||||
{
|
||||
mMaterial = material;
|
||||
}
|
||||
|
||||
const Ogre::AxisAlignedBox& Chunk::getBoundingBox(void) const
|
||||
{
|
||||
return mNode->getBoundingBox();
|
||||
}
|
||||
|
||||
Ogre::Real Chunk::getBoundingRadius(void) const
|
||||
{
|
||||
return mNode->getBoundingBox().getHalfSize().length();
|
||||
}
|
||||
|
||||
void Chunk::_updateRenderQueue(Ogre::RenderQueue* queue)
|
||||
{
|
||||
queue->addRenderable(this, mRenderQueueID);
|
||||
}
|
||||
|
||||
void Chunk::visitRenderables(Ogre::Renderable::Visitor* visitor,
|
||||
bool debugRenderables)
|
||||
{
|
||||
visitor->visit(this, 0, false);
|
||||
}
|
||||
|
||||
const Ogre::MaterialPtr& Chunk::getMaterial(void) const
|
||||
{
|
||||
return mMaterial;
|
||||
}
|
||||
|
||||
void Chunk::getRenderOperation(Ogre::RenderOperation& op)
|
||||
{
|
||||
assert (!mIndexBuffer.isNull() && "Trying to render, but no index buffer set!");
|
||||
op.useIndexes = true;
|
||||
op.operationType = Ogre::RenderOperation::OT_TRIANGLE_LIST;
|
||||
op.vertexData = mVertexData;
|
||||
op.indexData = mIndexData;
|
||||
}
|
||||
|
||||
void Chunk::getWorldTransforms(Ogre::Matrix4* xform) const
|
||||
{
|
||||
*xform = getParentSceneNode()->_getFullTransform();
|
||||
}
|
||||
|
||||
Ogre::Real Chunk::getSquaredViewDepth(const Ogre::Camera* cam) const
|
||||
{
|
||||
return getParentSceneNode()->getSquaredViewDepth(cam);
|
||||
}
|
||||
|
||||
const Ogre::LightList& Chunk::getLights(void) const
|
||||
{
|
||||
return queryLights();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
#ifndef COMPONENTS_TERRAIN_TERRAINBATCH_H
|
||||
#define COMPONENTS_TERRAIN_TERRAINBATCH_H
|
||||
|
||||
#include <OgreRenderable.h>
|
||||
#include <OgreMovableObject.h>
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
|
||||
class QuadTreeNode;
|
||||
|
||||
/**
|
||||
* @brief Renders a chunk of terrain, either using alpha splatting or a composite map.
|
||||
*/
|
||||
class Chunk : public Ogre::Renderable, public Ogre::MovableObject
|
||||
{
|
||||
public:
|
||||
/// @param lodLevel LOD level for the vertex buffer.
|
||||
Chunk (QuadTreeNode* node, short lodLevel);
|
||||
virtual ~Chunk();
|
||||
|
||||
void setMaterial (const Ogre::MaterialPtr& material);
|
||||
|
||||
/// Set additional LOD applied on top of vertex LOD. \n
|
||||
/// This is achieved by changing the index buffer to omit vertices.
|
||||
void setAdditionalLod (size_t lod) { mAdditionalLod = lod; }
|
||||
size_t getAdditionalLod() { return mAdditionalLod; }
|
||||
|
||||
void updateIndexBuffer();
|
||||
|
||||
// Inherited from MovableObject
|
||||
virtual const Ogre::String& getMovableType(void) const { static Ogre::String t = "MW_TERRAIN"; return t; }
|
||||
virtual const Ogre::AxisAlignedBox& getBoundingBox(void) const;
|
||||
virtual Ogre::Real getBoundingRadius(void) const;
|
||||
virtual void _updateRenderQueue(Ogre::RenderQueue* queue);
|
||||
virtual void visitRenderables(Renderable::Visitor* visitor,
|
||||
bool debugRenderables = false);
|
||||
|
||||
// Inherited from Renderable
|
||||
virtual const Ogre::MaterialPtr& getMaterial(void) const;
|
||||
virtual void getRenderOperation(Ogre::RenderOperation& op);
|
||||
virtual void getWorldTransforms(Ogre::Matrix4* xform) const;
|
||||
virtual Ogre::Real getSquaredViewDepth(const Ogre::Camera* cam) const;
|
||||
virtual const Ogre::LightList& getLights(void) const;
|
||||
|
||||
private:
|
||||
QuadTreeNode* mNode;
|
||||
Ogre::MaterialPtr mMaterial;
|
||||
|
||||
size_t mVertexLod;
|
||||
size_t mAdditionalLod;
|
||||
|
||||
Ogre::VertexData* mVertexData;
|
||||
Ogre::IndexData* mIndexData;
|
||||
Ogre::HardwareVertexBufferSharedPtr mVertexBuffer;
|
||||
Ogre::HardwareVertexBufferSharedPtr mNormalBuffer;
|
||||
Ogre::HardwareVertexBufferSharedPtr mColourBuffer;
|
||||
Ogre::HardwareIndexBufferSharedPtr mIndexBuffer;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -1,14 +0,0 @@
|
||||
#include "esm_land_factory.hpp"
|
||||
|
||||
// The first one already includes the others implicitly, but it
|
||||
// doesn't hurt to be explicit.
|
||||
#include "../esm_store/store.hpp"
|
||||
#include "../esm/esmreader.hpp"
|
||||
#include "../esm/loadland.hpp"
|
||||
|
||||
using namespace Terrain;
|
||||
|
||||
bool ESMLandFactory::has(int x, int y)
|
||||
{
|
||||
return store.landscapes.has(x,y);
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
#ifndef TERRAIN_ESM_LAND_FACTORY_H
|
||||
#define TERRAIN_ESM_LAND_FACTORY_H
|
||||
|
||||
#include "land_factory.hpp"
|
||||
|
||||
namespace ESMS
|
||||
{
|
||||
struct ESMStore;
|
||||
}
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
class ESMReader;
|
||||
}
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
/*
|
||||
Land factory that loads data from ESM files.
|
||||
*/
|
||||
class ESMLandFactory
|
||||
{
|
||||
ESMS::ESMStore &store;
|
||||
ESM::ESMReader &reader;
|
||||
|
||||
public:
|
||||
// Initialize the land factory. Note that refrences to the given
|
||||
// store and reader are stored in the class, so the given objects
|
||||
// must be valid for a long as you plan to use this factory.
|
||||
ESMLandFactory(ESMS::ESMStore &st, ESM::ESMReader &rd)
|
||||
: store(st), reader(rd) {}
|
||||
|
||||
// True if this factory has any data for the given grid cell.
|
||||
bool has(int x, int y);
|
||||
};
|
||||
}
|
||||
#endif
|
@ -1,38 +0,0 @@
|
||||
#ifndef TERRAIN_HEIGHTMAP_H
|
||||
#define TERRAIN_HEIGHTMAP_H
|
||||
|
||||
/*
|
||||
Generic interface for a structure holding heightmap data.
|
||||
|
||||
A HeightMap returns information about landscape data in the form of
|
||||
a regular grid of float heights.
|
||||
*/
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
struct HeightMap
|
||||
{
|
||||
// Get height from grid position, counted from 0 to getNumX/Y().
|
||||
virtual float getHeight(int x, int y) = 0;
|
||||
|
||||
// Get heigth from vertex index, assumed to be y*getNumX() + x.
|
||||
virtual float getHeight(int index) = 0;
|
||||
|
||||
virtual float getMinX() = 0;
|
||||
virtual float getMaxX() = 0;
|
||||
virtual float getMinY() = 0;
|
||||
virtual float getMaxY() = 0;
|
||||
|
||||
virtual int getNumX() = 0;
|
||||
virtual int getNumY() = 0;
|
||||
|
||||
// True if the given coordinate is within the grid
|
||||
bool isWithin(float x, float y)
|
||||
{
|
||||
return
|
||||
x >= getMinX() && x < getMaxX() &&
|
||||
y >= getMinY() && y < getMaxY();
|
||||
}
|
||||
};
|
||||
}
|
||||
#endif
|
@ -1,72 +0,0 @@
|
||||
#ifndef TERRAIN_HEIGHTMAPBUF_H
|
||||
#define TERRAIN_HEIGHTMAPBUF_H
|
||||
|
||||
/*
|
||||
A HeightMap implementation that stores heigths in a buffer.
|
||||
*/
|
||||
|
||||
#include "heightmap.hpp"
|
||||
#include "land_factory.hpp"
|
||||
#include <vector>
|
||||
#include <cassert>
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
class HeightMapBuffer : public HeightMap
|
||||
{
|
||||
std::vector<float> buf;
|
||||
|
||||
float beginX, sizeX, endX;
|
||||
float beginY, sizeY, endY;
|
||||
|
||||
int numX, numY;
|
||||
|
||||
public:
|
||||
void load(LandDataPtr data, const LandInfo &info)
|
||||
{
|
||||
// We don't support other kinds of grid data yet.
|
||||
assert(info.grid == LGT_Quadratic);
|
||||
assert(info.data == LDT_Float);
|
||||
|
||||
// Set up internal data
|
||||
beginX = info.xoffset;
|
||||
sizeX = info.xsize;
|
||||
endX = beginX+sizeX;
|
||||
numX = info.numx;
|
||||
|
||||
beginY = info.yoffset;
|
||||
sizeY = info.ysize;
|
||||
endY = beginY+sizeY;
|
||||
numY = info.numy;
|
||||
|
||||
// Prepare the buffer and load it
|
||||
buf.resize(numX*numY);
|
||||
|
||||
data.read(&buf[0], buf.size()*sizeof(float));
|
||||
}
|
||||
|
||||
// Functions inherited from HeightMap:
|
||||
|
||||
float getHeight(int x, int y)
|
||||
{
|
||||
assert(x>=0 && x<numX);
|
||||
assert(y>=0 && y<numY);
|
||||
return getHeight(x + y*numX);
|
||||
}
|
||||
|
||||
float getHeight(int index)
|
||||
{
|
||||
assert(index >= 0 && index < buf.size());
|
||||
return buf[index];
|
||||
}
|
||||
|
||||
float getMinX() { return beginX; }
|
||||
float getMaxX() { return endX; }
|
||||
float getMinY() { return beginY; }
|
||||
float getMaxY() { return endY; }
|
||||
|
||||
int getNumX() { return numX; }
|
||||
int getNumY() { return numY; }
|
||||
};
|
||||
}
|
||||
#endif
|
@ -1,43 +0,0 @@
|
||||
#ifndef TERRAIN_LAND_FACTORY_H
|
||||
#define TERRAIN_LAND_FACTORY_H
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
enum LandInfoGridType
|
||||
{
|
||||
LGT_Quadratic
|
||||
};
|
||||
|
||||
enum LandInfoDataType
|
||||
{
|
||||
LDT_Float
|
||||
};
|
||||
|
||||
struct LandInfo
|
||||
{
|
||||
// Type information
|
||||
LandInfoGridType grid;
|
||||
LandInfoDataType data;
|
||||
|
||||
// Landscape size and number of vertices. Note that xsize and
|
||||
// ysize may be negative, signaling a flipped landscape in that
|
||||
// direction.
|
||||
float xsize, ysize;
|
||||
int numx, numy;
|
||||
|
||||
// World offset along the same x/y axes. Whether these are set or
|
||||
// used depends on the client implementation.
|
||||
float xoffset, yoffset;
|
||||
};
|
||||
|
||||
/*
|
||||
Factory class that provides streams to land data cells. Each
|
||||
"cell" has a unique integer coordinate in the plane.
|
||||
*/
|
||||
struct LandFactory
|
||||
{
|
||||
// True if this factory has any data for the given grid cell.
|
||||
virtual bool has(int x, int y) = 0;
|
||||
};
|
||||
}
|
||||
#endif
|
@ -0,0 +1,302 @@
|
||||
#include "material.hpp"
|
||||
|
||||
#include <OgreMaterialManager.h>
|
||||
#include <OgreTechnique.h>
|
||||
#include <OgrePass.h>
|
||||
|
||||
#include <extern/shiny/Main/Factory.hpp>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
int getBlendmapIndexForLayer (int layerIndex)
|
||||
{
|
||||
return std::floor((layerIndex-1)/4.f);
|
||||
}
|
||||
|
||||
std::string getBlendmapComponentForLayer (int layerIndex)
|
||||
{
|
||||
int n = (layerIndex-1)%4;
|
||||
if (n == 0)
|
||||
return "x";
|
||||
if (n == 1)
|
||||
return "y";
|
||||
if (n == 2)
|
||||
return "z";
|
||||
else
|
||||
return "w";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
|
||||
MaterialGenerator::MaterialGenerator(bool shaders)
|
||||
: mShaders(shaders)
|
||||
, mShadows(false)
|
||||
, mSplitShadows(false)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
int MaterialGenerator::getMaxLayersPerPass ()
|
||||
{
|
||||
// count the texture units free
|
||||
Ogre::uint8 freeTextureUnits = 16;
|
||||
|
||||
// first layer doesn't need blendmap
|
||||
--freeTextureUnits;
|
||||
|
||||
if (mSplitShadows)
|
||||
freeTextureUnits -= 3;
|
||||
else if (mShadows)
|
||||
--freeTextureUnits;
|
||||
|
||||
// each layer needs 1.25 units (1xdiffusespec, 0.25xblend)
|
||||
return static_cast<Ogre::uint8>(freeTextureUnits / (1.25f)) + 1;
|
||||
}
|
||||
|
||||
int MaterialGenerator::getRequiredPasses ()
|
||||
{
|
||||
int maxLayersPerPass = getMaxLayersPerPass();
|
||||
return std::max(1.f, std::ceil(static_cast<float>(mLayerList.size()) / maxLayersPerPass));
|
||||
}
|
||||
|
||||
Ogre::MaterialPtr MaterialGenerator::generate(Ogre::MaterialPtr mat)
|
||||
{
|
||||
return create(mat, false, false);
|
||||
}
|
||||
|
||||
Ogre::MaterialPtr MaterialGenerator::generateForCompositeMapRTT(Ogre::MaterialPtr mat)
|
||||
{
|
||||
return create(mat, true, false);
|
||||
}
|
||||
|
||||
Ogre::MaterialPtr MaterialGenerator::generateForCompositeMap(Ogre::MaterialPtr mat)
|
||||
{
|
||||
return create(mat, false, true);
|
||||
}
|
||||
|
||||
Ogre::MaterialPtr MaterialGenerator::create(Ogre::MaterialPtr mat, bool renderCompositeMap, bool displayCompositeMap)
|
||||
{
|
||||
assert(!renderCompositeMap || !displayCompositeMap);
|
||||
if (!mat.isNull())
|
||||
{
|
||||
sh::Factory::getInstance().destroyMaterialInstance(mat->getName());
|
||||
Ogre::MaterialManager::getSingleton().remove(mat->getName());
|
||||
}
|
||||
|
||||
static int count = 0;
|
||||
std::stringstream name;
|
||||
name << "terrain/mat" << count++;
|
||||
|
||||
if (!mShaders)
|
||||
{
|
||||
mat = Ogre::MaterialManager::getSingleton().create(name.str(),
|
||||
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
|
||||
Ogre::Technique* technique = mat->getTechnique(0);
|
||||
technique->removeAllPasses();
|
||||
|
||||
if (displayCompositeMap)
|
||||
{
|
||||
Ogre::Pass* pass = technique->createPass();
|
||||
pass->setVertexColourTracking(Ogre::TVC_AMBIENT|Ogre::TVC_DIFFUSE);
|
||||
pass->createTextureUnitState(mCompositeMap)->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP);
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(mLayerList.size() == mBlendmapList.size()+1);
|
||||
std::vector<Ogre::TexturePtr>::iterator blend = mBlendmapList.begin();
|
||||
for (std::vector<std::string>::iterator layer = mLayerList.begin(); layer != mLayerList.end(); ++layer)
|
||||
{
|
||||
Ogre::Pass* pass = technique->createPass();
|
||||
pass->setLightingEnabled(false);
|
||||
pass->setVertexColourTracking(Ogre::TVC_NONE);
|
||||
// TODO: How to handle fog?
|
||||
pass->setFog(true, Ogre::FOG_NONE);
|
||||
|
||||
bool first = (layer == mLayerList.begin());
|
||||
|
||||
Ogre::TextureUnitState* tus;
|
||||
|
||||
if (!first)
|
||||
{
|
||||
pass->setSceneBlending(Ogre::SBT_TRANSPARENT_ALPHA);
|
||||
pass->setDepthFunction(Ogre::CMPF_EQUAL);
|
||||
|
||||
tus = pass->createTextureUnitState((*blend)->getName());
|
||||
tus->setAlphaOperation(Ogre::LBX_BLEND_TEXTURE_ALPHA,
|
||||
Ogre::LBS_TEXTURE,
|
||||
Ogre::LBS_TEXTURE);
|
||||
tus->setColourOperationEx(Ogre::LBX_BLEND_DIFFUSE_ALPHA,
|
||||
Ogre::LBS_TEXTURE,
|
||||
Ogre::LBS_TEXTURE);
|
||||
tus->setIsAlpha(true);
|
||||
tus->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP);
|
||||
|
||||
float scale = (16/(16.f+1.f));
|
||||
tus->setTextureScale(1.f/scale,1.f/scale);
|
||||
}
|
||||
|
||||
// Add the actual layer texture on top of the alpha map.
|
||||
tus = pass->createTextureUnitState("textures\\" + *layer);
|
||||
if (!first)
|
||||
tus->setColourOperationEx(Ogre::LBX_BLEND_DIFFUSE_ALPHA,
|
||||
Ogre::LBS_TEXTURE,
|
||||
Ogre::LBS_CURRENT);
|
||||
|
||||
tus->setTextureScale(1/16.f,1/16.f);
|
||||
|
||||
if (!first)
|
||||
++blend;
|
||||
}
|
||||
|
||||
if (!renderCompositeMap)
|
||||
{
|
||||
Ogre::Pass* lightingPass = technique->createPass();
|
||||
lightingPass->setSceneBlending(Ogre::SBT_MODULATE);
|
||||
lightingPass->setVertexColourTracking(Ogre::TVC_AMBIENT|Ogre::TVC_DIFFUSE);
|
||||
lightingPass->setFog(true, Ogre::FOG_NONE);
|
||||
}
|
||||
}
|
||||
|
||||
return mat;
|
||||
}
|
||||
else
|
||||
{
|
||||
sh::MaterialInstance* material = sh::Factory::getInstance().createMaterialInstance (name.str());
|
||||
material->setProperty ("allow_fixed_function", sh::makeProperty<sh::BooleanValue>(new sh::BooleanValue(false)));
|
||||
|
||||
if (displayCompositeMap)
|
||||
{
|
||||
sh::MaterialInstancePass* p = material->createPass ();
|
||||
|
||||
p->setProperty ("vertex_program", sh::makeProperty<sh::StringValue>(new sh::StringValue("terrain_vertex")));
|
||||
p->setProperty ("fragment_program", sh::makeProperty<sh::StringValue>(new sh::StringValue("terrain_fragment")));
|
||||
p->mShaderProperties.setProperty ("is_first_pass", sh::makeProperty(new sh::BooleanValue(true)));
|
||||
p->mShaderProperties.setProperty ("render_composite_map", sh::makeProperty(new sh::BooleanValue(false)));
|
||||
p->mShaderProperties.setProperty ("display_composite_map", sh::makeProperty(new sh::BooleanValue(true)));
|
||||
p->mShaderProperties.setProperty ("num_layers", sh::makeProperty (new sh::StringValue("0")));
|
||||
p->mShaderProperties.setProperty ("num_blendmaps", sh::makeProperty (new sh::StringValue("0")));
|
||||
|
||||
sh::MaterialInstanceTextureUnit* tex = p->createTextureUnit ("compositeMap");
|
||||
tex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(mCompositeMap)));
|
||||
tex->setProperty ("tex_address_mode", sh::makeProperty (new sh::StringValue("clamp")));
|
||||
|
||||
// shadow. TODO: repeated, put in function
|
||||
if (mShadows)
|
||||
{
|
||||
for (Ogre::uint i = 0; i < (mSplitShadows ? 3 : 1); ++i)
|
||||
{
|
||||
sh::MaterialInstanceTextureUnit* shadowTex = p->createTextureUnit ("shadowMap" + Ogre::StringConverter::toString(i));
|
||||
shadowTex->setProperty ("content_type", sh::makeProperty<sh::StringValue> (new sh::StringValue("shadow")));
|
||||
}
|
||||
}
|
||||
p->mShaderProperties.setProperty ("shadowtexture_offset", sh::makeProperty (new sh::StringValue(
|
||||
Ogre::StringConverter::toString(1))));
|
||||
|
||||
p->mShaderProperties.setProperty ("pass_index", sh::makeProperty(new sh::IntValue(0)));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
int numPasses = getRequiredPasses();
|
||||
assert(numPasses);
|
||||
int maxLayersInOnePass = getMaxLayersPerPass();
|
||||
|
||||
for (int pass=0; pass<numPasses; ++pass)
|
||||
{
|
||||
int layerOffset = maxLayersInOnePass * pass;
|
||||
int blendmapOffset = (pass == 0) ? 1 : 0; // the first layer of the first pass is the base layer and does not need a blend map
|
||||
|
||||
sh::MaterialInstancePass* p = material->createPass ();
|
||||
|
||||
p->setProperty ("vertex_program", sh::makeProperty<sh::StringValue>(new sh::StringValue("terrain_vertex")));
|
||||
p->setProperty ("fragment_program", sh::makeProperty<sh::StringValue>(new sh::StringValue("terrain_fragment")));
|
||||
if (pass != 0)
|
||||
{
|
||||
p->setProperty ("scene_blend", sh::makeProperty(new sh::StringValue("alpha_blend")));
|
||||
// Only write if depth is equal to the depth value written by the previous pass.
|
||||
p->setProperty ("depth_func", sh::makeProperty(new sh::StringValue("equal")));
|
||||
}
|
||||
|
||||
p->mShaderProperties.setProperty ("is_first_pass", sh::makeProperty(new sh::BooleanValue(pass == 0)));
|
||||
p->mShaderProperties.setProperty ("render_composite_map", sh::makeProperty(new sh::BooleanValue(renderCompositeMap)));
|
||||
p->mShaderProperties.setProperty ("display_composite_map", sh::makeProperty(new sh::BooleanValue(displayCompositeMap)));
|
||||
|
||||
Ogre::uint numLayersInThisPass = std::min(maxLayersInOnePass, (int)mLayerList.size()-layerOffset);
|
||||
|
||||
// a blend map might be shared between two passes
|
||||
Ogre::uint numBlendTextures=0;
|
||||
std::vector<std::string> blendTextures;
|
||||
for (unsigned int layer=blendmapOffset; layer<numLayersInThisPass; ++layer)
|
||||
{
|
||||
std::string blendTextureName = mBlendmapList[getBlendmapIndexForLayer(layerOffset+layer)]->getName();
|
||||
if (std::find(blendTextures.begin(), blendTextures.end(), blendTextureName) == blendTextures.end())
|
||||
{
|
||||
blendTextures.push_back(blendTextureName);
|
||||
++numBlendTextures;
|
||||
}
|
||||
}
|
||||
|
||||
p->mShaderProperties.setProperty ("num_layers", sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(numLayersInThisPass))));
|
||||
p->mShaderProperties.setProperty ("num_blendmaps", sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(numBlendTextures))));
|
||||
|
||||
// blend maps
|
||||
// the index of the first blend map used in this pass
|
||||
int blendmapStart;
|
||||
if (mLayerList.size() == 1) // special case. if there's only one layer, we don't need blend maps at all
|
||||
blendmapStart = 0;
|
||||
else
|
||||
blendmapStart = getBlendmapIndexForLayer(layerOffset+blendmapOffset);
|
||||
for (Ogre::uint i = 0; i < numBlendTextures; ++i)
|
||||
{
|
||||
sh::MaterialInstanceTextureUnit* blendTex = p->createTextureUnit ("blendMap" + Ogre::StringConverter::toString(i));
|
||||
blendTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(mBlendmapList[blendmapStart+i]->getName())));
|
||||
blendTex->setProperty ("tex_address_mode", sh::makeProperty (new sh::StringValue("clamp")));
|
||||
}
|
||||
|
||||
// layer maps
|
||||
for (Ogre::uint i = 0; i < numLayersInThisPass; ++i)
|
||||
{
|
||||
sh::MaterialInstanceTextureUnit* diffuseTex = p->createTextureUnit ("diffuseMap" + Ogre::StringConverter::toString(i));
|
||||
diffuseTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue("textures\\"+mLayerList[layerOffset+i])));
|
||||
|
||||
if (i+layerOffset > 0)
|
||||
{
|
||||
int blendTextureIndex = getBlendmapIndexForLayer(layerOffset+i);
|
||||
std::string blendTextureComponent = getBlendmapComponentForLayer(layerOffset+i);
|
||||
p->mShaderProperties.setProperty ("blendmap_component_" + Ogre::StringConverter::toString(i),
|
||||
sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(blendTextureIndex-blendmapStart) + "." + blendTextureComponent)));
|
||||
}
|
||||
else
|
||||
{
|
||||
// just to make it shut up about blendmap_component_0 not existing in the first pass.
|
||||
// it might be retrieved, but will never survive the preprocessing step.
|
||||
p->mShaderProperties.setProperty ("blendmap_component_" + Ogre::StringConverter::toString(i),
|
||||
sh::makeProperty (new sh::StringValue("")));
|
||||
}
|
||||
}
|
||||
|
||||
// shadow
|
||||
if (mShadows)
|
||||
{
|
||||
for (Ogre::uint i = 0; i < (mSplitShadows ? 3 : 1); ++i)
|
||||
{
|
||||
sh::MaterialInstanceTextureUnit* shadowTex = p->createTextureUnit ("shadowMap" + Ogre::StringConverter::toString(i));
|
||||
shadowTex->setProperty ("content_type", sh::makeProperty<sh::StringValue> (new sh::StringValue("shadow")));
|
||||
}
|
||||
}
|
||||
p->mShaderProperties.setProperty ("shadowtexture_offset", sh::makeProperty (new sh::StringValue(
|
||||
Ogre::StringConverter::toString(numBlendTextures + numLayersInThisPass))));
|
||||
|
||||
// Make sure the pass index is fed to the permutation handler, because blendmap components may be different
|
||||
p->mShaderProperties.setProperty ("pass_index", sh::makeProperty(new sh::IntValue(pass)));
|
||||
}
|
||||
}
|
||||
}
|
||||
return Ogre::MaterialManager::getSingleton().getByName(name.str());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
#ifndef COMPONENTS_TERRAIN_MATERIAL_H
|
||||
#define COMPONENTS_TERRAIN_MATERIAL_H
|
||||
|
||||
#include <OgreMaterial.h>
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
|
||||
class MaterialGenerator
|
||||
{
|
||||
public:
|
||||
/// @param layerList layer textures
|
||||
/// @param blendmapList blend textures
|
||||
/// @param shaders Whether to use shaders. With a shader, blendmap packing can be used (4 channels instead of one),
|
||||
/// so if this parameter is true, then the supplied blend maps are expected to be packed.
|
||||
MaterialGenerator (bool shaders);
|
||||
|
||||
void setLayerList (const std::vector<std::string>& layerList) { mLayerList = layerList; }
|
||||
bool hasLayers() { return mLayerList.size(); }
|
||||
void setBlendmapList (const std::vector<Ogre::TexturePtr>& blendmapList) { mBlendmapList = blendmapList; }
|
||||
const std::vector<Ogre::TexturePtr>& getBlendmapList() { return mBlendmapList; }
|
||||
void setCompositeMap (const std::string& name) { mCompositeMap = name; }
|
||||
|
||||
void enableShadows(bool shadows) { mShadows = shadows; }
|
||||
void enableSplitShadows(bool splitShadows) { mSplitShadows = splitShadows; }
|
||||
|
||||
/// Creates a material suitable for displaying a chunk of terrain using alpha-blending.
|
||||
/// @param mat Material that will be replaced by the generated material. May be empty as well, in which case
|
||||
/// a new material is created.
|
||||
Ogre::MaterialPtr generate (Ogre::MaterialPtr mat);
|
||||
|
||||
/// Creates a material suitable for displaying a chunk of terrain using a ready-made composite map.
|
||||
/// @param mat Material that will be replaced by the generated material. May be empty as well, in which case
|
||||
/// a new material is created.
|
||||
Ogre::MaterialPtr generateForCompositeMap (Ogre::MaterialPtr mat);
|
||||
|
||||
/// Creates a material suitable for rendering composite maps, i.e. for "baking" several layer textures
|
||||
/// into one. The main difference compared to a normal material is that no shading is applied at this point.
|
||||
/// @param mat Material that will be replaced by the generated material. May be empty as well, in which case
|
||||
/// a new material is created.
|
||||
Ogre::MaterialPtr generateForCompositeMapRTT (Ogre::MaterialPtr mat);
|
||||
|
||||
private:
|
||||
Ogre::MaterialPtr create (Ogre::MaterialPtr mat, bool renderCompositeMap, bool displayCompositeMap);
|
||||
|
||||
int getRequiredPasses ();
|
||||
int getMaxLayersPerPass ();
|
||||
|
||||
int mNumLayers;
|
||||
std::vector<std::string> mLayerList;
|
||||
std::vector<Ogre::TexturePtr> mBlendmapList;
|
||||
std::string mCompositeMap;
|
||||
bool mShaders;
|
||||
bool mShadows;
|
||||
bool mSplitShadows;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -0,0 +1,479 @@
|
||||
#include "quadtreenode.hpp"
|
||||
|
||||
#include <OgreSceneManager.h>
|
||||
#include <OgreManualObject.h>
|
||||
|
||||
#include "terrain.hpp"
|
||||
#include "chunk.hpp"
|
||||
#include "storage.hpp"
|
||||
|
||||
#include "material.hpp"
|
||||
|
||||
using namespace Terrain;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// Utility functions for neighbour finding algorithm
|
||||
ChildDirection reflect(ChildDirection dir, Direction dir2)
|
||||
{
|
||||
assert(dir != Root);
|
||||
|
||||
const int lookupTable[4][4] =
|
||||
{
|
||||
// NW NE SW SE
|
||||
{ SW, SE, NW, NE }, // N
|
||||
{ NE, NW, SE, SW }, // E
|
||||
{ SW, SE, NW, NE }, // S
|
||||
{ NE, NW, SE, SW } // W
|
||||
};
|
||||
return (ChildDirection)lookupTable[dir2][dir];
|
||||
}
|
||||
|
||||
bool adjacent(ChildDirection dir, Direction dir2)
|
||||
{
|
||||
assert(dir != Root);
|
||||
const bool lookupTable[4][4] =
|
||||
{
|
||||
// NW NE SW SE
|
||||
{ true, true, false, false }, // N
|
||||
{ false, true, false, true }, // E
|
||||
{ false, false, true, true }, // S
|
||||
{ true, false, true, false } // W
|
||||
};
|
||||
return lookupTable[dir2][dir];
|
||||
}
|
||||
|
||||
// Algorithm described by Hanan Samet - 'Neighbour Finding in Quadtrees'
|
||||
// http://www.cs.umd.edu/~hjs/pubs/SametPRIP81.pdf
|
||||
Terrain::QuadTreeNode* searchNeighbourRecursive (Terrain::QuadTreeNode* currentNode, Terrain::Direction dir)
|
||||
{
|
||||
if (!currentNode->getParent())
|
||||
return NULL; // Arrived at root node, the root node does not have neighbours
|
||||
|
||||
Terrain::QuadTreeNode* nextNode;
|
||||
if (adjacent(currentNode->getDirection(), dir))
|
||||
nextNode = searchNeighbourRecursive(currentNode->getParent(), dir);
|
||||
else
|
||||
nextNode = currentNode->getParent();
|
||||
|
||||
if (nextNode && nextNode->hasChildren())
|
||||
return nextNode->getChild(reflect(currentNode->getDirection(), dir));
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
// Ogre::AxisAlignedBox::distance is broken in 1.8.
|
||||
Ogre::Real distance(const Ogre::AxisAlignedBox& box, const Ogre::Vector3& v)
|
||||
{
|
||||
|
||||
if (box.contains(v))
|
||||
return 0;
|
||||
else
|
||||
{
|
||||
Ogre::Vector3 maxDist(0,0,0);
|
||||
const Ogre::Vector3& minimum = box.getMinimum();
|
||||
const Ogre::Vector3& maximum = box.getMaximum();
|
||||
|
||||
if (v.x < minimum.x)
|
||||
maxDist.x = minimum.x - v.x;
|
||||
else if (v.x > maximum.x)
|
||||
maxDist.x = v.x - maximum.x;
|
||||
|
||||
if (v.y < minimum.y)
|
||||
maxDist.y = minimum.y - v.y;
|
||||
else if (v.y > maximum.y)
|
||||
maxDist.y = v.y - maximum.y;
|
||||
|
||||
if (v.z < minimum.z)
|
||||
maxDist.z = minimum.z - v.z;
|
||||
else if (v.z > maximum.z)
|
||||
maxDist.z = v.z - maximum.z;
|
||||
|
||||
return maxDist.length();
|
||||
}
|
||||
}
|
||||
|
||||
// Create a 2D quad
|
||||
void makeQuad(Ogre::SceneManager* sceneMgr, float left, float top, float right, float bottom, Ogre::MaterialPtr material)
|
||||
{
|
||||
Ogre::ManualObject* manual = sceneMgr->createManualObject();
|
||||
|
||||
// Use identity view/projection matrices to get a 2d quad
|
||||
manual->setUseIdentityProjection(true);
|
||||
manual->setUseIdentityView(true);
|
||||
|
||||
manual->begin(material->getName());
|
||||
|
||||
float normLeft = left*2-1;
|
||||
float normTop = top*2-1;
|
||||
float normRight = right*2-1;
|
||||
float normBottom = bottom*2-1;
|
||||
|
||||
manual->position(normLeft, normTop, 0.0);
|
||||
manual->textureCoord(0, 1);
|
||||
manual->position(normRight, normTop, 0.0);
|
||||
manual->textureCoord(1, 1);
|
||||
manual->position(normRight, normBottom, 0.0);
|
||||
manual->textureCoord(1, 0);
|
||||
manual->position(normLeft, normBottom, 0.0);
|
||||
manual->textureCoord(0, 0);
|
||||
|
||||
manual->quad(0,1,2,3);
|
||||
|
||||
manual->end();
|
||||
|
||||
Ogre::AxisAlignedBox aabInf;
|
||||
aabInf.setInfinite();
|
||||
manual->setBoundingBox(aabInf);
|
||||
|
||||
sceneMgr->getRootSceneNode()->createChildSceneNode()->attachObject(manual);
|
||||
}
|
||||
}
|
||||
|
||||
QuadTreeNode::QuadTreeNode(Terrain* terrain, ChildDirection dir, float size, const Ogre::Vector2 ¢er, QuadTreeNode* parent)
|
||||
: mSize(size)
|
||||
, mCenter(center)
|
||||
, mParent(parent)
|
||||
, mDirection(dir)
|
||||
, mIsDummy(false)
|
||||
, mSceneNode(NULL)
|
||||
, mTerrain(terrain)
|
||||
, mChunk(NULL)
|
||||
, mMaterialGenerator(NULL)
|
||||
, mBounds(Ogre::AxisAlignedBox::BOX_NULL)
|
||||
, mWorldBounds(Ogre::AxisAlignedBox::BOX_NULL)
|
||||
{
|
||||
mBounds.setNull();
|
||||
for (int i=0; i<4; ++i)
|
||||
mChildren[i] = NULL;
|
||||
for (int i=0; i<4; ++i)
|
||||
mNeighbours[i] = NULL;
|
||||
|
||||
mSceneNode = mTerrain->getSceneManager()->getRootSceneNode()->createChildSceneNode(
|
||||
Ogre::Vector3(mCenter.x*8192, mCenter.y*8192, 0));
|
||||
|
||||
mLodLevel = log2(mSize);
|
||||
|
||||
mMaterialGenerator = new MaterialGenerator(mTerrain->getShadersEnabled());
|
||||
}
|
||||
|
||||
void QuadTreeNode::createChild(ChildDirection id, float size, const Ogre::Vector2 ¢er)
|
||||
{
|
||||
mChildren[id] = new QuadTreeNode(mTerrain, id, size, center, this);
|
||||
}
|
||||
|
||||
QuadTreeNode::~QuadTreeNode()
|
||||
{
|
||||
for (int i=0; i<4; ++i)
|
||||
delete mChildren[i];
|
||||
delete mChunk;
|
||||
delete mMaterialGenerator;
|
||||
}
|
||||
|
||||
QuadTreeNode* QuadTreeNode::getNeighbour(Direction dir)
|
||||
{
|
||||
return mNeighbours[static_cast<int>(dir)];
|
||||
}
|
||||
|
||||
void QuadTreeNode::initNeighbours()
|
||||
{
|
||||
for (int i=0; i<4; ++i)
|
||||
mNeighbours[i] = searchNeighbourRecursive(this, (Direction)i);
|
||||
|
||||
if (hasChildren())
|
||||
for (int i=0; i<4; ++i)
|
||||
mChildren[i]->initNeighbours();
|
||||
}
|
||||
|
||||
void QuadTreeNode::initAabb()
|
||||
{
|
||||
if (hasChildren())
|
||||
{
|
||||
for (int i=0; i<4; ++i)
|
||||
{
|
||||
mChildren[i]->initAabb();
|
||||
mBounds.merge(mChildren[i]->getBoundingBox());
|
||||
}
|
||||
mBounds = Ogre::AxisAlignedBox (Ogre::Vector3(-mSize/2*8192, -mSize/2*8192, mBounds.getMinimum().z),
|
||||
Ogre::Vector3(mSize/2*8192, mSize/2*8192, mBounds.getMaximum().z));
|
||||
}
|
||||
mWorldBounds = Ogre::AxisAlignedBox(mBounds.getMinimum() + Ogre::Vector3(mCenter.x*8192, mCenter.y*8192, 0),
|
||||
mBounds.getMaximum() + Ogre::Vector3(mCenter.x*8192, mCenter.y*8192, 0));
|
||||
}
|
||||
|
||||
void QuadTreeNode::setBoundingBox(const Ogre::AxisAlignedBox &box)
|
||||
{
|
||||
mBounds = box;
|
||||
}
|
||||
|
||||
const Ogre::AxisAlignedBox& QuadTreeNode::getBoundingBox()
|
||||
{
|
||||
return mBounds;
|
||||
}
|
||||
|
||||
void QuadTreeNode::update(const Ogre::Vector3 &cameraPos)
|
||||
{
|
||||
const Ogre::AxisAlignedBox& bounds = getBoundingBox();
|
||||
if (bounds.isNull())
|
||||
return;
|
||||
|
||||
float dist = distance(mWorldBounds, cameraPos);
|
||||
|
||||
if (!mTerrain->getDistantLandEnabled())
|
||||
{
|
||||
if (dist > 8192*2)
|
||||
{
|
||||
destroyChunks();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/// \todo implement error metrics or some other means of not using arbitrary values
|
||||
/// (general quality needs to be user configurable as well)
|
||||
size_t wantedLod = 0;
|
||||
if (dist > 8192*1)
|
||||
wantedLod = 1;
|
||||
if (dist > 8192*2)
|
||||
wantedLod = 2;
|
||||
if (dist > 8192*5)
|
||||
wantedLod = 3;
|
||||
if (dist > 8192*12)
|
||||
wantedLod = 4;
|
||||
if (dist > 8192*32)
|
||||
wantedLod = 5;
|
||||
if (dist > 8192*64)
|
||||
wantedLod = 6;
|
||||
|
||||
if (mSize <= mTerrain->getMaxBatchSize() && mLodLevel <= wantedLod)
|
||||
{
|
||||
bool hadChunk = hasChunk();
|
||||
// Wanted LOD is small enough to render this node in one chunk
|
||||
if (!mChunk)
|
||||
{
|
||||
mChunk = new Chunk(this, mLodLevel);
|
||||
mChunk->setVisibilityFlags(mTerrain->getVisiblityFlags());
|
||||
mChunk->setCastShadows(true);
|
||||
mSceneNode->attachObject(mChunk);
|
||||
|
||||
mMaterialGenerator->enableShadows(mTerrain->getShadowsEnabled());
|
||||
mMaterialGenerator->enableSplitShadows(mTerrain->getSplitShadowsEnabled());
|
||||
|
||||
if (mSize == 1)
|
||||
{
|
||||
ensureLayerInfo();
|
||||
mChunk->setMaterial(mMaterialGenerator->generate(mChunk->getMaterial()));
|
||||
}
|
||||
else
|
||||
{
|
||||
ensureCompositeMap();
|
||||
mMaterialGenerator->setCompositeMap(mCompositeMap->getName());
|
||||
mChunk->setMaterial(mMaterialGenerator->generateForCompositeMap(mChunk->getMaterial()));
|
||||
}
|
||||
}
|
||||
|
||||
// Additional (index buffer) LOD is currently disabled.
|
||||
// This is due to a problem with the LOD selection when a node splits.
|
||||
// After splitting, the distance is measured from the children's bounding boxes, which are possibly
|
||||
// further away than the original node's bounding box, possibly causing a child to switch to a *lower* LOD
|
||||
// than the original node.
|
||||
// In short, we'd sometimes get a switch to a lesser detail when actually moving closer.
|
||||
// This wouldn't be so bad, but unfortunately it also breaks LOD edge connections if a neighbour
|
||||
// node hasn't split yet, and has a higher LOD than our node's child:
|
||||
// ----- ----- ------------
|
||||
// | LOD | LOD | |
|
||||
// | 1 | 1 | |
|
||||
// |-----|-----| LOD 0 |
|
||||
// | LOD | LOD | |
|
||||
// | 0 | 0 | |
|
||||
// ----- ----- ------------
|
||||
// To prevent this, nodes of the same size need to always select the same LOD, which is basically what we're
|
||||
// doing here.
|
||||
// But this "solution" does increase triangle overhead, so eventually we need to find a more clever way.
|
||||
//mChunk->setAdditionalLod(wantedLod - mLodLevel);
|
||||
|
||||
mChunk->setVisible(true);
|
||||
|
||||
if (!hadChunk && hasChildren())
|
||||
{
|
||||
for (int i=0; i<4; ++i)
|
||||
mChildren[i]->hideChunks();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Wanted LOD is too detailed to be rendered in one chunk,
|
||||
// so split it up by delegating to child nodes
|
||||
if (mChunk)
|
||||
mChunk->setVisible(false);
|
||||
assert(hasChildren() && "Leaf node's LOD needs to be 0");
|
||||
for (int i=0; i<4; ++i)
|
||||
mChildren[i]->update(cameraPos);
|
||||
}
|
||||
}
|
||||
|
||||
void QuadTreeNode::hideChunks()
|
||||
{
|
||||
if (mChunk)
|
||||
mChunk->setVisible(false);
|
||||
else if (hasChildren())
|
||||
for (int i=0; i<4; ++i)
|
||||
mChildren[i]->hideChunks();
|
||||
}
|
||||
|
||||
void QuadTreeNode::destroyChunks()
|
||||
{
|
||||
if (mChunk)
|
||||
{
|
||||
Ogre::MaterialManager::getSingleton().remove(mChunk->getMaterial()->getName());
|
||||
mSceneNode->detachObject(mChunk);
|
||||
|
||||
delete mChunk;
|
||||
mChunk = NULL;
|
||||
// destroy blendmaps
|
||||
if (mMaterialGenerator)
|
||||
{
|
||||
const std::vector<Ogre::TexturePtr>& list = mMaterialGenerator->getBlendmapList();
|
||||
for (std::vector<Ogre::TexturePtr>::const_iterator it = list.begin(); it != list.end(); ++it)
|
||||
Ogre::TextureManager::getSingleton().remove((*it)->getName());
|
||||
mMaterialGenerator->setBlendmapList(std::vector<Ogre::TexturePtr>());
|
||||
mMaterialGenerator->setLayerList(std::vector<std::string>());
|
||||
mMaterialGenerator->setCompositeMap("");
|
||||
}
|
||||
|
||||
if (!mCompositeMap.isNull())
|
||||
{
|
||||
Ogre::TextureManager::getSingleton().remove(mCompositeMap->getName());
|
||||
mCompositeMap.setNull();
|
||||
}
|
||||
}
|
||||
else if (hasChildren())
|
||||
for (int i=0; i<4; ++i)
|
||||
mChildren[i]->destroyChunks();
|
||||
}
|
||||
|
||||
void QuadTreeNode::updateIndexBuffers()
|
||||
{
|
||||
if (hasChunk())
|
||||
mChunk->updateIndexBuffer();
|
||||
else if (hasChildren())
|
||||
{
|
||||
for (int i=0; i<4; ++i)
|
||||
mChildren[i]->updateIndexBuffers();
|
||||
}
|
||||
}
|
||||
|
||||
bool QuadTreeNode::hasChunk()
|
||||
{
|
||||
return mChunk && mChunk->getVisible();
|
||||
}
|
||||
|
||||
size_t QuadTreeNode::getActualLodLevel()
|
||||
{
|
||||
assert(hasChunk() && "Can't get actual LOD level if this node has no render chunk");
|
||||
return mLodLevel + mChunk->getAdditionalLod();
|
||||
}
|
||||
|
||||
void QuadTreeNode::ensureLayerInfo()
|
||||
{
|
||||
if (mMaterialGenerator->hasLayers())
|
||||
return;
|
||||
|
||||
std::vector<Ogre::TexturePtr> blendmaps;
|
||||
std::vector<std::string> layerList;
|
||||
mTerrain->getStorage()->getBlendmaps(mSize, mCenter, mTerrain->getShadersEnabled(), blendmaps, layerList);
|
||||
|
||||
mMaterialGenerator->setLayerList(layerList);
|
||||
mMaterialGenerator->setBlendmapList(blendmaps);
|
||||
}
|
||||
|
||||
void QuadTreeNode::prepareForCompositeMap(Ogre::TRect<float> area)
|
||||
{
|
||||
Ogre::SceneManager* sceneMgr = mTerrain->getCompositeMapSceneManager();
|
||||
|
||||
if (mIsDummy)
|
||||
{
|
||||
// TODO - why is this completely black?
|
||||
// TODO - store this default material somewhere instead of creating one for each empty cell
|
||||
MaterialGenerator matGen(mTerrain->getShadersEnabled());
|
||||
std::vector<std::string> layer;
|
||||
layer.push_back("_land_default.dds");
|
||||
matGen.setLayerList(layer);
|
||||
makeQuad(sceneMgr, area.left, area.top, area.right, area.bottom, matGen.generate(Ogre::MaterialPtr()));
|
||||
return;
|
||||
}
|
||||
if (mSize > 1)
|
||||
{
|
||||
assert(hasChildren());
|
||||
|
||||
// 0,0 -------- 1,0
|
||||
// | | |
|
||||
// |-----|------|
|
||||
// | | |
|
||||
// 0,1 -------- 1,1
|
||||
|
||||
float halfW = area.width()/2.f;
|
||||
float halfH = area.height()/2.f;
|
||||
mChildren[NW]->prepareForCompositeMap(Ogre::TRect<float>(area.left, area.top, area.right-halfW, area.bottom-halfH));
|
||||
mChildren[NE]->prepareForCompositeMap(Ogre::TRect<float>(area.left+halfW, area.top, area.right, area.bottom-halfH));
|
||||
mChildren[SW]->prepareForCompositeMap(Ogre::TRect<float>(area.left, area.top+halfH, area.right-halfW, area.bottom));
|
||||
mChildren[SE]->prepareForCompositeMap(Ogre::TRect<float>(area.left+halfW, area.top+halfH, area.right, area.bottom));
|
||||
}
|
||||
else
|
||||
{
|
||||
ensureLayerInfo();
|
||||
|
||||
Ogre::MaterialPtr material = mMaterialGenerator->generateForCompositeMapRTT(Ogre::MaterialPtr());
|
||||
makeQuad(sceneMgr, area.left, area.top, area.right, area.bottom, material);
|
||||
}
|
||||
}
|
||||
|
||||
void QuadTreeNode::ensureCompositeMap()
|
||||
{
|
||||
if (!mCompositeMap.isNull())
|
||||
return;
|
||||
|
||||
static int i=0;
|
||||
std::stringstream name;
|
||||
name << "terrain/comp" << i++;
|
||||
|
||||
const int size = 128;
|
||||
mCompositeMap = Ogre::TextureManager::getSingleton().createManual(
|
||||
name.str(), Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
|
||||
Ogre::TEX_TYPE_2D, size, size, Ogre::MIP_DEFAULT, Ogre::PF_A8B8G8R8);
|
||||
|
||||
// Create quads for each cell
|
||||
prepareForCompositeMap(Ogre::TRect<float>(0,0,1,1));
|
||||
|
||||
mTerrain->renderCompositeMap(mCompositeMap);
|
||||
|
||||
mTerrain->clearCompositeMapSceneManager();
|
||||
|
||||
}
|
||||
|
||||
void QuadTreeNode::applyMaterials()
|
||||
{
|
||||
if (mChunk)
|
||||
{
|
||||
mMaterialGenerator->enableShadows(mTerrain->getShadowsEnabled());
|
||||
mMaterialGenerator->enableSplitShadows(mTerrain->getSplitShadowsEnabled());
|
||||
if (mSize <= 1)
|
||||
mChunk->setMaterial(mMaterialGenerator->generate(Ogre::MaterialPtr()));
|
||||
else
|
||||
mChunk->setMaterial(mMaterialGenerator->generateForCompositeMap(Ogre::MaterialPtr()));
|
||||
}
|
||||
if (hasChildren())
|
||||
for (int i=0; i<4; ++i)
|
||||
mChildren[i]->applyMaterials();
|
||||
}
|
||||
|
||||
void QuadTreeNode::setVisible(bool visible)
|
||||
{
|
||||
if (!visible && mChunk)
|
||||
mChunk->setVisible(false);
|
||||
|
||||
if (hasChildren())
|
||||
for (int i=0; i<4; ++i)
|
||||
mChildren[i]->setVisible(visible);
|
||||
}
|
@ -0,0 +1,156 @@
|
||||
#ifndef COMPONENTS_TERRAIN_QUADTREENODE_H
|
||||
#define COMPONENTS_TERRAIN_QUADTREENODE_H
|
||||
|
||||
#include <OgreAxisAlignedBox.h>
|
||||
#include <OgreVector2.h>
|
||||
#include <OgreTexture.h>
|
||||
|
||||
namespace Ogre
|
||||
{
|
||||
class Rectangle2D;
|
||||
}
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
class Terrain;
|
||||
class Chunk;
|
||||
class MaterialGenerator;
|
||||
|
||||
enum Direction
|
||||
{
|
||||
North = 0,
|
||||
East = 1,
|
||||
South = 2,
|
||||
West = 3
|
||||
};
|
||||
|
||||
enum ChildDirection
|
||||
{
|
||||
NW = 0,
|
||||
NE = 1,
|
||||
SW = 2,
|
||||
SE = 3,
|
||||
Root
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A node in the quad tree for our terrain. Depending on LOD,
|
||||
* a node can either choose to render itself in one batch (merging its children),
|
||||
* or delegate the render process to its children, rendering each child in at least one batch.
|
||||
*/
|
||||
class QuadTreeNode
|
||||
{
|
||||
public:
|
||||
/// @param terrain
|
||||
/// @param dir relative to parent, or Root if we are the root node
|
||||
/// @param size size (in *cell* units!)
|
||||
/// @param center center (in *cell* units!)
|
||||
/// @param parent parent node
|
||||
QuadTreeNode (Terrain* terrain, ChildDirection dir, float size, const Ogre::Vector2& center, QuadTreeNode* parent);
|
||||
~QuadTreeNode();
|
||||
|
||||
void setVisible(bool visible);
|
||||
|
||||
/// Rebuild all materials
|
||||
void applyMaterials();
|
||||
|
||||
/// Initialize neighbours - do this after the quadtree is built
|
||||
void initNeighbours();
|
||||
/// Initialize bounding boxes of non-leafs by merging children bounding boxes.
|
||||
/// Do this after the quadtree is built - note that leaf bounding boxes
|
||||
/// need to be set first via setBoundingBox!
|
||||
void initAabb();
|
||||
|
||||
/// @note takes ownership of \a child
|
||||
void createChild (ChildDirection id, float size, const Ogre::Vector2& center);
|
||||
|
||||
/// Mark this node as a dummy node. This can happen if the terrain size isn't a power of two.
|
||||
/// For the QuadTree to work, we need to round the size up to a power of two, which means we'll
|
||||
/// end up with empty nodes that don't actually render anything.
|
||||
void markAsDummy() { mIsDummy = true; }
|
||||
bool isDummy() { return mIsDummy; }
|
||||
|
||||
QuadTreeNode* getParent() { return mParent; }
|
||||
|
||||
int getSize() { return mSize; }
|
||||
Ogre::Vector2 getCenter() { return mCenter; }
|
||||
|
||||
bool hasChildren() { return mChildren[0] != 0; }
|
||||
QuadTreeNode* getChild(ChildDirection dir) { return mChildren[dir]; }
|
||||
|
||||
/// Get neighbour node in this direction
|
||||
QuadTreeNode* getNeighbour (Direction dir);
|
||||
|
||||
/// Returns our direction relative to the parent node, or Root if we are the root node.
|
||||
ChildDirection getDirection() { return mDirection; }
|
||||
|
||||
/// Set bounding box in local coordinates. Should be done at load time for leaf nodes.
|
||||
/// Other nodes can merge AABB of child nodes.
|
||||
void setBoundingBox (const Ogre::AxisAlignedBox& box);
|
||||
|
||||
/// Get bounding box in local coordinates
|
||||
const Ogre::AxisAlignedBox& getBoundingBox();
|
||||
|
||||
Terrain* getTerrain() { return mTerrain; }
|
||||
|
||||
/// Adjust LODs for the given camera position, possibly splitting up chunks or merging them.
|
||||
void update (const Ogre::Vector3& cameraPos);
|
||||
|
||||
/// Adjust index buffers of chunks to stitch together chunks of different LOD, so that cracks are avoided.
|
||||
/// Call after QuadTreeNode::update!
|
||||
void updateIndexBuffers();
|
||||
|
||||
/// Hide chunks rendered by this node and all its children
|
||||
void hideChunks();
|
||||
|
||||
/// Destroy chunks rendered by this node and all its children
|
||||
void destroyChunks();
|
||||
|
||||
/// Get the effective LOD level if this node was rendered in one chunk
|
||||
/// with ESM::Land::LAND_SIZE^2 vertices
|
||||
size_t getNativeLodLevel() { return mLodLevel; }
|
||||
|
||||
/// Get the effective current LOD level used by the chunk rendering this node
|
||||
size_t getActualLodLevel();
|
||||
|
||||
/// Is this node currently configured to render itself?
|
||||
bool hasChunk();
|
||||
|
||||
/// Add a textured quad to a specific 2d area in the composite map scenemanager.
|
||||
/// Only nodes with size <= 1 can be rendered with alpha blending, so larger nodes will simply
|
||||
/// call this method on their children.
|
||||
/// @param area area in image space to put the quad
|
||||
/// @param quads collect quads here so they can be deleted later
|
||||
void prepareForCompositeMap(Ogre::TRect<float> area);
|
||||
|
||||
private:
|
||||
// Stored here for convenience in case we need layer list again
|
||||
MaterialGenerator* mMaterialGenerator;
|
||||
|
||||
bool mIsDummy;
|
||||
float mSize;
|
||||
size_t mLodLevel; // LOD if we were to render this node in one chunk
|
||||
Ogre::AxisAlignedBox mBounds;
|
||||
Ogre::AxisAlignedBox mWorldBounds;
|
||||
ChildDirection mDirection;
|
||||
Ogre::Vector2 mCenter;
|
||||
|
||||
Ogre::SceneNode* mSceneNode;
|
||||
|
||||
QuadTreeNode* mParent;
|
||||
QuadTreeNode* mChildren[4];
|
||||
QuadTreeNode* mNeighbours[4];
|
||||
|
||||
Chunk* mChunk;
|
||||
|
||||
Terrain* mTerrain;
|
||||
|
||||
Ogre::TexturePtr mCompositeMap;
|
||||
|
||||
void ensureLayerInfo();
|
||||
void ensureCompositeMap();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -0,0 +1,470 @@
|
||||
#include "storage.hpp"
|
||||
|
||||
#include <OgreVector2.h>
|
||||
#include <OgreTextureManager.h>
|
||||
#include <OgreStringConverter.h>
|
||||
#include <OgreRenderSystem.h>
|
||||
#include <OgreRoot.h>
|
||||
|
||||
#include <boost/multi_array.hpp>
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
|
||||
struct VertexElement
|
||||
{
|
||||
Ogre::Vector3 pos;
|
||||
Ogre::Vector3 normal;
|
||||
Ogre::ColourValue colour;
|
||||
};
|
||||
|
||||
bool Storage::getMinMaxHeights(float size, const Ogre::Vector2 ¢er, float &min, float &max)
|
||||
{
|
||||
assert (size <= 1 && "Storage::getMinMaxHeights, chunk size should be <= 1 cell");
|
||||
|
||||
/// \todo investigate if min/max heights should be stored at load time in ESM::Land instead
|
||||
|
||||
Ogre::Vector2 origin = center - Ogre::Vector2(size/2.f, size/2.f);
|
||||
|
||||
assert(origin.x == (int) origin.x);
|
||||
assert(origin.y == (int) origin.y);
|
||||
|
||||
int cellX = origin.x;
|
||||
int cellY = origin.y;
|
||||
|
||||
const ESM::Land* land = getLand(cellX, cellY);
|
||||
if (!land)
|
||||
return false;
|
||||
|
||||
min = std::numeric_limits<float>().max();
|
||||
max = -std::numeric_limits<float>().max();
|
||||
for (int row=0; row<ESM::Land::LAND_SIZE; ++row)
|
||||
{
|
||||
for (int col=0; col<ESM::Land::LAND_SIZE; ++col)
|
||||
{
|
||||
float h = land->mLandData->mHeights[col*ESM::Land::LAND_SIZE+row];
|
||||
if (h > max)
|
||||
max = h;
|
||||
if (h < min)
|
||||
min = h;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Storage::fixNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row)
|
||||
{
|
||||
while (col >= ESM::Land::LAND_SIZE-1)
|
||||
{
|
||||
++cellY;
|
||||
col -= ESM::Land::LAND_SIZE-1;
|
||||
}
|
||||
while (row >= ESM::Land::LAND_SIZE-1)
|
||||
{
|
||||
++cellX;
|
||||
row -= ESM::Land::LAND_SIZE-1;
|
||||
}
|
||||
while (col < 0)
|
||||
{
|
||||
--cellY;
|
||||
col += ESM::Land::LAND_SIZE-1;
|
||||
}
|
||||
while (row < 0)
|
||||
{
|
||||
--cellX;
|
||||
row += ESM::Land::LAND_SIZE-1;
|
||||
}
|
||||
ESM::Land* land = getLand(cellX, cellY);
|
||||
if (land && land->mHasData)
|
||||
{
|
||||
normal.x = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3];
|
||||
normal.y = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1];
|
||||
normal.z = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2];
|
||||
normal.normalise();
|
||||
}
|
||||
else
|
||||
normal = Ogre::Vector3(0,0,1);
|
||||
}
|
||||
|
||||
void Storage::averageNormal(Ogre::Vector3 &normal, int cellX, int cellY, int col, int row)
|
||||
{
|
||||
Ogre::Vector3 n1,n2,n3,n4;
|
||||
fixNormal(n1, cellX, cellY, col+1, row);
|
||||
fixNormal(n2, cellX, cellY, col-1, row);
|
||||
fixNormal(n3, cellX, cellY, col, row+1);
|
||||
fixNormal(n4, cellX, cellY, col, row-1);
|
||||
normal = (n1+n2+n3+n4);
|
||||
normal.normalise();
|
||||
}
|
||||
|
||||
void Storage::fixColour (Ogre::ColourValue& color, int cellX, int cellY, int col, int row)
|
||||
{
|
||||
if (col == ESM::Land::LAND_SIZE-1)
|
||||
{
|
||||
++cellY;
|
||||
col = 0;
|
||||
}
|
||||
if (row == ESM::Land::LAND_SIZE-1)
|
||||
{
|
||||
++cellX;
|
||||
row = 0;
|
||||
}
|
||||
ESM::Land* land = getLand(cellX, cellY);
|
||||
if (land && land->mLandData->mUsingColours)
|
||||
{
|
||||
color.r = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f;
|
||||
color.g = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f;
|
||||
color.b = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f;
|
||||
}
|
||||
else
|
||||
{
|
||||
color.r = 1;
|
||||
color.g = 1;
|
||||
color.b = 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Storage::fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center,
|
||||
Ogre::HardwareVertexBufferSharedPtr vertexBuffer,
|
||||
Ogre::HardwareVertexBufferSharedPtr normalBuffer,
|
||||
Ogre::HardwareVertexBufferSharedPtr colourBuffer)
|
||||
{
|
||||
// LOD level n means every 2^n-th vertex is kept
|
||||
size_t increment = std::pow(2, lodLevel);
|
||||
|
||||
Ogre::Vector2 origin = center - Ogre::Vector2(size/2.f, size/2.f);
|
||||
assert(origin.x == (int) origin.x);
|
||||
assert(origin.y == (int) origin.y);
|
||||
|
||||
int startX = origin.x;
|
||||
int startY = origin.y;
|
||||
|
||||
size_t numVerts = size*(ESM::Land::LAND_SIZE-1)/increment + 1;
|
||||
|
||||
std::vector<uint8_t> colors;
|
||||
colors.resize(numVerts*numVerts*4);
|
||||
std::vector<float> positions;
|
||||
positions.resize(numVerts*numVerts*3);
|
||||
std::vector<float> normals;
|
||||
normals.resize(numVerts*numVerts*3);
|
||||
|
||||
Ogre::Vector3 normal;
|
||||
Ogre::ColourValue color;
|
||||
|
||||
float vertY;
|
||||
float vertX;
|
||||
|
||||
float vertY_ = 0; // of current cell corner
|
||||
for (int cellY = startY; cellY < startY + std::ceil(size); ++cellY)
|
||||
{
|
||||
float vertX_ = 0; // of current cell corner
|
||||
for (int cellX = startX; cellX < startX + std::ceil(size); ++cellX)
|
||||
{
|
||||
ESM::Land* land = getLand(cellX, cellY);
|
||||
if (land && !land->mHasData)
|
||||
land = NULL;
|
||||
bool hasColors = land && land->mLandData->mUsingColours;
|
||||
|
||||
int rowStart = 0;
|
||||
int colStart = 0;
|
||||
// Skip the first row / column unless we're at a chunk edge,
|
||||
// since this row / column is already contained in a previous cell
|
||||
if (colStart == 0 && vertY_ != 0)
|
||||
colStart += increment;
|
||||
if (rowStart == 0 && vertX_ != 0)
|
||||
rowStart += increment;
|
||||
|
||||
vertY = vertY_;
|
||||
for (int col=colStart; col<ESM::Land::LAND_SIZE; col += increment)
|
||||
{
|
||||
vertX = vertX_;
|
||||
for (int row=rowStart; row<ESM::Land::LAND_SIZE; row += increment)
|
||||
{
|
||||
positions[vertX*numVerts*3 + vertY*3] = ((vertX/float(numVerts-1)-0.5) * size * 8192);
|
||||
positions[vertX*numVerts*3 + vertY*3 + 1] = ((vertY/float(numVerts-1)-0.5) * size * 8192);
|
||||
if (land)
|
||||
positions[vertX*numVerts*3 + vertY*3 + 2] = land->mLandData->mHeights[col*ESM::Land::LAND_SIZE+row];
|
||||
else
|
||||
positions[vertX*numVerts*3 + vertY*3 + 2] = -2048;
|
||||
|
||||
if (land)
|
||||
{
|
||||
normal.x = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3];
|
||||
normal.y = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1];
|
||||
normal.z = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2];
|
||||
normal.normalise();
|
||||
}
|
||||
else
|
||||
normal = Ogre::Vector3(0,0,1);
|
||||
|
||||
// Normals apparently don't connect seamlessly between cells
|
||||
if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1)
|
||||
fixNormal(normal, cellX, cellY, col, row);
|
||||
|
||||
// some corner normals appear to be complete garbage (z < 0)
|
||||
if ((row == 0 || row == ESM::Land::LAND_SIZE-1) && (col == 0 || col == ESM::Land::LAND_SIZE-1))
|
||||
averageNormal(normal, cellX, cellY, col, row);
|
||||
|
||||
assert(normal.z > 0);
|
||||
|
||||
normals[vertX*numVerts*3 + vertY*3] = normal.x;
|
||||
normals[vertX*numVerts*3 + vertY*3 + 1] = normal.y;
|
||||
normals[vertX*numVerts*3 + vertY*3 + 2] = normal.z;
|
||||
|
||||
if (hasColors)
|
||||
{
|
||||
color.r = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f;
|
||||
color.g = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f;
|
||||
color.b = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f;
|
||||
}
|
||||
else
|
||||
{
|
||||
color.r = 1;
|
||||
color.g = 1;
|
||||
color.b = 1;
|
||||
}
|
||||
|
||||
// Unlike normals, colors mostly connect seamlessly between cells, but not always...
|
||||
if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1)
|
||||
fixColour(color, cellX, cellY, col, row);
|
||||
|
||||
color.a = 1;
|
||||
Ogre::uint32 rsColor;
|
||||
Ogre::Root::getSingleton().getRenderSystem()->convertColourValue(color, &rsColor);
|
||||
memcpy(&colors[vertX*numVerts*4 + vertY*4], &rsColor, sizeof(Ogre::uint32));
|
||||
|
||||
++vertX;
|
||||
}
|
||||
++vertY;
|
||||
}
|
||||
vertX_ = vertX;
|
||||
}
|
||||
vertY_ = vertY;
|
||||
|
||||
assert(vertX_ == numVerts); // Ensure we covered whole area
|
||||
}
|
||||
assert(vertY_ == numVerts); // Ensure we covered whole area
|
||||
|
||||
vertexBuffer->writeData(0, vertexBuffer->getSizeInBytes(), &positions[0], true);
|
||||
normalBuffer->writeData(0, normalBuffer->getSizeInBytes(), &normals[0], true);
|
||||
colourBuffer->writeData(0, colourBuffer->getSizeInBytes(), &colors[0], true);
|
||||
}
|
||||
|
||||
Storage::UniqueTextureId Storage::getVtexIndexAt(int cellX, int cellY,
|
||||
int x, int y)
|
||||
{
|
||||
// For the first/last row/column, we need to get the texture from the neighbour cell
|
||||
// to get consistent blending at the borders
|
||||
--x;
|
||||
if (x < 0)
|
||||
{
|
||||
--cellX;
|
||||
x += ESM::Land::LAND_TEXTURE_SIZE;
|
||||
}
|
||||
if (y >= ESM::Land::LAND_TEXTURE_SIZE) // Y appears to be wrapped from the other side because why the hell not?
|
||||
{
|
||||
++cellY;
|
||||
y -= ESM::Land::LAND_TEXTURE_SIZE;
|
||||
}
|
||||
|
||||
assert(x<ESM::Land::LAND_TEXTURE_SIZE);
|
||||
assert(y<ESM::Land::LAND_TEXTURE_SIZE);
|
||||
|
||||
ESM::Land* land = getLand(cellX, cellY);
|
||||
if (land)
|
||||
{
|
||||
if (!land->isDataLoaded(ESM::Land::DATA_VTEX))
|
||||
land->loadData(ESM::Land::DATA_VTEX);
|
||||
|
||||
int tex = land->mLandData->mTextures[y * ESM::Land::LAND_TEXTURE_SIZE + x];
|
||||
if (tex == 0)
|
||||
return std::make_pair(0,0); // vtex 0 is always the base texture, regardless of plugin
|
||||
return std::make_pair(tex, land->mPlugin);
|
||||
}
|
||||
else
|
||||
return std::make_pair(0,0);
|
||||
}
|
||||
|
||||
std::string Storage::getTextureName(UniqueTextureId id)
|
||||
{
|
||||
if (id.first == 0)
|
||||
return "_land_default.dds"; // Not sure if the default texture floatly is hardcoded?
|
||||
|
||||
// NB: All vtex ids are +1 compared to the ltex ids
|
||||
const ESM::LandTexture* ltex = getLandTexture(id.first-1, id.second);
|
||||
|
||||
std::string texture = ltex->mTexture;
|
||||
//TODO this is needed due to MWs messed up texture handling
|
||||
texture = texture.substr(0, texture.rfind(".")) + ".dds";
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
void Storage::getBlendmaps(float chunkSize, const Ogre::Vector2 &chunkCenter,
|
||||
bool pack, std::vector<Ogre::TexturePtr> &blendmaps, std::vector<std::string> &layerList)
|
||||
{
|
||||
// TODO - blending isn't completely right yet; the blending radius appears to be
|
||||
// different at a cell transition (2 vertices, not 4), so we may need to create a larger blendmap
|
||||
// and interpolate the rest of the cell by hand? :/
|
||||
|
||||
Ogre::Vector2 origin = chunkCenter - Ogre::Vector2(chunkSize/2.f, chunkSize/2.f);
|
||||
int cellX = origin.x;
|
||||
int cellY = origin.y;
|
||||
|
||||
// Save the used texture indices so we know the total number of textures
|
||||
// and number of required blend maps
|
||||
std::set<UniqueTextureId> textureIndices;
|
||||
// Due to the way the blending works, the base layer will always shine through in between
|
||||
// blend transitions (eg halfway between two texels, both blend values will be 0.5, so 25% of base layer visible).
|
||||
// To get a consistent look, we need to make sure to use the same base layer in all cells.
|
||||
// So we're always adding _land_default.dds as the base layer here, even if it's not referenced in this cell.
|
||||
textureIndices.insert(std::make_pair(0,0));
|
||||
|
||||
for (int y=0; y<ESM::Land::LAND_TEXTURE_SIZE+1; ++y)
|
||||
for (int x=0; x<ESM::Land::LAND_TEXTURE_SIZE+1; ++x)
|
||||
{
|
||||
UniqueTextureId id = getVtexIndexAt(cellX, cellY, x, y);
|
||||
textureIndices.insert(id);
|
||||
}
|
||||
|
||||
// Makes sure the indices are sorted, or rather,
|
||||
// retrieved as sorted. This is important to keep the splatting order
|
||||
// consistent across cells.
|
||||
std::map<UniqueTextureId, int> textureIndicesMap;
|
||||
for (std::set<UniqueTextureId>::iterator it = textureIndices.begin(); it != textureIndices.end(); ++it)
|
||||
{
|
||||
int size = textureIndicesMap.size();
|
||||
textureIndicesMap[*it] = size;
|
||||
layerList.push_back(getTextureName(*it));
|
||||
}
|
||||
|
||||
int numTextures = textureIndices.size();
|
||||
// numTextures-1 since the base layer doesn't need blending
|
||||
int numBlendmaps = pack ? std::ceil((numTextures-1) / 4.f) : (numTextures-1);
|
||||
|
||||
int channels = pack ? 4 : 1;
|
||||
|
||||
// Second iteration - create and fill in the blend maps
|
||||
const int blendmapSize = ESM::Land::LAND_TEXTURE_SIZE+1;
|
||||
std::vector<Ogre::uchar> data;
|
||||
data.resize(blendmapSize * blendmapSize * channels, 0);
|
||||
|
||||
for (int i=0; i<numBlendmaps; ++i)
|
||||
{
|
||||
Ogre::PixelFormat format = pack ? Ogre::PF_A8B8G8R8 : Ogre::PF_A8;
|
||||
static int count=0;
|
||||
Ogre::TexturePtr map = Ogre::TextureManager::getSingleton().createManual("terrain/blend/"
|
||||
+ Ogre::StringConverter::toString(count++), Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
|
||||
Ogre::TEX_TYPE_2D, blendmapSize, blendmapSize, 0, format);
|
||||
|
||||
for (int y=0; y<blendmapSize; ++y)
|
||||
{
|
||||
for (int x=0; x<blendmapSize; ++x)
|
||||
{
|
||||
UniqueTextureId id = getVtexIndexAt(cellX, cellY, x, y);
|
||||
int layerIndex = textureIndicesMap.find(id)->second;
|
||||
int blendIndex = (pack ? std::floor((layerIndex-1)/4.f) : layerIndex-1);
|
||||
int channel = pack ? std::max(0, (layerIndex-1) % 4) : 0;
|
||||
|
||||
if (blendIndex == i)
|
||||
data[y*blendmapSize*channels + x*channels + channel] = 255;
|
||||
else
|
||||
data[y*blendmapSize*channels + x*channels + channel] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// All done, upload to GPU
|
||||
Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(&data[0], data.size()));
|
||||
map->loadRawData(stream, blendmapSize, blendmapSize, format);
|
||||
blendmaps.push_back(map);
|
||||
}
|
||||
}
|
||||
|
||||
float Storage::getHeightAt(const Ogre::Vector3 &worldPos)
|
||||
{
|
||||
int cellX = std::floor(worldPos.x / 8192.f);
|
||||
int cellY = std::floor(worldPos.y / 8192.f);
|
||||
|
||||
ESM::Land* land = getLand(cellX, cellY);
|
||||
if (!land)
|
||||
return -2048;
|
||||
|
||||
// Mostly lifted from Ogre::Terrain::getHeightAtTerrainPosition
|
||||
|
||||
// Normalized position in the cell
|
||||
float nX = (worldPos.x - (cellX * 8192))/8192.f;
|
||||
float nY = (worldPos.y - (cellY * 8192))/8192.f;
|
||||
|
||||
// get left / bottom points (rounded down)
|
||||
float factor = ESM::Land::LAND_SIZE - 1.0f;
|
||||
float invFactor = 1.0f / factor;
|
||||
|
||||
int startX = static_cast<int>(nX * factor);
|
||||
int startY = static_cast<int>(nY * factor);
|
||||
int endX = startX + 1;
|
||||
int endY = startY + 1;
|
||||
|
||||
assert(endX < ESM::Land::LAND_SIZE);
|
||||
assert(endY < ESM::Land::LAND_SIZE);
|
||||
|
||||
// now get points in terrain space (effectively rounding them to boundaries)
|
||||
float startXTS = startX * invFactor;
|
||||
float startYTS = startY * invFactor;
|
||||
float endXTS = endX * invFactor;
|
||||
float endYTS = endY * invFactor;
|
||||
|
||||
// get parametric from start coord to next point
|
||||
float xParam = (nX - startXTS) * factor;
|
||||
float yParam = (nY - startYTS) * factor;
|
||||
|
||||
/* For even / odd tri strip rows, triangles are this shape:
|
||||
even odd
|
||||
3---2 3---2
|
||||
| / | | \ |
|
||||
0---1 0---1
|
||||
*/
|
||||
|
||||
// Build all 4 positions in normalized cell space, using point-sampled height
|
||||
Ogre::Vector3 v0 (startXTS, startYTS, getVertexHeight(land, startX, startY) / 8192.f);
|
||||
Ogre::Vector3 v1 (endXTS, startYTS, getVertexHeight(land, endX, startY) / 8192.f);
|
||||
Ogre::Vector3 v2 (endXTS, endYTS, getVertexHeight(land, endX, endY) / 8192.f);
|
||||
Ogre::Vector3 v3 (startXTS, endYTS, getVertexHeight(land, startX, endY) / 8192.f);
|
||||
// define this plane in terrain space
|
||||
Ogre::Plane plane;
|
||||
// (At the moment, all rows have the same triangle alignment)
|
||||
if (true)
|
||||
{
|
||||
// odd row
|
||||
bool secondTri = ((1.0 - yParam) > xParam);
|
||||
if (secondTri)
|
||||
plane.redefine(v0, v1, v3);
|
||||
else
|
||||
plane.redefine(v1, v2, v3);
|
||||
}
|
||||
else
|
||||
{
|
||||
// even row
|
||||
bool secondTri = (yParam > xParam);
|
||||
if (secondTri)
|
||||
plane.redefine(v0, v2, v3);
|
||||
else
|
||||
plane.redefine(v0, v1, v2);
|
||||
}
|
||||
|
||||
// Solve plane equation for z
|
||||
return (-plane.normal.x * nX
|
||||
-plane.normal.y * nY
|
||||
- plane.d) / plane.normal.z * 8192;
|
||||
|
||||
}
|
||||
|
||||
float Storage::getVertexHeight(const ESM::Land *land, int x, int y)
|
||||
{
|
||||
assert(x < ESM::Land::LAND_SIZE);
|
||||
assert(y < ESM::Land::LAND_SIZE);
|
||||
return land->mLandData->mHeights[y * ESM::Land::LAND_SIZE + x];
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
#ifndef COMPONENTS_TERRAIN_STORAGE_H
|
||||
#define COMPONENTS_TERRAIN_STORAGE_H
|
||||
|
||||
#include <components/esm/loadland.hpp>
|
||||
#include <components/esm/loadltex.hpp>
|
||||
|
||||
#include <OgreAxisAlignedBox.h>
|
||||
|
||||
#include <OgreHardwareVertexBuffer.h>
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
|
||||
/// We keep storage of terrain data abstract here since we need different implementations for game and editor
|
||||
class Storage
|
||||
{
|
||||
private:
|
||||
virtual ESM::Land* getLand (int cellX, int cellY) = 0;
|
||||
virtual const ESM::LandTexture* getLandTexture(int index, short plugin) = 0;
|
||||
|
||||
public:
|
||||
/// Get bounds of the whole terrain in cell units
|
||||
virtual Ogre::AxisAlignedBox getBounds() = 0;
|
||||
|
||||
/// Get the minimum and maximum heights of a terrain chunk.
|
||||
/// @note Should only be called for chunks <= 1 cell, i.e. leafs of the quad tree.
|
||||
/// Larger chunks can simply merge AABB of children.
|
||||
/// @param size size of the chunk in cell units
|
||||
/// @param center center of the chunk in cell units
|
||||
/// @param min min height will be stored here
|
||||
/// @param max max height will be stored here
|
||||
/// @return true if there was data available for this terrain chunk
|
||||
bool getMinMaxHeights (float size, const Ogre::Vector2& center, float& min, float& max);
|
||||
|
||||
/// Fill vertex buffers for a terrain chunk.
|
||||
/// @param lodLevel LOD level, 0 = most detailed
|
||||
/// @param size size of the terrain chunk in cell units
|
||||
/// @param center center of the chunk in cell units
|
||||
/// @param vertexBuffer buffer to write vertices
|
||||
/// @param normalBuffer buffer to write vertex normals
|
||||
/// @param colourBuffer buffer to write vertex colours
|
||||
void fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center,
|
||||
Ogre::HardwareVertexBufferSharedPtr vertexBuffer,
|
||||
Ogre::HardwareVertexBufferSharedPtr normalBuffer,
|
||||
Ogre::HardwareVertexBufferSharedPtr colourBuffer);
|
||||
|
||||
/// Create textures holding layer blend values for a terrain chunk.
|
||||
/// @note The terrain chunk shouldn't be larger than one cell since otherwise we might
|
||||
/// have to do a ridiculous amount of different layers. For larger chunks, composite maps should be used.
|
||||
/// @param chunkSize size of the terrain chunk in cell units
|
||||
/// @param chunkCenter center of the chunk in cell units
|
||||
/// @param pack Whether to pack blend values for up to 4 layers into one texture (one in each channel) -
|
||||
/// otherwise, each texture contains blend values for one layer only. Shader-based rendering
|
||||
/// can utilize packing, FFP can't.
|
||||
/// @param blendmaps created blendmaps will be written here
|
||||
/// @param layerList names of the layer textures used will be written here
|
||||
void getBlendmaps (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack,
|
||||
std::vector<Ogre::TexturePtr>& blendmaps,
|
||||
std::vector<std::string>& layerList);
|
||||
|
||||
float getHeightAt (const Ogre::Vector3& worldPos);
|
||||
|
||||
private:
|
||||
void fixNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row);
|
||||
void fixColour (Ogre::ColourValue& colour, int cellX, int cellY, int col, int row);
|
||||
void averageNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row);
|
||||
|
||||
float getVertexHeight (const ESM::Land* land, int x, int y);
|
||||
|
||||
// Since plugins can define new texture palettes, we need to know the plugin index too
|
||||
// in order to retrieve the correct texture name.
|
||||
// pair <texture id, plugin id>
|
||||
typedef std::pair<short, short> UniqueTextureId;
|
||||
|
||||
UniqueTextureId getVtexIndexAt(int cellX, int cellY,
|
||||
int x, int y);
|
||||
std::string getTextureName (UniqueTextureId id);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -0,0 +1,392 @@
|
||||
#include "terrain.hpp"
|
||||
|
||||
#include <OgreAxisAlignedBox.h>
|
||||
#include <OgreCamera.h>
|
||||
#include <OgreHardwareBufferManager.h>
|
||||
#include <OgreHardwarePixelBuffer.h>
|
||||
#include <OgreRoot.h>
|
||||
|
||||
#include <components/esm/loadland.hpp>
|
||||
|
||||
#include "storage.hpp"
|
||||
#include "quadtreenode.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
bool isPowerOfTwo(int x)
|
||||
{
|
||||
return ( (x > 0) && ((x & (x - 1)) == 0) );
|
||||
}
|
||||
|
||||
int nextPowerOfTwo (int v)
|
||||
{
|
||||
if (isPowerOfTwo(v)) return v;
|
||||
int depth=0;
|
||||
while(v)
|
||||
{
|
||||
v >>= 1;
|
||||
depth++;
|
||||
}
|
||||
return 1 << depth;
|
||||
}
|
||||
|
||||
Terrain::QuadTreeNode* findNode (const Ogre::Vector2& center, Terrain::QuadTreeNode* node)
|
||||
{
|
||||
if (center == node->getCenter())
|
||||
return node;
|
||||
|
||||
if (center.x > node->getCenter().x && center.y > node->getCenter().y)
|
||||
return findNode(center, node->getChild(Terrain::NE));
|
||||
else if (center.x > node->getCenter().x && center.y < node->getCenter().y)
|
||||
return findNode(center, node->getChild(Terrain::SE));
|
||||
else if (center.x < node->getCenter().x && center.y > node->getCenter().y)
|
||||
return findNode(center, node->getChild(Terrain::NW));
|
||||
else //if (center.x < node->getCenter().x && center.y < node->getCenter().y)
|
||||
return findNode(center, node->getChild(Terrain::SW));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
|
||||
Terrain::Terrain(Ogre::SceneManager* sceneMgr, Storage* storage, int visibilityFlags, bool distantLand, bool shaders)
|
||||
: mStorage(storage)
|
||||
, mMinBatchSize(1)
|
||||
, mMaxBatchSize(64)
|
||||
, mSceneMgr(sceneMgr)
|
||||
, mVisibilityFlags(visibilityFlags)
|
||||
, mDistantLand(distantLand)
|
||||
, mShaders(shaders)
|
||||
{
|
||||
mCompositeMapSceneMgr = Ogre::Root::getSingleton().createSceneManager(Ogre::ST_GENERIC);
|
||||
|
||||
Ogre::Camera* compositeMapCam = mCompositeMapSceneMgr->createCamera("a");
|
||||
mCompositeMapRenderTexture = Ogre::TextureManager::getSingleton().createManual(
|
||||
"terrain/comp/rt", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
|
||||
Ogre::TEX_TYPE_2D, 128, 128, 0, Ogre::PF_A8B8G8R8, Ogre::TU_RENDERTARGET);
|
||||
mCompositeMapRenderTarget = mCompositeMapRenderTexture->getBuffer()->getRenderTarget();
|
||||
mCompositeMapRenderTarget->setAutoUpdated(false);
|
||||
mCompositeMapRenderTarget->addViewport(compositeMapCam);
|
||||
|
||||
mBounds = storage->getBounds();
|
||||
|
||||
int origSizeX = mBounds.getSize().x;
|
||||
int origSizeY = mBounds.getSize().y;
|
||||
|
||||
// Dividing a quad tree only works well for powers of two, so round up to the nearest one
|
||||
int size = nextPowerOfTwo(std::max(origSizeX, origSizeY));
|
||||
|
||||
// Adjust the center according to the new size
|
||||
Ogre::Vector3 center = mBounds.getCenter() + Ogre::Vector3((size-origSizeX)/2.f, (size-origSizeY)/2.f, 0);
|
||||
|
||||
mRootNode = new QuadTreeNode(this, Root, size, Ogre::Vector2(center.x, center.y), NULL);
|
||||
buildQuadTree(mRootNode);
|
||||
mRootNode->initAabb();
|
||||
mRootNode->initNeighbours();
|
||||
}
|
||||
|
||||
Terrain::~Terrain()
|
||||
{
|
||||
delete mRootNode;
|
||||
delete mStorage;
|
||||
}
|
||||
|
||||
void Terrain::buildQuadTree(QuadTreeNode *node)
|
||||
{
|
||||
float halfSize = node->getSize()/2.f;
|
||||
|
||||
if (node->getSize() <= mMinBatchSize)
|
||||
{
|
||||
// We arrived at a leaf
|
||||
float minZ,maxZ;
|
||||
Ogre::Vector2 center = node->getCenter();
|
||||
if (mStorage->getMinMaxHeights(node->getSize(), center, minZ, maxZ))
|
||||
node->setBoundingBox(Ogre::AxisAlignedBox(Ogre::Vector3(-halfSize*8192, -halfSize*8192, minZ),
|
||||
Ogre::Vector3(halfSize*8192, halfSize*8192, maxZ)));
|
||||
else
|
||||
node->markAsDummy(); // no data available for this node, skip it
|
||||
return;
|
||||
}
|
||||
|
||||
if (node->getCenter().x - halfSize > mBounds.getMaximum().x
|
||||
|| node->getCenter().x + halfSize < mBounds.getMinimum().x
|
||||
|| node->getCenter().y - halfSize > mBounds.getMaximum().y
|
||||
|| node->getCenter().y + halfSize < mBounds.getMinimum().y )
|
||||
// Out of bounds of the actual terrain - this will happen because
|
||||
// we rounded the size up to the next power of two
|
||||
{
|
||||
node->markAsDummy();
|
||||
return;
|
||||
}
|
||||
|
||||
// Not a leaf, create its children
|
||||
node->createChild(SW, halfSize, node->getCenter() - halfSize/2.f);
|
||||
node->createChild(SE, halfSize, node->getCenter() + Ogre::Vector2(halfSize/2.f, -halfSize/2.f));
|
||||
node->createChild(NW, halfSize, node->getCenter() + Ogre::Vector2(-halfSize/2.f, halfSize/2.f));
|
||||
node->createChild(NE, halfSize, node->getCenter() + halfSize/2.f);
|
||||
buildQuadTree(node->getChild(SW));
|
||||
buildQuadTree(node->getChild(SE));
|
||||
buildQuadTree(node->getChild(NW));
|
||||
buildQuadTree(node->getChild(NE));
|
||||
|
||||
// if all children are dummy, we are also dummy
|
||||
for (int i=0; i<4; ++i)
|
||||
{
|
||||
if (!node->getChild((ChildDirection)i)->isDummy())
|
||||
return;
|
||||
}
|
||||
node->markAsDummy();
|
||||
}
|
||||
|
||||
void Terrain::update(const Ogre::Vector3& cameraPos)
|
||||
{
|
||||
mRootNode->update(cameraPos);
|
||||
mRootNode->updateIndexBuffers();
|
||||
}
|
||||
|
||||
Ogre::AxisAlignedBox Terrain::getWorldBoundingBox (const Ogre::Vector2& center)
|
||||
{
|
||||
if (center.x > mBounds.getMaximum().x
|
||||
|| center.x < mBounds.getMinimum().x
|
||||
|| center.y > mBounds.getMaximum().y
|
||||
|| center.y < mBounds.getMinimum().y)
|
||||
return Ogre::AxisAlignedBox::BOX_NULL;
|
||||
QuadTreeNode* node = findNode(center, mRootNode);
|
||||
Ogre::AxisAlignedBox box = node->getBoundingBox();
|
||||
box.setExtents(box.getMinimum() + Ogre::Vector3(center.x, center.y, 0) * 8192,
|
||||
box.getMaximum() + Ogre::Vector3(center.x, center.y, 0) * 8192);
|
||||
return box;
|
||||
}
|
||||
|
||||
Ogre::HardwareVertexBufferSharedPtr Terrain::getVertexBuffer(int numVertsOneSide)
|
||||
{
|
||||
if (mUvBufferMap.find(numVertsOneSide) != mUvBufferMap.end())
|
||||
{
|
||||
return mUvBufferMap[numVertsOneSide];
|
||||
}
|
||||
|
||||
int vertexCount = numVertsOneSide * numVertsOneSide;
|
||||
|
||||
std::vector<float> uvs;
|
||||
uvs.reserve(vertexCount*2);
|
||||
|
||||
for (int col = 0; col < numVertsOneSide; ++col)
|
||||
{
|
||||
for (int row = 0; row < numVertsOneSide; ++row)
|
||||
{
|
||||
uvs.push_back(col / static_cast<float>(numVertsOneSide-1)); // U
|
||||
uvs.push_back(row / static_cast<float>(numVertsOneSide-1)); // V
|
||||
}
|
||||
}
|
||||
|
||||
Ogre::HardwareBufferManager* mgr = Ogre::HardwareBufferManager::getSingletonPtr();
|
||||
Ogre::HardwareVertexBufferSharedPtr buffer = mgr->createVertexBuffer(
|
||||
Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT2),
|
||||
vertexCount, Ogre::HardwareBuffer::HBU_STATIC);
|
||||
|
||||
buffer->writeData(0, buffer->getSizeInBytes(), &uvs[0], true);
|
||||
|
||||
mUvBufferMap[numVertsOneSide] = buffer;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
Ogre::HardwareIndexBufferSharedPtr Terrain::getIndexBuffer(int flags, size_t& numIndices)
|
||||
{
|
||||
if (mIndexBufferMap.find(flags) != mIndexBufferMap.end())
|
||||
{
|
||||
numIndices = mIndexBufferMap[flags]->getNumIndexes();
|
||||
return mIndexBufferMap[flags];
|
||||
}
|
||||
|
||||
// LOD level n means every 2^n-th vertex is kept
|
||||
size_t lodLevel = (flags >> (4*4));
|
||||
|
||||
size_t lodDeltas[4];
|
||||
for (int i=0; i<4; ++i)
|
||||
lodDeltas[i] = (flags >> (4*i)) & (0xf);
|
||||
|
||||
bool anyDeltas = (lodDeltas[North] || lodDeltas[South] || lodDeltas[West] || lodDeltas[East]);
|
||||
|
||||
size_t increment = std::pow(2, lodLevel);
|
||||
assert((int)increment < ESM::Land::LAND_SIZE);
|
||||
std::vector<short> indices;
|
||||
indices.reserve((ESM::Land::LAND_SIZE-1)*(ESM::Land::LAND_SIZE-1)*2*3 / increment);
|
||||
|
||||
size_t rowStart = 0, colStart = 0, rowEnd = ESM::Land::LAND_SIZE-1, colEnd = ESM::Land::LAND_SIZE-1;
|
||||
// If any edge needs stitching we'll skip all edges at this point,
|
||||
// mainly because stitching one edge would have an effect on corners and on the adjacent edges
|
||||
if (anyDeltas)
|
||||
{
|
||||
colStart += increment;
|
||||
colEnd -= increment;
|
||||
rowEnd -= increment;
|
||||
rowStart += increment;
|
||||
}
|
||||
for (size_t row = rowStart; row < rowEnd; row += increment)
|
||||
{
|
||||
for (size_t col = colStart; col < colEnd; col += increment)
|
||||
{
|
||||
indices.push_back(ESM::Land::LAND_SIZE*col+row);
|
||||
indices.push_back(ESM::Land::LAND_SIZE*(col+increment)+row+increment);
|
||||
indices.push_back(ESM::Land::LAND_SIZE*col+row+increment);
|
||||
|
||||
indices.push_back(ESM::Land::LAND_SIZE*col+row);
|
||||
indices.push_back(ESM::Land::LAND_SIZE*(col+increment)+row);
|
||||
indices.push_back(ESM::Land::LAND_SIZE*(col+increment)+row+increment);
|
||||
}
|
||||
}
|
||||
|
||||
size_t innerStep = increment;
|
||||
if (anyDeltas)
|
||||
{
|
||||
// Now configure LOD transitions at the edges - this is pretty tedious,
|
||||
// and some very long and boring code, but it works great
|
||||
|
||||
// South
|
||||
size_t row = 0;
|
||||
size_t outerStep = std::pow(2, lodDeltas[South] + lodLevel);
|
||||
for (size_t col = 0; col < ESM::Land::LAND_SIZE-1; col += outerStep)
|
||||
{
|
||||
indices.push_back(ESM::Land::LAND_SIZE*col+row);
|
||||
indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep)+row);
|
||||
// Make sure not to touch the right edge
|
||||
if (col+outerStep == ESM::Land::LAND_SIZE-1)
|
||||
indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep-innerStep)+row+innerStep);
|
||||
else
|
||||
indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep)+row+innerStep);
|
||||
|
||||
for (size_t i = 0; i < outerStep; i += innerStep)
|
||||
{
|
||||
// Make sure not to touch the left or right edges
|
||||
if (col+i == 0 || col+i == ESM::Land::LAND_SIZE-1-innerStep)
|
||||
continue;
|
||||
indices.push_back(ESM::Land::LAND_SIZE*(col)+row);
|
||||
indices.push_back(ESM::Land::LAND_SIZE*(col+i+innerStep)+row+innerStep);
|
||||
indices.push_back(ESM::Land::LAND_SIZE*(col+i)+row+innerStep);
|
||||
}
|
||||
}
|
||||
|
||||
// North
|
||||
row = ESM::Land::LAND_SIZE-1;
|
||||
outerStep = std::pow(2, lodDeltas[North] + lodLevel);
|
||||
for (size_t col = 0; col < ESM::Land::LAND_SIZE-1; col += outerStep)
|
||||
{
|
||||
indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep)+row);
|
||||
indices.push_back(ESM::Land::LAND_SIZE*col+row);
|
||||
// Make sure not to touch the left edge
|
||||
if (col == 0)
|
||||
indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row-innerStep);
|
||||
else
|
||||
indices.push_back(ESM::Land::LAND_SIZE*col+row-innerStep);
|
||||
|
||||
for (size_t i = 0; i < outerStep; i += innerStep)
|
||||
{
|
||||
// Make sure not to touch the left or right edges
|
||||
if (col+i == 0 || col+i == ESM::Land::LAND_SIZE-1-innerStep)
|
||||
continue;
|
||||
indices.push_back(ESM::Land::LAND_SIZE*(col+i)+row-innerStep);
|
||||
indices.push_back(ESM::Land::LAND_SIZE*(col+i+innerStep)+row-innerStep);
|
||||
indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep)+row);
|
||||
}
|
||||
}
|
||||
|
||||
// West
|
||||
size_t col = 0;
|
||||
outerStep = std::pow(2, lodDeltas[West] + lodLevel);
|
||||
for (size_t row = 0; row < ESM::Land::LAND_SIZE-1; row += outerStep)
|
||||
{
|
||||
indices.push_back(ESM::Land::LAND_SIZE*col+row+outerStep);
|
||||
indices.push_back(ESM::Land::LAND_SIZE*col+row);
|
||||
// Make sure not to touch the top edge
|
||||
if (row+outerStep == ESM::Land::LAND_SIZE-1)
|
||||
indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row+outerStep-innerStep);
|
||||
else
|
||||
indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row+outerStep);
|
||||
|
||||
for (size_t i = 0; i < outerStep; i += innerStep)
|
||||
{
|
||||
// Make sure not to touch the top or bottom edges
|
||||
if (row+i == 0 || row+i == ESM::Land::LAND_SIZE-1-innerStep)
|
||||
continue;
|
||||
indices.push_back(ESM::Land::LAND_SIZE*col+row);
|
||||
indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row+i);
|
||||
indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row+i+innerStep);
|
||||
}
|
||||
}
|
||||
|
||||
// East
|
||||
col = ESM::Land::LAND_SIZE-1;
|
||||
outerStep = std::pow(2, lodDeltas[East] + lodLevel);
|
||||
for (size_t row = 0; row < ESM::Land::LAND_SIZE-1; row += outerStep)
|
||||
{
|
||||
indices.push_back(ESM::Land::LAND_SIZE*col+row);
|
||||
indices.push_back(ESM::Land::LAND_SIZE*col+row+outerStep);
|
||||
// Make sure not to touch the bottom edge
|
||||
if (row == 0)
|
||||
indices.push_back(ESM::Land::LAND_SIZE*(col-innerStep)+row+innerStep);
|
||||
else
|
||||
indices.push_back(ESM::Land::LAND_SIZE*(col-innerStep)+row);
|
||||
|
||||
for (size_t i = 0; i < outerStep; i += innerStep)
|
||||
{
|
||||
// Make sure not to touch the top or bottom edges
|
||||
if (row+i == 0 || row+i == ESM::Land::LAND_SIZE-1-innerStep)
|
||||
continue;
|
||||
indices.push_back(ESM::Land::LAND_SIZE*col+row+outerStep);
|
||||
indices.push_back(ESM::Land::LAND_SIZE*(col-innerStep)+row+i+innerStep);
|
||||
indices.push_back(ESM::Land::LAND_SIZE*(col-innerStep)+row+i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
numIndices = indices.size();
|
||||
|
||||
Ogre::HardwareBufferManager* mgr = Ogre::HardwareBufferManager::getSingletonPtr();
|
||||
Ogre::HardwareIndexBufferSharedPtr buffer = mgr->createIndexBuffer(Ogre::HardwareIndexBuffer::IT_16BIT,
|
||||
numIndices, Ogre::HardwareBuffer::HBU_STATIC);
|
||||
buffer->writeData(0, buffer->getSizeInBytes(), &indices[0], true);
|
||||
mIndexBufferMap[flags] = buffer;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void Terrain::renderCompositeMap(Ogre::TexturePtr target)
|
||||
{
|
||||
mCompositeMapRenderTarget->update();
|
||||
target->getBuffer()->blit(mCompositeMapRenderTexture->getBuffer());
|
||||
}
|
||||
|
||||
void Terrain::clearCompositeMapSceneManager()
|
||||
{
|
||||
mCompositeMapSceneMgr->destroyAllManualObjects();
|
||||
mCompositeMapSceneMgr->clearScene();
|
||||
}
|
||||
|
||||
float Terrain::getHeightAt(const Ogre::Vector3 &worldPos)
|
||||
{
|
||||
return mStorage->getHeightAt(worldPos);
|
||||
}
|
||||
|
||||
void Terrain::applyMaterials(bool shadows, bool splitShadows)
|
||||
{
|
||||
mShadows = shadows;
|
||||
mSplitShadows = splitShadows;
|
||||
mRootNode->applyMaterials();
|
||||
}
|
||||
|
||||
void Terrain::setVisible(bool visible)
|
||||
{
|
||||
mVisible = visible;
|
||||
mRootNode->setVisible(visible);
|
||||
}
|
||||
|
||||
bool Terrain::getVisible()
|
||||
{
|
||||
return mVisible;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,140 @@
|
||||
#ifndef COMPONENTS_TERRAIN_H
|
||||
#define COMPONENTS_TERRAIN_H
|
||||
|
||||
#include <OgreHardwareIndexBuffer.h>
|
||||
#include <OgreHardwareVertexBuffer.h>
|
||||
#include <OgreAxisAlignedBox.h>
|
||||
#include <OgreTexture.h>
|
||||
|
||||
namespace Ogre
|
||||
{
|
||||
class Camera;
|
||||
}
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
|
||||
class QuadTreeNode;
|
||||
class Storage;
|
||||
|
||||
/**
|
||||
* @brief A quadtree-based terrain implementation suitable for large data sets. \n
|
||||
* Near cells are rendered with alpha splatting, distant cells are merged
|
||||
* together in batches and have their layers pre-rendered onto a composite map. \n
|
||||
* Cracks at LOD transitions are avoided using stitching.
|
||||
* @note Multiple cameras are not supported yet
|
||||
*/
|
||||
class Terrain
|
||||
{
|
||||
public:
|
||||
/// @note takes ownership of \a storage
|
||||
/// @param sceneMgr scene manager to use
|
||||
/// @param storage Storage instance to get terrain data from (heights, normals, colors, textures..)
|
||||
/// @param visbilityFlags visibility flags for the created meshes
|
||||
/// @param distantLand Whether to draw all of the terrain, or only a 3x3 grid around the camera.
|
||||
/// This is a temporary option until it can be streamlined.
|
||||
/// @param shaders Whether to use splatting shader, or multi-pass fixed function splatting. Shader is usually
|
||||
/// faster so this is just here for compatibility.
|
||||
Terrain(Ogre::SceneManager* sceneMgr, Storage* storage, int visiblityFlags, bool distantLand, bool shaders);
|
||||
~Terrain();
|
||||
|
||||
bool getDistantLandEnabled() { return mDistantLand; }
|
||||
bool getShadersEnabled() { return mShaders; }
|
||||
bool getShadowsEnabled() { return mShadows; }
|
||||
bool getSplitShadowsEnabled() { return mSplitShadows; }
|
||||
|
||||
float getHeightAt (const Ogre::Vector3& worldPos);
|
||||
|
||||
/// Update chunk LODs according to this camera position
|
||||
/// @note Calling this method might lead to composite textures being rendered, so it is best
|
||||
/// not to call it when render commands are still queued, since that would cause a flush.
|
||||
void update (const Ogre::Vector3& cameraPos);
|
||||
|
||||
/// Get the world bounding box of a chunk of terrain centered at \a center
|
||||
Ogre::AxisAlignedBox getWorldBoundingBox (const Ogre::Vector2& center);
|
||||
|
||||
Ogre::SceneManager* getSceneManager() { return mSceneMgr; }
|
||||
|
||||
Storage* getStorage() { return mStorage; }
|
||||
|
||||
/// Show or hide the whole terrain
|
||||
/// @note this setting will be invalidated once you call Terrain::update, so do not call it while the terrain should be hidden
|
||||
void setVisible(bool visible);
|
||||
bool getVisible();
|
||||
|
||||
/// Recreate materials used by terrain chunks. This should be called whenever settings of
|
||||
/// the material factory are changed. (Relying on the factory to update those materials is not
|
||||
/// enough, since turning a feature on/off can change the number of texture units available for layer/blend
|
||||
/// textures, and to properly respond to this we may need to change the structure of the material, such as
|
||||
/// adding or removing passes. This can only be achieved by a full rebuild.)
|
||||
void applyMaterials(bool shadows, bool splitShadows);
|
||||
|
||||
int getVisiblityFlags() { return mVisibilityFlags; }
|
||||
|
||||
int getMaxBatchSize() { return mMaxBatchSize; }
|
||||
|
||||
void enableSplattingShader(bool enabled);
|
||||
|
||||
private:
|
||||
bool mDistantLand;
|
||||
bool mShaders;
|
||||
bool mShadows;
|
||||
bool mSplitShadows;
|
||||
bool mVisible;
|
||||
|
||||
QuadTreeNode* mRootNode;
|
||||
Storage* mStorage;
|
||||
|
||||
int mVisibilityFlags;
|
||||
|
||||
Ogre::SceneManager* mSceneMgr;
|
||||
Ogre::SceneManager* mCompositeMapSceneMgr;
|
||||
|
||||
/// Bounds in cell units
|
||||
Ogre::AxisAlignedBox mBounds;
|
||||
|
||||
/// Minimum size of a terrain batch along one side (in cell units)
|
||||
float mMinBatchSize;
|
||||
/// Maximum size of a terrain batch along one side (in cell units)
|
||||
float mMaxBatchSize;
|
||||
|
||||
void buildQuadTree(QuadTreeNode* node);
|
||||
|
||||
public:
|
||||
// ----INTERNAL----
|
||||
|
||||
enum IndexBufferFlags
|
||||
{
|
||||
IBF_North = 1 << 0,
|
||||
IBF_East = 1 << 1,
|
||||
IBF_South = 1 << 2,
|
||||
IBF_West = 1 << 3
|
||||
};
|
||||
|
||||
/// @param flags first 4*4 bits are LOD deltas on each edge, respectively (4 bits each)
|
||||
/// next 4 bits are LOD level of the index buffer (LOD 0 = don't omit any vertices)
|
||||
/// @param numIndices number of indices that were used will be written here
|
||||
Ogre::HardwareIndexBufferSharedPtr getIndexBuffer (int flags, size_t& numIndices);
|
||||
|
||||
Ogre::HardwareVertexBufferSharedPtr getVertexBuffer (int numVertsOneSide);
|
||||
|
||||
Ogre::SceneManager* getCompositeMapSceneManager() { return mCompositeMapSceneMgr; }
|
||||
|
||||
// Delete all quads
|
||||
void clearCompositeMapSceneManager();
|
||||
void renderCompositeMap (Ogre::TexturePtr target);
|
||||
|
||||
private:
|
||||
// Index buffers are shared across terrain batches where possible. There is one index buffer for each
|
||||
// combination of LOD deltas and index buffer LOD we may need.
|
||||
std::map<int, Ogre::HardwareIndexBufferSharedPtr> mIndexBufferMap;
|
||||
|
||||
std::map<int, Ogre::HardwareVertexBufferSharedPtr> mUvBufferMap;
|
||||
|
||||
Ogre::RenderTarget* mCompositeMapRenderTarget;
|
||||
Ogre::TexturePtr mCompositeMapRenderTexture;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -1 +0,0 @@
|
||||
*_test
|
@ -1,14 +0,0 @@
|
||||
GCC=g++
|
||||
|
||||
all: triangle_test esm_test
|
||||
|
||||
LIB_INC=-I../../../libs/
|
||||
|
||||
triangle_test: triangle_test.cpp
|
||||
$(GCC) $^ -o $@
|
||||
|
||||
esm_test: esm_test.cpp
|
||||
$(GCC) $^ -o $@ $(LIB_INC)
|
||||
|
||||
clean:
|
||||
rm *_test
|
@ -1,10 +0,0 @@
|
||||
#include <iostream>
|
||||
using namespace std;
|
||||
|
||||
#include "../esm_land_factory.hpp"
|
||||
|
||||
int main()
|
||||
{
|
||||
cout << "under development\n";
|
||||
return 0;
|
||||
}
|
@ -1 +0,0 @@
|
||||
under development
|
@ -1,55 +0,0 @@
|
||||
Cell types:
|
||||
\ / \ /
|
||||
/ \ / \
|
||||
\ / \ /
|
||||
/ \ / \
|
||||
|
||||
Full index list:
|
||||
0
|
||||
6
|
||||
5
|
||||
0
|
||||
1
|
||||
6
|
||||
1
|
||||
2
|
||||
6
|
||||
6
|
||||
2
|
||||
7
|
||||
2
|
||||
8
|
||||
7
|
||||
2
|
||||
3
|
||||
8
|
||||
3
|
||||
4
|
||||
8
|
||||
8
|
||||
4
|
||||
9
|
||||
5
|
||||
6
|
||||
10
|
||||
10
|
||||
6
|
||||
11
|
||||
6
|
||||
12
|
||||
11
|
||||
6
|
||||
7
|
||||
12
|
||||
7
|
||||
8
|
||||
12
|
||||
12
|
||||
8
|
||||
13
|
||||
8
|
||||
14
|
||||
13
|
||||
8
|
||||
9
|
||||
14
|
@ -1,18 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
make || exit
|
||||
|
||||
mkdir -p output
|
||||
|
||||
PROGS=*_test
|
||||
|
||||
for a in $PROGS; do
|
||||
if [ -f "output/$a.out" ]; then
|
||||
echo "Running $a:"
|
||||
./$a | diff output/$a.out -
|
||||
else
|
||||
echo "Creating $a.out"
|
||||
./$a > "output/$a.out"
|
||||
git add "output/$a.out"
|
||||
fi
|
||||
done
|
@ -1,93 +0,0 @@
|
||||
#include <iostream>
|
||||
using namespace std;
|
||||
|
||||
#include "../triangulator.hpp"
|
||||
|
||||
const int X = 4;
|
||||
const int Y = 4;
|
||||
|
||||
typedef Terrain::Triangulator<short,X,Y> Triangles4x4;
|
||||
|
||||
int main()
|
||||
{
|
||||
Triangles4x4 t;
|
||||
|
||||
cout << "Cell types:\n";
|
||||
for(int y=0;y<Y;y++)
|
||||
{
|
||||
for(int x=0;x<X;x++)
|
||||
{
|
||||
if(t.cellType(x,y)) cout << "/ ";
|
||||
else cout << "\\ ";
|
||||
}
|
||||
cout << endl;
|
||||
}
|
||||
cout << endl;
|
||||
|
||||
cout << "Full index list:\n";
|
||||
for(int i=0; i<X*Y*3; i++)
|
||||
cout << t.getData()[i] << endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Code we might add later:
|
||||
|
||||
// Get the vertex indices belonging to a given triangle
|
||||
void getTriangle(int trinum, Index &p1, Index &p2, Index &p3)
|
||||
{
|
||||
assert(trinum >= 0 && trinum < TriNum);
|
||||
trinum *= 3;
|
||||
|
||||
p1 = array[trinum++];
|
||||
p2 = array[trinum++];
|
||||
p3 = array[trinum];
|
||||
}
|
||||
|
||||
/*
|
||||
Get height interpolation weights for a given grid square. The
|
||||
input is the grid square number (x,y) and the relative position
|
||||
within that square (xrel,yrel = [0.0..1.0].) The weights are
|
||||
returned as three vertex index + weight factor pairs.
|
||||
|
||||
A more user-friendly version for HeightMap structs is given
|
||||
below.
|
||||
* /
|
||||
void getWeights(int x, int y, float xrel, float yrel,
|
||||
Index &p1, float w1,
|
||||
Index &p2, float w2,
|
||||
Index &p3, float w3)
|
||||
{
|
||||
// Find cell index
|
||||
int index = y*SizeX + x;
|
||||
|
||||
// First triangle in cell
|
||||
index *= 2;
|
||||
|
||||
// The rest depends on how the cell is triangulated
|
||||
if(cellType(x,y))
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
// Cell is divided as \ from 0,0 to 1,1
|
||||
if(xrel < yrel)
|
||||
{
|
||||
// Bottom left triangle.
|
||||
|
||||
// Order is (0,0),(1,1),(0,1).
|
||||
getTriangle(index, p1,p2,p3);
|
||||
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
// Top right triangle
|
||||
|
||||
// Order is (0,0),(1,0),(1,1).
|
||||
getTriangle(index+1, p1,p2,p3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
@ -1,104 +0,0 @@
|
||||
#ifndef TERRAIN_TRIANGULATOR_H
|
||||
#define TERRAIN_TRIANGULATOR_H
|
||||
|
||||
/*
|
||||
The triangulator is a simple math helper class, used for dividing a
|
||||
regular square grid into alternating set of triangles. It divides a
|
||||
grid like this:
|
||||
|
||||
+----+----+
|
||||
| | |
|
||||
| | |
|
||||
+----+----+
|
||||
| | |
|
||||
| | |
|
||||
+----+----+
|
||||
|
||||
into this:
|
||||
|
||||
+----+----+
|
||||
| \ 2|3 / |
|
||||
|1 \ | / 4|
|
||||
+----+----+
|
||||
|5 / | \ 8|
|
||||
| / 6|7 \ |
|
||||
+----+----+
|
||||
|
||||
Since the triangulation information is typically the same for all
|
||||
terrains of the same size, once instance can usually be shared.
|
||||
*/
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
// Index number type, number of grid cells (not vertices) in X and Y
|
||||
// directions.
|
||||
template <typename Index, int SizeX, int SizeY>
|
||||
class Triangulator
|
||||
{
|
||||
// Number of triangles
|
||||
static const int TriNum = SizeX * SizeY * 2;
|
||||
|
||||
// 3 indices per triangle
|
||||
Index array[TriNum * 3];
|
||||
|
||||
public:
|
||||
// Get raw triangle data pointer. Typically used for creating
|
||||
// meshes.
|
||||
const Index *getData() { return array; }
|
||||
|
||||
// Return whether a given cell is divided as / (true) or \
|
||||
// (false).
|
||||
static bool cellType(int x, int y)
|
||||
{
|
||||
assert(x >= 0 && x < SizeX);
|
||||
assert(y >= 0 && y < SizeY);
|
||||
|
||||
bool even = (x & 1) == 1;
|
||||
if((y & 1) == 1) even = !even;
|
||||
return even;
|
||||
}
|
||||
|
||||
// Constructor sets up the index buffer
|
||||
Triangulator()
|
||||
{
|
||||
int index = 0;
|
||||
for ( int y = 0; y < SizeX; y++ )
|
||||
for ( int x = 0; x < SizeY; x++ )
|
||||
{
|
||||
// Get vertex indices
|
||||
Index line1 = y*(SizeX+1) + x;
|
||||
Index line2 = line1 + SizeX+1;
|
||||
|
||||
if(cellType(x,y))
|
||||
{
|
||||
// Top left
|
||||
array[index++] = line1;
|
||||
array[index++] = line1 + 1;
|
||||
array[index++] = line2;
|
||||
|
||||
// Bottom right
|
||||
array[index++] = line2;
|
||||
array[index++] = line1 + 1;
|
||||
array[index++] = line2 + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Bottom left
|
||||
array[index++] = line1;
|
||||
array[index++] = line2 + 1;
|
||||
array[index++] = line2;
|
||||
|
||||
// Top right
|
||||
array[index++] = line1;
|
||||
array[index++] = line1 + 1;
|
||||
array[index++] = line2 + 1;
|
||||
}
|
||||
}
|
||||
assert(index == TriNum*3);
|
||||
}
|
||||
};
|
||||
} // Namespace
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue