Merge remote-tracking branch 'zini/master' into physics

actorid
Chris Robinson 12 years ago
commit a993af53e7

@ -15,7 +15,7 @@ before_install:
- sudo apt-get install -qq libqt4-dev libxaw7-dev libxrandr-dev libfreeimage-dev libpng-dev
- sudo apt-get install -qq libopenal-dev libmpg123-dev libsndfile1-dev
- sudo apt-get install -qq libavcodec-dev libavformat-dev libavdevice-dev libavutil-dev libswscale-dev libpostproc-dev
- sudo apt-get install -qq libbullet-dev libogre-static-dev libmygui-static-dev libsdl2-static-dev
- sudo apt-get install -qq libbullet-dev libogre-static-dev libmygui-static-dev libsdl2-static-dev libunshield-dev
- sudo mkdir /usr/src/gtest/build
- cd /usr/src/gtest/build
- sudo cmake .. -DBUILD_SHARED_LIBS=1

@ -216,7 +216,6 @@ ENDIF(WIN32)
ENDIF(OGRE_STATIC)
include_directories("."
${OGRE_INCLUDE_DIR} ${OGRE_INCLUDE_DIR}/Ogre ${OGRE_INCLUDE_DIR}/OGRE ${OGRE_PLUGIN_INCLUDE_DIRS}
${OGRE_Terrain_INCLUDE_DIR}
${SDL2_INCLUDE_DIR}
${Boost_INCLUDE_DIR}
${PLATFORM_INCLUDE_DIR}
@ -521,6 +520,13 @@ if (BUILD_ESMTOOL)
endif()
if (BUILD_LAUNCHER)
if(NOT WIN32)
find_package(LIBUNSHIELD REQUIRED)
if(NOT LIBUNSHIELD_FOUND)
message(SEND_ERROR "Failed to find libunshield")
endif(NOT LIBUNSHIELD_FOUND)
endif(NOT WIN32)
add_subdirectory( apps/launcher )
endif()

@ -4,6 +4,7 @@ set(LAUNCHER
main.cpp
maindialog.cpp
playpage.cpp
textslotmsgbox.cpp
settings/gamesettings.cpp
settings/graphicssettings.cpp
@ -14,12 +15,17 @@ set(LAUNCHER
${CMAKE_SOURCE_DIR}/files/launcher/launcher.rc
)
if(NOT WIN32)
LIST(APPEND LAUNCHER unshieldthread.cpp)
endif(NOT WIN32)
set(LAUNCHER_HEADER
datafilespage.hpp
graphicspage.hpp
maindialog.hpp
playpage.hpp
unshieldthread.hpp
textslotmsgbox.hpp
settings/gamesettings.hpp
settings/graphicssettings.hpp
@ -30,6 +36,10 @@ set(LAUNCHER_HEADER
utils/textinputdialog.hpp
)
if(NOT WIN32)
LIST(APPEND LAUNCHER_HEADER unshieldthread.hpp)
endif(NOT WIN32)
# Headers that must be pre-processed
set(LAUNCHER_HEADER_MOC
@ -37,11 +47,18 @@ set(LAUNCHER_HEADER_MOC
graphicspage.hpp
maindialog.hpp
playpage.hpp
unshieldthread.hpp
textslotmsgbox.hpp
utils/checkablemessagebox.hpp
utils/textinputdialog.hpp
)
if(NOT WIN32)
LIST(APPEND LAUNCHER_HEADER_MOC unshieldthread.hpp)
endif(NOT WIN32)
set(LAUNCHER_UI
${CMAKE_SOURCE_DIR}/files/ui/datafilespage.ui
${CMAKE_SOURCE_DIR}/files/ui/graphicspage.ui
@ -64,8 +81,12 @@ QT4_ADD_RESOURCES(RCC_SRCS ${CMAKE_SOURCE_DIR}/files/launcher/launcher.qrc)
QT4_WRAP_CPP(MOC_SRCS ${LAUNCHER_HEADER_MOC})
QT4_WRAP_UI(UI_HDRS ${LAUNCHER_UI})
include(${QT_USE_FILE})
include_directories(${CMAKE_CURRENT_BINARY_DIR})
if(NOT WIN32)
include_directories(${LIBUNSHIELD_INCLUDE})
endif(NOT WIN32)
# Main executable
IF(OGRE_STATIC)
@ -94,6 +115,13 @@ target_link_libraries(omwlauncher
${QT_LIBRARIES}
components
)
if(NOT WIN32)
target_link_libraries(omwlauncher
${LIBUNSHIELD_LIBRARY}
)
endif(NOT WIN32)
if(DPKG_PROGRAM)
INSTALL(TARGETS omwlauncher RUNTIME DESTINATION games COMPONENT omwlauncher)

@ -11,6 +11,12 @@
#include <QDebug>
#ifndef WIN32
#include "unshieldthread.hpp"
#endif
#include "textslotmsgbox.hpp"
#include "utils/checkablemessagebox.hpp"
#include "playpage.hpp"
@ -128,11 +134,16 @@ bool MainDialog::showFirstRunDialog()
QDir dir(path);
dir.setPath(dir.canonicalPath()); // Resolve symlinks
if (!dir.cdUp())
continue; // Cannot move from Data Files
if (dir.exists(QString("Morrowind.ini")))
iniPaths.append(dir.absoluteFilePath(QString("Morrowind.ini")));
else
{
if (!dir.cdUp())
continue; // Cannot move from Data Files
if (dir.exists(QString("Morrowind.ini")))
iniPaths.append(dir.absoluteFilePath(QString("Morrowind.ini")));
}
}
// Ask the user where the Morrowind.ini is
@ -344,6 +355,78 @@ bool MainDialog::setupLauncherSettings()
return true;
}
#ifndef WIN32
bool expansions(UnshieldThread& cd)
{
if(cd.BloodmoonDone())
{
cd.Done();
return false;
}
QMessageBox expansionsBox;
expansionsBox.setText(QObject::tr("<br>Would you like to install expansions now ? (make sure you have the disc)<br> \
If you want to install both Bloodmoon and Tribunal, you have to install Tribunal first.<br>"));
QAbstractButton* tribunalButton = NULL;
if(!cd.TribunalDone())
tribunalButton = expansionsBox.addButton(QObject::tr("&Tribunal"), QMessageBox::ActionRole);
QAbstractButton* bloodmoonButton = expansionsBox.addButton(QObject::tr("&Bloodmoon"), QMessageBox::ActionRole);
QAbstractButton* noneButton = expansionsBox.addButton(QObject::tr("&None"), QMessageBox::ActionRole);
expansionsBox.exec();
if(expansionsBox.clickedButton() == noneButton)
{
cd.Done();
return false;
}
else if(expansionsBox.clickedButton() == tribunalButton)
{
TextSlotMsgBox cdbox;
cdbox.setStandardButtons(QMessageBox::Cancel);
QObject::connect(&cd,SIGNAL(signalGUI(const QString&)), &cdbox, SLOT(setTextSlot(const QString&)));
QObject::connect(&cd,SIGNAL(close()), &cdbox, SLOT(reject()));
cd.SetTribunalPath(
QFileDialog::getOpenFileName(
NULL,
QObject::tr("Select data1.hdr from Tribunal Installation CD (Tribunal/data1.hdr on GOTY CDs)"),
QDir::currentPath(),
QString(QObject::tr("Installshield hdr file (*.hdr)"))).toUtf8().constData());
cd.start();
cdbox.exec();
}
else if(expansionsBox.clickedButton() == bloodmoonButton)
{
TextSlotMsgBox cdbox;
cdbox.setStandardButtons(QMessageBox::Cancel);
QObject::connect(&cd,SIGNAL(signalGUI(const QString&)), &cdbox, SLOT(setTextSlot(const QString&)));
QObject::connect(&cd,SIGNAL(close()), &cdbox, SLOT(reject()));
cd.SetBloodmoonPath(
QFileDialog::getOpenFileName(
NULL,
QObject::tr("Select data1.hdr from Bloodmoon Installation CD (Bloodmoon/data1.hdr on GOTY CDs)"),
QDir::currentPath(),
QString(QObject::tr("Installshield hdr file (*.hdr)"))).toUtf8().constData());
cd.start();
cdbox.exec();
}
return true;
}
#endif // WIN32
bool MainDialog::setupGameSettings()
{
QString userPath = QString::fromStdString(mCfgMgr.getUserPath().string());
@ -401,9 +484,15 @@ bool MainDialog::setupGameSettings()
Press \"Browse...\" to specify the location manually.<br>"));
QAbstractButton *dirSelectButton =
msgBox.addButton(QObject::tr("B&rowse..."), QMessageBox::ActionRole);
msgBox.addButton(QObject::tr("Browse to &Install..."), QMessageBox::ActionRole);
#ifndef WIN32
QAbstractButton *cdSelectButton =
msgBox.addButton(QObject::tr("Browse to &CD..."), QMessageBox::ActionRole);
#endif
msgBox.exec();
msgBox.exec();
QString selectedFile;
if (msgBox.clickedButton() == dirSelectButton) {
@ -413,6 +502,40 @@ bool MainDialog::setupGameSettings()
QDir::currentPath(),
QString(tr("Morrowind master file (*.esm)")));
}
#ifndef WIN32
else if(msgBox.clickedButton() == cdSelectButton) {
UnshieldThread cd;
{
TextSlotMsgBox cdbox;
cdbox.setStandardButtons(QMessageBox::Cancel);
QObject::connect(&cd,SIGNAL(signalGUI(const QString&)), &cdbox, SLOT(setTextSlot(const QString&)));
QObject::connect(&cd,SIGNAL(close()), &cdbox, SLOT(reject()));
cd.SetMorrowindPath(
QFileDialog::getOpenFileName(
NULL,
QObject::tr("Select data1.hdr from Morrowind Installation CD"),
QDir::currentPath(),
QString(tr("Installshield hdr file (*.hdr)"))).toUtf8().constData());
cd.SetOutputPath(
QFileDialog::getExistingDirectory(
NULL,
QObject::tr("Select where to extract files to"),
QDir::currentPath(),
QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks).toUtf8().constData());
cd.start();
cdbox.exec();
}
while(expansions(cd));
selectedFile = QString::fromStdString(cd.GetMWEsmPath());
}
#endif // WIN32
if (selectedFile.isEmpty())
return false; // Cancel was clicked;

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

@ -15,8 +15,9 @@ source_group(game FILES ${GAME} ${GAME_HEADER})
add_openmw_dir (mwrender
renderingmanager debugging sky camera animation npcanimation creatureanimation activatoranimation
actors objects renderinginterface localmap occlusionquery terrain terrainmaterial water shadows
actors objects renderinginterface localmap occlusionquery water shadows
compositors characterpreview externalrendering globalmap videoplayer ripplesimulation refraction
terrainstorage
)
add_openmw_dir (mwinput
@ -104,7 +105,6 @@ add_definitions(${SOUND_DEFINE})
target_link_libraries(openmw
${OGRE_LIBRARIES}
${OGRE_Terrain_LIBRARY}
${OGRE_STATIC_PLUGINS}
${Boost_LIBRARIES}
${OPENAL_LIBRARY}

@ -69,8 +69,8 @@ void OMW::Engine::setAnimationVerbose(bool animverbose)
bool OMW::Engine::frameStarted (const Ogre::FrameEvent& evt)
{
if (!MWBase::Environment::get().getWindowManager()->isGuiMode())
MWBase::Environment::get().getWorld()->frameStarted(evt.timeSinceLastFrame);
bool paused = MWBase::Environment::get().getWindowManager()->isGuiMode();
MWBase::Environment::get().getWorld()->frameStarted(evt.timeSinceLastFrame, paused);
MWBase::Environment::get().getWindowManager ()->frameStarted(evt.timeSinceLastFrame);
return true;
}
@ -597,4 +597,4 @@ void OMW::Engine::setStartupScript (const std::string& path)
void OMW::Engine::setActivationDistanceOverride (int distance)
{
mActivationDistanceOverride = distance;
}
}

@ -372,7 +372,7 @@ namespace MWBase
/// \todo this does not belong here
virtual void playVideo(const std::string& name, bool allowSkipping) = 0;
virtual void stopVideo() = 0;
virtual void frameStarted (float dt) = 0;
virtual void frameStarted (float dt, bool paused) = 0;
/// Find default position inside exterior cell specified by name
/// \return false if exterior with given name not exists, true otherwise

@ -125,7 +125,7 @@ namespace MWGui
getWidget(mActorShadows, "ActorShadows");
getWidget(mStaticsShadows, "StaticsShadows");
getWidget(mMiscShadows, "MiscShadows");
getWidget(mShadowsDebug, "ShadowsDebug");
getWidget(mTerrainShadows, "TerrainShadows");
getWidget(mControlsBox, "ControlsBox");
getWidget(mResetControlsButton, "ResetControlsButton");
getWidget(mInvertYButton, "InvertYButton");
@ -161,7 +161,7 @@ namespace MWGui
mActorShadows->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled);
mStaticsShadows->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled);
mMiscShadows->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled);
mShadowsDebug->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled);
mTerrainShadows->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled);
mMasterVolumeSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition);
mVoiceVolumeSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition);
@ -238,7 +238,7 @@ namespace MWGui
mActorShadows->setCaptionWithReplacing(Settings::Manager::getBool("actor shadows", "Shadows") ? "#{sOn}" : "#{sOff}");
mStaticsShadows->setCaptionWithReplacing(Settings::Manager::getBool("statics shadows", "Shadows") ? "#{sOn}" : "#{sOff}");
mMiscShadows->setCaptionWithReplacing(Settings::Manager::getBool("misc shadows", "Shadows") ? "#{sOn}" : "#{sOff}");
mShadowsDebug->setCaptionWithReplacing(Settings::Manager::getBool("debug", "Shadows") ? "#{sOn}" : "#{sOff}");
mTerrainShadows->setCaptionWithReplacing(Settings::Manager::getBool("terrain shadows", "Shadows") ? "#{sOn}" : "#{sOff}");
float cameraSens = (Settings::Manager::getFloat("camera sensitivity", "Input")-0.2)/(5.0-0.2);
mCameraSensitivitySlider->setScrollPosition (cameraSens * (mCameraSensitivitySlider->getScrollRange()-1));
@ -394,8 +394,8 @@ namespace MWGui
Settings::Manager::setBool("statics shadows", "Shadows", newState);
else if (_sender == mMiscShadows)
Settings::Manager::setBool("misc shadows", "Shadows", newState);
else if (_sender == mShadowsDebug)
Settings::Manager::setBool("debug", "Shadows", newState);
else if (_sender == mTerrainShadows)
Settings::Manager::setBool("terrain shadows", "Shadows", newState);
else if (_sender == mInvertYButton)
Settings::Manager::setBool("invert y axis", "Input", newState);
else if (_sender == mCrosshairButton)

