- integrated Yacobys landscape. First commit.

git-svn-id: https://openmw.svn.sourceforge.net/svnroot/openmw/trunk@112 ea6a568a-9f4f-0410-981a-c910a81bb256
actorid
nkorslund 15 years ago
parent ea486dd770
commit a46804dae3

@ -7,8 +7,12 @@ DMD=gdmd -version=Posix
# Some extra flags for niftool and bsatool
NIFFLAGS=-debug=warnstd -debug=check -debug=statecheck -debug=strict -debug=verbose
# Linker flags
LFLAGS= -L-lopenal -L-lOgreMain -L-lOIS -L-lmygui -L-luuid -L-lavcodec -L-lavformat bullet/libbulletdynamics.a bullet/libbulletcollision.a bullet/libbulletmath.a -L-lboost_serialization
# Compiler settings for Ogre, OIS and MyGUI
CF_OIS=$(shell pkg-config --cflags OIS OGRE MyGUI)
# TODO: the -I when we're done
CF_OIS=$(shell pkg-config --cflags OIS OGRE MyGUI) -Iterrain/
OGCC=$(CXX) $(CXXFLAGS) $(CF_OIS)
# Compiler settings for ffmpeg.
@ -23,10 +27,15 @@ BGCC=$(CXX) $(CXXFLAGS) $(CF_BULLET)
# passed to the compiler, the rest are dependencies.
ogre_cpp=ogre framelistener interface bsaarchive
# MyGUI C++ files, gui/cpp_X.cpp. These are currently included into
# with cpp_ogre.cpp.
# MyGUI C++ files, gui/cpp_X.cpp. These are currently included
# cpp_ogre.o with cpp_ogre.cpp.
mygui_cpp=mygui console
# Ditto for the landscape engine, in terrain/cpp_X.cpp
terrain_cpp=baseland esm framelistener generator index landdata\
materialgen meshinterface mwheightmap mwquadmatgen palette point2\
quad quaddata quadsegment terraincls terrain terrainmesh
# FFmpeg files, in the form sound/cpp_X.cpp.
avcodec_cpp=avcodec
@ -35,7 +44,10 @@ bullet_cpp=bullet player scale
#### No modifications should be required below this line. ####
ogre_cpp_files=$(ogre_cpp:%=ogre/cpp_%.cpp) $(mygui_cpp:%=gui/cpp_%.cpp)
ogre_cpp_files=\
$(ogre_cpp:%=ogre/cpp_%.cpp) \
$(mygui_cpp:%=gui/cpp_%.cpp) \
$(terrain_cpp:%=terrain/cpp_%.cpp)
avcodec_cpp_files=$(avcodec_cpp:%=sound/cpp_%.cpp)
bullet_cpp_files=$(bullet_cpp:%=bullet/cpp_%.cpp)
@ -43,7 +55,7 @@ bullet_cpp_files=$(bullet_cpp:%=bullet/cpp_%.cpp)
src := $(wildcard bsa/*.d) $(wildcard bullet/*.d) $(wildcard core/*.d) \
$(wildcard esm/*.d) $(wildcard input/*.d) $(wildcard nif/*.d) $(wildcard ogre/*.d) \
$(wildcard scene/*.d) $(wildcard sound/*.d) $(wildcard util/*.d) $(wildcard gui/*.d)
src := $(src) $(wildcard mscripts/*.d)
src := $(src) $(wildcard mscripts/*.d) $(wildcard terrain/*.d)
src := $(src) monster/monster.d \
$(wildcard monster/vm/*.d) \
$(wildcard monster/compiler/*.d) \
@ -85,10 +97,10 @@ nifobjs/%.o: %.d
$(DMD) $(NIFFLAGS) -c $< -of$@
openmw: openmw.d cpp_ogre.o cpp_avcodec.o cpp_bullet.o $(obj)
$(DMD) $^ -of$@ -L-lopenal -L-lOgreMain -L-lOIS -L-lmygui -L-luuid -L-lavcodec -L-lavformat bullet/libbulletdynamics.a bullet/libbulletcollision.a bullet/libbulletmath.a
$(DMD) $^ -of$@ $(LFLAGS)
esmtool: esmtool.d cpp_ogre.o cpp_avcodec.o cpp_bullet.o $(obj)
$(DMD) $^ -of$@ -L-lopenal -L-lOgreMain -L-lOIS -L-lmygui -L-luuid -L-lavcodec -L-lavformat bullet/libbulletdynamics.a bullet/libbulletcollision.a bullet/libbulletmath.a
$(DMD) $^ -of$@ $(LFLAGS)
niftool: niftool.d $(obj_nif)
$(DMD) $^ -of$@

@ -32,6 +32,7 @@ typedef void* WidgetPtr;
void gui_setupGUI(int debugOut);
void gui_toggleGui();
void gui_setCellName(char *str);
void gui_showHUD();
// Console stuff
void gui_toggleConsole();

@ -450,6 +450,12 @@ extern "C" void gui_toggleGui()
extern "C" int32_t* gui_getGuiModePtr() { return &guiMode; }
extern "C" void gui_showHUD()
{
if(hud)
hud->setVisible(true);
}
extern "C" void gui_setupGUI(int32_t debugOut)
{
ResourceGroupManager::getSingleton().
@ -480,7 +486,6 @@ extern "C" void gui_setupGUI(int32_t debugOut)
state.X.abs = mWidth / 2;
state.Y.abs = mHeight / 2;
//*
// Set up the HUD
hud = new HUD();
@ -494,7 +499,8 @@ extern "C" void gui_setupGUI(int32_t debugOut)
hud->setSpellStatus(65, 100);
hud->setEffect("icons\\s\\tx_s_chameleon.dds");
//*/
hud->setVisible(false);
//new MainMenu();
}

@ -232,6 +232,24 @@ void getHeight()
stack.pushInt(gui_getHeight(null));
}
void initGUI(bool debugOut)
{
// Load the GUI system
gui_setupGUI(debugOut);
}
void startGUI()
{
gui_showHUD();
// Run GUI scripts
// Create the HUD and windows
vm.run("makegui.mn");
// Run the fps ticker
vm.run("fpsticker.mn");
}
void setupGUIScripts()
{
vm.addPath("mscripts/guiscripts/");

@ -214,10 +214,14 @@ const float sndRefresh = 0.17;
// Refresh rate for music fadeing, seconds.
const float musRefresh = 0.05;
// Walking / floating speed, in points per second.
float speed = 300;
float sndCumTime = 0;
float musCumTime = 0;
void initializeInput()
// Move the player according to playerData.position
void movePlayer()
{
// Move the player into place. TODO: This isn't really input-related
// at all, and should be moved.
@ -228,6 +232,11 @@ void initializeInput()
bullet_movePlayer(position[0], position[1], position[2]);
}
}
void initializeInput()
{
movePlayer();
// TODO/FIXME: This should have been in config, but DMD's module
// system is on the brink of collapsing, and it won't compile if I
@ -250,6 +259,22 @@ bool isPressed(Keys key)
return false;
}
// Enable superman mode, ie. flight and super-speed. This is getting
// very spaghetti-ish.
extern(C) void d_superman()
{
bullet_fly();
speed = 8000;
with(*playerData.position)
{
position[0] = 0;
position[1] = 0;
position[2] = 12000;
}
movePlayer();
}
extern(C) int d_frameStarted(float time)
{
if(doExit) return 0;
@ -270,9 +295,6 @@ extern(C) int d_frameStarted(float time)
// The rest is ignored in pause or GUI mode
if(pause || *guiMode > 0) return 1;
// Walking / floating speed, in points per second.
const float speed = 300;
// Check if the movement keys are pressed
float moveX = 0, moveY = 0, moveZ = 0;
float x, y, z, ox, oy, oz;

@ -54,17 +54,6 @@ void initMonsterScripts()
setupGUIScripts();
}
// Run the GUI scripts. These should be run only after the
// GUI/rendering system has been initialized
void runGUIScripts()
{
// Create the HUD and windows
vm.run("makegui.mn");
// Run the fps ticker
vm.run("fpsticker.mn");
}
// This should probably not be here:
import monster.vm.dbg;

