#include #include #include #include #include #include #include #include #include #include #include #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 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 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() .altar_items.size(); i++) { const struct PlacementObjects::item &item = ECS::get() .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().mTerrainReady) { ECS::get_mut().mTerrainReady = true; ECS::modified(); } } collider_queue = output; } }; class DummyPageProvider : public Ogre::PageProvider { public: DummyPageProvider(btDynamicsWorld *world) : Ogre::PageProvider() , mBtWorld(world) { } std::unordered_map 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().add(flecs::Singleton); ecs.component().add(flecs::Singleton); ecs.set({ nullptr, {} }); ecs.system("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({}); } 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("UpdateTerrainStatus") .kind(flecs::OnUpdate) .without() .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(); } }); ecs.system("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(); float height = get_height(terrain.mTerrainGroup, loc.position); loc.position.y = height + 0.0f; player.get().mBodyNode->setPosition( loc.position); player.get().mBodyNode->setOrientation( Ogre::Quaternion()); player.modified(); }); } 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; } }