@ -59,7 +59,7 @@ namespace MWGui
MyGUI::Button* mActorShadows;
MyGUI::Button* mStaticsShadows;
MyGUI::Button* mMiscShadows;
MyGUI::Button* mShadowsDebug;
MyGUI::Button* mTerrainShadows;
// audio
MyGUI::ScrollBar* mMasterVolumeSlider;

@ -58,8 +58,6 @@ namespace MWRender
//if (!boost::filesystem::exists(mCacheDir + "/GlobalMap.png"))
if (1)
{
Ogre::Image image;
std::vector<Ogre::uchar> data (mWidth * mHeight * 3);
for (int x = mMinX; x <= mMaxX; ++x)
@ -144,9 +142,6 @@ namespace MWRender
b = waterDeepColour.b * 255;
}
// uncomment this line to outline cell borders
//if (cellX == 0 || cellX == cellSize-1 || cellY == 0|| cellY == cellSize-1) r = 255;
data[texelY * mWidth * 3 + texelX * 3] = r;
data[texelY * mWidth * 3 + texelX * 3+1] = g;
data[texelY * mWidth * 3 + texelX * 3+2] = b;
@ -155,13 +150,11 @@ namespace MWRender
}
}
image.loadDynamicImage (&data[0], mWidth, mHeight, Ogre::PF_B8G8R8);
//image.save (mCacheDir + "/GlobalMap.png");
Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(&data[0], data.size()));
tex = Ogre::TextureManager::getSingleton ().createManual ("GlobalMap.png", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
Ogre::TEX_TYPE_2D, mWidth, mHeight, 0, Ogre::PF_B8G8R8, Ogre::TU_STATIC);
tex->loadImage(image);
tex->loadRawData(stream, mWidth, mHeight, Ogre::PF_B8G8R8);
}
else
tex = Ogre::TextureManager::getSingleton ().getByName ("GlobalMap.png");