@ -59,34 +59,9 @@ extern "C" int32_t ogre_configure(
mRoot = new Root(plugincfg, "ogre.cfg", "");
// Add the BSA archive manager before reading the config file.
// Add the BSA archive manager
ArchiveManager::getSingleton().addArchiveFactory( &mBSAFactory );
/* The only entry we use from resources.cfg is the "BSA=internal"
entry, which we can put in manually.
// Load resource paths from config file
ConfigFile cf;
cf.load("resources.cfg");
// Go through all sections & settings in the file
ConfigFile::SectionIterator seci = cf.getSectionIterator();
String secName, typeName, archName;
while (seci.hasMoreElements())
{
secName = seci.peekNextKey();
ConfigFile::SettingsMultiMap *settings = seci.getNext();
ConfigFile::SettingsMultiMap::iterator i;
for (i = settings->begin(); i != settings->end(); ++i)
{
typeName = i->first;
archName = i->second;
ResourceGroupManager::getSingleton().addResourceLocation(
archName, typeName, secName);
}
}
*/
ResourceGroupManager::getSingleton().
addResourceLocation("internal", "BSA", "General");
@ -328,10 +303,7 @@ void cloneNode(SceneNode *from, SceneNode *to, char* name)
SceneNode::ObjectIterator it = from->getAttachedObjectIterator();
while(it.hasMoreElements())
{
// We can't handle non-entities. To be honest I have no idea
// what dynamic_cast does or if it's correct here. I used to be
// a C++ person but after discovering D I dropped C++ like it
// was red hot iron and never looked back.
// We can't handle non-entities.
Entity *e = dynamic_cast<Entity*> (it.getNext());
if(e)
{

@ -70,8 +70,11 @@ int32_t guiMode = 0;
// Morrowind.
SceneNode *root;
// Include the other parts of the code, and make one big object file.
// Include the other parts of the code, and make one big happy object
// file. This is extremely against the grain of C++ "recomended
// practice", but I don't care.
#include "../gui/cpp_mygui.cpp"
#include "cpp_framelistener.cpp"
#include "cpp_bsaarchive.cpp"
#include "cpp_interface.cpp"
#include "../terrain/cpp_terrain.cpp"

@ -119,12 +119,6 @@ void setupOgre(bool debugOut)
// exterior cells differently, etc.
ogre_makeScene();
// Load the GUI system
gui_setupGUI(debugOut);
// Run the GUI scripts
runGUIScripts();
ogreSetup = true;
}

@ -1,6 +1,6 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Copyright (C) 2008-2009 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
@ -31,6 +31,7 @@ import std.file;
import ogre.ogre;
import ogre.bindings;
import gui.bindings;
import gui.gui;
import bullet.bullet;
@ -51,6 +52,8 @@ import sound.audio;
import input.events;
import terrain.terrain;
//*
import std.gc;
import gcstats;
@ -72,6 +75,7 @@ void main(char[][] args)
bool showOgreFlag = false;
bool noSound = false;
bool debugOut = false;
bool extTest = false;
// Some examples to try:
//
@ -88,16 +92,10 @@ void main(char[][] args)
// Cells to load
char[][] cells;
int[] eCells;
foreach(char[] a; args[1..$])
if(a == "-n") render = false;
else if(a.begins("-e"))
{
int i = find(a,',');
eCells ~= atoi(a[2..i]);
eCells ~= atoi(a[i+1..$]);
}
else if(a == "-ex") extTest = true;
else if(a == "-h") help=true;
else if(a == "-rk") resetKeys = true;
else if(a == "-oc") showOgreFlag = true;
@ -112,7 +110,7 @@ void main(char[][] args)
}
else cells ~= a;
if(cells.length + eCells.length/2 > 1 )
if(cells.length > 1)
{
writefln("More than one cell specified, rendering disabled");
render=false;
@ -123,7 +121,7 @@ void main(char[][] args)
writefln("Syntax: %s [options] cell-name [cell-name]", args[0]);
writefln(" Options:");
writefln(" -n Only load, do not render");
writefln(" -ex,y Load exterior cell (x,y)");
writefln(" -ex Test the terrain system");
writefln(" -rk Reset key bindings to default");
writefln(" -oc Show the Ogre config dialogue");
writefln(" -ns Completely disable sound");
@ -163,14 +161,14 @@ void main(char[][] args)
// setting.
if(showOgreFlag) config.finalOgreConfig = true;
if(cells.length == 0 && eCells.length == 0)
if(cells.length == 0)
if(config.defaultCell.length)
cells ~= config.defaultCell;
if(cells.length == 1)
config.defaultCell = cells[0];
if(cells.length == 0 && eCells.length == 0)
if(cells.length == 0)
{
showHelp();
return;
@ -206,27 +204,6 @@ Try specifying another cell name on the command line, or edit openmw.ini.");
}
}
for(int i; i<eCells.length; i+=2)
{
int x = eCells[i];
int y = eCells[i+1];
// Release the last cell data
cellList.release(cd);
// Get a cell data holder and load an interior cell
cd = cellList.get();
if(debugOut) writefln("Will load %s,%s", x, y);
try cd.loadExtCell(x,y);
catch(Exception e)
{
writefln(e);
writefln("\nUnable to load cell (%s,%s). Aborting", x,y);
return;
}
}
// Simple safety hack
NodePtr putObject(MeshIndex m, Placement *pos, float scale,
bool collide=false)
@ -250,21 +227,23 @@ Try specifying another cell name on the command line, or edit openmw.ini.");
setupOgre(debugOut);
scope(exit) cleanupOgre();
// Create the GUI system
initGUI(debugOut);
// Set up Bullet
initBullet();
scope(exit) cleanupBullet();
if(cd.inCell)
{
setAmbient(cd.ambi.ambient, cd.ambi.sunlight,
cd.ambi.fog, cd.ambi.fogDensity);
// Initialize the internal input and event manager. The
// lower-level input system (OIS) is initialized by the
// setupOgre() call further up.
initializeInput();
// Not all interior cells have water
if(cd.inCell.flags & CellFlags.HasWater)
ogre_createWater(cd.water);
}
else
{
// Play some old tunes
if(extTest)
{
// Exterior cell
/*
Color c;
c.red = 180;
c.green = 180;
@ -276,104 +255,118 @@ Try specifying another cell name on the command line, or edit openmw.ini.");
// Create an ugly sky
ogre_makeSky();
}
*/
// Insert the meshes of statics into the scene
foreach(ref LiveStatic ls; cd.statics)
putObject(ls.m.model, ls.getPos(), ls.getScale(), true);
// Inventory lights
foreach(ref LiveLight ls; cd.lights)
{
NodePtr n = putObject(ls.m.model, ls.getPos(), ls.getScale());
ls.lightNode = attachLight(n, ls.m.data.color, ls.m.data.radius);
if(!noSound)
{
Sound *s = ls.m.sound;
if(s)
{
ls.loopSound = soundScene.insert(s, true);
if(ls.loopSound)
initTerrain();
}
else
{
// Interior cell
assert(cd.inCell !is null);
setAmbient(cd.ambi.ambient, cd.ambi.sunlight,
cd.ambi.fog, cd.ambi.fogDensity);
// Not all interior cells have water
if(cd.inCell.flags & CellFlags.HasWater)
ogre_createWater(cd.water);
// Insert the meshes of statics into the scene
foreach(ref LiveStatic ls; cd.statics)
putObject(ls.m.model, ls.getPos(), ls.getScale(), true);
// Inventory lights
foreach(ref LiveLight ls; cd.lights)
{
NodePtr n = putObject(ls.m.model, ls.getPos(), ls.getScale());
ls.lightNode = attachLight(n, ls.m.data.color, ls.m.data.radius);
if(!noSound)
{
auto p = ls.getPos();
ls.loopSound.setPos(p.position[0],
p.position[1],
p.position[2]);
Sound *s = ls.m.sound;
if(s)
{
ls.loopSound = soundScene.insert(s, true);
if(ls.loopSound)
{
auto p = ls.getPos();
ls.loopSound.setPos(p.position[0],
p.position[1],
p.position[2]);
}
}
}
}
}
}
// Static lights
foreach(ref LiveLight ls; cd.statLights)
{
NodePtr n = putObject(ls.m.model, ls.getPos(), ls.getScale(), true);
ls.lightNode = attachLight(n, ls.m.data.color, ls.m.data.radius);
if(!noSound)
{
Sound *s = ls.m.sound;
if(s)
{
ls.loopSound = soundScene.insert(s, true);
if(ls.loopSound)
}
// Static lights
foreach(ref LiveLight ls; cd.statLights)
{
NodePtr n = putObject(ls.m.model, ls.getPos(), ls.getScale(), true);
ls.lightNode = attachLight(n, ls.m.data.color, ls.m.data.radius);
if(!noSound)
{
auto p = ls.getPos();
ls.loopSound.setPos(p.position[0],
p.position[1],
p.position[2]);
Sound *s = ls.m.sound;
if(s)
{
ls.loopSound = soundScene.insert(s, true);
if(ls.loopSound)
{
auto p = ls.getPos();
ls.loopSound.setPos(p.position[0],
p.position[1],
p.position[2]);
}
}
}
}
}
}
// Misc items
foreach(ref LiveMisc ls; cd.miscItems)
putObject(ls.m.model, ls.getPos(), ls.getScale());
/*
// NPCs (these are complicated, usually do not have normal meshes)
foreach(ref LiveNPC ls; cd.npcs)
putObject(ls.m.model, ls.getPos(), ls.getScale());
*/
// Containers
foreach(ref LiveContainer ls; cd.containers)
putObject(ls.m.model, ls.getPos(), ls.getScale(), true);
// Doors
foreach(ref LiveDoor ls; cd.doors)
putObject(ls.m.model, ls.getPos(), ls.getScale());
// Activators (including beds etc)
foreach(ref LiveActivator ls; cd.activators)
putObject(ls.m.model, ls.getPos(), ls.getScale(), true);
// Potions
foreach(ref LivePotion ls; cd.potions)
putObject(ls.m.model, ls.getPos(), ls.getScale());
// Apparatus
foreach(ref LiveApparatus ls; cd.appas)
putObject(ls.m.model, ls.getPos(), ls.getScale());
// Ingredients
foreach(ref LiveIngredient ls; cd.ingredients)
putObject(ls.m.model, ls.getPos(), ls.getScale());
// Armors
foreach(ref LiveArmor ls; cd.armors)
putObject(ls.m.model, ls.getPos(), ls.getScale());
// Weapons
foreach(ref LiveWeapon ls; cd.weapons)
putObject(ls.m.model, ls.getPos(), ls.getScale());
// Books
foreach(ref LiveBook ls; cd.books)
putObject(ls.m.model, ls.getPos(), ls.getScale());
// Clothes
foreach(ref LiveClothing ls; cd.clothes)
putObject(ls.m.model, ls.getPos(), ls.getScale());
// Tools
foreach(ref LiveTool ls; cd.tools)
putObject(ls.m.model, ls.getPos(), ls.getScale());
// Creatures (not displayed very well yet)
foreach(ref LiveCreature ls; cd.creatures)
putObject(ls.m.model, ls.getPos(), ls.getScale());
// Initialize the internal input and event manager. The
// lower-level input system (OIS) is initialized by the
// setupOgre() call further up.
initializeInput();
// Start swangin'
}
// Misc items
foreach(ref LiveMisc ls; cd.miscItems)
putObject(ls.m.model, ls.getPos(), ls.getScale());
/*
// NPCs (these are complicated, usually do not have normal meshes)
foreach(ref LiveNPC ls; cd.npcs)
putObject(ls.m.model, ls.getPos(), ls.getScale());
*/
// Containers
foreach(ref LiveContainer ls; cd.containers)
putObject(ls.m.model, ls.getPos(), ls.getScale(), true);
// Doors
foreach(ref LiveDoor ls; cd.doors)
putObject(ls.m.model, ls.getPos(), ls.getScale());
// Activators (including beds etc)
foreach(ref LiveActivator ls; cd.activators)
putObject(ls.m.model, ls.getPos(), ls.getScale(), true);
// Potions
foreach(ref LivePotion ls; cd.potions)
putObject(ls.m.model, ls.getPos(), ls.getScale());
// Apparatus
foreach(ref LiveApparatus ls; cd.appas)
putObject(ls.m.model, ls.getPos(), ls.getScale());
// Ingredients
foreach(ref LiveIngredient ls; cd.ingredients)
putObject(ls.m.model, ls.getPos(), ls.getScale());
// Armors
foreach(ref LiveArmor ls; cd.armors)
putObject(ls.m.model, ls.getPos(), ls.getScale());
// Weapons
foreach(ref LiveWeapon ls; cd.weapons)
putObject(ls.m.model, ls.getPos(), ls.getScale());
// Books
foreach(ref LiveBook ls; cd.books)
putObject(ls.m.model, ls.getPos(), ls.getScale());
// Clothes
foreach(ref LiveClothing ls; cd.clothes)
putObject(ls.m.model, ls.getPos(), ls.getScale());
// Tools
foreach(ref LiveTool ls; cd.tools)
putObject(ls.m.model, ls.getPos(), ls.getScale());
// Creatures (not displayed very well yet)
foreach(ref LiveCreature ls; cd.creatures)
putObject(ls.m.model, ls.getPos(), ls.getScale());
// End of interior cell
}
// Run GUI system
startGUI();
// Play some old tunes
if(!noSound)
Music.play();
@ -410,23 +403,6 @@ Try specifying another cell name on the command line, or edit openmw.ini.");
writefln("%d creatures", cd.creatures.length);
writefln();
}
/*
writefln("Races:");
foreach(s; races.names)
{
writefln("%s:", s.id);
writefln(" strength: ", s.data.strength[0]);
writefln(" intelligence: ", s.data.intelligence[0]);
writefln(" willpower: ", s.data.willpower[0]);
writefln(" agility: ", s.data.agility[0]);
writefln(" speed: ", s.data.speed[0]);
writefln(" endurance: ", s.data.endurance[0]);
writefln(" personality: ", s.data.personality[0]);
writefln(" luck: ", s.data.luck[0]);
writefln(" height: ", s.data.height[0]);
writefln(" weight: ", s.data.weight[0]);
}
*/
// This isn't necessary but it's here for testing purposes.
cellList.release(cd);

@ -0,0 +1,116 @@
class BaseLand
{
public:
BaseLand(Ogre::SceneNode* s)
: mTerrainSceneNode(s)
{
createMaterial();
createMesh();
}
~BaseLand()
{
destroyMaterial();
destroyMesh();
}
/**
* @brief repositions the mesh, and ensures that it is the right size
*/
void update()
{
Ogre::Real vd = mCamera->getFarClipDistance();
if ( vd > mMeshDistance ) {
destroyMaterial();
destroyMesh();
createMaterial();
createMesh();
}
Ogre::Vector3 p = mCamera->getDerivedPosition();
p.x -= ((int)p.x % 8192);
p.z -= ((int)p.z % 8192);
float h = p.y + 2048;
h = pow(h/8192*2,2);
if ( h < 0 ) h = 0;
mNode->setPosition(p.x, -32 - h, p.z);
}
private:
void createMesh()
{
mObject = mSceneMgr->createManualObject("BaseLand");
mObject->begin("BaseLandMat", Ogre::RenderOperation::OT_TRIANGLE_LIST);
Ogre::Real vd = mCamera->getFarClipDistance();
vd += 8192 - ((int)vd % 8192);
mMeshDistance = vd;
mObject->position(-vd,-2048, vd);
mObject->textureCoord(0, 1);
mObject->position(vd,-2048, vd);
mObject->textureCoord(1, 1);
mObject->position(vd,-2048, -vd);
mObject->textureCoord(1, 0);
mObject->position(-vd,-2048, -vd);
mObject->textureCoord(0, 0);
mObject->quad(0,1,2,3);
mObject->end();
mNode = mTerrainSceneNode->createChildSceneNode();
mNode->attachObject(mObject);
}
void destroyMesh()
{
mNode->detachAllObjects();
mSceneMgr->destroyManualObject(mObject);
mNode->getParentSceneNode()->removeAndDestroyChild(mNode->getName());
}
void createMaterial()
{
float vd = mCamera->getFarClipDistance();
vd += 8192 - ((int)vd % 8192);
vd = vd/8192 * 2;
mMat = Ogre::MaterialManager::getSingleton().
create(std::string("BaseLandMat"),
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
Ogre::TextureUnitState* us = mMat->getTechnique(0)->getPass(0)->createTextureUnitState("_land_default.dds");
us->setTextureScale(0.1f/vd,0.1f/vd);
mMat->getTechnique(0)->getPass(0)->setDepthBias(-1);
}
void destroyMaterial()
{
mMat->getCreator()->remove(mMat->getHandle());
mMat = Ogre::MaterialPtr();
}
///the created mesh
Ogre::ManualObject* mObject;
///The material for the mesh
Ogre::MaterialPtr mMat;
///scene node for the mesh
Ogre::SceneNode* mNode;
///In essence, the farViewDistance of the camera last frame
Ogre::Real mMeshDistance;
Ogre::SceneNode* mTerrainSceneNode;
};

@ -0,0 +1,169 @@
/*
Copyright (c) Jacob Essex 2009
This file is part of MWLand.
MWLand is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
MWLand is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with MWLand. If not, see <http://www.gnu.org/licenses/>.
*/
///generic subrecord
struct SubRecord
{
SubRecord(){}
SubRecord(const std::string& n, const std::string& d) : subName(n), subData(d){}
std::string subName;
std::string subData;
};
///generic record
class Record {
public:
Record(const std::string& type)
: mType(type) {}
inline void addSubRecord(const std::string& id,
const SubRecord& subRecord)
{mSubRecords[id] = subRecord; }
std::string getSubRecordData(const std::string& recordName)
{
SubRecordMapItr itr = mSubRecords.find(recordName );
if ( itr != mSubRecords.end() )
return itr->second.subData;
return std::string("");
}
bool hasSubRecord(const std::string& recordName)
{
if ( mSubRecords.find(recordName ) != mSubRecords.end() )
return true;
return false;
}
inline const std::string& getID() { return mId; }
inline void setID( const std::string& id) { mId = id;}
const std::string& getType(){return mType;}
private:
typedef std::map<std::string, SubRecord> SubRecordMap;
typedef SubRecordMap::iterator SubRecordMapItr;
SubRecordMap mSubRecords;
std::string mType;
std::string mId;
};
typedef boost::shared_ptr<Record> RecordPtr;
typedef std::list<RecordPtr> RecordList;
typedef RecordList::iterator RecordListItr;
typedef boost::shared_ptr<RecordList> RecordListPtr;
typedef std::map<std::string, RecordPtr> RecordMap;
typedef RecordMap::iterator RecordMapItr;
///top level class for loading and saving esp files.
class ESM
{
private:
/// types of records to load
std::map<std::string, std::string> mLoadTypes;
/// map<id, record> of the record
RecordMap mRecords;
///checks if the given type should be loaded
inline bool loadType(const std::string& t)
{
return ( mLoadTypes.find(t) != mLoadTypes.end() );
}
public:
inline void addRecordType(const std::string& t,
const std::string& i = "NAME")
{ mLoadTypes[t] = i; }
bool loadFile(const std::string& file)
{
std::ifstream esp (file.c_str(), std::ios::in | std::ios::binary);
if ( !esp.is_open() ) return false; //check open
esp.seekg(4);
long hdrSize; //get offset for start of data
esp.read ((char *)&hdrSize, sizeof(long));
//get num records
esp.seekg(16 + 8 + 296);
long numRecords;
esp.read ((char *)&numRecords, sizeof(long));
esp.seekg(hdrSize + 16); //go to end of header
for ( long i = 0; i < numRecords; i++ ){
char type[5];
esp.get(type, 5);
type[4] = '\0';
long recordSize;
esp.read ((char *)&recordSize, 4);
esp.seekg(8, std::ofstream::cur);
long endPos = recordSize + esp.tellg();
if ( loadType(type) ) {
RecordPtr record = RecordPtr(new Record(type));
//load all subrecords
while ( esp.tellg() < endPos ) {
char subType[5];
esp.get(subType, 5);
long subRecLength;
esp.read ((char *)&subRecLength, 4);
long subRecEnd = subRecLength + esp.tellg();
char* subRecData = new char[subRecLength];
esp.read(subRecData, subRecLength);
record->addSubRecord(subType, SubRecord(subType,std::string(subRecData, subRecLength)));
delete [] subRecData;
assert(subRecEnd==esp.tellg());
}
record->setID(record->getSubRecordData(mLoadTypes[type]));
mRecords[record->getSubRecordData(mLoadTypes[type])] = record;
}else{
esp.seekg(endPos);
}
assert(endPos==esp.tellg());
}
esp.close();
return true;
}
inline RecordPtr getRecord(const std::string& id){ return mRecords[id]; }
RecordListPtr getRecordsByType(const std::string& t)
{
RecordListPtr r = RecordListPtr(new RecordList); //need pointer....
for ( RecordMapItr iter = mRecords.begin(); iter != mRecords.end(); ++iter)
if ( t == iter->second->getType() )
r->push_back(iter->second);
return r;
}
};

@ -0,0 +1,58 @@
class TerrainFrameListener : public FrameListener
{
protected:
Terrain* mTerrain;
MWHeightmap* mHeights;
/**
* Updates the quad tree
*/
bool frameEnded(const FrameEvent& evt)
{
mTerrain->update(evt.timeSinceLastFrame);
return true;
}
public:
void setup()
{
// Add the frame listener
mRoot->addFrameListener(this);
//our derived heightmap
mHeights = new MWHeightmap();
mHeights->load(TERRAIN_OUTPUT);
//setup terrain
mTerrain = new Terrain( mHeights, //heightmap
mSceneMgr->getRootSceneNode()->createChildSceneNode("TERRAIN_ROOT")); //root scene node
//fix settings
mTerrain->setMorphingEnabled(false);
mTerrain->setTextureFadingEnabled(false);
//create the quad node
mTerrain->create();
}
/* KILLME
void drawLine(std::string name, Ogre::Vector3 start, Ogre::Vector3 end)
{
Ogre::ManualObject* mo = mSceneMgr->createManualObject( name);
Ogre::SceneNode* node = mSceneMgr->getRootSceneNode()->createChildSceneNode( name+"node");
Ogre::MaterialPtr mat = Ogre::MaterialManager::getSingleton().create( name+"Material","debugger");
mat->setReceiveShadows(false);
mat->getTechnique(0)->setLightingEnabled(true);
mat->getTechnique(0)->getPass(0)->setDiffuse(0,0,1,0);
mat->getTechnique(0)->getPass(0)->setAmbient(0,0,1);
mat->getTechnique(0)->getPass(0)->setSelfIllumination(0,0,1);
mo->begin(name+"Material", Ogre::RenderOperation::OT_LINE_LIST);
mo->position(start);
mo->position(end);
mo->end();
node->attachObject(mo);
}
*/
};

@ -0,0 +1,441 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2009 Jacob Essex
WWW: http://openmw.sourceforge.net/
This file (cpp_generator.cpp) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
class Generator
{
public:
Generator(const std::string& baseFileName) :
mBaseFileName(baseFileName)
{
mDataO.open(std::string(baseFileName+".data").c_str(), std::ios::binary);
}
inline void addLandData(RecordPtr record, const std::string& source)
{ mMWLand.addLandData(record, source); }
inline void addLandTextureData(RecordPtr record, const std::string& source)
{ mMWLand.addLandTextureData(record, source); }
void beginGeneration()
{
//maxiumum distance form 0, 0 in any direction
int max = 0;
max = std::max<int>(mMWLand.getMaxX(), max);
max = std::max<int>(mMWLand.getMaxY(), max);
max = std::max<int>(-mMWLand.getMinX(), max);
max = std::max<int>(-mMWLand.getMinY(), max);
//round up to nearest binary number. makes some stuff easier iirc
//FIXME
for ( int i = 1;;i++ ) {
if ( max < pow((float)2, i) )
{
max = pow((float)2, i);
break;
}
assert(i<=8); //don't go too high. Used for debug
}
int maxDepth = 0; //temp var used below
// FIXME: make 8192 a constant
mIndex.setRootSideLength(max*2*8192); //given that 8192 is the length of a cell
//Keep deviding the root side length by 2 (thereby simulating a
//split) until we reach the width of the base cell (or the
//smallest quad)
for (long i = mIndex.getRootSideLength(); i > 8192; i/=2 )
maxDepth++;
mIndex.setMaxDepth(maxDepth);
}
void generateLODLevel(int level, bool createTexture, int textureSize)
{
std::cout << "Generating Level " << level << "\n";
assert(level <= mIndex.getMaxDepth());
assert(level > 0 );
assert(textureSize>2);
assert(textureSize<=4096); //change to gpu max if pos
const int initialLevel = level;
// FIXME: Should probably use another name than 'level' here
level = pow((float)2, level); //gap between verts that we want
const int halfLevel = level/2;
assert(halfLevel > 0 );
// FIXME. Just search for all pow() calls, really
int cellDist = pow((float)2, mIndex.getMaxDepth());
// Temporary storage
MWQuadData qd(0);
qd.setVertexSeperation(128*halfLevel); //dist between two verts
std::vector<float>& gh = qd.getHeightsRef(); //ref to the data storage in the quad
std::vector<char>& gn = qd.getNormalsRef();
gh.resize(LAND_NUM_VERTS); //allocate memory for mesh functions
gn.resize(LAND_NUM_VERTS*3);
//the 16*16 array used for holding the LTEX records (what texure is splatted where)
std::vector<int>& gl = qd.getTextureIndexRef();
gl.resize((LAND_LTEX_WIDTH+2)*(LAND_LTEX_WIDTH+2));
const std::string stringLevel(Ogre::StringConverter::toString(level)); //cache this
const std::string defaultTexture(stringLevel + "_default.png");
bool hasUsedDefault = false;
// loops over the start of each quad we want to get
for( int y = -(cellDist/2); y < (cellDist/2); y+=halfLevel )
for( int x = -(cellDist/2); x < (cellDist/2); x+=halfLevel )
{
qd.setParentTexture("");
bool usingDefaultTexture = false;
if ( initialLevel == 1 )
generateLTEXData(x, y, gl);
else if ( createTexture )
{
//this is the name of the file that OGRE will
//look for
std::string name = stringLevel + "_" +
Ogre::StringConverter::toString(x) + "_" +
Ogre::StringConverter::toString(y) + ".png";
//where as the arg1 here is the file name to save it.
bool hasGen = generateTexture(std::string(TEXTURE_OUTPUT) + name, textureSize, x, y, halfLevel);
if ( hasGen ) qd.setTexture(name);
else
{
qd.setTexture(defaultTexture);
hasUsedDefault = true;
usingDefaultTexture = true;
}
}
//calculate parent texture
if ( initialLevel != mIndex.getMaxDepth() )
{
//calcualte the level one higher
int higherLevel = pow((float)2, (initialLevel+1));
int highHalfLevel = higherLevel/2;
int higherX = x;
if ( (higherX-halfLevel) % highHalfLevel == 0 )
higherX -= halfLevel;
int higherY = y;
if ( (higherY-halfLevel) % highHalfLevel == 0 )
higherY -= halfLevel;
std::string higherName = Ogre::StringConverter::toString(higherLevel) + "_" +
Ogre::StringConverter::toString(higherX) + "_" +
Ogre::StringConverter::toString(higherY) + ".png";
//check file exists without needing boost filesystenm libs
FILE* fp = fopen((std::string(TEXTURE_OUTPUT) + higherName).c_str(), "r");
if ( fp )
{
qd.setParentTexture(higherName);
fclose(fp);
}
else
qd.setParentTexture("");
}
generateMesh(gh, gn, x, y, halfLevel );
bool isEmptyQuad = true;
if ( usingDefaultTexture )
{
for ( int i = 0; i < LAND_NUM_VERTS; i++ ){
if ( gh.at(i) != LAND_DEFAULT_HEIGHT ){
isEmptyQuad = false;
break;
}
}
}
else isEmptyQuad = false;
if ( isEmptyQuad )
continue;
//save data
//the data is the position of the generated quad
mIndex.setOffset(x*8192+halfLevel*8192/2,
y*8192+halfLevel*8192/2,
mDataO.tellp());
boost::archive::binary_oarchive oa(mDataO); //using boost fs to write the quad
oa << qd;
}
//check if we have used a default texture
if ( hasUsedDefault )
{
std::vector<int> ltex;
ltex.resize(halfLevel*LAND_LTEX_WIDTH*halfLevel*LAND_LTEX_WIDTH, mPalette.getOrAddIndex("_land_default.dds"));
renderTexture(std::string(TEXTURE_OUTPUT) + defaultTexture, ltex, textureSize, halfLevel*LAND_LTEX_WIDTH);
}
}
void endGeneration()
{
// FIXME: Just write one file?
std::ofstream ofi(std::string(mBaseFileName + ".index").c_str(), std::ios::binary);
std::ofstream ofp(std::string(mBaseFileName + ".palette").c_str(), std::ios::binary);
boost::archive::binary_oarchive oai(ofi);
boost::archive::binary_oarchive oap(ofp);
oai << mIndex;
oap << mPalette;
}
private:
void generateLTEXData(int x, int y, std::vector<int>& index)
{
for ( int texY = 0; texY < LAND_LTEX_WIDTH+2; texY++ )
for ( int texX = 0; texX < LAND_LTEX_WIDTH+2; texX++ )
{
int tempX = x;
int tempY = y;
int sourceX = texX - 1;
int sourceY = texY - 1;
if ( sourceX == -1 )
{
tempX--;
sourceX = LAND_LTEX_WIDTH-1;
}
else if ( sourceX == LAND_LTEX_WIDTH)
{
tempX++;
sourceX = 0;
}
if ( sourceY == -1 )
{
tempY--;
sourceY = LAND_LTEX_WIDTH-1;
}
else if ( sourceY == LAND_LTEX_WIDTH )
{
tempY++;
sourceY = 0;
}
std::string source;
short texID = 0;
if ( mMWLand.hasData(tempX, tempY) )
{
source = mMWLand.getSource(tempX, tempY);
texID = mMWLand.getLTEXIndex(tempX,tempY, sourceX, sourceY);
}
std::string texturePath = "_land_default.dds";
if ( texID != 0 && mMWLand.hasLTEXRecord(source,--texID) )
texturePath = mMWLand.getLTEXRecord(source,texID);
// textures given as tga, when they are a dds. I hate that "feature"
// FIXME: do we handle this already?
size_t d = texturePath.find_last_of(".") + 1;
texturePath = texturePath.substr(0, d) + "dds";
std::transform(texturePath.begin(), texturePath.end(), texturePath.begin(), tolower);
index[texY*(LAND_LTEX_WIDTH+2)+texX] = mPalette.getOrAddIndex(texturePath);
}
}
bool generateTexture(const std::string& name, int size,
int blockX, int blockY, int halfLevel)
{
int width = size/(halfLevel*LAND_LTEX_WIDTH);
assert(width>=1);
std::vector<int> ltex; //prealloc, as we know exactly what size it needs to be
ltex.resize(halfLevel*LAND_LTEX_WIDTH*halfLevel*LAND_LTEX_WIDTH, 0);
//for each cell
for ( int y = 0; y < halfLevel; y++ )
for ( int x = 0; x < halfLevel; x++ )
//for each texture in the cell
for ( int texY = 0; texY < LAND_LTEX_WIDTH; texY++ )
for ( int texX = 0; texX < LAND_LTEX_WIDTH; texX++ )
{
std::string source;
short texID = 0;
if ( mMWLand.hasData(x + blockX, y + blockY) )
{
source = mMWLand.getSource(x + blockX, y + blockY);
texID = mMWLand.getLTEXIndex(x + blockX, y + blockY, texX, texY);
}
std::string texturePath = "_land_default.dds";
if ( texID != 0 && mMWLand.hasLTEXRecord(source,--texID) )
texturePath = mMWLand.getLTEXRecord(source,texID);
//textures given as tga, when they are a dds. I hate that "feature"
//FIXME: ditto earlier comment
size_t d = texturePath.find_last_of(".") + 1;
texturePath = texturePath.substr(0, d) + "dds";
//FIXME: BTW, didn't we make the BSA reader case insensitive?
std::transform(texturePath.begin(), texturePath.end(), texturePath.begin(), tolower);
const int index = (y*LAND_LTEX_WIDTH+texY)*halfLevel*LAND_LTEX_WIDTH + x*LAND_LTEX_WIDTH+texX;
ltex[index] = mPalette.getOrAddIndex(texturePath);
}
//see if we have used anything at all
// FIXME: Now, I KNOW this isn't needed :)
int sum = 0;
for ( std::vector<int>::iterator i = ltex.begin(); i != ltex.end(); ++i )
sum += *i;
if ( sum == 0 ) //not used any textures
return false;
renderTexture(name, ltex, size, halfLevel*LAND_LTEX_WIDTH);
return true;
}
// FIXME: renderTexture and getRenderedTexture don't strike me as
// the optimal ways of doing this. For one it's hardware/driver
// dependent, when it should be a simple computational exercise. But
// understand what it actually does, before you change anything.
void renderTexture(const std::string& outputName, std::vector<int>& ltex,
int texSize, int alphaSize)
{
std::cout << " Creating " << outputName << "\n";
assert(Ogre::Math::Sqrt(ltex.size())==alphaSize);
std::list<Ogre::ResourcePtr> createdResources;
MaterialGenerator mg;
mg.setTexturePaths(mPalette.getPalette());
const int scaleDiv = alphaSize/LAND_LTEX_WIDTH;
//genetate material/aplahas
Ogre::MaterialPtr mp = mg.getAlphaMat("Rtt_Alpha1", ltex, alphaSize, 0, scaleDiv,createdResources);
Ogre::TexturePtr tex1 = getRenderedTexture(mp, "RTT_TEX_1",texSize, Ogre::PF_R8G8B8);
tex1->getBuffer()->getRenderTarget()->writeContentsToFile(outputName);
Ogre::MaterialManager::getSingleton().remove(mp->getHandle());
//remove the texture we have just written to the fs
Ogre::TextureManager::getSingleton().remove(tex1->getHandle());
//remove all the materials
const std::list<Ogre::ResourcePtr>::const_iterator iend = createdResources.end();
for ( std::list<Ogre::ResourcePtr>::const_iterator itr = createdResources.begin();
itr != iend;
++itr) {
(*itr)->getCreator()->remove((*itr)->getHandle());
}
}
Ogre::TexturePtr getRenderedTexture(Ogre::MaterialPtr mp, const std::string& name,
int texSize, Ogre::PixelFormat tt)
{
Ogre::CompositorPtr cp = Ogre::CompositorManager::getSingleton().
create("Rtt_Comp",
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
//output pass
Ogre::CompositionTargetPass* ctp = cp->createTechnique()->getOutputTargetPass();
Ogre::CompositionPass* cpass = ctp->createPass();
cpass->setType(Ogre::CompositionPass::PT_RENDERQUAD);
cpass->setMaterial(mp);
//create a texture to write the texture to...
Ogre::TexturePtr texture = Ogre::TextureManager::getSingleton().
createManual(
name,
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
Ogre::TEX_TYPE_2D,
texSize,
texSize,
0,
tt,
Ogre::TU_RENDERTARGET
);
Ogre::RenderTexture* renderTexture = texture->getBuffer()->getRenderTarget();
Ogre::Viewport* vp = renderTexture->addViewport(mCamera);
Ogre::CompositorManager::getSingleton().addCompositor(vp, "Rtt_Comp");
Ogre::CompositorManager::getSingleton().setCompositorEnabled(vp,"Rtt_Comp", true);
renderTexture->update();
//required for some reason?
//Not implementing resulted in a black tex using openGL, Linux, and a nvidia 6150 in 1.4.?
Ogre::Root::getSingleton().renderOneFrame();
Ogre::CompositorManager::getSingleton().removeCompositor(vp, "Rtt_Comp");
Ogre::CompositorManager::getSingleton().remove(cp->getHandle());
renderTexture->removeAllViewports(); //needed?
return texture;
}
void generateMesh(std::vector<float>& gh, std::vector<char>& gn, int blockX,
int blockY, int halfLevel)
{
int gnc = 0;
int ghc = 0;
for ( int y = 0; y < LAND_VERT_WIDTH; y++ )
for ( int x = 0; x < LAND_VERT_WIDTH; x++ )
{
//FIXME: Eh, what?
int cellY = floor((float)y/LAND_VERT_WIDTH*halfLevel) + blockY;
int cellX = floor((float)x/LAND_VERT_WIDTH*halfLevel) + blockX;
std::vector<float>& ch = mMWLand.getHeights(cellX,cellY);
std::vector<char>& cn = mMWLand.getNormals(cellX,cellY);
int vertY = (((float)y/LAND_VERT_WIDTH*halfLevel) -
(float)floor((float)y/LAND_VERT_WIDTH*halfLevel)) * LAND_VERT_WIDTH;
int vertX = (((float)x/LAND_VERT_WIDTH*halfLevel) -
(float)floor((float)x/LAND_VERT_WIDTH*halfLevel)) * LAND_VERT_WIDTH;
assert(vertY < LAND_VERT_WIDTH && vertX < LAND_VERT_WIDTH);
//store data
gh[ghc++] = ch[vertY*LAND_VERT_WIDTH+vertX];
for ( int z = 0; z < 3; z++ )
gn[gnc++] = cn[(vertY*LAND_VERT_WIDTH+vertX)*3+z];
}
}
std::string mBaseFileName; ///base file name so we gen mBaseFileName + ".index" etc
std::ofstream mDataO;
Index mIndex; ///the index of the data. holds offsets and max depth, rsl etc
TexturePalette mPalette; ///all the textures from all mods are merge into a single palette
MWLand mMWLand; ///deals with MW land stuff
};

@ -0,0 +1,91 @@
/**
* @brief holds data about positions of data and general header info
*/
class Index {
public:
///saves my fingers :P
typedef std::map<long, std::map<long, long> >::iterator OffsetItr;
typedef std::map<long, std::map<long, long> >::const_iterator OffsetConstItr;
/**
* @brief sets the root quads side length in gu
* @param l the side length
*
* This is used for working out the locations of quad children.
* I am assuming a long is enough...
*/
inline void setRootSideLength(long l) {
mRootSideLength = l;
}
/**
* @return the side length of the root quad.
*/
inline long getRootSideLength() const {
return mRootSideLength;
}
inline void setMaxDepth(int d) {
mMaxDepth = d;
}
inline int getMaxDepth() const {
return mMaxDepth;
}
/**
* @return -1 is returned if there is no offset
* @param x, y the position of the quad in gu
*
* Slightly faster using hasOffset to check if it exists
* Shouldn't be noticable diffrence.
*/
inline long getOffset(long x, long y) const { //inline?
OffsetConstItr itr1 = mQuadOffsets.find(x);
if ( itr1 == mQuadOffsets.end() ) return -1;
std::map<long, long>::const_iterator itr2 = itr1->second.find(y);
if ( itr2 == itr1->second.end() ) return -1;
return itr2->second;
}
/**
* @brief checks if a quad for the given position exists
* @return true/false
* @param x, y the position of the quad in gu
*
* @todo Would it be worth merging this with getOffset?
*/
inline bool hasOffset(long x, long y) const {
OffsetConstItr itr = mQuadOffsets.find(x);
if ( itr == mQuadOffsets.end() ) return false;
return (itr->second.find(y) != itr->second.end());
}
/**
* @brief sets an offset of a quad
* @param x, y the position in gu of the quad
* @param o the offset within the file of the records for this quad
*/
inline void setOffset(long x, long y, long o) {
mQuadOffsets[x][y] = o;
}
protected:
std::map<long, std::map<long, long> > mQuadOffsets;
long mRootSideLength; ///length in gu of the root quad
int mMaxDepth; ///maximum depth assuming root quad depth = 0
friend class boost::serialization::access;
/**
* Saves the data for the max depth, the root side legnth, and the quad offsets
*/
template<class Archive>
inline void serialize(Archive& ar, const unsigned int version){
ar &mMaxDepth;
ar &mRootSideLength;
ar &mQuadOffsets;
}
};
BOOST_CLASS_TRACKING(Index, boost::serialization::track_never);

@ -0,0 +1,209 @@
class MWLand
{
public:
MWLand()
{
mMaxX = mMaxY = mMinX = mMinY = 0;
}
void addLandTextureData(RecordPtr record, const std::string& source)
{
LandTexture l;
l.name = record->getSubRecordData("NAME");
l.data = record->getSubRecordData("DATA");
l.intv = *((short*) record->getSubRecordData("INTV").c_str());
mLTEXRecords[source][l.intv] = l;
}
void addLandData(RecordPtr record, const std::string& source)
{
if ( !record->hasSubRecord("VHGT") || !record->hasSubRecord("VTEX") ) //ensure all records exist
return;
//copy these, else we end up with invliad data
LAND::INTV intv = *(LAND::INTV*)record->getSubRecordData("INTV").c_str();
LAND::VHGT vhgt = *(LAND::VHGT*)record->getSubRecordData("VHGT").c_str();
LAND::VNML vnml = *(LAND::VNML*)record->getSubRecordData("VNML").c_str();
LAND::VTEX vtex = *(LAND::VTEX*)record->getSubRecordData("VTEX").c_str();
//GridPosition gp(intv.x, intv.y);
mLandRecords[intv.x][intv.y].heights = parseHeights(&vhgt); //convert into a format we want
mLandRecords[intv.x][intv.y].normals = parseNormals(&vnml);
mLandRecords[intv.x][intv.y].textures = parseTextures(&vtex);
mLandRecords[intv.x][intv.y].source = source;
mMaxX = std::max<int>(mMaxX, intv.x);
mMaxY = std::max<int>(mMaxY, intv.y);
mMinX = std::min<int>(mMinX, intv.x);
mMinY = std::min<int>(mMinY, intv.y);
}
///Maximum distance of a cell on the X plane from grid pos 0 in the positive direction
inline int getMaxX() const { return mMaxX; }
///Maximum distance of a cell on the Y plane from grid pos 0 in the positvie direction
inline int getMaxY() const { return mMaxY; }
///Maximum distance of a cell on the X plane from grid pos 0 in the negative direction
inline int getMinX() const { return mMinX; }
///see others
inline int getMinY() const { return mMinY; }
inline std::vector<float>& getHeights(int x, int y)
{
if ( hasData(x,y) )
return mLandRecords[x][y].heights;
static std::vector<float> e(LAND_NUM_VERTS, LAND_DEFAULT_HEIGHT);
return e;
}
inline std::vector<char>& getNormals(int x, int y)
{
if ( hasData(x,y) )
return mLandRecords[x][y].normals;
static std::vector<char> e(LAND_NUM_VERTS*3,0);
return e;
}
inline const std::string& getSource(int x, int y)
{
assert(hasData(x,y));
return mLandRecords[x][y].source;
}
inline bool hasData(int x, int y) const
{
std::map<int , std::map<int, LandData> >::const_iterator itr = mLandRecords.find(x);
if ( itr == mLandRecords.end() )
return false;
if ( itr->second.find(y) == itr->second.end() )
return false;
return true;
}
inline std::string& getLTEXRecord(const std::string& source, short i)
{
return mLTEXRecords[source][i].data;
}
inline bool hasLTEXRecord(const std::string& source, short index) const
{
std::map<std::string, std::map<short, LandTexture> >::const_iterator itr = mLTEXRecords.find(source);
if ( itr == mLTEXRecords.end() )
return false;
if ( itr->second.find(index) == itr->second.end() )
return false;
return true;
}
inline short getLTEXIndex(int x, int y, int pos)
{
assert(hasData(x,y));
return mLandRecords[x][y].textures[pos];
}
inline short getLTEXIndex(int x1, int y1, int x2, int y2)
{
return getLTEXIndex(x1, y1, y2*LAND_LTEX_WIDTH+x2);
}
private:
///the min/max size of cells
int mMaxY, mMinY;
int mMaxX, mMinX;
// Land structure as held in the ESM
struct LAND {
struct INTV { /// x, y grid pos of the cell
long x;
long y;
};
struct VNML { ///vertex normal data
struct NORMAL {
char x;
char y;
char z;
};
NORMAL normals[LAND_NUM_VERTS];
};
struct VHGT { ///height data
float heightOffset;
char heightData[LAND_NUM_VERTS];
short unknown1;
char unknown2;
};
struct VTEX { ///splat texture data
short index[LAND_NUM_LTEX];
};
INTV* intv;
VNML* vnml;
VHGT* vhgt;
VTEX* vtex;
};
std::vector<float> parseHeights(LAND::VHGT* vhgt)
{
std::vector<float> ph;
ph.resize(LAND_NUM_VERTS, LAND_DEFAULT_HEIGHT);
float offset = vhgt->heightOffset;
for (int y = 0; y < LAND_VERT_WIDTH; y++) { //code from MGE
offset += vhgt->heightData[y*LAND_VERT_WIDTH];
ph[y*LAND_VERT_WIDTH] =+ (float)offset*8;
float pos = offset;
for (int x = 1; x < LAND_VERT_WIDTH; x++) {
pos += vhgt->heightData[y*LAND_VERT_WIDTH+x];
ph[y*LAND_VERT_WIDTH+x] = pos*8; //flipped x
}
}
return ph;
}
std::vector<char> parseNormals( LAND::VNML* vnml )
{
std::vector<char> n;
n.resize(LAND_NUM_VERTS*3,0);
for ( int y = 0; y < LAND_VERT_WIDTH; y++ ) { //this could just be cast.
for ( int x = 0; x < LAND_VERT_WIDTH; x++ ) { //as a vector is a continus segment of mem...
n[(y*LAND_VERT_WIDTH+x)*3] = vnml->normals[y*LAND_VERT_WIDTH+x].x;
n[(y*LAND_VERT_WIDTH+x)*3+1] = vnml->normals[y*LAND_VERT_WIDTH+x].y;
n[(y*LAND_VERT_WIDTH+x)*3+2] = vnml->normals[y*LAND_VERT_WIDTH+x].z;
}
}
return n;
}
std::vector<short> parseTextures( LAND::VTEX* vtex )
{
std::vector<short> t;
t.resize(LAND_NUM_LTEX,0);
//thanks to timeslip (MGE) for the code
int rpos = 0; //bit ugly, but it works
for ( int y1 = 0; y1 < 4; y1++ )
for ( int x1 = 0; x1 < 4; x1++ )
for ( int y2 = 0; y2 < 4; y2++)
for ( int x2 = 0; x2 < 4; x2++ )
t[(y1*4+y2)*16+(x1*4+x2)]=vtex->index[rpos++];
return t;
}
/**
* Holds the representation of a cell in the way that is most usefull to me
*/
struct LandData
{
std::string source; //data file the data is from
std::vector<float> heights;
std::vector<char> normals;
std::vector<short> textures;
};
struct LandTexture
{
std::string name, data;
short intv;
};
std::map<std::string, std::map<short, LandTexture> > mLTEXRecords;
std::map<int, std::map<int,LandData> > mLandRecords;
};

@ -0,0 +1,367 @@
/**
* Handles the runtime generation of materials
*
*/
class MaterialGenerator
{
class TextureSplatter
{
public:
inline TextureSplatter(const std::vector<int>& ltex,
int texSize,
int ltexsize,
int border) :
mLTEX(ltex),
mTexSize(texSize), //the root of the size of the texture
mLTEXSize(ltexsize), //the root of the ltex array
mBorder(border) { //the size of the border of the ltex
mSizeDiff = texSize/(ltexsize-border*2);
}
int getAlphaAtPixel(int x, int y, int tid) {
//offset for border
x += (mBorder*mSizeDiff);
y += (mBorder*mSizeDiff);
if ( getTextureAtPixel(x,y) == tid ) {
return 255;
} else if ( mBorder > 0 ) { //hacky remove fix. perofrmance issues. skips if not ingame gen.
float amount = 0;
for ( int ty = y-1; ty <= y+1; ++ty ) {
for ( int tx = x-1; tx <= x+1; ++tx ) {
if ( ty < -mBorder*mSizeDiff ||
tx < -mBorder*mSizeDiff ||
ty >= mTexSize+mBorder*mSizeDiff ||
tx >= mTexSize+mBorder*mSizeDiff )
continue;
if ( getTextureAtPixel(tx,ty) == tid )
amount += 0.18f;
}
}
if ( amount > 1 ) amount = 1;
assert(amount>=0&&amount<=1);
return amount*255;
}
return 0;
}
private:
int getTextureAtPixel(int x, int y) {
x = (x - x%mSizeDiff)/mSizeDiff;
y = (y - y%mSizeDiff)/mSizeDiff;
//y = floor(float(y)/float(mSizeDiff));
//x = floor(float(x)/float(mSizeDiff));
return mLTEX[(y)*mLTEXSize+(x)];
}
const std::vector<int>& mLTEX;
const int mTexSize;
const int mLTEXSize;
const int mBorder;
int mSizeDiff;
};
public:
/**
* @brief generates a material for a quad using a single texture
*/
Ogre::MaterialPtr generateSingleTexture(const std::string& matname, const std::string& texName, std::list<Ogre::ResourcePtr> createdResources)
{
if ( !Ogre::MaterialManager::getSingleton().resourceExists(matname) )
{
Ogre::MaterialPtr mat = Ogre::MaterialManager::getSingleton().create(matname,Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
Ogre::Pass* p = mat->getTechnique(0)->getPass(0);
p->setLightingEnabled(false);
p->createTextureUnitState(texName)->setTextureAddressingMode(TextureUnitState::TAM_CLAMP);
createdResources.push_back(mat);
return mat;
}
return Ogre::MaterialPtr();
}
/**
* Currently doesn't work
*/
Ogre::MaterialPtr getShaderAlpha(const std::string& materialName, std::vector<int>& ltex, int size, std::list<Ogre::TexturePtr>& generatedAlphas)
{
Ogre::TexturePtr currentAlphaTexture; //ptr to the texture we are currnly writing to
Ogre::uint8* currentAlphaPtr = 0; //pointer to the data
Ogre::HardwarePixelBufferSharedPtr currentPixelBuffer; //pointer to the buffer for ctrling locks/unlicjks
int currentColour = 0; //current colour index. ARGB.
std::vector<std::string> texturesWritten; //a list of "done" textures. Order important
std::vector<std::string> alphasGenerated; //alphas generated
std::set<short> tidDone; //holds the list of done textures
const int rootSideLength = Ogre::Math::Sqrt(ltex.size()); //used for looping
//loop over every splat possition
for ( int y1 = 0; y1 < rootSideLength; y1++ ) {
for ( int x1 = 0; x1 < rootSideLength; x1++ ) {
const short tid = ltex[y1*rootSideLength+x1];
//if already done.
if ( tidDone.find(tid) != tidDone.end()) continue;
//insert it into the done list
tidDone.insert(tid);
//get the textuyre path. If it is default we don't need to do anything, as it
//is done in the first pass. CHANGE? We end up using a whole pass for it??
const std::string tn(mTexturePaths[tid]);
if ( tn == "_land_default.dds" ) continue;
texturesWritten.push_back(tn);
//unqiue alpha name
const std::string alphaName(materialName + "_A_" + tn);
if ( Ogre::TextureManager::getSingleton().resourceExists(alphaName) )
OGRE_EXCEPT(0, "ALPHA Already Exists", ""); //shouldn't happen.
//check if we need to create a new apla
if ( currentColour == 4 || //we only have 4 colours per tex ofc
currentAlphaTexture.isNull() ) { //no texture assigned yet
//unlock old buffer, if we have one
if ( !currentAlphaTexture.isNull() )
currentPixelBuffer->unlock();
//new texture
currentAlphaTexture = Ogre::TextureManager::getSingleton().
createManual(
alphaName,
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
Ogre::TEX_TYPE_2D,
size,size, //size ofc
1, 0, //depth, mipmaps
Ogre::PF_A8R8G8B8, //4 channesl for 4 splats
Ogre::TU_STATIC_WRITE_ONLY //we only need to write data
);
generatedAlphas.push_back(currentAlphaTexture); //record
alphasGenerated.push_back(alphaName);
currentPixelBuffer = currentAlphaTexture->getBuffer();
currentPixelBuffer->lock(Ogre::HardwareBuffer::HBL_DISCARD);
const Ogre::PixelBox& pixelBox = currentPixelBuffer->getCurrentLock();
currentAlphaPtr = static_cast<Ogre::uint8*>(pixelBox.data);
//zero out all data
for ( int i = 0; i < size*size*4; i++ )
currentAlphaPtr[i] = 0;
currentColour = 0;
} else {
currentColour++; //increase the colour we are working with by one
}
//for every splat that the texture is splatted,
//check if it is the same as the one in the current splat
for ( int y2 = 0; y2 < rootSideLength; y2++ ) {
for ( int x2 = 0; x2 < rootSideLength; x2++ ) {
if ( ltex[y2*rootSideLength+x2] == tid ) {
//add splat to current alpha map
const int splatSize = size/rootSideLength;
for ( int ys = 0; ys < splatSize ; ys++ ) {
for ( int xs = 0; xs < splatSize; xs++ ) {
//calc position on main texture
const int pxpos = splatSize*x2+xs;
const int pypos = splatSize*y2+ys;
//write splat to buffer
const int index = (pypos*size*4)+(pxpos*4)+currentColour;
currentAlphaPtr[index] = 255;
}
}
}
}
}//for ( int y2 = 0; y2 < rootSideLength; y2++ ){
}
}//for ( int y1 = 0; y1 < rootSideLength; y1++ ){
//check to see if we need to unlock a buff again
if ( !currentAlphaTexture.isNull() )
currentPixelBuffer->unlock();
//sort material
assert(Ogre::MaterialManager::getSingleton().getByName("AlphaSplatTerrain").getPointer());
Ogre::MaterialPtr material = ((Ogre::Material*)Ogre::MaterialManager::getSingleton().getByName("AlphaSplatTerrain").getPointer())->clone(materialName);
Ogre::Pass* pass = material->getTechnique(0)->getPass(0);
//write alphas
if ( alphasGenerated.size() > 0 )
pass->getTextureUnitState(0)->setTextureName(alphasGenerated[0]);
if ( alphasGenerated.size() > 1 )
pass->getTextureUnitState(1)->setTextureName(alphasGenerated[1]);
//write 8 textures
int c = 1;
for ( std::vector<std::string>::iterator itr = texturesWritten.begin();
itr != texturesWritten.end();
++itr ) {
if ( ++c > 8 ) break;
pass->getTextureUnitState(1+c)->setTextureName(*itr);
//ta["Splat" + Ogre::StringConverter::toString(c)] = *itr;
}
//pass->applyTextureAliases(ta, true);
return material;
}
/**
* gets the material for the alpha textures
*/
Ogre::MaterialPtr getAlphaMat(const std::string& materialName,
std::vector<int>& ltex,
int size,
int border,
float scaleDiv,
std::list<Ogre::ResourcePtr>& createdResources)
{
const int sizeDiff = 4;
size *= sizeDiff;
const int rootSideLength = Ogre::Math::Sqrt(ltex.size())-(border*2); //used for looping
const int ltexWidth = Ogre::Math::Sqrt(ltex.size());
assert(size%rootSideLength==0);
Ogre::MaterialPtr material = Ogre::MaterialManager::getSingleton().
create(materialName,Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
createdResources.push_back(material);
Ogre::Pass* np = material->getTechnique(0)->getPass(0);
np->setLightingEnabled(false);
np->createTextureUnitState("_land_default.dds")->setTextureScale(0.1f/scaleDiv,0.1f/scaleDiv);
std::set<int> textures;
for ( int y1 = 0; y1 < ltexWidth; y1++ ) {
for ( int x1 = 0; x1 < ltexWidth; x1++ ) {
textures.insert(ltex[(y1)*ltexWidth+x1]);
}
}
for ( std::set<int>::iterator itr = textures.begin(); itr != textures.end(); ++itr )
{
int tid = *itr;
const std::string tn(mTexturePaths[tid]);
if ( tn == "_land_default.dds" )
continue;
//std::cout << " Generating texture " << tn << "\n";
std::string alphaName(materialName + "_A_" + tn);
if ( Ogre::TextureManager::getSingleton().resourceExists(alphaName) )
OGRE_EXCEPT(0, "ALPHA Already Exists", "");
//create alpha map
Ogre::TexturePtr texPtr = Ogre::TextureManager::getSingleton().
createManual(
alphaName, // Name of texture
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, // Name of resource group in which the texture should be created
Ogre::TEX_TYPE_2D,
size,size, //size ofc
1, 0, //depth, mipmaps
Ogre::PF_A8, //we only need one channel to hold the splatting texture
Ogre::TU_STATIC_WRITE_ONLY //best performace, we shopuldn't need the data again
);
createdResources.push_back(texPtr);
Ogre::HardwarePixelBufferSharedPtr pixelBuffer = texPtr->getBuffer();
pixelBuffer->lock(Ogre::HardwareBuffer::HBL_DISCARD);
const Ogre::PixelBox& pixelBox = pixelBuffer->getCurrentLock();
Ogre::uint8* pDest = static_cast<Ogre::uint8*>(pixelBox.data);
memset(pDest,0, sizeof(Ogre::uint8)*size*size);
TextureSplatter ts(ltex, size, ltexWidth, border);
for ( int ty = 0; ty < size; ty++ ) {
for ( int tx = 0; tx < size; tx++ ) {
pDest[ty*size+tx] = ts.getAlphaAtPixel(tx,ty, tid);
}
}
pixelBuffer->unlock();
np = material->getTechnique(0)->createPass();
np->setSceneBlending(Ogre::SBT_TRANSPARENT_ALPHA);
np->setLightingEnabled(false);
np->setDepthFunction(Ogre::CMPF_EQUAL);
Ogre::TextureUnitState* tus = np->createTextureUnitState(alphaName);
tus->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP);
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 = np->createTextureUnitState(tn);
tus->setColourOperationEx( Ogre::LBX_BLEND_DIFFUSE_ALPHA,
Ogre::LBS_TEXTURE,
Ogre::LBS_CURRENT);
tus->setTextureScale(0.1f/scaleDiv,0.1f/scaleDiv);
}
return material;
}
std::string generateAlphaMap(const std::string& name,
std::vector<int>& ltex,
int size,
int border,
float scaleDiv,
std::list<Ogre::ResourcePtr>& createdResources)
{
//std::string materialName = "TEX_" + Ogre::StringConverter::toString(cp.x) + "_" + Ogre::StringConverter::toString(cp.y);
if ( Ogre::MaterialManager::getSingleton().resourceExists(name) )
assert(0);
//return materialName;
getAlphaMat(name, ltex, size, border, scaleDiv, createdResources);
//getShaderAlpha(materialName, ltex, size, newTextures);
return name;
}
inline void setTexturePaths( std::map<int, std::string> r) {
mTexturePaths = r;
}
private:
/**
* Merged records accross all mods for LTEX data
*/
std::map<int, std::string> mTexturePaths;
};

@ -0,0 +1,110 @@
MeshInterface::MeshInterface(Quad* p, Terrain* t) :
mParentQuad(p),
mTerrain(t),
mMax(0),
mMin(0),
mSplitState(SS_NONE) {
mQuadData = t->getTerrainData()->getData(mParentQuad);
//the mesh is created a zero, so an offset is applied
const Ogre::Vector3 pos(mParentQuad->getPosition().x - mParentQuad->getSideLength()/2,
0,
mParentQuad->getPosition().y - mParentQuad->getSideLength()/2);
mSceneNode = mTerrain->getTerrainSceneNode()->createChildSceneNode(pos);
}
MeshInterface::~MeshInterface() {
for ( std::vector<TerrainObjectGroup*>::iterator itr = mTerrainObjects.begin();
itr != mTerrainObjects.end();
++itr )
delete *itr;
mSceneNode->removeAndDestroyAllChildren();
mSceneMgr->destroySceneNode(mSceneNode);
mTerrain->getTerrainSceneNode()->detachAllObjects();
mTerrain->_quadDestroyed(mQuadData);
delete mQuadData;
}
void MeshInterface::create() {
//LOG("Creating");
if ( mParentQuad->getDepth() == mTerrain->getMaxDepth() ) {
for ( int y = 0; y < 4; ++y ) {
for ( int x = 0; x < 4; ++x ) {
addNewObject(
Ogre::Vector3(x*16*128, 0, y*16*128), //pos
17, //size
false, //skirts
0.25f, float(x)/4.0f, float(y)/4.0f); //quad seg location
}
}
} else {
addNewObject(Ogre::Vector3(0,0,0), 65);
}
getBounds();
mTerrain->_quadCreated(mQuadData);
}
void MeshInterface::addNewObject(const Ogre::Vector3& pos,
int terrainSize,
bool skirts /*= true*/,
float segmentSize /*= 1*/,
float startX /*= 0*/,
float startY /*= 0*/ ) {
TerrainObjectGroup* to = new TerrainObjectGroup();
to->segment = new QuadSegment(mQuadData, segmentSize, startX, startY);
to->node = mSceneNode->createChildSceneNode(pos);
to->terrain = new TerrainRenderable(mTerrain, to->segment, terrainSize, mParentQuad->getDepth(), skirts);
to->terrain->create(to->node);
mMax = std::max(to->terrain->getMax(), mMax);
mMin = std::max(to->terrain->getMin(), mMin);
mTerrainObjects.push_back(to);
}
void MeshInterface::update(Ogre::Real time) {
const Ogre::Vector3 cpos = mCamera->getDerivedPosition();
Ogre::Vector3 diff(0, 0, 0);
//copy?
Ogre::AxisAlignedBox worldBounds = mBounds;
worldBounds.transformAffine(mSceneNode->_getFullTransform());
diff.makeFloor(cpos - worldBounds.getMinimum() );
diff.makeCeil(cpos - worldBounds.getMaximum() );
const Ogre::Real camDist = diff.squaredLength();
mSplitState = SS_NONE;
if ( camDist < mSplitDistance ) mSplitState = SS_SPLIT;
else if ( camDist > mUnsplitDistance ) mSplitState = SS_UNSPLIT;
for ( std::vector<TerrainObjectGroup*>::iterator itr = mTerrainObjects.begin();
itr != mTerrainObjects.end();
++itr )
(*itr)->terrain->update(time, camDist, mUnsplitDistance, mMorphDistance);
}
void MeshInterface::getBounds() {
mBounds.setExtents( 0,
mMin,
0,
(65 - 1) * mQuadData->getVertexSeperation(),
mMax,
(65 - 1) * mQuadData->getVertexSeperation());
mBoundingRadius = (mBounds.getMaximum() - mBounds.getMinimum()).length() / 2;
mSplitDistance = pow(mBoundingRadius * 0.5, 2);
mUnsplitDistance = pow(mBoundingRadius * 2.0, 2);
mMorphDistance = pow(mBoundingRadius * 1.5, 2);
}

@ -0,0 +1,117 @@
/**
* Interface between the quad and the terrain renderble classes, to the
* quad it looks like this rendereds a single mesh for the quad. This
* may not be the case.
*
* It also could allow several optomizations (e.g. multiple splits)
*/
class MeshInterface
{
/**
*
* @brief Holds a group of objects and destorys them in the
* destructor. Avoids the needs for 100s of vectors
*/
struct TerrainObjectGroup {
/**
* @brief inits all ptrs at 0
*/
inline TerrainObjectGroup() : segment(0), terrain(0), node(0) {}
/**
* @brief destorys all objects
*/
inline ~TerrainObjectGroup() {
if ( node ) {
node->detachAllObjects();
node->getCreator()->destroySceneNode(node);
}
delete terrain;
delete segment;
}
QuadSegment* segment;
TerrainRenderable* terrain;
Ogre::SceneNode* node;
};
public:
enum SplitState { SS_NONE, SS_SPLIT, SS_UNSPLIT };
MeshInterface(Quad* p, Terrain* t);
~MeshInterface() ;
/**
* @brief creates all required meshes. If it is at the max depth, it creates 16, otherwise just one
*/
void create();
/**
* @brief updates all meshes.
* @remarks the camera distance is calculated here so that all terrain has the correct morph levels etc
*/
void update(Ogre::Real time);
inline SplitState getSplitState() {
return mSplitState;
}
/**
* @brief propergates the just split through all terrain
*/
inline void justSplit() {
for ( std::vector<TerrainObjectGroup*>::iterator itr = mTerrainObjects.begin();
itr != mTerrainObjects.end();
++itr )
(*itr)->terrain->justSplit();
}
/**
* @brief propergates the just unsplit through all terrain
*/
inline void justUnsplit() {
for ( std::vector<TerrainObjectGroup*>::iterator itr = mTerrainObjects.begin();
itr != mTerrainObjects.end();
++itr )
(*itr)->terrain->justUnsplit();
}
private:
Quad* mParentQuad;
Terrain* mTerrain;
///Must be a ptr, else it destorys before we are ready
std::vector<TerrainObjectGroup*> mTerrainObjects;
Ogre::SceneNode* mSceneNode;
///use for split distances
Ogre::Real mBoundingRadius;
Ogre::AxisAlignedBox mBounds;
///max and min heights
float mMax, mMin;
Ogre::Real mSplitDistance,mUnsplitDistance,mMorphDistance;
SplitState mSplitState;
QuadData* mQuadData;
/**
* @brief sets the bounds and split radius of the object
*/
void getBounds();
/**
* @brief Adds a new mesh
* @param pos the position in relation to mSceneNode
* @param terrainSize the size of the terrain in verts. Should be n^2+1
* @param skirts true if the terrain should have skirts
* @param segmentSize the size of the segment. So if splitting terrain into 4*4, it should be 0.25
* @param startX, startY the start position of this segment (0 <= startX < 1)
*/
void addNewObject(const Ogre::Vector3& pos, int terrainSize,
bool skirts = true, float segmentSize = 1,
float startX = 0, float startY = 0 );
};

@ -0,0 +1,84 @@
/**
* @brief the class that deals with loading quads from the disk @todo a
* major improvment would be to store the data as a quad tree. It might
* improve lookup times. Then again. Might not
*/
class MWHeightmap : public TerrainHeightmap
{
public:
QuadData* getData(Quad* q)
{
MWQuadData* data = loadQuad(q->getPosition().x,q->getPosition().y);
if ( !data )
assert(0);
return data;
}
inline bool hasData(Quad* q) {
return hasQuad(q->getPosition().x,q->getPosition().y);
}
inline long getRootSideLength() {
return mIndex.getRootSideLength();
}
inline int getMaxDepth() {
return mIndex.getMaxDepth();
}
/**
* Loads the index and palette
*/
bool load(const std::string& fn)
{
{
std::ifstream ifs(std::string(fn + ".index").c_str(), std::ios::binary);
boost::archive::binary_iarchive oa(ifs);
oa >> mIndex;
}
{
std::ifstream ifs(std::string(fn + ".palette").c_str(), std::ios::binary);
boost::archive::binary_iarchive oa(ifs);
oa >> mPalette;
}
mMaterialGen.setTexturePaths(mPalette.getPalette());
mMaterialGenerator = new MWQuadMaterialGen(&mMaterialGen);
mDataIFS.open(std::string(fn + ".data").c_str(), std::ios::binary);
return true;
}
private:
inline long hasQuad(long x, long y) {
return (mIndex.getOffset(x,y) != -1 ) ;
}
/**
* @brief loads the quad data from the disk
*/
MWQuadData* loadQuad(long x, long y)
{
long offset = mIndex.getOffset(x,y);
if ( offset == -1 ) //check we have xy
return 0;
mDataIFS.seekg(offset);
//load the quad from the file
MWQuadData* q = new MWQuadData(this);
boost::archive::binary_iarchive oa(mDataIFS);
oa >> *q;
return q;
}
///ifs for the data file. Opned on load
std::ifstream mDataIFS;
///holds the offsets of the quads
Index mIndex;
TexturePalette mPalette;
///material generator. gens a ogre::material from quad data
MaterialGenerator mMaterialGen;
};

@ -0,0 +1,84 @@
/**
* @todo mergre with matgen class
*/
class MWQuadMaterialGen : public QuadMaterialGenerator
{
public:
MWQuadMaterialGen(MaterialGenerator* mg) : mMatGen(mg), mCount(0){}
Ogre::MaterialPtr getMaterial(QuadData* qd){
return _getMaterial((MWQuadData*)qd);
}
Ogre::MaterialPtr getMaterialSegment(QuadData* qd, QuadSegment* qs){
return _getMaterialSegment((MWQuadData*)qd,qs);
}
private:
Ogre::MaterialPtr _getMaterial(MWQuadData* qd){
assert(qd);
const std::string name("MAT" + Ogre::StringConverter::toString(mCount++));
if ( qd->getTexture().length() ){
return mMatGen->generateSingleTexture(name, qd->getTexture(), qd->getUsedResourcesRef());
}else{
assert(0);
}
return Ogre::MaterialPtr();
}
Ogre::MaterialPtr _getMaterialSegment(MWQuadData* qd, QuadSegment* qs){
const std::string name("MAT" + Ogre::StringConverter::toString(mCount++));
if ( qd->getTexture().length() )
assert(0&&"NOT IMPLEMENTED");
const std::vector<int>& tref = qd->getTextureIndexRef();
const int indexSize = sqrt(tref.size());
const int cellIndexSize = indexSize - 2;
//plus 1 to take into account border
const int xoff = float(cellIndexSize) * qs->getStartX();
const int yoff = float(cellIndexSize) * qs->getStartY();
const int size = float(cellIndexSize) * qs->getSegmentSize();
std::vector<int> ti;
ti.resize((size+2)*(size+2), -1);
for ( int y = 0; y < size+2; ++y ){
for ( int x = 0; x < size+2; ++x ){
ti[(y)*(size+2)+(x)] = tref.at((y+yoff)*(indexSize)+(x+xoff));
}
}
/*
ti.resize((size)*(size));
for ( int y = 1; y < size+1; ++y ){
for ( int x = 1; x < size+1; ++x ){
if ( y+yoff >= indexSize ) assert(0);
if ( x+xoff >= indexSize ) assert(0);
ti.at((y-1)*(size)+(x-1)) = tref.at((y+yoff)*(indexSize)+(x+xoff));
}
}
*/
Ogre::MaterialPtr t = Ogre::MaterialManager::getSingleton().
getByName(mMatGen->generateAlphaMap
(name,
ti,size,
1, 1.0f/size,
qd->getUsedResourcesRef()));
return t;
}
MaterialGenerator* mMatGen;
unsigned int mCount;
};

@ -0,0 +1,75 @@
/**
* @brief a class that holds a texture palette (basicly, an index accoicated with a texture)
*
* Unfortunaly, this uses a std::map class, which means that hasIndex is slow.
* A fix would be to use a class that has fast lookups for both key/value (both are keys).
* Or something better than that. Whatever.
*
* Yeah, so this is a bit like a std::map
*/
class TexturePalette {
public:
inline bool hasTexture(int index) {
return (mPalette.find(index) != mPalette.end());
}
/**
* Not a great function. Very slow :(
*/
inline bool hasIndex(const std::string& texture) {
for ( Palette::iterator i = mPalette.begin(); i != mPalette.end(); ++i) {
if ( i->second == texture )
return true;
}
return false;
}
inline int getIndex(const std::string& texture) {
for ( Palette::iterator i = mPalette.begin(); i != mPalette.end(); ++i) {
if ( i->second == texture )
return i->first;
}
return -1;
}
inline int getOrAddIndex(const std::string& texture) {
if ( hasIndex(texture) )
return getIndex(texture);
return addTexture(texture);
}
inline const std::string& getTexture(int index) {
return mPalette[index];
}
inline void setTexture(int index, std::string texture) {
mPalette[index] = texture;
}
/**
* @todo add proper error thing rather than assert(0)
*/
inline int addTexture(const std::string& texture) {
for ( int i = 0; i >= 0; i++ ) { //this loop is not infinate, as it will go to -2^31
if ( mPalette.find(i) != mPalette.end() )
continue;
mPalette[i] = texture;
return i;
}
assert(0); //this should never happen. Seeing as we can assign about 2^31 images
}
inline std::map<int, std::string>& getPalette() {
return mPalette;
}
private:
typedef std::map<int, std::string> Palette;
Palette mPalette;
friend class boost::serialization::access;
/**
* @brief saves the palette
*/
template<class Archive>
inline void serialize(Archive& ar, const unsigned int version){
ar &mPalette;
}
};
BOOST_CLASS_TRACKING(TexturePalette, boost::serialization::track_never);

@ -0,0 +1,52 @@
/*
Copyright (c) Jacob Essex 2009
This file is part of MWLand.
MWLand is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
MWLand is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with MWLand. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @brief utility class for holding two values
*
* This is mainly used for querying the position of the quad.
* Each quad has a center position, which we can use as a unique identifier
*/
template<class T>
struct Point2 {
T x, y; //held values.
inline Point2() {}
inline Point2(T ix, T iy) {
x = ix;
y = iy;
}
inline Point2(const Point2<T>& i) {
x = i.x;
y = i.y;
}
/**
* @brief comparison operator. Although not used directly, this
* class is used in std::map a lot, which used the < operator
*/
inline bool operator<(const Point2<T>& rhs) const{
return ( x < rhs.x || !( rhs.x < x) && y < rhs.y );
}
inline Point2 operator + (const Point2<T>& rhs) {
return Point2(x + rhs.x, y + rhs.y);
}
};

@ -0,0 +1,327 @@
/**
* @brief defines an area of Landscape
*
* A quad can either hold a mesh, or 4 other sub quads
* The functions split and unslip either break the current quad into smaller quads, or
* alternatively remove the lower quads and create the terrain mesh on the the current (now the lowest) level
*
* needUnsplit and needSplit query the state of the meshes to see if it needs spliting or unspliting
*
*/
class Quad
{
public:
/**
* when each quad is split, the children can be one of 4 places,
* topleft (NW) top right (NE) etc etc. The other position is the
* root which is the top quad. The root quad doesn't have a mesh,
* and should always have 4 children.
*/
// FIXME: There's probably a better way to do this
enum QuadLocation { QL_NW, QL_NE, QL_SW, QL_SE, QL_ROOT };
/**
* @param l the location of the quad
* @param p the parent quad. Leave 0 if it is root
* @param t the terrain object
*
* Constructor mainly sets up the position variables/depth etc
*/
Quad(QuadLocation l, Quad* p, Terrain* t)
: mParent(p), mTerrain(t), mTerrainMesh(0), mLocation(l)
{
//as mentioned elsewhere, the children should all be null.
memset(mChildren, NULL, sizeof(Quad*)*NUM_CHILDREN);
//find the location of the quad
if ( l != Quad::QL_ROOT )
{ //if it isn't the root node
mDepth = p->getDepth() + 1;
mPosition = p->getPosition();
mSideLength = p->getSideLength()/2;
//horrible bit of code
switch (l) {
case Quad::QL_NE:
mPosition.x += mSideLength/2;
mPosition.y += mSideLength/2;
break;
case Quad::QL_NW:
mPosition.x -= mSideLength/2;
mPosition.y += mSideLength/2;
break;
case Quad::QL_SE:
mPosition.x += mSideLength/2;
mPosition.y -= mSideLength/2;
break;
case Quad::QL_SW:
mPosition.x -= mSideLength/2;
mPosition.y -= mSideLength/2;
break;
default:
break;//get rid of warning
}
//set after positions have been retrived
TerrainHeightmap* d = mTerrain->getTerrainData();
mMaxDepth = d->getMaxDepth();
mHasData = d->hasData(this);
if ( needSplit() ) //need to "semi" build terrain
split();
else if ( mHasData )
{
buildTerrain();
getMesh()->justSplit();
}
}
else
{ //assume it is root node, get data and possition
mDepth = 0; //root
mSideLength = mTerrain->getTerrainData()->getRootSideLength();
mPosition = Point2<long>(0,0); //see Quad::getPosition as to why this is always 0
mHasData = false;
//always split, as this node never has data
split();
}
}
/**
* Destroyes the terrain mesh OR all children.
* If it needs to do both, there is a bug, as that would lead to a terrain mesh existing
* On more than one level in the same space.
* assert checks that there is only one or the other
*/
~Quad()
{
destroyTerrain();
for (size_t i = 0; i < NUM_CHILDREN; i++)
delete mChildren[i];
}
void update(Ogre::Real t)
{
if ( needSplit() )
{
split();
return;
}
else if ( needUnsplit() )
{
unsplit();
return;
}
//deal with updating the mesh.
if ( mTerrainMesh )
mTerrainMesh->update(t);
else if ( hasChildren() )
{
for (size_t i = 0; i < NUM_CHILDREN; ++i) {
assert( mChildren[i] );
mChildren[i]->update(t);
}
}
}
/**
* @return true if the node needs to be split.
*
* The issue with this is that at present this requires the mesh to
* be built to check. This is fine but it could lead to a lot more
* loading when teleporting
*/
bool needSplit()
{
if ( hasChildren() ||
getDepth() == mMaxDepth ||
!hasData() )
return false;
return ( mTerrainMesh && mTerrainMesh->getSplitState()
== MeshInterface::SS_SPLIT );
}
/**
* The functions preforms work on the quad tree state. There is no
* requirement for this to be called every frame, but it is not
* thread safe due to interacting with OGRE The function checks if a
* split or unsplit is needed. It also calls update on all children
* @param t the time since the last frame
*/
/**
* Deletes the landscape, if there is any
* Creates children, and either splits them, or creates landscape for them
*/
void split()
{
destroyTerrain();
//create a new terrain
for ( size_t i = 0; i < NUM_CHILDREN; ++i )
mChildren[i] = new Quad((QuadLocation)i, this, mTerrain);
assert(!needUnsplit());
}
/**
* @brief removes all children, and builds terrain on this level
*/
void unsplit()
{
//shouldn't unsplit 0 depth
assert(getDepth());
for ( size_t i = 0; i < NUM_CHILDREN; i++ )//{
delete mChildren[i];
memset(mChildren, NULL, sizeof(Quad*)*NUM_CHILDREN);
if ( mHasData ) {
buildTerrain();
getMesh()->justUnsplit();
}
assert(!needSplit());
}
/**
* @return true if the node needs to delete all its child nodes, and
* rebuild the terrain its level
*/
bool needUnsplit()
{
if ( hasChildren() && getDepth() ) {
for (size_t i=0;i< NUM_CHILDREN;i++) {
if ( mChildren[i]->hasData() ) {
if ( !mChildren[i]->hasMesh() )
return false;
else if ( mChildren[i]->getMesh()->getSplitState() != MeshInterface::SS_UNSPLIT)
return false;
}
}
return true;
}
//get depth ensures the root doesn't try and unsplit
if ( getDepth() && !hasData() )
return true;
return false;
}
/**
* @brief constructs the terrain on this level. The terrain must on
* exist before hand
*/
void buildTerrain()
{
assert(hasData());
assert(getMesh() == NULL); //the terrain sould not exist
mTerrainMesh = new MeshInterface(this, mTerrain);
mTerrainMesh->create();
}
/**
* @brief destroys the terrain.
*/
void destroyTerrain()
{
delete mTerrainMesh;
mTerrainMesh = NULL;
}
/**
* @brief gets the position in relation to the root (always 0,0)
* @return the position as a long in a container holding the .x and .y vals
*
* This is called form the subnodes of this node, and the TerrainRenderable to work out what needs positiong where
* This is a long (as opposed to a float) so it can be used in comparisons
* @todo typedef this, so it can be changed to int or long long (long64)
*
* The roots position is always 0. This is as the roots position is totally independent of the position
* that the terrain is rendered, as you can just move the root terrain node.
* In other words, it makes everything simpler
*
* The position of the quad is always taken from the center of the quad. Therefore, a top left quads location can be
* defined as:
* xpos = parent x pos - sidelength/2
* ypos = parent y pos + sidelength/2
*
* Where the side length is half the parents side length.
* The calcs are all handled in the consturctor (Quad::Quad)
*/
inline Point2<long> getPosition() const{ return mPosition; }
/**
* @return the location of the quad. E.g. North West
*/
inline QuadLocation getLocation() const{ return mLocation; }
/**
* @brief simply the length of one side of the current quad.
* @return the side length of the current quad
*
* As all quads are square, all sides are this length.
* Obviously this has to fit in sizeof(long)
*/
inline long getSideLength() const{ return mSideLength;}
/**
* @brief The depth is how many splits have taken place since the root node.
* @return the depth of the current quad
*
* The root node has a depth 0. As this is the case, all of its children will have
* a depth of one. Simply depth = parent depth + 1
*
* Set in the consturctor (Quad::Quad)
*/
inline int getDepth() const{return mDepth;}
/**
* @return true if their is a terrain mesh alocated
*/
inline bool hasMesh() const{ return mTerrainMesh != 0; }
/**
* @return true if there are any children
*/
inline bool hasChildren() const { return mChildren[0] != 0; }
/**
* @return the mesh. 0 if there is no mesh
*/
MeshInterface* getMesh()
{
if ( mTerrainMesh == 0 ) return 0;
return mTerrainMesh;
}
/**
* @brief checks if the quad has any data (i.e. a mesh avaible for rendering
*/
inline bool hasData() const{ return mHasData; }
private:
static const size_t NUM_CHILDREN = 4;
Quad* mParent; /// this is the node above this. 0 if this is root
Quad* mChildren[4]; ///optionaly the children. Should be 0 if not exist
Terrain* mTerrain; ///the pointer to the root terrain
MeshInterface* mTerrainMesh; ///the terrain mesh, only used if this is the bottom node
Quad::QuadLocation mLocation; ///the location within the quad (ne, se, nw, sw). See Quad::QuadLocation
Point2<long> mPosition; ///the center of the mesh. this is a long so can be used as comparison. See Quad::getPosition
long mSideLength; ///the length in units of one side of the quad. See Quad::getSideLength
int mDepth; ///depth of the node. See Quad::getDepth for more info
bool mHasData; ///holds if there is terrain data about this quad
int mMaxDepth; ///the maxmium depth. Cached. This is not valid is mDepth == 0
};
//const size_t Quad::NUM_CHILDREN = 4;

@ -0,0 +1,203 @@
/**
* @brief gets a material for the quad data
*/
class QuadMaterialGenerator
{
public:
virtual Ogre::MaterialPtr getMaterial(QuadData* qd) = 0;
/**
* @brief can't overload :(
*/
virtual Ogre::MaterialPtr getMaterialSegment(QuadData* qd,QuadSegment* qs) = 0;
private:
/**
* @brief fix for gcc not putting the vtable anywhere unless there is a none inline, none virtual function
* @remarks does absolutly nothing ofc
*/
void _vtablefix();
};
/**
* @brief abstract class used for getting LOD data.
*
* This class enables storing of data in whatever form is wanted
*/
class TerrainHeightmap {
public:
TerrainHeightmap() : mMaterialGenerator(0){
}
virtual ~TerrainHeightmap() {
delete mMaterialGenerator;
}
/**
* @brief called to load some data for the given quad
* @return NULL if the data doesn't exist
*
* The deleting of the memory is handled by TerrainMesh
*/
virtual QuadData* getData(Quad* q) = 0;
/**
* @brief check if any data exists for this level
* @param q the quad that is asking for the data
*/
virtual bool hasData(Quad* q) = 0;
/**
* @brief get the distance from one end of the map to the other
*/
virtual long getRootSideLength() = 0;
virtual int getMaxDepth() = 0;
inline QuadMaterialGenerator* getMaterialGenerator(){
assert(mMaterialGenerator);
return mMaterialGenerator;
}
protected:
QuadMaterialGenerator* mMaterialGenerator;
};
/**
* @brief holds data that is passed to the mesh renderer. heights normals etc
*
* This needs a rework, as really the mesh renderer should accept just a set of verts
* Normals and indicies to allow us to pass optoizied meshes
*/
class QuadData
{
public:
QuadData(TerrainHeightmap* p)
: mHeightmap(p) {}
virtual ~QuadData() {}
/**
* How many vertes wide the qd is. Usally 65.
* @todo cache?
*/
inline int getVertexWidth() {
return sqrt(getHeightsRef().size());
}
inline std::vector<float>& getHeightsRef() {
return mHeights;
}
inline float getVertex(int offset){
return getHeightsRef().at(offset);
}
inline std::vector<char>& getNormalsRef() {
return mNormals;
}
inline float getNormal(int offset){
return getNormalsRef().at(offset);
}
/**
* @brief should be removed when we get around to developing optomized meshes
*/
inline int getVertexSeperation() {
return mVertexSeperation;
}
inline void setVertexSeperation(int vs) {
mVertexSeperation = vs;
}
/**
* @brief lazy get function. Avoids creating materail until requested
*/
inline Ogre::MaterialPtr getMaterial() {
if ( mMaterial.isNull() )
createMaterial();
assert(!mMaterial.isNull());
return mMaterial;
}
QuadMaterialGenerator* getMaterialGenerator()
{
return mHeightmap->getMaterialGenerator();
}
/**
* @brief gets the texture for the above quad
*/
inline const std::string& getParentTexture() const {
return mParentTexture;
}
inline bool hasParentTexture() const {
return (mParentTexture.length() > 0);
}
inline void setParentTexture(const std::string& c) {
mParentTexture = c;
}
protected:
void createMaterial()
{
mMaterial = getMaterialGenerator()->getMaterial(this);
}
TerrainHeightmap* mHeightmap;
Ogre::MaterialPtr mMaterial;
std::string mParentTexture;
int mVertexSeperation;
std::vector<float> mHeights;
std::vector<char> mNormals;
};
class MWQuadData : public QuadData
{
public:
typedef std::list<Ogre::ResourcePtr> ResourceList;
typedef std::list<Ogre::ResourcePtr>::const_iterator ResourceListCItr;
MWQuadData(TerrainHeightmap* thm) : QuadData(thm) {}
~MWQuadData ()
{
const ResourceListCItr end = mResources.end();
for ( ResourceListCItr itr = mResources.begin(); itr != end; ++itr )
(*itr)->getCreator()->remove((*itr)->getHandle());
}
/**
* @return a ref to the list of used resourcs
*/
inline ResourceList& getUsedResourcesRef()
{ return mResources; }
inline void setTexture(const std::string& t)
{ mTexture = t; }
inline std::string& getTexture()
{ return mTexture; }
inline std::vector<int>& getTextureIndexRef()
{ return mTextureIndex; }
private:
///Holds the resources used by the quad
ResourceList mResources;
std::vector<int> mTextureIndex; ///holds index that corespond the the palette
std::string mTexture; ///The land texture. Mutally exclusive with the above
friend class boost::serialization::access;
/**
* Saves the data for the heights, noramals and texture indexies.
* Texture as well
*/
template<class Archive>
inline void serialize(Archive& ar, const unsigned int version){
ar &mVertexSeperation;
ar &mHeights;
ar &mNormals;
ar &mParentTexture;
ar &mTextureIndex;
ar &mTexture;
}
};
BOOST_CLASS_TRACKING(MWQuadData, boost::serialization::track_never);

@ -0,0 +1,145 @@
/* Represents a segment of a quad. It can also represent a large segment
*
* this is needed for experimenting with splitting the lowest
* quad level into small segments for better culling. Due to the
* intented design of using optomized meshes, this should not work on
* optomized meshes, as there is no easy way to segment the data
*
* This uses floating point numbers to define the areas of the
* quads. There may be issues due to accuracy if we go to small but it
* should be caught in debug mode
*/
#define QSDEBUG
class QuadSegment
{
public:
/**
* @param qd the parent quad data
* @param size the proportion of the total size that the segment is. Must be greater than 0, but less than or equal to 1
* @param x,y the start positions of the segment
*/
QuadSegment(QuadData* qd, float size = 1, float x = 0, float y = 0)
: mQuadData(qd),
mSegmentSize(size),
mX(x),
mY(y)
{
assert(qd);
assert(size>0&&size<=1);
assert(mY>=0&&mY<=1);
assert(mX>=0&&mY<=1);
#ifdef QSDEBUG
{
//check sizes
const float qw = mQuadData->getVertexWidth()-1;
const float fsw = qw*size;
const int isw = (int)fsw;
assert(fsw==isw);
}
#endif
//precalc offsets, as getVertex/getNormal get called a lot (1000s of times)
computeOffsets();
}
/**
* Gets how many vertexes wide this quad segment is. Should always be 2^n+1
* @return the vertex width of this quad segment
*/
int getVertexWidth()
{
return (mQuadData->getVertexWidth()-1)*mSegmentSize+1;
}
/**
* @brief gets a vertex assuming that x = 0, y = 0 addresses the start of the quad
*/
float getVertex(int x, int y)
{
#ifdef QSDEBUG
{
const int vw = getVertexWidth();
assert(x<vw);
}
#endif
return mQuadData->getVertex((mYOffset + y)*mQuadData->getVertexWidth()+(mXOffset+x));
}
float getNormal(int x, int y, int z)
{
assert(z>=0&&z<3);
return mQuadData->getNormal(((mYOffset + y)*mQuadData->getVertexWidth()+(mXOffset+x))*3+z);
}
/**
* @brief lazy function for getting a material
*/
Ogre::MaterialPtr getMaterial()
{
if ( mMaterial.isNull() )
createMaterial();
assert(!mMaterial.isNull());
return mMaterial;
}
void createMaterial()
{
assert(mSegmentSize>0);
if ( mSegmentSize == 1 ) //we can use the top level material
mMaterial = mQuadData->getMaterial();
else //generate a material spesificly for this
mMaterial = mQuadData->getMaterialGenerator()->
getMaterialSegment(mQuadData,this);
assert(!mMaterial.isNull());
}
inline bool hasParentTexture() const{
return mQuadData->hasParentTexture();
}
inline const std::string& getParentTexture() const{
return mQuadData->getParentTexture();
}
inline int getVertexSeperation(){
return mQuadData->getVertexSeperation();
}
inline float getSegmentSize(){
return mSegmentSize;
}
inline float getStartX(){
return mX;
}
inline float getStartY(){
return mY;
}
private:
int computeOffset(float x)
{
#ifdef QSDEBUG
{
//check it is a valid position
const int start = (mQuadData->getVertexWidth()-1)*x;
const int vw = getVertexWidth()-1;
assert(vw>0);
assert((start%vw)==0);
}
#endif
return float((mQuadData->getVertexWidth()-1))*x;
}
void computeOffsets()
{
mXOffset = computeOffset(mX);
mYOffset = computeOffset(mY);
}
QuadData* mQuadData;
float mSegmentSize;
float mX, mY;
Ogre::MaterialPtr mMaterial;
int mXOffset, mYOffset;
};

@ -0,0 +1,151 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2009 Jacob Essex, Nicolay Korslund
WWW: http://openmw.sourceforge.net/
This file (cpp_terrain.cpp) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
// Shader names
#define MORPH_VERTEX_PROGRAM "mw_terrain_VS"
#define FADE_FRAGMENT_PROGRAM "mw_terrain_texfade_FP"
// Directories
#define TEXTURE_OUTPUT "cache/terrain/"
#define TERRAIN_OUTPUT "cache/terrain/landscape"
///no texture assigned
const int LAND_LTEX_NONE = 0;
///the default land height that it defaults to in the TESCS
const int LAND_DEFAULT_HEIGHT = -2048;
///how many verts wide (and long) the cell is
const int LAND_VERT_WIDTH = 65;
///Number of verts that make up a cell
const int LAND_NUM_VERTS = LAND_VERT_WIDTH*LAND_VERT_WIDTH;
const int LAND_LTEX_WIDTH = 16;
const int LAND_NUM_LTEX = LAND_LTEX_WIDTH*LAND_LTEX_WIDTH;
//stops it crashing, now it leaks.
#define ENABLED_CRASHING 0
class Quad;
class QuadData;
class QuadSegment;
class Terrain;
// Prerequisites
#include <vector>
#include <map>
#include <fstream>
#include <string>
#include <list>
#include <algorithm>
#include <boost/shared_ptr.hpp>
#include <boost/archive/binary_iarchive.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/serialization/map.hpp>
// For generation
#include "cpp_esm.cpp"
#include "cpp_landdata.cpp"
#include "cpp_quaddata.cpp"
#include "cpp_point2.cpp"
#include "cpp_materialgen.cpp"
#include "cpp_index.cpp"
#include "cpp_palette.cpp"
#include "cpp_generator.cpp"
// For rendering
#include "cpp_quadsegment.cpp"
#include "cpp_baseland.cpp"
#include "cpp_mwquadmatgen.cpp"
// These depend on each other, so our usual hackery won't work. We
// need the header files first.
#include "cpp_terrainmesh.h"
#include "cpp_meshinterface.h"
#include "cpp_terraincls.h"
#include "cpp_quad.cpp"
#include "cpp_terraincls.cpp"
#include "cpp_terrainmesh.cpp"
#include "cpp_meshinterface.cpp"
#include "cpp_mwheightmap.cpp"
#include "cpp_framelistener.cpp"
TerrainFrameListener terrainListener;
extern "C" void d_superman();
// Set up the rendering system
extern "C" void terr_setupRendering()
{
// Add the terrain directory
ResourceGroupManager::getSingleton().
addResourceLocation(TEXTURE_OUTPUT, "FileSystem", "General");
// Set up the terrain frame listener
terrainListener.setup();
// Enter superman mode
mCamera->setFarClipDistance(32*8192);
d_superman();
}
// Generate all cached data.
extern "C" void terr_genData()
{
Ogre::Root::getSingleton().renderOneFrame();
Generator mhm(TERRAIN_OUTPUT);
{
ESM esm;
const std::string fn("data/Morrowind.esm");
esm.addRecordType("LAND", "INTV");
esm.addRecordType("LTEX", "INTV");
esm.loadFile(fn);
RecordListPtr land = esm.getRecordsByType("LAND");
for ( RecordListItr itr = land->begin(); itr != land->end(); ++itr )
mhm.addLandData(*itr, fn);
RecordListPtr ltex = esm.getRecordsByType("LTEX");
for ( RecordListItr itr = ltex->begin(); itr != ltex->end(); ++itr )
mhm.addLandTextureData(*itr, fn);
}
mhm.beginGeneration();
mhm.generateLODLevel(6, true, 1024);
mhm.generateLODLevel(5, true, 512);
mhm.generateLODLevel(4, true, 256);
mhm.generateLODLevel(3, true, 256);
mhm.generateLODLevel(2, true, 256);
mhm.generateLODLevel(1, false, 128);
mhm.endGeneration();
}

@ -0,0 +1,45 @@
Terrain::Terrain(TerrainHeightmap* d,
Ogre::SceneNode* r)
: mTerrainData(d),
mTerrainSceneNode(r),
mQuadRoot(0),
mQuadCreateFunction(0),
mQuadDestroyFunction(0),
mMorphingEnabled(true),
mTextureFadingEnabled(true),
mBaseLand(r)
{
}
//----------------------------------------------
Terrain::~Terrain(){
delete mQuadRoot;
}
//----------------------------------------------
void Terrain::create(){
mQuadRoot = new Quad(Quad::QL_ROOT, 0, this); //cleaned in Terrain::~Terrain
}
//----------------------------------------------
void Terrain::update(Ogre::Real t){
assert(mQuadRoot);
mQuadRoot->update(t);
mBaseLand.update();
}
//----------------------------------------------
void Terrain::_quadCreated(QuadData* qd){
if ( mQuadCreateFunction ) (*mQuadCreateFunction)(qd);
}
//----------------------------------------------
void Terrain::_quadDestroyed(QuadData* qd){
if ( mQuadDestroyFunction ) (*mQuadDestroyFunction)(qd);
}
//----------------------------------------------
int Terrain::getMaxDepth(){
return mTerrainData->getMaxDepth();
}
//----------------------------------------------
void Terrain::reload(){
delete mQuadRoot;
mQuadRoot = new Quad(Quad::QL_ROOT, 0, this);
}
//----------------------------------------------

@ -0,0 +1,152 @@
class Terrain
{
public:
/**
* @brief inits vars and creates quad tree
* @param d the heightmap that contains the data
* @param s the current scne manager
* @param r the root scene node that all terrain nodes are barsed off
* @param c the camera that the poistion data is taken from
*
* The terrain is create in the constructor.
* @todo change quad root creation to create/destroy funcs?
*/
explicit Terrain(TerrainHeightmap* d, Ogre::SceneNode* r);
/**
* @brief deletes the quad tree
*/
~Terrain();
/**
* @brief creates the quad tree
* @remarks This must be called before any call to upate(). We cannot call it from the constructor as options
* may not have been set yet
*/
void create();
/**
* @brief recreates the quad tree by destroying everything and starting again.
* @remarks this is very slow, as it drops all created alpha maps and meshes.
* @todo check this works
*/
void reload();
/**
* @brief sets the scene node that all of the terrain nodes are based off
* @param r the scene node to use for terrain
*/
inline void setTerrainSceneNode(Ogre::SceneNode* r){ mTerrainSceneNode = r; }
/**
* @return the scene node that should be used as the root of the terrain
*
* This is mainly used by the quad tree
*/
inline Ogre::SceneNode* getTerrainSceneNode(){return mTerrainSceneNode;}
/**
* @brief updates the quad tree, splittig and unsplitting where needed.
*
* There is no requirement for this to be called every frame
*/
void update(Ogre::Real t);
/**
* @return the heightmap data
*/
inline TerrainHeightmap* getTerrainData(){ return mTerrainData; }
/**
* @brief handles the actions to take on the creation of a terrain mesh
* @param qd the quad data. It is valid until the same variable is passed to _quadDestroyed
*/
void _quadCreated(QuadData* qd);
/**
* @brief The quad as defined by the quad data qd has been destroyed
* @param qd the quad that has been destroyed. This is only valid for this function. Is is deleted just after
*/
void _quadDestroyed(QuadData* qd);
/**
* @brief sets the function to be used in the callback when a quad is created
* @param f the function to use. Set to null to disable callbacks
*/
inline void setQuadCreateFunction(void (*f)(QuadData*)){
mQuadCreateFunction = f;
}
/**
* @brief sets the function to be used in the callback when a quad is destroyed
* @param f the function to use. Set to null to disable callbacks
*/
inline void setQuadDestroyFunction(void (*f)(QuadData*)){
mQuadDestroyFunction = f;
}
/**
* @brief time in seconds to morph to full detail after an unsplit.
*/
inline Ogre::Real getMorphSpeed(){return 1.0f;}
/**
* @brief the time in seconds it takes for the texture to fade to the higher detail
*/
inline Ogre::Real getTextureFadeSpeed(){ return 2.0f;}
/**
* @return the maximum depth of the quad tree
* @remarks this is used by the terrain renderable for adding morhping to textures. It doesn't want to happen
* on the lowest detail
*/
int getMaxDepth();
/**
* @brief disables/enables morphing
* @remarks This will not happen instantly unless reload() is called
*/
inline void setMorphingEnabled(bool enabled){
mMorphingEnabled = enabled;
}
/**
* @return true if morphing is enabled
*/
inline bool isMorhpingEnabled() const{
return mMorphingEnabled;
}
/**
* @brief disables/enables texture fading.
* @remarks This will not happen instantly unless reload() is called. Morphing is required atm
*/
inline void setTextureFadingEnabled(bool enabled){
if ( enabled && !mMorphingEnabled )
OGRE_EXCEPT(Ogre::Exception::ERR_NOT_IMPLEMENTED, "Cannot have fading but not morphing active", "Terrain::setTextureFadingEnabled");
mTextureFadingEnabled = enabled;
}
/**
* @return true if texture fading is enabled
*/
inline bool isTextureFadingEnabled() const{
return mTextureFadingEnabled;
}
protected:
TerrainHeightmap* mTerrainData;
/// the scenenode that every other node is decended from. This
/// should be surplied by the user
Ogre::SceneNode* mTerrainSceneNode;
///the root node for all the quads.
Quad* mQuadRoot;
///quad callback function
void (*mQuadCreateFunction)(QuadData*);
///quad callback function
void (*mQuadDestroyFunction)(QuadData*);
bool mMorphingEnabled;
bool mTextureFadingEnabled;
BaseLand mBaseLand;
};

@ -0,0 +1,478 @@
//----------------------------------------------
TerrainRenderable::TerrainRenderable(Terrain* t, QuadSegment* qs,int width, int depth, bool skirts) :
Ogre::Renderable(),
Ogre::MovableObject(),
mWidth(width),
mUseSkirts(skirts),
mBuilt(false),
mDepth(depth),
mSegment(qs),
mTerrain(t),
mVertexes(0),
mIndices(0),
mLODMorphFactor(0),
mTextureFadeFactor(0),
mMin(30000),
mMax(-30000),
mExtraMorphAmount(0),
mHasFadePass(false) {}
//----------------------------------------------
TerrainRenderable::~TerrainRenderable() {
destroy();
}
//----------------------------------------------
void TerrainRenderable::create(Ogre::SceneNode* sn) {
using namespace Ogre;
if ( mBuilt ) return;
createVertexBuffer();
calculateVetexValues();
calculateIndexValues();
setBounds();
sn->attachObject(this);
mMaterial = mSegment->getMaterial();
if ( mTerrain->isMorhpingEnabled() && mDepth != mTerrain->getMaxDepth() ) {
Ogre::Technique* tech = getMaterial()->getTechnique(0);
for ( size_t i = 0; i < tech->getNumPasses(); ++i ) {
assert(mTerrain->isMorhpingEnabled());
tech->getPass(i)->setVertexProgram(MORPH_VERTEX_PROGRAM);
}
}
if ( mTerrain->isMorhpingEnabled() )
calculateDeltaValues();
mBuilt = true;
}
//----------------------------------------------
void TerrainRenderable::destroy() {
if ( !mBuilt ) return;
//deleting null values is fine iirc
delete mIndices;
# if ENABLED_CRASHING == 1
{
delete mVertexes;
}
# else
{
if ( mDepth != mTerrain->getMaxDepth() ){
delete mVertexes;
}
}
# endif
mBuilt = false;
}
//----------------------------------------------
void TerrainRenderable::update(Ogre::Real time, Ogre::Real camDist, Ogre::Real usplitDist, Ogre::Real morphDist) {
//if ( USE_MORPH ){
//as aprocesh mUnsplitDistance, lower detail
if ( camDist > morphDist && mDepth > 1 ) {
mLODMorphFactor = 1 - (usplitDist - camDist)/(usplitDist-morphDist);
} else
mLODMorphFactor = 0;
mTextureFadeFactor = mLODMorphFactor;
//on an split, it sets the extra morph amount to 1, we then ensure this ends up at 0... slowly
if ( mExtraMorphAmount > 0 ) {
mLODMorphFactor += mExtraMorphAmount;
mExtraMorphAmount -= (time/mTerrain->getMorphSpeed()); //decrease slowly
}
if ( mExtraFadeAmount > 0 ) {
mTextureFadeFactor += mExtraFadeAmount;
mExtraFadeAmount -= (time/mTerrain->getTextureFadeSpeed());
}
//Ensure within valid bounds
if ( mLODMorphFactor > 1 )
mLODMorphFactor = 1;
else if ( mLODMorphFactor < 0 )
mLODMorphFactor = 0;
if ( mTextureFadeFactor > 1 )
mTextureFadeFactor = 1;
else if ( mTextureFadeFactor < 0 )
mTextureFadeFactor = 0;
//}
//remove pass. Keep outside in case terrain fading is removed while it is active
if ( mHasFadePass && mTextureFadeFactor == 0 ) {
removeFadePass();
} else if ( mTerrain->isTextureFadingEnabled() &&
!mHasFadePass &&
mTextureFadeFactor > 0 &&
mSegment->hasParentTexture() ) {
addFadePass();
}
}
//----------------------------------------------
void TerrainRenderable::addFadePass() {
assert(mHasFadePass==false);
if ( mDepth == mTerrain->getMaxDepth() ) return;
mHasFadePass = true;
Ogre::MaterialPtr mat = getMaterial();
Ogre::Pass* newPass = mat->getTechnique(0)->createPass();
newPass->setSceneBlending(Ogre::SBF_SOURCE_ALPHA, Ogre::SBF_ONE_MINUS_SOURCE_ALPHA);
//set fragment program
assert(mTerrain->isTextureFadingEnabled());
newPass->setFragmentProgram(FADE_FRAGMENT_PROGRAM);
if ( mTerrain->isMorhpingEnabled() && mDepth != mTerrain->getMaxDepth() ) {
assert(mTerrain->isMorhpingEnabled());
newPass->setVertexProgram(MORPH_VERTEX_PROGRAM);
}
//set texture to be used
newPass->createTextureUnitState(mSegment->getParentTexture(), 1);
}
//----------------------------------------------
void TerrainRenderable::removeFadePass() {
assert(mHasFadePass==true);
mHasFadePass = false;
Ogre::MaterialPtr mat = getMaterial();
Ogre::Technique* tech = mat->getTechnique(0);
tech->removePass(tech->getNumPasses()-1);
}
//----------------------------------------------
void TerrainRenderable::justSplit() {
mExtraMorphAmount = 1;
mLODMorphFactor = 1;
mTextureFadeFactor = 1;
mExtraFadeAmount = 1;
if ( mTerrain->isTextureFadingEnabled() && mSegment->hasParentTexture() )
addFadePass();
}
//----------------------------------------------
void TerrainRenderable::_updateCustomGpuParameter(
const GpuProgramParameters::AutoConstantEntry& constantEntry,
GpuProgramParameters* params) const {
using namespace Ogre;
if (constantEntry.data == MORPH_CUSTOM_PARAM_ID)
params->_writeRawConstant(constantEntry.physicalIndex, mLODMorphFactor);
else if ( constantEntry.data == FADE_CUSTOM_PARAM_ID )
params->_writeRawConstant(constantEntry.physicalIndex, mTextureFadeFactor);
else
Renderable::_updateCustomGpuParameter(constantEntry, params);
}
//----------------------------------------------
float TerrainRenderable::getVertexHeight(int x, int y) {
return mSegment->getVertex(x,y);
}
//----------------------------------------------
Ogre::Vector3 TerrainRenderable::getVertexPosition(int x, int y) {
return Ogre::Vector3(x*mSegment->getVertexSeperation(), getVertexHeight(x,y) , y*mSegment->getVertexSeperation());
}
//----------------------------------------------
void TerrainRenderable::calculateVetexValues() {
using namespace Ogre;
//get the texture offsets for the higher uv
float xUVOffset = 0;
float yUVOffset = 0;
if ( mTerrain->isTextureFadingEnabled() ) {
assert(0);
}
/*
switch (mInterface->getLocation()) {
case Quad::QL_NW :
yUVOffset = 32.0f/64.0f;
break;
case Quad::QL_NE:
yUVOffset = 32.0f/64.0f;
xUVOffset = 32.0f/64.0f;
break;
case Quad::QL_SE:
xUVOffset = 32.0f/64.0f;
break;
default:
break;
}
*/
int start = 0;
int end = mWidth;
if ( mUseSkirts ) {
--start;
++end;
}
float* verts = static_cast<float*>(mMainBuffer->lock(HardwareBuffer::HBL_DISCARD));
for ( int y = start; y < end; y++ ) {
for ( int x = start; x < end; x++ ) {
//the skirts
if ( y < 0 || y > (mWidth-1) || x < 0 || x > (mWidth-1) ) {
if ( x < 0 ) *verts++ = 0;
else if ( x > (mWidth-1) ) *verts++ = (mWidth-1)*mSegment->getVertexSeperation();
else *verts++ = x*mSegment->getVertexSeperation();
*verts++ = -4096; //2048 below base sea floor height
if ( y < 0 ) *verts++ = 0;
else if ( y > (mWidth-1) ) *verts++ = (mWidth-1)*mSegment->getVertexSeperation();
else *verts++ = y*mSegment->getVertexSeperation();
for ( Ogre::uint i = 0; i < 3; i++ )
*verts++ = 0;
float u = (float)(x) / (mWidth-1);
float v = (float)(y) / (mWidth-1);
//clamped, so shouldn't matter if > 1
*verts++ = u;
*verts++ = v;
if ( mTerrain->isTextureFadingEnabled() ) {
*verts++ = u;
*verts++ = v;
}
} else {
assert(y*mWidth+x>=0&&y*mWidth+x<mWidth*mWidth);
//verts
*verts++ = x*mSegment->getVertexSeperation();
*verts++ = getVertexHeight(x,y);
*verts++ = y*mSegment->getVertexSeperation();
mMax = std::max(mMax, getVertexHeight(x,y));
mMin = std::min(mMin, getVertexHeight(x,y));
//normals
for ( Ogre::uint i = 0; i < 3; i++ )
*verts++ = mSegment->getNormal(x,y,i);
//*verts++ = mInterface->getNormal((y*mWidth+x)*3+i);
const float u = (float)(x) / (mWidth-1);
const float v = (float)(y) / (mWidth-1);
assert(u>=0&&v>=0);
assert(u<=1&&v<=1);
*verts++ = u;
*verts++ = v;
if ( mTerrain->isTextureFadingEnabled() ) {
*verts++ = u/2.0f + xUVOffset;
*verts++ = v/2.0f + yUVOffset;
}
}
}
}
mMainBuffer->unlock();
}
//----------------------------------------------
void TerrainRenderable::setBounds() {
mBounds.setExtents(0,mMin,0,
(mWidth - 1) * mSegment->getVertexSeperation(),
mMax,
(mWidth - 1) * mSegment->getVertexSeperation());
mCenter = Ogre::Vector3( ( (mWidth - 1) * mSegment->getVertexSeperation() ) / 2,
( mMin + mMax ) / 2,
( (mWidth - 1) * mSegment->getVertexSeperation() ) / 2);
mBoundingRadius = (mBounds.getMaximum() - mBounds.getMinimum()).length() / 2;
}
//----------------------------------------------
void TerrainRenderable::createVertexBuffer() {
using namespace Ogre;
size_t vw = mWidth;
if ( mUseSkirts ) vw += 2;
mVertexes = new VertexData();
mVertexes->vertexStart = 0;
mVertexes->vertexCount = vw*vw;// VERTEX_WIDTH;
VertexDeclaration* vertexDecl = mVertexes->vertexDeclaration;
size_t currOffset = 0;
vertexDecl->addElement(MAIN_BINDING, currOffset, VET_FLOAT3, VES_POSITION);
currOffset += VertexElement::getTypeSize(VET_FLOAT3);
vertexDecl->addElement(MAIN_BINDING, currOffset, VET_FLOAT3, VES_NORMAL);
currOffset += VertexElement::getTypeSize(VET_FLOAT3);
vertexDecl->addElement(MAIN_BINDING, currOffset, VET_FLOAT2, VES_TEXTURE_COORDINATES, 0);
currOffset += VertexElement::getTypeSize(VET_FLOAT2);
if ( mTerrain->isTextureFadingEnabled() ) {
vertexDecl->addElement(MAIN_BINDING, currOffset, VET_FLOAT2, Ogre::VES_TEXTURE_COORDINATES, 1);
currOffset += VertexElement::getTypeSize(VET_FLOAT2);
}
mMainBuffer = HardwareBufferManager::getSingleton().createVertexBuffer(
vertexDecl->getVertexSize(0), // size of one whole vertex
mVertexes->vertexCount, // number of vertices
HardwareBuffer::HBU_STATIC_WRITE_ONLY, // usage
false); // no shadow buffer
mVertexes->vertexBufferBinding->setBinding(MAIN_BINDING, mMainBuffer); //bind the data
if ( mTerrain->isMorhpingEnabled() )
vertexDecl->addElement(DELTA_BINDING, 0, VET_FLOAT1, VES_BLEND_WEIGHTS);
}
Ogre::HardwareVertexBufferSharedPtr TerrainRenderable::createDeltaBuffer( ) {
size_t vw = mWidth;
if ( mUseSkirts ) vw += 2;
const size_t totalVerts = (vw * vw);
return Ogre::HardwareBufferManager::getSingleton().createVertexBuffer(
Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT1),
totalVerts,
Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY,
false); //no shadow buff
}
//----------------------------------------------
#define SET_DELTA_AT(x, y, v) \
if ( mUseSkirts ) pDeltas[( y + 1)*vw+ x + 1] = v; \
else pDeltas[( y)*vw+ x] = v;
void TerrainRenderable::calculateDeltaValues() {
using namespace Ogre;
size_t vw = mWidth;
if ( mUseSkirts ) vw += 2;
//must be using morphing to use this function
assert(mTerrain->isMorhpingEnabled());
const size_t step = 2;
// Create a set of delta values
mDeltaBuffer = createDeltaBuffer();
float* pDeltas = static_cast<float*>(mDeltaBuffer->lock(HardwareBuffer::HBL_DISCARD));
memset(pDeltas, 0, (vw)*(vw) * sizeof(float));
return;
bool flag=false;
for ( size_t y = 0; y < mWidth-step; y += step ) {
for ( size_t x = 0; x < mWidth-step; x += step ) {
//create the diffrence between the full vertex if the vertex wasn't their
float bottom_left = getVertexHeight(x,y);
float bottom_right = getVertexHeight(x+step,y);
float top_left = getVertexHeight(x,y+step);
float top_right = getVertexHeight(x+step,y+step);
//const int vw = mWidth+2;
SET_DELTA_AT(x, y+1, (bottom_left+top_left)/2 - getVertexHeight(x, y+1)) //left
SET_DELTA_AT(x+2, y+1, (bottom_right+top_right)/2 - getVertexHeight(x+2, y+1)) //right
SET_DELTA_AT(x+1, y+2, (top_right+top_left)/2 - getVertexHeight(x+1, y+2)) //top
SET_DELTA_AT(x+1, y, (bottom_right+bottom_left)/2 - getVertexHeight(x+1, y)) //bottom
//this might not be correct
if ( !flag )
SET_DELTA_AT(x+1, y+1, (bottom_left+top_right)/2 - getVertexHeight(x+1, y+1)) //center
else
SET_DELTA_AT(x+1, y+1, (bottom_right+top_left)/2 - getVertexHeight(x+1, y+1)) //center
flag = !flag;
}
flag = !flag; //flip tries for next row
}
mDeltaBuffer->unlock();
mVertexes->vertexBufferBinding->setBinding(DELTA_BINDING,mDeltaBuffer);
}
#undef SET_DELTA_AT
//----------------------------------------------
void TerrainRenderable::calculateIndexValues() {
using namespace Ogre;
size_t vw = mWidth-1;
if ( mUseSkirts ) vw += 2;
const size_t indexCount = (vw)*(vw)*6;
//need to manage allocation if not null
assert(mIndices==0);
mIndices = new IndexData();
mIndices->indexCount = indexCount;
mIndices->indexBuffer = HardwareBufferManager::getSingleton().createIndexBuffer(
HardwareIndexBuffer::IT_16BIT,
indexCount, HardwareBuffer::HBU_STATIC_WRITE_ONLY, false);
unsigned short* indices = static_cast<unsigned short*>(mIndices->indexBuffer->lock(0,
mIndices->indexBuffer->getSizeInBytes(),
HardwareBuffer::HBL_DISCARD));
bool flag = false;
Ogre::uint indNum = 0;
for ( Ogre::uint y = 0; y < (vw); y+=1 ) {
for ( Ogre::uint x = 0; x < (vw); x+=1 ) {
const int line1 = y * (vw+1) + x;
const int line2 = (y + 1) * (vw+1) + x;
if ( flag ) {
*indices++ = line1;
*indices++ = line2;
*indices++ = line1 + 1;
*indices++ = line1 + 1;
*indices++ = line2;
*indices++ = line2 + 1;
} else {
*indices++ = line1;
*indices++ = line2;
*indices++ = line2 + 1;
*indices++ = line1;
*indices++ = line2 + 1;
*indices++ = line1 + 1;
}
flag = !flag; //flip tris for next time
indNum+=6;
}
flag = !flag; //flip tries for next row
}
assert(indNum==indexCount);
mIndices->indexBuffer->unlock();
//return mIndices;
}

@ -0,0 +1,229 @@
/**
* @brief Terrain for one cell. Handles building, destroying and LOD morphing
*/
class TerrainRenderable : public Ogre::Renderable, public Ogre::MovableObject {
public:
explicit TerrainRenderable(Terrain* t, QuadSegment* qs, int width, int depth, bool skirts);
/**
* Calls destroy();
*/
~TerrainRenderable();
/**
* Creates the actual mesh, it has to be called manually, as it is not called in the constructor
*/
void create(Ogre::SceneNode* sn) ;
/**
* Can be called manually and can be called from the destuctor. This destroyes the mesh
*/
void destroy();
/**
* @brief Checks if it needs to be split or unsplit and deals with the morph factor
* @brief time seconds since last frame
*/
void update(Ogre::Real time, Ogre::Real camDist, Ogre::Real usplitDist, Ogre::Real morphDist);
/**
* @todo Needs to work out what this does (if it does what it is meant to)
*/
void visitRenderables(Renderable::Visitor* visitor,
bool debugRenderables = false) {
visitor->visit(this, 0, false);
}
virtual const Ogre::MaterialPtr& getMaterial( void ) const {
return mMaterial;
}
//-----------------------------------------------------------------------
void getRenderOperation( Ogre::RenderOperation& op ) {
op.useIndexes = true;
op.operationType = Ogre::RenderOperation::OT_TRIANGLE_LIST;
op.vertexData = mVertexes;
op.indexData = mIndices;
}
//-----------------------------------------------------------------------
void getWorldTransforms( Ogre::Matrix4* xform ) const {
*xform = mParentNode->_getFullTransform();
}
//-----------------------------------------------------------------------
const Ogre::Quaternion& getWorldOrientation(void) const {
return mParentNode->_getDerivedOrientation();
}
//-----------------------------------------------------------------------
const Ogre::Vector3& getWorldPosition(void) const {
return mParentNode->_getDerivedPosition();
}
//-----------------------------------------------------------------------
Ogre::Real getSquaredViewDepth(const Ogre::Camera *cam) const {
Ogre::Vector3 diff = mCenter - cam->getDerivedPosition();
// Use squared length to avoid square root
return diff.squaredLength();
}
//-----------------------------------------------------------------------
const Ogre::LightList& getLights(void) const {
if (mLightListDirty) {
getParentSceneNode()->getCreator()->_populateLightList(
mCenter, this->getBoundingRadius(), mLightList);
mLightListDirty = false;
}
return mLightList;
}
//-----------------------------------------------------------------------
virtual const Ogre::String& getMovableType( void ) const {
static Ogre::String t = "MW_TERRAIN";
return t;
}
//-----------------------------------------------------------------------
void _updateRenderQueue( Ogre::RenderQueue* queue ) {
mLightListDirty = true;
queue->addRenderable(this, mRenderQueueID);
}
const Ogre::AxisAlignedBox& getBoundingBox( void ) const {
return mBounds;
};
//-----------------------------------------------------------------------
Ogre::Real getBoundingRadius(void) const {
return mBoundingRadius;
}
//-----------------------------------------------------------------------
/**
* @brief passes the morph factor to the custom vertex program
*/
void _updateCustomGpuParameter(const Ogre::GpuProgramParameters::AutoConstantEntry& constantEntry,
Ogre::GpuProgramParameters* params) const;
/**
* @brief sets the mExtraMorphAmount so it slowly regains detail from the lowest morph factor
*/
void justSplit();
/**
* @brief Does nothing
*/
inline void justUnsplit(){
}
inline float getMax(){
return mMax;
}
inline float getMin(){
return mMin;
}
private:
/**
* @brief Adds another pass to the material to fade in/out the material from a higher level
*/
void addFadePass();
/**
* @brief removes the last pass from the material. Assumed to be the fade pass
*/
void removeFadePass();
/**
* @return the height at the given vertex
*/
float getVertexHeight(int x, int y);
/**
* Inits the vertex stuff
*/
void createVertexBuffer();
/**
* @brief fills the vertex buffer with data
* @todo I don't think tex co-ords are right
*/
void calculateVetexValues();
/**
* @brief returns a a new Vertex Buffer ready for input
* @remarks Unlike other terrain libs, this isn't 0s when it is returend
*/
Ogre::HardwareVertexBufferSharedPtr createDeltaBuffer( );
/**
* @brief DOESN'T WORK FULLY
* @todo fix
*/
void calculateDeltaValues();
/**
* @brief gets the position of a vertex. It will not interpolate
*/
Ogre::Vector3 getVertexPosition(int x, int y);
/**
* @brief gets the indicies for the vertex data.
*/
void calculateIndexValues();
/**
* @brief sets the bounds on the renderable. This requires that mMin/mMax have been set. They are set in createVertexData
* @remarks this may look as though it is done twice, as it is also done in MeshInterface, however, there is no guarentee that
* the mesh sizes are the same
*/
void setBounds();
const int mWidth;
const bool mUseSkirts;
///true if the land has been built
bool mBuilt;
int mDepth;
QuadSegment* mSegment;
Terrain* mTerrain;
Ogre::MaterialPtr mMaterial;
Ogre::ManualObject* mObject;
Ogre::SceneNode* mSceneNode;
Ogre::VertexData* mVertexes;
Ogre::IndexData* mIndices;
Ogre::HardwareVertexBufferSharedPtr mMainBuffer;
Ogre::HardwareVertexBufferSharedPtr mDeltaBuffer;
mutable bool mLightListDirty;
Ogre::Real mBoundingRadius;
Ogre::AxisAlignedBox mBounds;
Ogre::Vector3 mCenter;
Ogre::Real mLODMorphFactor, mTextureFadeFactor;
/** Max and min heights of the mesh */
float mMin, mMax;
/**
* @brief holds the amount to morph by, this decreases to 0 over a periord of time
* It is changed and used in the update() function
*/
Ogre::Real mExtraMorphAmount, mExtraFadeAmount;
/**
* True if the fade pass has been added to the material.
*/
bool mHasFadePass;
//Custom params used in terrian shaders.
static const size_t MORPH_CUSTOM_PARAM_ID = 77;
static const size_t FADE_CUSTOM_PARAM_ID = 78;
//Terrain bindings
static const int MAIN_BINDING = 0;
static const int DELTA_BINDING = 1;
};

@ -0,0 +1,68 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008-2009 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (terrain.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module terrain.terrain;
import std.stdio;
import std.file;
import monster.util.string;
void fail(char[] msg)
{
throw new Exception(msg);
}
// Move elsewhere, make part of the general cache system later
void makeDir(char[] pt)
{
if(exists(pt))
{
if(!isdir(pt))
fail(pt ~ " is not a directory");
}
else
mkdir(pt);
}
void makePath(char[] pt)
{
assert(!pt.begins("/"));
foreach(int i, char c; pt)
if(c == '/')
makeDir(pt[0..i]);
if(!pt.ends("/"))
makeDir(pt);
}
void initTerrain()
{
makePath("cache/terrain");
//terr_genData();
terr_setupRendering();
}
extern(C):
void terr_genData();
void terr_setupRendering();
Loading…
Cancel
Save