Files
ogre-prototype/src/gamedata/TerrainModule.cpp
2025-09-16 20:38:29 +03:00

621 lines
18 KiB
C++

#include <unordered_set>
#include <iostream>
#include <Ogre.h>
#include <OgreBullet.h>
#include <BulletCollision/CollisionShapes/btHeightfieldTerrainShape.h>
#include <OgreTerrain.h>
#include <OgreTerrainGroup.h>
#include <OgrePageManager.h>
#include <OgreTerrainPagedWorldSection.h>
#include <OgrePage.h>
#include <OgreTerrainPaging.h>
#include "GameData.h"
#include "Components.h"
#include "CharacterModule.h"
#include "SunModule.h"
#include "TerrainModule.h"
#define TERRAIN_SIZE 129
#define TERRAIN_WORLD_SIZE 4000.0f
#define ENDLESS_TERRAIN_FILE_PREFIX Ogre::String("EndlessWorldTerrain")
#define ENDLESS_TERRAIN_FILE_SUFFIX Ogre::String("dat")
#define ENDLESS_PAGING
// max range for a int16
#define ENDLESS_PAGE_MIN_X (-0x7FFF)
#define ENDLESS_PAGE_MIN_Y (-0x7FFF)
#define ENDLESS_PAGE_MAX_X 0x7FFF
#define ENDLESS_PAGE_MAX_Y 0x7FFF
namespace ECS
{
#define BRUSH_SIZE 64
struct HeightData {
Ogre::Image img;
Ogre::Image img_brushes;
Ogre::Image img_noise;
static HeightData *singleton;
HeightData()
{
img.load(
"world_map.png",
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
img_brushes.load(
"brushes.png",
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
img_noise.load(
"terrain.png",
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
}
static HeightData *get_singleton()
{
if (!singleton)
singleton = new HeightData();
return singleton;
}
float get_brush_height(int id, int x, int y)
{
int m = 0;
switch (id) {
case 0:
m = 0;
break;
case 1:
m = BRUSH_SIZE;
break;
default:
OgreAssert(false, "bad brush id");
break;
}
return img_brushes.getColourAt(x, y + m, 0).r;
}
float get_base_height(const Ogre::Vector2 &worldOffset, int x, int y)
{
float height = 0.0f;
int world_x = worldOffset.x + x;
int world_y = worldOffset.y + y;
int world_img_x =
world_x + (int)img.getWidth() * BRUSH_SIZE / 2;
int world_img_y =
world_y + (int)img.getHeight() * BRUSH_SIZE / 2;
Ogre::ColourValue color, colorb1, colorb2;
// float d;
int map_img_x = world_img_x / (BRUSH_SIZE);
int map_img_y = world_img_y / (BRUSH_SIZE);
int brush_img_x = world_img_x % BRUSH_SIZE;
int brush_img_y = world_img_y % BRUSH_SIZE;
if (world_img_x < 0 ||
world_img_x >= img.getWidth() * BRUSH_SIZE ||
world_img_y < 0 ||
world_img_y >= img.getWidth() * BRUSH_SIZE) {
height = -1.0f;
goto out;
}
color = img.getColourAt(map_img_x, map_img_y, 0);
colorb1 = img_brushes.getColourAt(brush_img_x,
brush_img_y + BRUSH_SIZE, 0);
colorb2 = img_brushes.getColourAt(brush_img_x, brush_img_y, 0);
// d = Ogre::Math::saturate(color.r - 0.05f);
height = color.r;
out:
return height;
}
float get_noise_height(const Ogre::Vector2 &worldOffset, int x, int y)
{
int h;
Ogre::Vector2 noisePoint;
struct noise_types {
Ogre::Vector2 noiseOfft;
float noiseMul;
float noiseBias;
float noiseAmp;
};
static struct noise_types noise_pass[] = {
{ { -100.0f, 70.0f }, 10.2f, -0.55f, 5.0f },
{ { -130.0f, 55.0f }, 5.35f, -0.55f, 1.0f }
};
static float noise_values[] = { 0.0f, 0.0f };
for (h = 0; h < (int)sizeof(noise_values) /
(int)sizeof(noise_values[0]);
h++) {
noisePoint = (worldOffset + Ogre::Vector2(x, y) +
noise_pass[h].noiseOfft) *
noise_pass[h].noiseMul;
int noise_x =
(int)(noisePoint.x + img_noise.getWidth() / 2) %
img_noise.getWidth();
int noise_y = (int)(noisePoint.y +
img_noise.getHeight() / 2) %
img_noise.getHeight();
Ogre::ColourValue noise_color =
img_noise.getColourAt(noise_x, noise_y, 0);
noise_values[h] =
(noise_color.r + noise_pass[h].noiseBias) *
noise_pass[h].noiseAmp;
}
return noise_values[0] + noise_values[1];
}
float get_height(Ogre::TerrainGroup *terrainGroup, long x, long y,
int j, int i)
{
uint16_t terrainSize = terrainGroup->getTerrainSize();
Ogre::Vector2 worldOffset(Ogre::Real(x * (terrainSize - 1)),
Ogre::Real(y * (terrainSize - 1)));
float brush_height0 =
HeightData::get_singleton()->get_brush_height(
0, j % BRUSH_SIZE, i % BRUSH_SIZE) -
0.55f;
float brush_height1 =
HeightData::get_singleton()->get_brush_height(
1, j % BRUSH_SIZE, i % BRUSH_SIZE) -
0.55f;
float mheight =
Ogre::Math::lerp(
brush_height1, brush_height0,
HeightData::get_singleton()->get_base_height(
worldOffset, j, i)) *
120.0f;
float height = mheight;
if (mheight > 0.5f)
height += 2.0f + get_noise_height(worldOffset, j, i);
else if (mheight < -0.5f)
height -= 2.0f + get_noise_height(worldOffset, j, i);
return height;
}
};
HeightData *HeightData::singleton = nullptr;
class FlatTerrainDefiner
: public Ogre::TerrainPagedWorldSection::TerrainDefiner,
public Ogre::FrameListener {
Ogre::SceneManager *mScnMgr;
Ogre::Bullet::DynamicsWorld *mWorld;
struct gen_collider {
Ogre::TerrainGroup *group;
long x;
long y;
};
std::deque<struct gen_collider> collider_queue;
public:
FlatTerrainDefiner(Ogre::SceneManager *scm,
Ogre::Bullet::DynamicsWorld *world)
: Ogre::TerrainPagedWorldSection::TerrainDefiner()
, Ogre::FrameListener()
, mScnMgr(scm)
, mWorld(world)
{
Ogre::Root::getSingleton().addFrameListener(this);
}
private:
public:
void define(Ogre::TerrainGroup *terrainGroup, long x, long y) override
{
uint16_t terrainSize = terrainGroup->getTerrainSize();
float *heightMap = OGRE_ALLOC_T(float, terrainSize *terrainSize,
MEMCATEGORY_GEOMETRY);
// float *heightMapCollider = OGRE_ALLOC_T(
// float, terrainSize *terrainSize, MEMCATEGORY_GEOMETRY);
// Ogre::Vector2 worldOrigin =
// Ogre::Vector2(img.getWidth(), img.getHeight()) * 0.5f;
float chunk = 128.0f;
Ogre::Vector2 revisedValuePoint;
for (int i = 0; i < terrainSize; i++)
for (int j = 0; j < terrainSize; j++) {
float height =
HeightData::get_singleton()->get_height(
terrainGroup, x, y, j, i);
// height = -2.0f;
heightMap[i * terrainSize + j] = height;
// heightMapCollider[(terrainSize - i - 1) *
// terrainSize +
// j] = height;
}
terrainGroup->defineTerrain(x, y, heightMap);
Ogre::LogManager::getSingleton().logError(
"defined terrain at " +
Ogre::StringConverter::toString(x) + " " +
Ogre::StringConverter::toString(y));
// collider_queue.push_back(
// { terrainGroup, x, y, heightMapCollider });
delete[] heightMap;
collider_queue.push_back({ terrainGroup, x, y });
}
bool frameStarted(const Ogre::FrameEvent &evt) override
{
(void)evt;
update();
return true;
}
void update()
{
static bool created = false;
std::deque<struct gen_collider> output;
while (!collider_queue.empty()) {
Ogre::TerrainGroup *group =
collider_queue.front().group;
long x = collider_queue.front().x;
long y = collider_queue.front().y;
Ogre::Terrain *terrain = group->getTerrain(x, y);
Ogre::Vector3 worldPos;
group->convertTerrainSlotToWorldPosition(x, y,
&worldPos);
if (terrain && terrain->getHeightData() &&
terrain->isLoaded() &&
!terrain->isDerivedDataUpdateInProgress()) {
Ogre::LogManager::getSingleton().logError(
"can create collider for " +
Ogre::StringConverter::toString(x) +
" " +
Ogre::StringConverter::toString(y));
float minH = terrain->getMinHeight();
float maxH = terrain->getMaxHeight();
int size = terrain->getSize();
float worldSize = terrain->getWorldSize();
if (true) {
btRigidBody *body =
mWorld->addTerrainRigidBody(
group, x, y, 2,
0x7ffffffd & (~16));
OgreAssert(
body,
"Could not create RigidBody");
Ogre::LogManager::getSingleton().logError(
"created rigid body " +
Ogre::StringConverter::toString(
Ogre::Bullet::convert(
body->getWorldTransform()
.getOrigin())));
Ogre::LogManager::getSingleton().logError(
"minHeight " +
Ogre::StringConverter::toString(
minH));
Ogre::LogManager::getSingleton().logError(
"maxHeight " +
Ogre::StringConverter::toString(
maxH));
Ogre::LogManager::getSingleton().logError(
"size " +
Ogre::StringConverter::toString(
size));
Ogre::LogManager::getSingleton().logError(
"world size " +
Ogre::StringConverter::toString(
worldSize));
Ogre::LogManager::getSingleton().logError(
"created collider for " +
Ogre::StringConverter::toString(
x) +
" " +
Ogre::StringConverter::toString(
y));
created = true;
}
collider_queue.pop_front();
// FIXME: create entities and components instead
Ogre::SceneNode *items =
terrain->_getRootSceneNode()
->createChildSceneNode();
for (int i = 0; i < ECS::get<PlacementObjects>()
.altar_items.size();
i++) {
const struct PlacementObjects::item &item =
ECS::get<PlacementObjects>()
.altar_items[i];
Ogre::Entity *ent =
group->getSceneManager()
->createEntity(
item.entity);
Ogre::SceneNode *what =
items->createChildSceneNode();
what->attachObject(ent);
what->setOrientation(item.rotation);
what->setPosition(item.position);
}
} else {
output.push_back(collider_queue.front());
collider_queue.pop_front();
}
if (collider_queue.empty() &&
!ECS::get<Terrain>().mTerrainReady) {
ECS::get_mut<Terrain>().mTerrainReady = true;
ECS::modified<Terrain>();
}
}
collider_queue = output;
}
};
class DummyPageProvider : public Ogre::PageProvider {
public:
DummyPageProvider(btDynamicsWorld *world)
: Ogre::PageProvider()
, mBtWorld(world)
{
}
std::unordered_map<Ogre::PageID, btRigidBody *> body;
btDynamicsWorld *mBtWorld;
bool prepareProceduralPage(Ogre::Page *page,
Ogre::PagedWorldSection *section)
{
return true;
}
bool loadProceduralPage(Ogre::Page *page,
Ogre::PagedWorldSection *section)
{
return true;
}
bool unloadProceduralPage(Ogre::Page *page,
Ogre::PagedWorldSection *section)
{
return true;
}
bool unprepareProceduralPage(Ogre::Page *page,
Ogre::PagedWorldSection *section)
{
return true;
}
};
struct TerrainPrivate {
DummyPageProvider *mDummyPageProvider;
Ogre::Timer mSunUpdate;
};
TerrainModule::TerrainModule(flecs::world &ecs)
{
ecs.component<Terrain>().add(flecs::Singleton);
ecs.component<TerrainPrivate>().add(flecs::Singleton);
ecs.set<TerrainPrivate>({ nullptr, {} });
ecs.system<const EngineData, const Camera, const Sun, Terrain,
TerrainPrivate>("SetupUpdateTerrain")
.kind(flecs::OnUpdate)
.each([](const EngineData &eng, const Camera &camera,
const Sun &sun, Terrain &terrain,
TerrainPrivate &priv) {
if (!terrain.mTerrainGroup && sun.mSun && eng.mScnMgr) {
std::cout << "Terrain setup\n";
if (!priv.mDummyPageProvider)
priv.mDummyPageProvider =
new DummyPageProvider(
eng.mWorld
->getBtWorld());
terrain.mTerrainGlobals =
OGRE_NEW Ogre::TerrainGlobalOptions();
OgreAssert(terrain.mTerrainGlobals,
"Failed to allocate global options");
Ogre::LogManager::getSingleton().setMinLogLevel(
Ogre::LML_TRIVIAL);
terrain.mTerrainGroup =
OGRE_NEW Ogre::TerrainGroup(
eng.mScnMgr,
Ogre::Terrain::ALIGN_X_Z,
TERRAIN_SIZE,
TERRAIN_WORLD_SIZE);
terrain.mTerrainGroup->setFilenameConvention(
ENDLESS_TERRAIN_FILE_PREFIX,
ENDLESS_TERRAIN_FILE_SUFFIX);
terrain.mTerrainGroup->setOrigin(
terrain.mTerrainPos);
// Configure global
terrain.mTerrainGlobals->setMaxPixelError(0);
// testing composite map
// mTerrainGlobals->setCompositeMapDistance(30);
terrain.mTerrainGlobals->setCompositeMapDistance(
500);
//mTerrainGlobals->setUseRayBoxDistanceCalculation(true);
terrain.mTerrainGlobals
->getDefaultMaterialGenerator()
->setLightmapEnabled(false);
terrain.mTerrainGlobals->setCompositeMapAmbient(
eng.mScnMgr->getAmbientLight());
terrain.mTerrainGlobals->setCompositeMapDiffuse(
sun.mSun->getDiffuseColour());
terrain.mTerrainGlobals->setLightMapDirection(
sun.mSun->getDerivedDirection());
// Configure default import settings for if we use imported image
Ogre::Terrain::ImportData &defaultimp =
terrain.mTerrainGroup
->getDefaultImportSettings();
defaultimp.terrainSize = TERRAIN_SIZE;
defaultimp.worldSize = TERRAIN_WORLD_SIZE;
defaultimp.inputScale = 1.0f;
defaultimp.minBatchSize = 33;
defaultimp.maxBatchSize = 65;
Ogre::Image combined;
combined.loadTwoImagesAsRGBA(
"Ground23_col.jpg", "Ground23_spec.png",
"General");
Ogre::TextureManager::getSingleton().loadImage(
"Ground23_diffspec", "General",
combined);
defaultimp.layerList.resize(1);
defaultimp.layerList[0].worldSize = 60;
defaultimp.layerList[0].textureNames.push_back(
"Ground23_diffspec");
// Paging setup
terrain.mPageManager =
OGRE_NEW Ogre::PageManager();
// Since we're not loading any pages from .page files, we need a way just
// to say we've loaded them without them actually being loaded
terrain.mPageManager->setPageProvider(
priv.mDummyPageProvider);
terrain.mPageManager->addCamera(camera.mCamera);
terrain.mPageManager->setDebugDisplayLevel(0);
terrain.mTerrainPaging =
OGRE_NEW Ogre::TerrainPaging(
terrain.mPageManager);
terrain.mPagedWorld =
terrain.mPageManager->createWorld();
terrain.mTerrainPagedWorldSection =
terrain.mTerrainPaging
->createWorldSection(
terrain.mPagedWorld,
terrain.mTerrainGroup,
300, 800,
ENDLESS_PAGE_MIN_X,
ENDLESS_PAGE_MIN_Y,
ENDLESS_PAGE_MAX_X,
ENDLESS_PAGE_MAX_Y);
terrain.mTerrainPagedWorldSection->setDefiner(
OGRE_NEW FlatTerrainDefiner(
eng.mScnMgr, eng.mWorld));
terrain.mTerrainGroup->freeTemporaryResources();
std::cout << "Terrain setup done\n";
ECS::get().set<PlacementObjects>({});
}
if (sun.mSun &&
priv.mSunUpdate.getMilliseconds() > 1000) {
terrain.mTerrainGlobals->setCompositeMapAmbient(
eng.mScnMgr->getAmbientLight());
terrain.mTerrainGlobals->setCompositeMapDiffuse(
sun.mSun->getDiffuseColour());
terrain.mTerrainGlobals->setLightMapDirection(
sun.mSun->getDerivedDirection());
std::cout << "sun pitch: "
<< sun.mSunNode->getOrientation()
.getPitch()
<< "\n";
priv.mSunUpdate.reset();
}
});
ecs.system<const CharacterBase, const Terrain>("UpdateTerrainStatus")
.kind(flecs::OnUpdate)
.without<TerrainReady>()
.each([](const CharacterBase &ch, const Terrain &terrain) {
std::cout << "mTerrainReady: " << terrain.mTerrainReady
<< "\n";
std::cout << "mBodyNode: " << ch.mBodyNode << "\n";
if (ch.mBodyNode && terrain.mTerrainReady) {
long x, y;
Ogre::Vector3 pos = ch.mBodyNode->getPosition();
terrain.mTerrainGroup
->convertWorldPositionToTerrainSlot(
pos, &x, &y);
if (terrain.mTerrainGroup->getTerrain(x, y) &&
terrain.mTerrainGroup->getTerrain(x, y)
->isLoaded())
ECS::get().add<TerrainReady>();
}
});
ecs.system<const Terrain, PlacementObjects>("UpdatePlacementObjects")
.kind(flecs::OnUpdate)
.each([](const Terrain &terrain, PlacementObjects &placement) {
if (placement.altar_items.size() == 0) {
struct PlacementObjects::item item;
int i, j;
int worldSize = terrain.mTerrainGroup
->getTerrainWorldSize();
uint16_t terrainSize =
terrain.mTerrainGroup->getTerrainSize();
item.entity = "altar.glb";
item.rotation = Ogre::Quaternion(0, 0, 0, 1);
item.position = Ogre::Vector3(0, 0, 0);
float height =
HeightData::get_singleton()->get_height(
terrain.mTerrainGroup, 0, 0, 0,
0);
item.position.y = height;
placement.altar_items.push_back(item);
for (i = -64000; i < 64000; i += 1000)
for (j = -64000; j < 64000; j += 1000) {
if (i == 0 && j == 0)
continue;
Ogre::Vector3 position(i, 0, j);
long xslot, yslot;
terrain.mTerrainGroup
->convertWorldPositionToTerrainSlot(
position,
&xslot, &yslot);
Ogre::Vector3 slotpos;
terrain.mTerrainGroup
->convertTerrainSlotToWorldPosition(
xslot, yslot,
&slotpos);
Ogre::Vector3 offset =
(position - slotpos) *
terrainSize / worldSize;
height =
HeightData::get_singleton()
->get_height(
terrain.mTerrainGroup,
xslot,
yslot,
(int)offset.x +
(terrainSize -
1) / 2,
(int)offset.z +
(terrainSize -
1) / 2);
#if 0
height =
terrain.mTerrainGroup
->getHeightAtWorldPosition(
position);
#endif
if (height > -9.0f)
continue;
std::cout << "worldSize: "
<< worldSize - 1
<< std::endl;
std::cout << "height: " << i
<< " " << j << " "
<< height
<< std::endl;
item.entity = "altar.glb";
item.rotation =
Ogre::Quaternion(0, 0,
0, 1);
position.y = height;
item.position = position;
placement.altar_items.push_back(
item);
}
for (i = 0; i < placement.altar_items.size();
i++) {
std::cout << "placement: " << i << " "
<< placement.altar_items[i]
.position
<< std::endl;
}
}
flecs::entity player = ECS::player;
CharacterLocation &loc =
player.get_mut<CharacterLocation>();
float height =
get_height(terrain.mTerrainGroup, loc.position);
loc.position.y = height + 0.0f;
player.get<CharacterBase>().mBodyNode->setPosition(
loc.position);
player.get<CharacterBase>().mBodyNode->setOrientation(
Ogre::Quaternion());
player.modified<CharacterLocation>();
});
}
float TerrainModule::get_height(Ogre::TerrainGroup *group,
const Ogre::Vector3 &position)
{
int worldSize = group->getTerrainWorldSize();
uint16_t terrainSize = group->getTerrainSize();
long xslot, yslot;
group->convertWorldPositionToTerrainSlot(position, &xslot, &yslot);
Ogre::Vector3 slotpos;
group->convertTerrainSlotToWorldPosition(xslot, yslot, &slotpos);
Ogre::Vector3 offset = (position - slotpos) * terrainSize / worldSize;
float height = HeightData::get_singleton()->get_height(
group, xslot, yslot, (int)offset.x + (terrainSize - 1) / 2,
(int)offset.z + (terrainSize - 1) / 2);
return height;
}
}