@ -2,6 +2,10 @@
#include <OgreMaterialManager.h>
#include <OgreHardwarePixelBuffer.h>
#include <OgreSceneManager.h>
#include <OgreSceneNode.h>
#include <OgreCamera.h>
#include <OgreTextureManager.h>
#include "../mwworld/esmstore.hpp"
@ -104,7 +108,7 @@ void LocalMap::saveFogOfWar(MWWorld::Ptr::CellStore* cell)
}
}
void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell)
void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell, float zMin, float zMax)
{
mInterior = false;
@ -118,7 +122,7 @@ void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell)
mCameraPosNode->setPosition(Vector3(0,0,0));
render((x+0.5)*sSize, (y+0.5)*sSize, -10000, 10000, sSize, sSize, name);
render((x+0.5)*sSize, (y+0.5)*sSize, zMin, zMax, sSize, sSize, name);
}
void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell,

@ -28,16 +28,18 @@ namespace MWRender
* Request the local map for an exterior cell.
* @remarks It will either be loaded from a disk cache,
* or rendered if it is not already cached.
* @param exterior cell
* @param cell exterior cell
* @param zMin min height of objects or terrain in cell
* @param zMax max height of objects or terrain in cell
*/
void requestMap (MWWorld::CellStore* cell);
void requestMap (MWWorld::CellStore* cell, float zMin, float zMax);
/**
* Request the local map for an interior cell.
* @remarks It will either be loaded from a disk cache,
* or rendered if it is not already cached.
* @param interior cell
* @param bounding box of the cell
* @param cell interior cell
* @param bounds bounding box of the cell
*/
void requestMap (MWWorld::CellStore* cell,
Ogre::AxisAlignedBox bounds);

@ -25,6 +25,8 @@
#include <components/esm/loadstat.hpp>
#include <components/settings/settings.hpp>
#include <components/terrain/terrain.hpp>
#include "../mwworld/esmstore.hpp"
#include "../mwworld/class.hpp"
@ -46,6 +48,7 @@
#include "externalrendering.hpp"
#include "globalmap.hpp"
#include "videoplayer.hpp"
#include "terrainstorage.hpp"
using namespace MWRender;
using namespace Ogre;
@ -63,6 +66,7 @@ RenderingManager::RenderingManager(OEngine::Render::OgreRenderer& _rend, const b
, mAmbientMode(0)
, mSunEnabled(0)
, mPhysicsEngine(engine)
, mTerrain(NULL)
{
// select best shader mode
bool openGL = (Ogre::Root::getSingleton ().getRenderSystem ()->getName().find("OpenGL") != std::string::npos);
@ -166,8 +170,6 @@ RenderingManager::RenderingManager(OEngine::Render::OgreRenderer& _rend, const b
mShadows = new Shadows(&mRendering);
mTerrainManager = new TerrainManager(mRendering.getScene(), this);
mSkyManager = new SkyManager(mRootNode, mRendering.getCamera());
mOcclusionQuery = new OcclusionQuery(&mRendering, mSkyManager->getSunNode());
@ -194,7 +196,7 @@ RenderingManager::~RenderingManager ()
delete mSkyManager;
delete mDebugging;
delete mShadows;
delete mTerrainManager;
delete mTerrain;
delete mLocalMap;
delete mOcclusionQuery;
delete mCompositors;
@ -225,8 +227,6 @@ void RenderingManager::removeCell (MWWorld::Ptr::CellStore *store)
mObjects.removeCell(store);
mActors.removeCell(store);
mDebugging->cellRemoved(store);
if (store->mCell->isExterior())
mTerrainManager->cellRemoved(store);
}
void RenderingManager::removeWater ()
@ -244,8 +244,6 @@ void RenderingManager::cellAdded (MWWorld::Ptr::CellStore *store)
mObjects.buildStaticGeometry (*store);
sh::Factory::getInstance().unloadUnreferencedMaterials();
mDebugging->cellAdded(store);
if (store->mCell->isExterior())
mTerrainManager->cellAdded(store);
waterAdded(store);
}
@ -548,12 +546,6 @@ void RenderingManager::setAmbientMode()
}
}
float RenderingManager::getTerrainHeightAt(Ogre::Vector3 worldPos)
{
return mTerrainManager->getTerrainHeightAt(worldPos);
}
void RenderingManager::configureAmbient(MWWorld::Ptr::CellStore &mCell)
{
mAmbientColor.setAsABGR (mCell.mCell->mAmbi.mAmbient);
@ -595,7 +587,6 @@ void RenderingManager::setSunColour(const Ogre::ColourValue& colour)
if (!mSunEnabled) return;
mSun->setDiffuseColour(colour);
mSun->setSpecularColour(colour);
mTerrainManager->setDiffuse(colour);
}
void RenderingManager::setAmbientColour(const Ogre::ColourValue& colour)
@ -608,7 +599,6 @@ void RenderingManager::setAmbientColour(const Ogre::ColourValue& colour)
final += Ogre::ColourValue(0.7,0.7,0.7,0) * std::min(1.f, (nightEye/100.f));
mRendering.getScene()->setAmbientLight(final);
mTerrainManager->setAmbient(final);
}
void RenderingManager::sunEnable(bool real)
@ -652,7 +642,17 @@ void RenderingManager::setGlare(bool glare)
void RenderingManager::requestMap(MWWorld::Ptr::CellStore* cell)
{
if (cell->mCell->isExterior())
mLocalMap->requestMap(cell);
{
assert(mTerrain);
Ogre::AxisAlignedBox dims = mObjects.getDimensions(cell);
Ogre::Vector2 center(cell->mCell->getGridX() + 0.5, cell->mCell->getGridY() + 0.5);
dims.merge(mTerrain->getWorldBoundingBox(center));
mTerrain->update(dims.getCenter());
mLocalMap->requestMap(cell, dims.getMinimum().z, dims.getMaximum().z);
}
else
mLocalMap->requestMap(cell, mObjects.getDimensions(cell));
}
@ -838,7 +838,12 @@ void RenderingManager::processChangedSettings(const Settings::CategorySettingVec
mWater->processChangedSettings(settings);
if (rebuild)
{
mObjects.rebuildStaticGeometry();
if (mTerrain)
mTerrain->applyMaterials(Settings::Manager::getBool("enabled", "Shadows"),
Settings::Manager::getBool("split", "Shadows"));
}
}
void RenderingManager::setMenuTransparency(float val)
@ -982,9 +987,13 @@ void RenderingManager::updateWaterRippleEmitterPtr (const MWWorld::Ptr& old, con
mWater->updateEmitterPtr(old, ptr);
}
void RenderingManager::frameStarted(float dt)
void RenderingManager::frameStarted(float dt, bool paused)
{
mWater->frameStarted(dt);
if (mTerrain && mTerrain->getVisible())
mTerrain->update(mRendering.getCamera()->getRealPosition());
if (!paused)
mWater->frameStarted(dt);
}
void RenderingManager::resetCamera()
@ -992,4 +1001,30 @@ void RenderingManager::resetCamera()
mCamera->reset();
}
float RenderingManager::getTerrainHeightAt(Ogre::Vector3 worldPos)
{
assert(mTerrain);
return mTerrain->getHeightAt(worldPos);
}
void RenderingManager::enableTerrain(bool enable)
{
if (enable)
{
if (!mTerrain)
{
mTerrain = new Terrain::Terrain(mRendering.getScene(), new MWRender::TerrainStorage(), RV_Terrain,
Settings::Manager::getBool("distant land", "Terrain"),
Settings::Manager::getBool("shader", "Terrain"));
mTerrain->applyMaterials(Settings::Manager::getBool("enabled", "Shadows"),
Settings::Manager::getBool("split", "Shadows"));
mTerrain->update(mRendering.getCamera()->getRealPosition());
}
mTerrain->setVisible(true);
}
else
if (mTerrain)
mTerrain->setVisible(false);
}
} // namespace

@ -2,7 +2,6 @@
#define _GAME_RENDERING_MANAGER_H
#include "sky.hpp"
#include "terrain.hpp"
#include "debugging.hpp"
#include <openengine/ogre/fader.hpp>
@ -39,6 +38,11 @@ namespace sh
class Factory;
}
namespace Terrain
{
class Terrain;
}
namespace MWRender
{
class Shadows;
@ -106,6 +110,8 @@ public:
void cellAdded (MWWorld::CellStore *store);
void waterAdded(MWWorld::CellStore *store);
void enableTerrain(bool enable);
void removeWater();
void preCellChange (MWWorld::CellStore* store);
@ -153,6 +159,8 @@ public:
bool occlusionQuerySupported() { return mOcclusionQuery->supported(); }
OcclusionQuery* getOcclusionQuery() { return mOcclusionQuery; }
float getTerrainHeightAt (Ogre::Vector3 worldPos);
Shadows* getShadows();
void switchToInterior();
@ -160,8 +168,6 @@ public:
void getTriangleBatchCount(unsigned int &triangles, unsigned int &batches);
float getTerrainHeightAt (Ogre::Vector3 worldPos);
void setGlare(bool glare);
void skyEnable ();
void skyDisable ();
@ -205,7 +211,7 @@ public:
void playVideo(const std::string& name, bool allowSkipping);
void stopVideo();
void frameStarted(float dt);
void frameStarted(float dt, bool paused);
protected:
virtual void windowResized(int x, int y);
@ -228,7 +234,7 @@ private:
OcclusionQuery* mOcclusionQuery;
TerrainManager* mTerrainManager;
Terrain::Terrain* mTerrain;
MWRender::Water *mWater;

@ -107,8 +107,8 @@ void Shadows::recreate()
// Set visibility mask for the shadow render textures
int visibilityMask = RV_Actors * Settings::Manager::getBool("actor shadows", "Shadows")
+ (RV_Statics + RV_StaticsSmall) * Settings::Manager::getBool("statics shadows", "Shadows")
+ RV_Misc * Settings::Manager::getBool("misc shadows", "Shadows");
+ RV_Misc * Settings::Manager::getBool("misc shadows", "Shadows")
+ RV_Terrain * (Settings::Manager::getBool("terrain shadows", "Shadows"));
for (int i = 0; i < (split ? 3 : 1); ++i)
{
TexturePtr shadowTexture = mSceneMgr->getShadowTexture(i);

@ -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> &ltexStore =
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

@ -202,7 +202,10 @@ Water::Water (Ogre::Camera *camera, RenderingManager* rend) :
mWaterPlane = Plane(Vector3::UNIT_Z, 0);
MeshManager::getSingleton().createPlane("water", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, mWaterPlane, CELL_SIZE*5, CELL_SIZE * 5, 10, 10, true, 1, 3,3, Vector3::UNIT_Y);
int waterScale = 300;
MeshManager::getSingleton().createPlane("water", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, mWaterPlane,
CELL_SIZE*5*waterScale, CELL_SIZE*5*waterScale, 10, 10, true, 1, 3*waterScale,3*waterScale, Vector3::UNIT_Y);
mWater = mSceneMgr->createEntity("water");
mWater->setVisibilityFlags(RV_Water);

@ -1,5 +1,7 @@
#include "scene.hpp"
#include <OgreSceneNode.h>
#include <components/nif/niffile.hpp>
#include <libs/openengine/ogre/fader.hpp>
@ -360,6 +362,8 @@ namespace MWWorld
const MWWorld::Store<ESM::GameSetting> &gmst =
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
mRendering.enableTerrain(false);
std::string loadingInteriorText;
loadingInteriorText = gmst.find ("sLoadingMessage2")->getString();
@ -438,6 +442,8 @@ namespace MWWorld
MWBase::Environment::get().getWorld()->positionToIndex (position.pos[0], position.pos[1], x, y);
mRendering.enableTerrain(true);
changeCell (x, y, position, true);
}

@ -1,5 +1,7 @@
#include "worldimp.hpp"
#include <OgreSceneNode.h>
#include <libs/openengine/bullet/physic.hpp>
#include <components/bsa/bsa_archive.hpp>
@ -1024,10 +1026,13 @@ namespace MWWorld
return;
}
float terrainHeight = mRendering->getTerrainHeightAt(pos);
if (ptr.getCell()->isExterior())
{
float terrainHeight = mRendering->getTerrainHeightAt(pos);
if (pos.z < terrainHeight)
pos.z = terrainHeight;
if (pos.z < terrainHeight)
pos.z = terrainHeight;
}
ptr.getRefData().getPosition().pos[2] = pos.z + 20; // place slightly above. will snap down to ground with code below
@ -1072,15 +1077,8 @@ namespace MWWorld
{
const int cellSize = 8192;
cellX = static_cast<int> (x/cellSize);
if (x<0)
--cellX;
cellY = static_cast<int> (y/cellSize);
if (y<0)
--cellY;
cellX = std::floor(x/cellSize);
cellY = std::floor(y/cellSize);
}
void World::queueMovement(const Ptr &ptr, const Vector3 &velocity)
@ -1677,9 +1675,9 @@ namespace MWWorld
mRendering->stopVideo();
}
void World::frameStarted (float dt)
void World::frameStarted (float dt, bool paused)
{
mRendering->frameStarted(dt);
mRendering->frameStarted(dt, paused);
}
void World::activateDoor(const MWWorld::Ptr& door)

@ -419,7 +419,7 @@ namespace MWWorld
/// \todo this does not belong here
virtual void playVideo(const std::string& name, bool allowSkipping);
virtual void stopVideo();
virtual void frameStarted (float dt);
virtual void frameStarted (float dt, bool paused);
/// Find center of exterior cell above land surface
/// \return false if exterior with given name not exists, true otherwise

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

@ -65,6 +65,10 @@ add_component_dir (interpreter
add_component_dir (translation
translation
)
add_component_dir (terrain
quadtreenode chunk terrain storage material
)
find_package(Qt4 COMPONENTS QtCore QtGui)

@ -70,7 +70,7 @@ struct Land
};
#pragma pack(pop)
typedef uint8_t VNML[LAND_NUM_VERTS * 3];
typedef signed char VNML[LAND_NUM_VERTS * 3];
struct LandData
{

@ -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 &center, 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 &center)
{
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 &center, 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,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,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

@ -80,7 +80,6 @@
#endif
#if VERTEX_LIGHTING
shUniform(float, lightCount) @shAutoConstant(lightCount, light_count)
shUniform(float4, lightPosition[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightPosition, light_position_view_space_array, @shGlobalSettingString(num_lights))
shUniform(float4, lightDiffuse[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightDiffuse, light_diffuse_colour_array, @shGlobalSettingString(num_lights))
shUniform(float4, lightAttenuation[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightAttenuation, light_attenuation_array, @shGlobalSettingString(num_lights))

@ -2,7 +2,7 @@
#define IS_FIRST_PASS (@shPropertyString(pass_index) == 0)
#define FOG @shGlobalSettingBool(fog)
#define FOG (@shGlobalSettingBool(fog) && !@shPropertyBool(render_composite_map))
#define SHADOWS_PSSM @shGlobalSettingBool(shadows_pssm)
#define SHADOWS @shGlobalSettingBool(shadows)
@ -11,8 +11,6 @@
#include "shadows.h"
#endif
#define COLOUR_MAP @shPropertyBool(colour_map)
#define NUM_LAYERS @shPropertyString(num_layers)
#if FOG || SHADOWS_PSSM
@ -23,9 +21,12 @@
#define VIEWPROJ_FIX @shGlobalSettingBool(viewproj_fix)
#if !IS_FIRST_PASS
// This is not the first pass.
#endif
#define RENDERCMP @shPropertyBool(render_composite_map)
#define LIGHTING !RENDERCMP
#define COMPOSITE_MAP @shPropertyBool(display_composite_map)
#if NEED_DEPTH
@shAllocatePassthrough(1, depth)
@ -35,6 +36,11 @@
@shAllocatePassthrough(3, worldPos)
#if LIGHTING
@shAllocatePassthrough(3, lightResult)
@shAllocatePassthrough(3, directionalResult)
#endif
#if SHADOWS
@shAllocatePassthrough(4, lightSpacePos0)
#endif
@ -55,11 +61,19 @@
#if VIEWPROJ_FIX
shUniform(float4, vpRow2Fix) @shSharedParameter(vpRow2Fix, vpRow2Fix)
#endif
shUniform(float2, lodMorph) @shAutoConstant(lodMorph, custom, 1001)
shVertexInput(float2, uv0)
shVertexInput(float2, uv1) // lodDelta, lodThreshold
#if LIGHTING
shNormalInput(float4)
shColourInput(float4)
shUniform(float, lightCount) @shAutoConstant(lightCount, light_count)
shUniform(float4, lightPosition[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightPosition, light_position_object_space_array, @shGlobalSettingString(num_lights))
shUniform(float4, lightDiffuse[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightDiffuse, light_diffuse_colour_array, @shGlobalSettingString(num_lights))
shUniform(float4, lightAttenuation[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightAttenuation, light_attenuation_array, @shGlobalSettingString(num_lights))
shUniform(float4, lightAmbient) @shAutoConstant(lightAmbient, ambient_light_colour)
#if SHADOWS
shUniform(float4x4, texViewProjMatrix0) @shAutoConstant(texViewProjMatrix0, texture_viewproj_matrix)
@ -71,31 +85,15 @@
@shEndForeach
#endif
#endif
@shPassthroughVertexOutputs
SH_START_PROGRAM
{
float4 worldPos = shMatrixMult(worldMatrix, shInputPosition);
// determine whether to apply the LOD morph to this vertex
// we store the deltas against all vertices so we only want to apply
// the morph to the ones which would disappear. The target LOD which is
// being morphed to is stored in lodMorph.y, and the LOD at which
// the vertex should be morphed is stored in uv.w. If we subtract
// the former from the latter, and arrange to only morph if the
// result is negative (it will only be -1 in fact, since after that
// the vertex will never be indexed), we will achieve our aim.
// sign(vertexLOD - targetLOD) == -1 is to morph
float toMorph = -min(0, sign(uv1.y - lodMorph.y));
// morph
// this assumes XY terrain alignment
worldPos.z += uv1.x * toMorph * lodMorph.x;
shOutputPosition = shMatrixMult(viewProjMatrix, worldPos);
#if NEED_DEPTH
@ -124,6 +122,8 @@
@shPassthroughAssign(worldPos, worldPos.xyz);
#if LIGHTING
#if SHADOWS
float4 lightSpacePos = shMatrixMult(texViewProjMatrix0, shMatrixMult(worldMatrix, shInputPosition));
@shPassthroughAssign(lightSpacePos0, lightSpacePos);
@ -138,6 +138,34 @@
@shEndForeach
#endif
// Lighting
float3 lightDir;
float d;
float3 lightResult = float3(0,0,0);
float3 directionalResult = float3(0,0,0);
@shForeach(@shGlobalSettingString(num_lights))
lightDir = lightPosition[@shIterator].xyz - (shInputPosition.xyz * lightPosition[@shIterator].w);
d = length(lightDir);
lightDir = normalize(lightDir);
lightResult.xyz += lightDiffuse[@shIterator].xyz
* shSaturate(1.0 / ((lightAttenuation[@shIterator].y) + (lightAttenuation[@shIterator].z * d) + (lightAttenuation[@shIterator].w * d * d)))
* max(dot(normal.xyz, lightDir), 0);
#if @shIterator == 0
directionalResult = lightResult.xyz;
#endif
@shEndForeach
lightResult.xyz += lightAmbient.xyz;
lightResult.xyz *= colour.xyz;
directionalResult.xyz *= colour.xyz;
@shPassthroughAssign(lightResult, lightResult);
@shPassthroughAssign(directionalResult, directionalResult);
#endif
}
#else
@ -151,12 +179,9 @@
SH_BEGIN_PROGRAM
#if COLOUR_MAP
shSampler2D(colourMap)
#endif
shSampler2D(normalMap) // global normal map
#if COMPOSITE_MAP
shSampler2D(compositeMap)
#else
@shForeach(@shPropertyString(num_blendmaps))
shSampler2D(blendMap@shIterator)
@ -165,6 +190,8 @@
@shForeach(@shPropertyString(num_layers))
shSampler2D(diffuseMap@shIterator)
@shEndForeach
#endif
#if FOG
shUniform(float3, fogColour) @shAutoConstant(fogColour, fog_colour)
@ -215,9 +242,6 @@
float2 UV = @shPassthroughReceive(UV);
float3 worldPos = @shPassthroughReceive(worldPos);
float3 normal = shSample(normalMap, UV).rgb * 2 - 1;
normal = normalize(normal);
#if UNDERWATER
@ -230,17 +254,26 @@
float previousAlpha = 1.f;
#endif
shOutputColour(0) = float4(1,1,1,1);
#if COMPOSITE_MAP
shOutputColour(0).xyz = shSample(compositeMap, UV).xyz;
#else
// Layer calculations
// rescale UV to directly map vertices to texel centers
// rescale UV to directly map edge vertices to texel centers - this is
// important to get correct blending at cell transitions
// TODO: parameterize texel size
float2 blendUV = (UV - 0.5) * (8.0 / (8.0+1.0)) + 0.5;
float2 blendUV = (UV - 0.5) * (16.0 / (16.0+1.0)) + 0.5;
@shForeach(@shPropertyString(num_blendmaps))
float4 blendValues@shIterator = shSample(blendMap@shIterator, blendUV);
float4 blendValues@shIterator = shSaturate(shSample(blendMap@shIterator, blendUV));
@shEndForeach
float3 albedo = float3(0,0,0);
float2 layerUV = UV * 8;
float2 layerUV = UV * 16;
@shForeach(@shPropertyString(num_layers))
@ -262,25 +295,14 @@ float2 blendUV = (UV - 0.5) * (8.0 / (8.0+1.0)) + 0.5;
#endif
@shEndForeach
shOutputColour(0) = float4(1,1,1,1);
shOutputColour(0).rgb *= albedo;
#if COLOUR_MAP
// Since we're emulating vertex colors here,
// rescale UV to directly map vertices to texel centers. TODO: parameterize texel size
const float colourmapSize = 33.f;
float2 colourUV = (UV - 0.5) * (colourmapSize / (colourmapSize+1.f)) + 0.5;
shOutputColour(0).rgb *= shSample(colourMap, colourUV).rgb;
#endif
shOutputColour(0).rgb *= albedo;
#if LIGHTING
// Lighting
float3 lightResult = @shPassthroughReceive(lightResult);
float3 directionalResult = @shPassthroughReceive(directionalResult);
// shadows only for the first (directional) light
#if SHADOWS
@ -305,40 +327,9 @@ float2 blendUV = (UV - 0.5) * (8.0 / (8.0+1.0)) + 0.5;
float shadow = 1.0;
#endif
float3 lightDir;
float3 diffuse = float3(0,0,0);
float d;
@shForeach(@shGlobalSettingString(terrain_num_lights))
lightDir = lightPosObjSpace@shIterator.xyz - (worldPos.xyz * lightPosObjSpace@shIterator.w);
d = length(lightDir);
lightDir = normalize(lightDir);
#if @shIterator == 0
#if (SHADOWS || SHADOWS_PSSM)
diffuse += lightDiffuse@shIterator.xyz * (1.0 / ((lightAttenuation@shIterator.y) + (lightAttenuation@shIterator.z * d) + (lightAttenuation@shIterator.w * d * d))) * max(dot(normal, lightDir), 0) * shadow;
#else
diffuse += lightDiffuse@shIterator.xyz * (1.0 / ((lightAttenuation@shIterator.y) + (lightAttenuation@shIterator.z * d) + (lightAttenuation@shIterator.w * d * d))) * max(dot(normal, lightDir), 0);
#endif
#else
diffuse += lightDiffuse@shIterator.xyz * (1.0 / ((lightAttenuation@shIterator.y) + (lightAttenuation@shIterator.z * d) + (lightAttenuation@shIterator.w * d * d))) * max(dot(normal, lightDir), 0);
shOutputColour(0).xyz *= (lightResult - directionalResult * (1.0-shadow));
#endif
@shEndForeach
shOutputColour(0).xyz *= (lightAmbient.xyz + diffuse);
#if FOG
float fogValue = shSaturate((depth - fogParams.y) * fogParams.w);
@ -357,7 +348,6 @@ float2 blendUV = (UV - 0.5) * (8.0 / (8.0+1.0)) + 0.5;
#else
shOutputColour(0).a = 1.f-previousAlpha;
#endif
}
#endif

@ -335,12 +335,12 @@
</Widget>
</Widget>
<Widget type="HBox" position="4 172 350 24">
<Widget type="AutoSizedButton" skin="MW_Button" align="Left Top" name="ShadowsDebug"/>
<Widget type="HBox" position="4 140 350 24">
<Widget type="AutoSizedButton" skin="MW_Button" align="Left Top" name="TerrainShadows"/>
<Widget type="AutoSizedTextBox" skin="SandText" align="Left Top">
<Property key="Caption" value="Debug overlay"/>
<Property key="Caption" value="Terrain shadows"/>
</Widget>
</Widget>
</Widget>
</Widget>

@ -80,6 +80,7 @@ texture size = 1024
actor shadows = true
misc shadows = true
statics shadows = true
terrain shadows = true
# Fraction of the total shadow distance after which the shadow starts to fade out
fade start = 0.8
@ -125,8 +126,9 @@ fog start factor = 0.5
fog end factor = 1.0
[Terrain]
# Max. number of lights that affect the terrain. Setting to 1 will only reflect sunlight
num lights = 8
distant land = false
shader = true
[Water]
shader = true

@ -56,7 +56,7 @@ void ImageRotate::rotate(const std::string& sourceImage, const std::string& dest
TEX_TYPE_2D,
width, height,
0,
PF_FLOAT16_RGBA,
PF_A8B8G8R8,
TU_RENDERTARGET);
RenderTarget* rtt = destTextureRot->getBuffer()->getRenderTarget();
@ -75,7 +75,7 @@ void ImageRotate::rotate(const std::string& sourceImage, const std::string& dest
TEX_TYPE_2D,
width, height,
0,
PF_FLOAT16_RGBA,
PF_A8B8G8R8,
Ogre::TU_STATIC);
destTexture->getBuffer()->blit(destTextureRot->getBuffer());

Loading…
Cancel
Save