#include "../model/Skeletons.hpp"
#include "../world/BlockLookup.hpp"
#include "../world/BlockType.hpp"
+#include "../world/ChunkIndex.hpp"
#include "../world/Entity.hpp"
#include "../world/World.hpp"
if (e.Dead()) {
delete *iter;
iter = controllers.erase(iter);
+ end = controllers.end();
continue;
}
bool safe = false;
- for (const Entity *ref : refs) {
- glm::vec3 diff(ref->AbsoluteDifference(e));
+ for (const Player &ref : refs) {
+ glm::vec3 diff(ref.entity->AbsoluteDifference(e));
if (dot(diff, diff) < despawn_range) {
safe = true;
break;
e.Kill();
delete *iter;
iter = controllers.erase(iter);
+ end = controllers.end();
} else {
++iter;
}
// select random player to punish
auto &players = world.Players();
if (players.size() == 0) return;
- Entity &player = *players[random.Next<unsigned short>() % players.size()];
+ const Player &player = players[random.Next<unsigned short>() % players.size()];
- glm::ivec3 chunk(
- (random.Next<unsigned char>() % (chunk_range * 2 + 1)) - chunk_range,
- (random.Next<unsigned char>() % (chunk_range * 2 + 1)) - chunk_range,
- (random.Next<unsigned char>() % (chunk_range * 2 + 1)) - chunk_range
- );
+ int index = random.Next<unsigned int>() % player.chunks->TotalChunks();
+
+ glm::ivec3 chunk(player.chunks->PositionOf(index));
glm::ivec3 pos(
random.Next<unsigned char>() % Chunk::width,
);
// distance check
- glm::vec3 diff(glm::vec3(chunk * Chunk::Extent() - pos) + player.Position());
- float dist = dot(diff, diff);
- if (dist > despawn_range || dist < spawn_distance) {
- return;
- }
+ //glm::vec3 diff(glm::vec3(chunk * Chunk::Extent() - pos) + player.entity->Position());
+ //float dist = dot(diff, diff);
+ //if (dist > despawn_range || dist < spawn_distance) {
+ // return;
+ //}
// check if the spawn block and the one above it are loaded and inhabitable
- BlockLookup spawn_block(
- world.Loader().Loaded(player.ChunkCoords()),
- chunk * Chunk::Extent() + pos);
+ BlockLookup spawn_block((*player.chunks)[index], pos);
if (!spawn_block || spawn_block.GetType().collide_block) {
return;
}
return;
}
- Spawn(player, player.ChunkCoords() + chunk, glm::vec3(pos) + glm::vec3(0.5f));
+ Spawn(*player.entity, chunk, glm::vec3(pos) + glm::vec3(0.5f));
}
void Spawner::Spawn(Entity &reference, const glm::ivec3 &chunk, const glm::vec3 &pos) {
void PreloadState::Update(int dt) {
loader.LoadN(per_update);
- if (loader.ToLoad() == 0) {
+ if (loader.ToLoad() <= 0) {
env.state.Pop();
render.Update(render.MissingChunks());
} else {
#include "../net/Client.hpp"
#include "../net/Server.hpp"
#include "../ui/Interface.hpp"
+#include "../world/Generator.hpp"
#include "../world/World.hpp"
#include <cstddef>
int multisampling = 1;
Client::Config client = Client::Config();
+ Generator::Config gen = Generator::Config();
HeadlessEnvironment::Config env = HeadlessEnvironment::Config();
Interface::Config interface = Interface::Config();
Server::Config server = Server::Config();
ServerState::ServerState(
HeadlessEnvironment &env,
+ const Generator::Config &gc,
const World::Config &wc,
const WorldSave &ws,
const Server::Config &sc
)
: env(env)
, block_types()
-, world(block_types, wc, ws)
+, world(block_types, wc)
+, generator(gc)
+, chunk_loader(world.Chunks(), generator, ws)
, skeletons()
-, spawner(world, skeletons, wc.gen.seed)
+, spawner(world, skeletons, gc.seed)
, server(sc, world)
, push_timer(16) {
TextureIndex tex_index;
#include "../model/Skeletons.hpp"
#include "../net/Server.hpp"
#include "../world/BlockTypeRegistry.hpp"
+#include "../world/ChunkLoader.hpp"
+#include "../world/Generator.hpp"
#include "../world/World.hpp"
namespace blank {
class HeadlessEnvironment;
+class WorldSave;
class ServerState
: public State {
public:
ServerState(
HeadlessEnvironment &,
+ const Generator::Config &,
const World::Config &,
const WorldSave &,
const Server::Config &
HeadlessEnvironment &env;
BlockTypeRegistry block_types;
World world;
+ Generator generator;
+ ChunkLoader chunk_loader;
Skeletons skeletons;
Spawner spawner;
Server server;
namespace blank {
-UnloadState::UnloadState(Environment &env, ChunkLoader &loader)
+UnloadState::UnloadState(
+ Environment &env,
+ ChunkStore &chunks,
+ const WorldSave &save)
: env(env)
-, loader(loader)
+, chunks(chunks)
+, save(save)
, progress(env.assets.large_ui_font)
-, cur(loader.Loaded().begin())
-, end(loader.Loaded().end())
+, cur(chunks.begin())
+, end(chunks.end())
, done(0)
-, total(loader.Loaded().size())
+, total(chunks.NumLoaded())
, per_update(64) {
progress.Position(glm::vec3(0.0f), Gravity::CENTER);
progress.Template("Unloading chunks: %d/%d (%d%%)");
void UnloadState::OnResume() {
- cur = loader.Loaded().begin();
- end = loader.Loaded().end();
+ cur = chunks.begin();
+ end = chunks.end();
done = 0;
- total = loader.Loaded().size();
+ total = chunks.NumLoaded();
}
void UnloadState::Update(int dt) {
for (std::size_t i = 0; i < per_update && cur != end; ++i, ++cur, ++done) {
if (cur->ShouldUpdateSave()) {
- loader.SaveFile().Write(*cur);
+ save.Write(*cur);
}
}
if (cur == end) {
namespace blank {
class Chunk;
-class ChunkLoader;
+class ChunkStore;
class Environment;
+class WorldSave;
class UnloadState
: public State {
public:
- UnloadState(Environment &, ChunkLoader &);
+ UnloadState(Environment &, ChunkStore &, const WorldSave &);
void OnResume();
private:
Environment &env;
- ChunkLoader &loader;
+ ChunkStore &chunks;
+ const WorldSave &save;
Progress progress;
std::list<Chunk>::iterator cur;
std::list<Chunk>::iterator end;
WorldState::WorldState(
Environment &env,
+ const Generator::Config &gc,
const Interface::Config &ic,
const World::Config &wc,
const WorldSave &save
)
: env(env)
, block_types()
-, world(block_types, wc, save)
-, chunk_renderer(world, wc.load.load_dist)
+, world(block_types, wc)
+, interface(ic, env, world, world.AddPlayer(ic.player_name))
+, generator(gc)
+, chunk_loader(world.Chunks(), generator, save)
+, chunk_renderer(*interface.GetPlayer().chunks)
, skeletons()
-, spawner(world, skeletons, wc.gen.seed)
-, interface(ic, env, world, *world.AddPlayer(ic.player_name))
-, preload(env, world.Loader(), chunk_renderer)
-, unload(env, world.Loader()) {
+, spawner(world, skeletons, gc.seed)
+, preload(env, chunk_loader, chunk_renderer)
+, unload(env, world.Chunks(), save) {
TextureIndex tex_index;
env.loader.LoadBlockTypes("default", block_types, tex_index);
chunk_renderer.LoadTextures(env.loader, tex_index);
interface.Update(dt);
spawner.Update(dt);
world.Update(dt);
- chunk_renderer.Rebase(interface.Player().ChunkCoords());
+ chunk_loader.Update(dt);
chunk_renderer.Update(dt);
- glm::mat4 trans = interface.Player().Transform(interface.Player().ChunkCoords());
+ Entity &player = *interface.GetPlayer().entity;
+
+ glm::mat4 trans = player.Transform(player.ChunkCoords());
glm::vec3 dir(trans * glm::vec4(0.0f, 0.0f, -1.0f, 0.0f));
glm::vec3 up(trans * glm::vec4(0.0f, 1.0f, 0.0f, 0.0f));
- env.audio.Position(interface.Player().Position());
- env.audio.Velocity(interface.Player().Velocity());
+ env.audio.Position(player.Position());
+ env.audio.Velocity(player.Velocity());
env.audio.Orientation(dir, up);
}
void WorldState::Render(Viewport &viewport) {
- viewport.WorldPosition(interface.Player().Transform(interface.Player().ChunkCoords()));
+ Entity &player = *interface.GetPlayer().entity;
+ viewport.WorldPosition(player.Transform(player.ChunkCoords()));
chunk_renderer.Render(viewport);
world.Render(viewport);
interface.Render(viewport);
#include "../model/Skeletons.hpp"
#include "../ui/Interface.hpp"
#include "../world/BlockTypeRegistry.hpp"
+#include "../world/ChunkLoader.hpp"
#include "../world/ChunkRenderer.hpp"
+#include "../world/Generator.hpp"
#include "../world/World.hpp"
public:
WorldState(
Environment &,
+ const Generator::Config &,
const Interface::Config &,
const World::Config &,
const WorldSave &
Environment &env;
BlockTypeRegistry block_types;
World world;
+ Interface interface;
+ Generator generator;
+ ChunkLoader chunk_loader;
ChunkRenderer chunk_renderer;
Skeletons skeletons;
Spawner spawner;
- Interface interface;
PreloadState preload;
UnloadState unload;
cerr << "missing argument to -s" << endl;
error = true;
} else {
- config.world.gen.seed = strtoul(argv[i], nullptr, 10);
+ config.gen.seed = strtoul(argv[i], nullptr, 10);
}
break;
case 't':
WorldSave save(config.env.GetWorldPath(config.world.name));
if (save.Exists()) {
save.Read(config.world);
+ save.Read(config.gen);
} else {
save.Write(config.world);
+ save.Write(config.gen);
}
Application app(env);
- WorldState world_state(env, config.interface, config.world, save);
+ WorldState world_state(env, config.gen, config.interface, config.world, save);
app.PushState(&world_state);
Run(app);
}
WorldSave save(config.env.GetWorldPath(config.world.name));
if (save.Exists()) {
save.Read(config.world);
+ save.Read(config.gen);
} else {
save.Write(config.world);
+ save.Write(config.gen);
}
HeadlessApplication app(env);
- ServerState server_state(env, config.world, save, config.server);
+ ServerState server_state(env, config.gen, config.world, save, config.server);
app.PushState(&server_state);
Run(app);
}
BlockTypeRegistry block_types;
WorldSave save;
World world;
- ChunkRenderer chunk_renderer;
Interface interface;
+ ChunkRenderer chunk_renderer;
};
: master(master)
, block_types()
, save(master.GetEnv().config.GetWorldPath(master.GetWorldConf().name, master.GetClientConf().host))
-, world(block_types, master.GetWorldConf(), save)
-, chunk_renderer(world, master.GetWorldConf().load.load_dist)
+, world(block_types, master.GetWorldConf())
, interface(
master.GetInterfaceConf(),
master.GetEnv(),
world,
- *world.AddPlayer(master.GetInterfaceConf().player_name, player_id)
-) {
+ world.AddPlayer(master.GetInterfaceConf().player_name, player_id)
+)
+, chunk_renderer(*interface.GetPlayer().chunks) {
TextureIndex tex_index;
master.GetEnv().loader.LoadBlockTypes("default", block_types, tex_index);
chunk_renderer.LoadTextures(master.GetEnv().loader, tex_index);
interface.Update(dt);
world.Update(dt);
- chunk_renderer.Rebase(interface.Player().ChunkCoords());
chunk_renderer.Update(dt);
- master.GetClient().SendPlayerUpdate(interface.Player());
+ Entity &player = *interface.GetPlayer().entity;
- glm::mat4 trans = interface.Player().Transform(interface.Player().ChunkCoords());
+ master.GetClient().SendPlayerUpdate(player);
+
+ glm::mat4 trans = player.Transform(player.ChunkCoords());
glm::vec3 dir(trans * glm::vec4(0.0f, 0.0f, -1.0f, 0.0f));
glm::vec3 up(trans * glm::vec4(0.0f, 1.0f, 0.0f, 0.0f));
- master.GetEnv().audio.Position(interface.Player().Position());
- master.GetEnv().audio.Velocity(interface.Player().Velocity());
+ master.GetEnv().audio.Position(player.Position());
+ master.GetEnv().audio.Velocity(player.Velocity());
master.GetEnv().audio.Orientation(dir, up);
}
void InteractiveState::Render(Viewport &viewport) {
- viewport.WorldPosition(interface.Player().Transform(interface.Player().ChunkCoords()));
+ Entity &player = *interface.GetPlayer().entity;
+ viewport.WorldPosition(player.Transform(player.ChunkCoords()));
chunk_renderer.Render(viewport);
world.Render(viewport);
interface.Render(viewport);
pack.ReadPlayerID(player_id);
state.reset(new InteractiveState(*this, player_id));
- pack.ReadPlayer(state->GetInterface().Player());
+ pack.ReadPlayer(*state->GetInterface().GetPlayer().entity);
env.state.PopAfter(this);
env.state.Push(state.get());
WorldSave::WorldSave(const string &path)
: root_path(path)
-, conf_path(path + "world.conf")
+, world_conf_path(path + "world.conf")
+, gen_conf_path(path + "gen.conf")
, chunk_path(path + "chunks/%d/%d/%d.gz")
, chunk_bufsiz(chunk_path.length() + 3 * std::numeric_limits<int>::digits10)
, chunk_buf(new char[chunk_bufsiz]) {
bool WorldSave::Exists() const noexcept {
- return is_dir(root_path) && is_file(conf_path);
+ return is_dir(root_path) && is_file(world_conf_path);
}
+// TODO: better implementation of config files
void WorldSave::Read(World::Config &conf) const {
- ifstream in(conf_path);
+ ifstream in(world_conf_path);
if (!in) {
throw runtime_error("failed to open world config");
}
string name(line, name_begin, name_end - name_begin + 1);
string value(line, value_begin, value_end - value_begin + 1);
- if (name == "seed") {
- conf.gen.seed = stoul(value);
- } else {
+ // if (name == "seed") {
+ // conf.gen.seed = stoul(value);
+ // } else {
throw runtime_error("unknown world option: " + name);
- }
+ // }
}
if (in.bad()) {
throw runtime_error("IO error reading world config");
throw runtime_error("failed to create world save directory");
}
- ofstream out(conf_path);
- out << "seed = " << conf.gen.seed << endl;
+ ofstream out(world_conf_path);
+ //out << "seed = " << conf.gen.seed << endl;
out.close();
if (!out) {
}
+void WorldSave::Read(Generator::Config &conf) const {
+ ifstream in(gen_conf_path);
+ if (!in) {
+ throw runtime_error("failed to open generator config");
+ }
+
+ constexpr char spaces[] = "\n\r\t ";
+
+ string line;
+ while (getline(in, line)) {
+ if (line.empty() || line[0] == '#') continue;
+ auto equals_pos = line.find_first_of('=');
+
+ auto name_begin = line.find_first_not_of(spaces, 0, sizeof(spaces));
+ auto name_end = equals_pos - 1;
+ while (name_end > name_begin && isspace(line[name_end])) {
+ --name_end;
+ }
+
+ auto value_begin = line.find_first_not_of(spaces, equals_pos + 1, sizeof(spaces));
+ auto value_end = line.length() - 1;
+ while (value_end > value_begin && isspace(line[value_end])) {
+ --value_end;
+ }
+
+ string name(line, name_begin, name_end - name_begin + 1);
+ string value(line, value_begin, value_end - value_begin + 1);
+
+ if (name == "seed") {
+ conf.seed = stoul(value);
+ } else {
+ throw runtime_error("unknown generator option: " + name);
+ }
+ }
+ if (in.bad()) {
+ throw runtime_error("IO error reading world config");
+ }
+}
+
+void WorldSave::Write(const Generator::Config &conf) const {
+ if (!make_dirs(root_path)) {
+ throw runtime_error("failed to create world save directory");
+ }
+
+ ofstream out(gen_conf_path);
+ out << "seed = " << conf.seed << endl;
+ out.close();
+
+ if (!out) {
+ throw runtime_error("failed to write generator config");
+ }
+}
+
+
bool WorldSave::Exists(const Chunk::Pos &pos) const noexcept {
return is_file(ChunkPath(pos));
}
#define BLANK_IO_WORLDSAVE_HPP_
#include "../world/Chunk.hpp"
+#include "../world/Generator.hpp"
#include "../world/World.hpp"
#include <memory>
bool Exists() const noexcept;
void Read(World::Config &) const;
void Write(const World::Config &) const;
+ void Read(Generator::Config &) const;
+ void Write(const Generator::Config &) const;
// single chunk
bool Exists(const Chunk::Pos &) const noexcept;
private:
std::string root_path;
- std::string conf_path;
+ std::string world_conf_path;
+ std::string gen_conf_path;
std::string chunk_path;
std::size_t chunk_bufsiz;
std::unique_ptr<char[]> chunk_buf;
string name;
pack.ReadPlayerName(name);
- Entity *new_player = server.GetWorld().AddPlayer(name);
+ Entity *new_player = server.GetWorld().AddPlayer(name).entity;
if (new_player) {
// success!
#include "../model/OutlineModel.hpp"
#include "../world/Block.hpp"
#include "../world/EntityCollision.hpp"
+#include "../world/Player.hpp"
#include "../world/WorldCollision.hpp"
#include <string>
bool visual_disabled = false;
};
- Interface(const Config &, Environment &, World &, Entity &);
+ Interface(const Config &, Environment &, World &, const Player &);
- Entity &Player() noexcept { return ctrl.Controlled(); }
- const Entity &Player() const noexcept { return ctrl.Controlled(); }
+ const Player &GetPlayer() noexcept { return player; }
void HandlePress(const SDL_KeyboardEvent &);
void HandleRelease(const SDL_KeyboardEvent &);
private:
Environment &env;
World &world;
+ Player player;
FPSController ctrl;
HUD hud;
const Config &config,
Environment &env,
World &world,
- Entity &player)
+ const Player &player)
: env(env)
, world(world)
-// let's assume this succeeds and hope for the best for now
-, ctrl(player)
+, player(player)
+, ctrl(*player.entity)
, hud(world.BlockTypes(), env.assets.small_ui_font)
, aim{{ 0, 0, 0 }, { 0, 0, -1 }}
, aim_world()
outl_buf.Clear();
aim_world.GetType().FillOutlineModel(outl_buf);
outline.Update(outl_buf);
- outline_transform = aim_world.GetChunk().Transform(Player().ChunkCoords());
+ outline_transform = aim_world.GetChunk().Transform(player.entity->ChunkCoords());
outline_transform *= aim_world.BlockTransform();
outline_transform *= glm::scale(glm::vec3(1.005f));
}
bool IsSurface(const Block::Pos &pos) const noexcept { return IsSurface(Pos(pos)); }
bool IsSurface(const Pos &pos) const noexcept;
- void SetNeighbor(Chunk &) noexcept;
+ void SetNeighbor(Block::Face, Chunk &) noexcept;
bool HasNeighbor(Block::Face f) const noexcept { return neighbor[f]; }
Chunk &GetNeighbor(Block::Face f) noexcept { return *neighbor[f]; }
const Chunk &GetNeighbor(Block::Face f) const noexcept { return *neighbor[f]; }
- void ClearNeighbors() noexcept;
void Unlink() noexcept;
// check which faces of a block at given index are obstructed (and therefore invisible)
const void *BlockData() const noexcept { return &blocks[0]; }
static constexpr std::size_t BlockSize() noexcept { return sizeof(blocks) + sizeof(light); }
+ void Ref() noexcept { ++ref_count; }
+ void UnRef() noexcept { --ref_count; }
+ bool Referenced() const noexcept { return ref_count > 0; }
+
void Invalidate() noexcept { dirty_model = dirty_save = true; }
void InvalidateModel() noexcept { dirty_model = true; }
void ClearModel() noexcept { dirty_model = false; }
Block blocks[size];
unsigned char light[size];
Pos position;
+ int ref_count;
bool dirty_model;
bool dirty_save;
--- /dev/null
+#ifndef BLANK_WORLD_CHUNKINDEX_HPP_
+#define BLANK_WORLD_CHUNKINDEX_HPP_
+
+#include "Chunk.hpp"
+
+#include <vector>
+
+
+namespace blank {
+
+class ChunkStore;
+
+class ChunkIndex {
+
+public:
+ ChunkIndex(ChunkStore &, const Chunk::Pos &base, int extent);
+ ~ChunkIndex();
+
+ ChunkIndex(const ChunkIndex &) = delete;
+ ChunkIndex &operator =(const ChunkIndex &) = delete;
+
+public:
+ bool InRange(const Chunk::Pos &) const noexcept;
+ int IndexOf(const Chunk::Pos &) const noexcept;
+ Chunk::Pos PositionOf(int) const noexcept;
+ /// returns nullptr if given position is out of range or the chunk
+ /// is not loaded, so also works as a "has" function
+ Chunk *Get(const Chunk::Pos &) noexcept;
+ const Chunk *Get(const Chunk::Pos &) const noexcept;
+ Chunk *operator [](int i) noexcept { return chunks[i]; }
+ const Chunk *operator [](int i) const noexcept { return chunks[i]; }
+
+ void Register(Chunk &) noexcept;
+
+ int TotalChunks() const noexcept { return total_length; }
+ int IndexedChunks() const noexcept { return total_indexed; }
+ int MissingChunks() const noexcept { return total_length - total_indexed; }
+
+ Chunk::Pos NextMissing() noexcept;
+
+ const Chunk::Pos &Base() const noexcept { return base; }
+ void Rebase(const Chunk::Pos &);
+
+private:
+ int GetCol(int) const noexcept;
+
+ void Shift(Block::Face);
+
+ void Clear() noexcept;
+ void Scan() noexcept;
+
+ void Set(int index, Chunk &) noexcept;
+ void Unset(int index) noexcept;
+
+private:
+ ChunkStore &store;
+ Chunk::Pos base;
+ int extent;
+ int side_length;
+ int total_length;
+ int total_indexed;
+ int last_missing;
+ glm::ivec3 stride;
+ std::vector<Chunk *> chunks;
+
+};
+
+}
+
+#endif
#ifndef BLANK_WORLD_CHUNKLOADER_HPP_
#define BLANK_WORLD_CHUNKLOADER_HPP_
-#include "Chunk.hpp"
-#include "../app/IntervalTimer.hpp"
-
#include <list>
namespace blank {
-class BlockTypeRegistry;
+class ChunkStore;
class Generator;
class WorldSave;
class ChunkLoader {
public:
- struct Config {
- int load_dist = 6;
- int unload_dist = 8;
- int gen_limit = 16;
- };
-
ChunkLoader(
- const Config &,
- const BlockTypeRegistry &,
+ ChunkStore &,
const Generator &,
const WorldSave &
) noexcept;
- void Queue(const Chunk::Pos &from, const Chunk::Pos &to);
- void QueueSurrounding(const Chunk::Pos &);
-
- std::list<Chunk> &Loaded() noexcept { return loaded; }
const WorldSave &SaveFile() const noexcept { return save; }
- Chunk *Loaded(const Chunk::Pos &) noexcept;
- bool Queued(const Chunk::Pos &) noexcept;
- bool Known(const Chunk::Pos &) noexcept;
- Chunk &ForceLoad(const Chunk::Pos &);
-
- bool OutOfRange(const Chunk &c) const noexcept { return OutOfRange(c.Position()); }
- bool OutOfRange(const Chunk::Pos &) const noexcept;
-
- void Rebase(const Chunk::Pos &);
void Update(int dt);
- std::size_t ToLoad() const noexcept { return to_load.size(); }
+ int ToLoad() const noexcept;
+
// returns true if the chunk was generated
+ // (as opposed to loaded from file)
bool LoadOne();
void LoadN(std::size_t n);
private:
- Chunk &Load(const Chunk::Pos &pos);
- // link given chunk to all loaded neighbors
- void Insert(Chunk &) noexcept;
- // remove a loaded chunk
- // this unlinks it from its neighbors as well as moves it to the free list
- // given iterator must point to a chunk from the loaded list
- // returns an iterator to the chunk following the removed one
- // in the loaded list (end for the last one)
- std::list<Chunk>::iterator Remove(std::list<Chunk>::iterator) noexcept;
-
-private:
- Chunk::Pos base;
-
- const BlockTypeRegistry ®
+ ChunkStore &store;
const Generator &gen;
const WorldSave &save;
- std::list<Chunk> loaded;
- std::list<Chunk::Pos> to_load;
- std::list<Chunk> to_free;
-
- IntervalTimer gen_timer;
-
- int load_dist;
- int unload_dist;
-
};
}
namespace blank {
class AssetLoader;
+class BlockModel;
+class ChunkIndex;
class TextureIndex;
class Viewport;
-class World;
class ChunkRenderer {
public:
- /// render_distance in chunks, excluding the base chunk which is always rendered
- ChunkRenderer(World &, int render_distance);
+ explicit ChunkRenderer(ChunkIndex &);
+ ~ChunkRenderer();
void LoadTextures(const AssetLoader &, const TextureIndex &);
void FogDensity(float d) noexcept { fog_density = d; }
- bool InRange(const Chunk::Pos &) const noexcept;
- int IndexOf(const Chunk::Pos &) const noexcept;
+ int MissingChunks() const noexcept;
- int TotalChunks() const noexcept { return total_length; }
- int IndexedChunks() const noexcept { return total_indexed; }
- int MissingChunks() const noexcept { return total_length - total_indexed; }
-
- void Rebase(const Chunk::Pos &);
- void Rescan();
- void Scan();
void Update(int dt);
void Render(Viewport &);
private:
- int GetCol(int) const noexcept;
-
- void Shift(Block::Face);
-
-private:
- World &world;
- ArrayTexture block_tex;
-
- int render_dist;
- int side_length;
- int total_length;
- int total_indexed;
- glm::ivec3 stride;
+ ChunkIndex &index;
std::vector<BlockModel> models;
- std::vector<Chunk *> chunks;
- Chunk::Pos base;
+ ArrayTexture block_tex;
float fog_density;
--- /dev/null
+#ifndef BLANK_WORLD_CHUNKSTORE_HPP_
+#define BLANK_WORLD_CHUNKSTORE_HPP_
+
+#include "Chunk.hpp"
+
+#include <list>
+
+
+namespace blank {
+
+class ChunkIndex;
+
+class ChunkStore {
+
+public:
+ ChunkStore(const BlockTypeRegistry &);
+ ~ChunkStore();
+
+ ChunkStore(const ChunkStore &) = delete;
+ ChunkStore &operator =(const ChunkStore &) = delete;
+
+public:
+ ChunkIndex &MakeIndex(const Chunk::Pos &base, int extent);
+ void UnregisterIndex(ChunkIndex &);
+
+ /// returns nullptr if given position is not loaded
+ Chunk *Get(const Chunk::Pos &);
+ /// returns nullptr if given position is not indexed
+ Chunk *Allocate(const Chunk::Pos &);
+
+ std::list<Chunk>::iterator begin() noexcept { return loaded.begin(); }
+ std::list<Chunk>::iterator end() noexcept { return loaded.end(); }
+
+ std::size_t NumLoaded() const noexcept { return loaded.size(); }
+
+ /// returns true if one of the indices is incomplete
+ bool HasMissing() const noexcept;
+ /// get the total number of missing chunks
+ /// this is an estimate and represents the upper bound since
+ /// chunks may be counted more than once if indices overlap
+ int EstimateMissing() const noexcept;
+
+ /// get coordinates of a missing chunk
+ /// this will return garbage if none are actually missing
+ Chunk::Pos NextMissing() noexcept;
+
+ void Clean();
+
+private:
+ const BlockTypeRegistry &types;
+
+ std::list<Chunk> loaded;
+ std::list<Chunk> free;
+
+ std::list<ChunkIndex> indices;
+
+};
+
+}
+
+#endif
, typeNoise(config.seed)
, stretch(1.0f/config.stretch)
, solid_threshold(config.solid_threshold)
+// TODO: stable dynamic generator configuration
, space(0)
-, light(0)
-, solids() {
+, light(13)
+, solids({ 1, 4, 7, 10 }) {
}
}
}
}
- //chunk.CheckUpdate();
}
}
--- /dev/null
+#ifndef BLANK_WORLD_PLAYER_HPP_
+#define BLANK_WORLD_PLAYER_HPP_
+
+namespace blank {
+
+class ChunkIndex;
+class Entity;
+
+struct Player {
+
+ Entity *entity;
+ ChunkIndex *chunks;
+
+ Player(Entity *e, ChunkIndex *i)
+ : entity(e), chunks(i) { }
+
+};
+
+}
+
+#endif
#include "World.hpp"
+#include "ChunkIndex.hpp"
#include "EntityCollision.hpp"
#include "WorldCollision.hpp"
#include "../app/Assets.hpp"
namespace blank {
-World::World(const BlockTypeRegistry &types, const Config &config, const WorldSave &save)
+World::World(const BlockTypeRegistry &types, const Config &config)
: config(config)
, block_type(types)
-, generate(config.gen)
-, chunks(config.load, types, generate, save)
+, chunks(types)
+// TODO: set spawn base and extent from config
+, spawn_index(chunks.MakeIndex(Chunk::Pos(0, 0, 0), 3))
, players()
, entities()
, light_direction(config.light_direction)
, fog_density(config.fog_density) {
- generate.Space(0);
- generate.Light(13);
- generate.Solids({ 1, 4, 7, 10 });
+
+}
+
+World::~World() {
+ chunks.UnregisterIndex(spawn_index);
}
-Entity *World::AddPlayer(const std::string &name) {
- for (Entity *e : players) {
- if (e->Name() == name) {
- return nullptr;
+Player World::AddPlayer(const std::string &name) {
+ for (Player &p : players) {
+ if (p.entity->Name() == name) {
+ return { nullptr, nullptr };
}
}
- Entity &player = AddEntity();
- player.Name(name);
+ Entity &entity = AddEntity();
+ entity.Name(name);
// TODO: load from save file here
- player.Bounds({ { -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f } });
- player.WorldCollidable(true);
- player.Position(config.spawn);
- players.push_back(&player);
- chunks.QueueSurrounding(player.ChunkCoords());
- return &player;
+ entity.Bounds({ { -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f } });
+ entity.WorldCollidable(true);
+ entity.Position(config.spawn);
+ ChunkIndex *index = &chunks.MakeIndex(entity.ChunkCoords(), 6);
+ players.emplace_back(&entity, index);
+ return players.back();
}
-Entity *World::AddPlayer(const std::string &name, std::uint32_t id) {
- for (Entity *e : players) {
- if (e->Name() == name) {
- return nullptr;
+Player World::AddPlayer(const std::string &name, std::uint32_t id) {
+ for (Player &p : players) {
+ if (p.entity->Name() == name) {
+ return { nullptr, nullptr };
}
}
- Entity *player = AddEntity(id);
- if (!player) {
- return nullptr;
+ Entity *entity = AddEntity(id);
+ if (!entity) {
+ return { nullptr, nullptr };
}
- player->Name(name);
+ entity->Name(name);
// TODO: load from save file here
- player->Bounds({ { -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f } });
- player->WorldCollidable(true);
- player->Position(config.spawn);
- players.push_back(player);
- chunks.QueueSurrounding(player->ChunkCoords());
- return player;
+ entity->Bounds({ { -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f } });
+ entity->WorldCollidable(true);
+ entity->Position(config.spawn);
+ ChunkIndex *index = &chunks.MakeIndex(entity->ChunkCoords(), 6);
+ players.emplace_back(entity, index);
+ return players.back();
}
Entity &World::AddEntity() {
) {
candidates.clear();
- for (Chunk &cur_chunk : chunks.Loaded()) {
+ for (Chunk &cur_chunk : chunks) {
float cur_dist;
if (cur_chunk.Intersection(ray, M * cur_chunk.Transform(reference), cur_dist)) {
candidates.push_back({ &cur_chunk, cur_dist });
Chunk::Pos reference = e.ChunkCoords();
glm::mat4 M = e.Transform(reference);
bool any = false;
- for (Chunk &cur_chunk : chunks.Loaded()) {
+ for (Chunk &cur_chunk : chunks) {
if (manhattan_radius(cur_chunk.Position() - e.ChunkCoords()) > 1) {
// chunk is not one of the 3x3x3 surrounding the entity
// since there's no entity which can extent over 16 blocks, they can be skipped
Resolve(entity, col);
}
}
+ for (Player &player : players) {
+ player.chunks->Rebase(player.entity->ChunkCoords());
+ }
for (auto iter = entities.begin(), end = entities.end(); iter != end;) {
if (iter->CanRemove()) {
iter = RemoveEntity(iter);
++iter;
}
}
- // TODO: make flexible
- chunks.Rebase(players[0]->ChunkCoords());
- chunks.Update(dt);
}
void World::Resolve(Entity &e, std::vector<WorldCollision> &col) {
World::EntityHandle World::RemoveEntity(EntityHandle &eh) {
// check for player
- auto player = std::find(players.begin(), players.end(), &*eh);
- if (player != players.end()) {
- players.erase(player);
+ for (auto player = players.begin(), end = players.end(); player != end;) {
+ if (player->entity == &*eh) {
+ chunks.UnregisterIndex(*player->chunks);
+ player = players.erase(player);
+ } else {
+ ++player;
+ }
}
return entities.erase(eh);
}
entity_prog.SetFogDensity(fog_density);
for (Entity &entity : entities) {
- entity.Render(entity.ChunkTransform(players[0]->ChunkCoords()), entity_prog);
+ entity.Render(entity.ChunkTransform(players[0].entity->ChunkCoords()), entity_prog);
}
}
#ifndef BLANK_WORLD_WORLD_HPP_
#define BLANK_WORLD_WORLD_HPP_
-#include "ChunkLoader.hpp"
+#include "ChunkStore.hpp"
#include "Entity.hpp"
#include "Generator.hpp"
+#include "Player.hpp"
#include <cstdint>
#include <list>
// I chose 0.011 because it yields 91 and 124 for those, so
// slightly less than 6 and 8 chunks
float fog_density = 0.011f;
-
- Generator::Config gen = Generator::Config();
- ChunkLoader::Config load = ChunkLoader::Config();
};
- World(const BlockTypeRegistry &, const Config &, const WorldSave &);
+ World(const BlockTypeRegistry &, const Config &);
+ ~World();
const std::string &Name() const noexcept { return config.name; }
void Resolve(Entity &e, std::vector<WorldCollision> &);
const BlockTypeRegistry &BlockTypes() noexcept { return block_type; }
- ChunkLoader &Loader() noexcept { return chunks; }
+ ChunkStore &Chunks() noexcept { return chunks; }
/// add player with given name
- /// returns nullptr if the name is already taken
- Entity *AddPlayer(const std::string &name);
+ /// returns nullptr in entity if the name is already taken
+ Player AddPlayer(const std::string &name);
/// add player with given name and ID
- /// returns nullptr if the name or ID is already taken
- Entity *AddPlayer(const std::string &name, std::uint32_t id);
+ /// returns nullptr in entity if the name or ID is already taken
+ Player AddPlayer(const std::string &name, std::uint32_t id);
/// add an entity with an autogenerated ID
Entity &AddEntity();
/// add entity with given ID
/// returns nullptr if the ID is already taken
Entity *AddEntity(std::uint32_t id);
- const std::vector<Entity *> &Players() const noexcept { return players; }
+ const std::vector<Player> &Players() const noexcept { return players; }
std::list<Entity> &Entities() noexcept { return entities; }
const std::list<Entity> &Entities() const noexcept { return entities; }
const BlockTypeRegistry &block_type;
- Generator generate;
- ChunkLoader chunks;
+ ChunkStore chunks;
+ ChunkIndex &spawn_index;
- std::vector<Entity *> players;
+ std::vector<Player> players;
std::list<Entity> entities;
glm::vec3 light_direction;
#include "BlockLookup.hpp"
#include "Chunk.hpp"
+#include "ChunkIndex.hpp"
#include "ChunkLoader.hpp"
+#include "ChunkRenderer.hpp"
+#include "ChunkStore.hpp"
#include "Generator.hpp"
#include "WorldCollision.hpp"
+#include "../app/Assets.hpp"
+#include "../graphics/BlockLighting.hpp"
+#include "../graphics/Viewport.hpp"
#include "../io/WorldSave.hpp"
+#include "../model/BlockModel.hpp"
#include <algorithm>
#include <limits>
, blocks{}
, light{0}
, position(0, 0, 0)
+, ref_count(0)
, dirty_model(false)
, dirty_save(false) {
}
-void Chunk::SetNeighbor(Chunk &other) noexcept {
- if (other.position == position + Pos(-1, 0, 0)) {
- if (neighbor[Block::FACE_LEFT] != &other) {
- neighbor[Block::FACE_LEFT] = &other;
- other.neighbor[Block::FACE_RIGHT] = this;
+void Chunk::SetNeighbor(Block::Face face, Chunk &other) noexcept {
+ neighbor[face] = &other;
+ other.neighbor[Block::Opposite(face)] = this;
+
+ switch (face) {
+
+ default:
+ // error
+ break;
+
+ case Block::FACE_LEFT:
for (int z = 0; z < depth; ++z) {
for (int y = 0; y < height; ++y) {
Pos my_pos(0, y, z);
edge_light(other, other_pos, *this, my_pos);
}
}
- work_light();
- }
- } else if (other.position == position + Pos(1, 0, 0)) {
- if (neighbor[Block::FACE_RIGHT] != &other) {
- neighbor[Block::FACE_RIGHT] = &other;
- other.neighbor[Block::FACE_LEFT] = this;
+ break;
+
+ case Block::FACE_RIGHT:
for (int z = 0; z < depth; ++z) {
for (int y = 0; y < height; ++y) {
Pos my_pos(width - 1, y, z);
edge_light(other, other_pos, *this, my_pos);
}
}
- work_light();
- }
- } else if (other.position == position + Pos(0, -1, 0)) {
- if (neighbor[Block::FACE_DOWN] != &other) {
- neighbor[Block::FACE_DOWN] = &other;
- other.neighbor[Block::FACE_UP] = this;
+ break;
+
+ case Block::FACE_DOWN:
for (int z = 0; z < depth; ++z) {
for (int x = 0; x < width; ++x) {
Pos my_pos(x, 0, z);
edge_light(other, other_pos, *this, my_pos);
}
}
- work_light();
- }
- } else if (other.position == position + Pos(0, 1, 0)) {
- if (neighbor[Block::FACE_UP] != &other) {
- neighbor[Block::FACE_UP] = &other;
- other.neighbor[Block::FACE_DOWN] = this;
+ break;
+
+ case Block::FACE_UP:
for (int z = 0; z < depth; ++z) {
for (int x = 0; x < width; ++x) {
Pos my_pos(x, height - 1, z);
edge_light(other, other_pos, *this, my_pos);
}
}
- work_light();
- }
- } else if (other.position == position + Pos(0, 0, -1)) {
- if (neighbor[Block::FACE_BACK] != &other) {
- neighbor[Block::FACE_BACK] = &other;
- other.neighbor[Block::FACE_FRONT] = this;
+ break;
+
+ case Block::FACE_BACK:
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
Pos my_pos(x, y, 0);
edge_light(other, other_pos, *this, my_pos);
}
}
- work_light();
- }
- } else if (other.position == position + Pos(0, 0, 1)) {
- if (neighbor[Block::FACE_FRONT] != &other) {
- neighbor[Block::FACE_FRONT] = &other;
- other.neighbor[Block::FACE_BACK] = this;
+ break;
+
+ case Block::FACE_FRONT:
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
Pos my_pos(x, y, depth - 1);
edge_light(other, other_pos, *this, my_pos);
}
}
- work_light();
- }
+ break;
+
}
+ work_light();
}
-void Chunk::ClearNeighbors() noexcept {
+void Chunk::Unlink() noexcept {
for (int face = 0; face < Block::FACE_COUNT; ++face) {
if (neighbor[face]) {
neighbor[face]->neighbor[Block::Opposite(Block::Face(face))] = nullptr;
ChunkLoader::ChunkLoader(
- const Config &config,
- const BlockTypeRegistry ®,
+ ChunkStore &store,
const Generator &gen,
const WorldSave &save
) noexcept
-: base(0, 0, 0)
-, reg(reg)
+: store(store)
, gen(gen)
-, save(save)
-, loaded()
-, to_load()
-, to_free()
-, gen_timer(config.gen_limit)
-, load_dist(config.load_dist)
-, unload_dist(config.unload_dist) {
- gen_timer.Start();
+, save(save) {
+
}
-namespace {
+void ChunkLoader::Update(int dt) {
+ // check if there's chunks waiting to be loaded
+ // load until one of load or generation limits was hit
+ constexpr int max_load = 10;
+ constexpr int max_gen = 1;
+ int loaded = 0;
+ int generated = 0;
+ while (loaded < max_load && generated < max_gen && store.HasMissing()) {
+ if (LoadOne()) {
+ ++generated;
+ } else {
+ ++loaded;
+ }
+ }
+
+ // store a few chunks as well
+ constexpr int max_save = 10;
+ int saved = 0;
+ for (Chunk &chunk : store) {
+ if (chunk.ShouldUpdateSave()) {
+ save.Write(chunk);
+ ++saved;
+ if (saved >= max_save) {
+ break;
+ }
+ }
+ }
+}
-struct ChunkLess {
+int ChunkLoader::ToLoad() const noexcept {
+ return store.EstimateMissing();
+}
- explicit ChunkLess(const Chunk::Pos &base) noexcept
- : base(base) { }
+bool ChunkLoader::LoadOne() {
+ if (!store.HasMissing()) return false;
- bool operator ()(const Chunk::Pos &a, const Chunk::Pos &b) const noexcept {
- Chunk::Pos da(base - a);
- Chunk::Pos db(base - b);
- return
- da.x * da.x + da.y * da.y + da.z * da.z <
- db.x * db.x + db.y * db.y + db.z * db.z;
+ Chunk::Pos pos = store.NextMissing();
+ Chunk *chunk = store.Allocate(pos);
+ if (!chunk) {
+ // chunk store corrupted?
+ return false;
}
- Chunk::Pos base;
+ if (save.Exists(pos)) {
+ save.Read(*chunk);
+ return false;
+ } else {
+ gen(*chunk);
+ return true;
+ }
+}
+
+void ChunkLoader::LoadN(std::size_t n) {
+ std::size_t end = std::min(n, std::size_t(ToLoad()));
+ for (std::size_t i = 0; i < end && store.HasMissing(); ++i) {
+ LoadOne();
+ }
+}
-};
+
+ChunkRenderer::ChunkRenderer(ChunkIndex &index)
+: index(index)
+, models(index.TotalChunks())
+, block_tex()
+, fog_density(0.0f) {
}
-void ChunkLoader::Queue(const Chunk::Pos &from, const Chunk::Pos &to) {
- for (int z = from.z; z < to.z; ++z) {
- for (int y = from.y; y < to.y; ++y) {
- for (int x = from.x; x < to.x; ++x) {
- Chunk::Pos pos(x, y, z);
- if (Known(pos)) {
- continue;
- } else if (pos == base) {
- Load(pos);
-
- // light testing
- // for (int i = 0; i < 16; ++i) {
- // for (int j = 0; j < 16; ++j) {
- // loaded.back().SetBlock(Chunk::Pos{ i, j, 0 }, Block(1));
- // loaded.back().SetBlock(Chunk::Pos{ i, j, 15 }, Block(1));
- // loaded.back().SetBlock(Chunk::Pos{ 0, j, i }, Block(1));
- // loaded.back().SetBlock(Chunk::Pos{ 15, j, i }, Block(1));
- // }
- // }
- // loaded.back().SetBlock(Chunk::Pos{ 1, 0, 1 }, Block(13));
- // loaded.back().SetBlock(Chunk::Pos{ 14, 0, 1 }, Block(13));
- // loaded.back().SetBlock(Chunk::Pos{ 1, 0, 14 }, Block(13));
- // loaded.back().SetBlock(Chunk::Pos{ 14, 0, 14 }, Block(13));
- // loaded.back().SetBlock(Chunk::Pos{ 1, 15, 1 }, Block(13));
- // loaded.back().SetBlock(Chunk::Pos{ 14, 15, 1 }, Block(13));
- // loaded.back().SetBlock(Chunk::Pos{ 1, 15, 14 }, Block(13));
- // loaded.back().SetBlock(Chunk::Pos{ 14, 15, 14 }, Block(13));
- // loaded.back().SetBlock(Chunk::Pos{ 7, 7, 0 }, Block(13));
- // loaded.back().SetBlock(Chunk::Pos{ 8, 7, 0 }, Block(13));
- // loaded.back().SetBlock(Chunk::Pos{ 7, 8, 0 }, Block(13));
- // loaded.back().SetBlock(Chunk::Pos{ 8, 8, 0 }, Block(13));
- // loaded.back().SetBlock(Chunk::Pos{ 7, 7, 15 }, Block(13));
- // loaded.back().SetBlock(Chunk::Pos{ 8, 7, 15 }, Block(13));
- // loaded.back().SetBlock(Chunk::Pos{ 7, 8, 15 }, Block(13));
- // loaded.back().SetBlock(Chunk::Pos{ 8, 8, 15 }, Block(13));
- // loaded.back().SetBlock(Chunk::Pos{ 0, 7, 7 }, Block(13));
- // loaded.back().SetBlock(Chunk::Pos{ 0, 7, 8 }, Block(13));
- // loaded.back().SetBlock(Chunk::Pos{ 0, 8, 7 }, Block(13));
- // loaded.back().SetBlock(Chunk::Pos{ 0, 8, 8 }, Block(13));
- // loaded.back().SetBlock(Chunk::Pos{ 15, 7, 7 }, Block(13));
- // loaded.back().SetBlock(Chunk::Pos{ 15, 7, 8 }, Block(13));
- // loaded.back().SetBlock(Chunk::Pos{ 15, 8, 7 }, Block(13));
- // loaded.back().SetBlock(Chunk::Pos{ 15, 8, 8 }, Block(13));
- // loaded.back().Invalidate();
- // loaded.back().CheckUpdate();
-
- // orientation testing
- // for (int i = 0; i < Block::FACE_COUNT; ++i) {
- // for (int j = 0; j < Block::TURN_COUNT; ++j) {
- // loaded.back().BlockAt(512 * j + 2 * i) = Block(3 * (j + 1), Block::Face(i), Block::Turn(j));
- // }
- // }
- // loaded.back().Invalidate();
- // loaded.back().CheckUpdate();
- } else {
- to_load.emplace_back(pos);
- }
+ChunkRenderer::~ChunkRenderer() {
+
+}
+
+int ChunkRenderer::MissingChunks() const noexcept {
+ return index.MissingChunks();
+}
+
+void ChunkRenderer::LoadTextures(const AssetLoader &loader, const TextureIndex &tex_index) {
+ block_tex.Bind();
+ loader.LoadTextures(tex_index, block_tex);
+ block_tex.FilterNearest();
+}
+
+void ChunkRenderer::Update(int dt) {
+ for (int i = 0, updates = 0; updates < dt && i < index.TotalChunks(); ++i) {
+ if (index[i] && index[i]->ShouldUpdateModel()) {
+ index[i]->Update(models[i]);
+ ++updates;
+ }
+ }
+}
+
+void ChunkRenderer::Render(Viewport &viewport) {
+ BlockLighting &chunk_prog = viewport.ChunkProgram();
+ chunk_prog.SetTexture(block_tex);
+ chunk_prog.SetFogDensity(fog_density);
+
+ for (int i = 0; i < index.TotalChunks(); ++i) {
+ if (!index[i]) continue;
+ glm::mat4 m(index[i]->Transform(index.Base()));
+ glm::mat4 mvp(chunk_prog.GetVP() * m);
+ if (!CullTest(Chunk::Bounds(), mvp)) {
+ if (index[i]->ShouldUpdateModel()) {
+ index[i]->Update(models[i]);
}
+ chunk_prog.SetM(m);
+ models[i].Draw();
}
}
- to_load.sort(ChunkLess(base));
}
-Chunk &ChunkLoader::Load(const Chunk::Pos &pos) {
- loaded.emplace_back(reg);
- Chunk &chunk = loaded.back();
- chunk.Position(pos);
- if (save.Exists(pos)) {
- save.Read(chunk);
+
+ChunkIndex::ChunkIndex(ChunkStore &store, const Chunk::Pos &base, int extent)
+: store(store)
+, base(base)
+, extent(extent)
+, side_length(2 * extent + 1)
+, total_length(side_length * side_length * side_length)
+, total_indexed(0)
+, last_missing(0)
+, stride(1, side_length, side_length * side_length)
+, chunks(total_length, nullptr) {
+ Scan();
+}
+
+ChunkIndex::~ChunkIndex() {
+ Clear();
+}
+
+bool ChunkIndex::InRange(const Chunk::Pos &pos) const noexcept {
+ return manhattan_radius(pos - base) <= extent;
+}
+
+int ChunkIndex::IndexOf(const Chunk::Pos &pos) const noexcept {
+ Chunk::Pos mod_pos(
+ GetCol(pos.x),
+ GetCol(pos.y),
+ GetCol(pos.z)
+ );
+ return mod_pos.x * stride.x
+ + mod_pos.y * stride.y
+ + mod_pos.z * stride.z;
+}
+
+Chunk::Pos ChunkIndex::PositionOf(int i) const noexcept {
+ Chunk::Pos zero_pos(
+ (i / stride.x) % side_length,
+ (i / stride.y) % side_length,
+ (i / stride.z) % side_length
+ );
+ Chunk::Pos zero_base(
+ GetCol(base.x),
+ GetCol(base.y),
+ GetCol(base.z)
+ );
+ Chunk::Pos base_relative(zero_pos - zero_base);
+ if (base_relative.x > extent) base_relative.x -= side_length;
+ else if (base_relative.x < -extent) base_relative.x += side_length;
+ if (base_relative.y > extent) base_relative.y -= side_length;
+ else if (base_relative.y < -extent) base_relative.y += side_length;
+ if (base_relative.z > extent) base_relative.z -= side_length;
+ else if (base_relative.z < -extent) base_relative.z += side_length;
+ return base + base_relative;
+}
+
+Chunk *ChunkIndex::Get(const Chunk::Pos &pos) noexcept {
+ if (InRange(pos)) {
+ return chunks[IndexOf(pos)];
} else {
- gen(chunk);
+ return nullptr;
}
- Insert(chunk);
- return chunk;
}
-void ChunkLoader::Insert(Chunk &chunk) noexcept {
- for (Chunk &other : loaded) {
- chunk.SetNeighbor(other);
+const Chunk *ChunkIndex::Get(const Chunk::Pos &pos) const noexcept {
+ if (InRange(pos)) {
+ return chunks[IndexOf(pos)];
+ } else {
+ return nullptr;
}
}
-std::list<Chunk>::iterator ChunkLoader::Remove(std::list<Chunk>::iterator chunk) noexcept {
- // fetch next entry while chunk's still in the list
- std::list<Chunk>::iterator next = chunk;
- ++next;
- // unlink neighbors so they won't reference a dead chunk
- chunk->ClearNeighbors();
- // if it should be saved, do it now
- if (chunk->ShouldUpdateSave()) {
- save.Write(*chunk);
+void ChunkIndex::Rebase(const Chunk::Pos &new_base) {
+ if (new_base == base) return;
+
+ Chunk::Pos diff(new_base - base);
+
+ if (manhattan_radius(diff) > extent) {
+ // that's more than half, so probably not worth shifting
+ base = new_base;
+ Clear();
+ Scan();
+ store.Clean();
+ return;
+ }
+
+ while (diff.x > 0) {
+ Shift(Block::FACE_RIGHT);
+ --diff.x;
+ }
+ while (diff.x < 0) {
+ Shift(Block::FACE_LEFT);
+ ++diff.x;
+ }
+ while (diff.y > 0) {
+ Shift(Block::FACE_UP);
+ --diff.y;
}
- // and move it from loaded to free list
- to_free.splice(to_free.end(), loaded, chunk);
- return next;
+ while (diff.y < 0) {
+ Shift(Block::FACE_DOWN);
+ ++diff.y;
+ }
+ while (diff.z > 0) {
+ Shift(Block::FACE_FRONT);
+ --diff.z;
+ }
+ while (diff.z < 0) {
+ Shift(Block::FACE_BACK);
+ ++diff.z;
+ }
+ store.Clean();
+}
+
+int ChunkIndex::GetCol(int c) const noexcept {
+ c %= side_length;
+ if (c < 0) c += side_length;
+ return c;
}
-Chunk *ChunkLoader::Loaded(const Chunk::Pos &pos) noexcept {
- for (Chunk &chunk : loaded) {
- if (chunk.Position() == pos) {
- return &chunk;
+void ChunkIndex::Shift(Block::Face f) {
+ int a_axis = Block::Axis(f);
+ int b_axis = (a_axis + 1) % 3;
+ int c_axis = (a_axis + 2) % 3;
+ int dir = Block::Direction(f);
+ base[a_axis] += dir;
+ int a = GetCol(base[a_axis] + (extent * dir));
+ int a_stride = a * stride[a_axis];
+ for (int b = 0; b < side_length; ++b) {
+ int b_stride = b * stride[b_axis];
+ for (int c = 0; c < side_length; ++c) {
+ int bc_stride = b_stride + c * stride[c_axis];
+ int index = a_stride + bc_stride;
+ Unset(index);
+ int neighbor = ((a - dir + side_length) % side_length) * stride[a_axis] + bc_stride;
+ if (chunks[neighbor] && chunks[neighbor]->HasNeighbor(f)) {
+ Set(index, chunks[neighbor]->GetNeighbor(f));
+ }
}
}
- return nullptr;
}
-bool ChunkLoader::Queued(const Chunk::Pos &pos) noexcept {
- for (const Chunk::Pos &chunk : to_load) {
- if (chunk == pos) {
- return true;
- }
+void ChunkIndex::Clear() noexcept {
+ for (int i = 0; i < total_length && total_indexed > 0; ++i) {
+ Unset(i);
}
- return false;
}
-bool ChunkLoader::Known(const Chunk::Pos &pos) noexcept {
- if (Loaded(pos)) return true;
- return Queued(pos);
+void ChunkIndex::Scan() noexcept {
+ for (Chunk &chunk : store) {
+ Register(chunk);
+ }
}
-Chunk &ChunkLoader::ForceLoad(const Chunk::Pos &pos) {
- Chunk *chunk = Loaded(pos);
- if (chunk) {
- return *chunk;
+void ChunkIndex::Register(Chunk &chunk) noexcept {
+ if (InRange(chunk.Position())) {
+ Set(IndexOf(chunk.Position()), chunk);
+ }
+}
+
+void ChunkIndex::Set(int index, Chunk &chunk) noexcept {
+ Unset(index);
+ chunks[index] = &chunk;
+ chunk.Ref();
+ ++total_indexed;
+}
+
+void ChunkIndex::Unset(int index) noexcept {
+ if (chunks[index]) {
+ chunks[index]->UnRef();
+ chunks[index] = nullptr;
+ --total_indexed;
}
+}
- for (auto iter(to_load.begin()), end(to_load.end()); iter != end; ++iter) {
- if (*iter == pos) {
- to_load.erase(iter);
+Chunk::Pos ChunkIndex::NextMissing() noexcept {
+ int roundtrip = last_missing;
+ while (chunks[last_missing]) {
+ ++last_missing;
+ last_missing %= total_length;
+ if (last_missing == roundtrip) {
break;
}
}
+ return PositionOf(last_missing);
+}
+
+
+ChunkStore::ChunkStore(const BlockTypeRegistry &types)
+: types(types)
+, loaded()
+, free()
+, indices() {
- return Load(pos);
}
-bool ChunkLoader::OutOfRange(const Chunk::Pos &pos) const noexcept {
- return std::abs(base.x - pos.x) > unload_dist
- || std::abs(base.y - pos.y) > unload_dist
- || std::abs(base.z - pos.z) > unload_dist;
+ChunkStore::~ChunkStore() {
+
}
-void ChunkLoader::Rebase(const Chunk::Pos &new_base) {
- if (new_base == base) {
- return;
- }
- base = new_base;
+ChunkIndex &ChunkStore::MakeIndex(const Chunk::Pos &pos, int extent) {
+ indices.emplace_back(*this, pos, extent);
+ return indices.back();
+}
- // unload far away chunks
- for (auto iter(loaded.begin()), end(loaded.end()); iter != end;) {
- if (OutOfRange(*iter)) {
- iter = Remove(iter);
- } else {
- ++iter;
- }
- }
- // abort far away queued chunks
- for (auto iter(to_load.begin()), end(to_load.end()); iter != end;) {
- if (OutOfRange(*iter)) {
- iter = to_load.erase(iter);
+void ChunkStore::UnregisterIndex(ChunkIndex &index) {
+ for (auto i = indices.begin(), end = indices.end(); i != end; ++i) {
+ if (&*i == &index) {
+ indices.erase(i);
+ return;
} else {
- ++iter;
+ ++i;
}
}
- // add missing new chunks
- QueueSurrounding(base);
}
-void ChunkLoader::QueueSurrounding(const Chunk::Pos &pos) {
- const Chunk::Pos offset(load_dist, load_dist, load_dist);
- Queue(pos - offset, pos + offset);
+Chunk *ChunkStore::Get(const Chunk::Pos &pos) {
+ for (ChunkIndex &index : indices) {
+ Chunk *chunk = index.Get(pos);
+ if (chunk) {
+ return chunk;
+ }
+ }
+ return nullptr;
}
-void ChunkLoader::Update(int dt) {
- // check if a chunk load is scheduled for this frame
- // and if there's chunks waiting to be loaded
- gen_timer.Update(dt);
- if (gen_timer.Hit()) {
- // we may
- // load until one of load or generation limits was hit
- constexpr int max_load = 10;
- constexpr int max_gen = 1;
- int loaded = 0;
- int generated = 0;
- while (!to_load.empty() && loaded < max_load && generated < max_gen) {
- if (LoadOne()) {
- ++generated;
- } else {
- ++loaded;
- }
+Chunk *ChunkStore::Allocate(const Chunk::Pos &pos) {
+ Chunk *chunk = Get(pos);
+ if (chunk) {
+ return chunk;
+ }
+ if (free.empty()) {
+ loaded.emplace(loaded.begin(), types);
+ } else {
+ loaded.splice(loaded.begin(), free, free.begin());
+ loaded.front().Unlink();
+ }
+ chunk = &loaded.front();
+ chunk->Position(pos);
+ for (ChunkIndex &index : indices) {
+ if (index.InRange(pos)) {
+ index.Register(*chunk);
}
}
-
- constexpr int max_save = 10;
- int saved = 0;
- for (Chunk &chunk : loaded) {
- if (chunk.ShouldUpdateSave()) {
- save.Write(chunk);
- ++saved;
- if (saved >= max_save) {
- break;
- }
+ for (int i = 0; i < Block::FACE_COUNT; ++i) {
+ Block::Face face = Block::Face(i);
+ Chunk::Pos neighbor_pos(pos + Block::FaceNormal(face));
+ Chunk *neighbor = Get(neighbor_pos);
+ if (neighbor) {
+ chunk->SetNeighbor(face, *neighbor);
}
}
+ return chunk;
}
-void ChunkLoader::LoadN(std::size_t n) {
- std::size_t end = std::min(n, ToLoad());
- for (std::size_t i = 0; i < end; ++i) {
- LoadOne();
+bool ChunkStore::HasMissing() const noexcept {
+ for (const ChunkIndex &index : indices) {
+ if (index.MissingChunks() > 0) {
+ return true;
+ }
}
+ return false;
}
-bool ChunkLoader::LoadOne() {
- if (to_load.empty()) return false;
-
- // take position of next chunk in queue
- Chunk::Pos pos(to_load.front());
- to_load.pop_front();
-
- // look if the same chunk was already generated and still lingering
- for (auto iter(to_free.begin()), end(to_free.end()); iter != end; ++iter) {
- if (iter->Position() == pos) {
- loaded.splice(loaded.end(), to_free, iter);
- Insert(loaded.back());
- return false;
- }
+int ChunkStore::EstimateMissing() const noexcept {
+ int missing = 0;
+ for (const ChunkIndex &index : indices) {
+ missing += index.MissingChunks();
}
+ return missing;
+}
- // if the free list is empty, allocate a new chunk
- // otherwise clear an unused one
- if (to_free.empty()) {
- loaded.emplace_back(reg);
- } else {
- to_free.front().ClearNeighbors();
- loaded.splice(loaded.end(), to_free, to_free.begin());
+Chunk::Pos ChunkStore::NextMissing() noexcept {
+ for (ChunkIndex &index : indices) {
+ if (index.MissingChunks()) {
+ return index.NextMissing();
+ }
}
+ return Chunk::Pos(0, 0, 0);
+}
- bool generated = false;
- Chunk &chunk = loaded.back();
- chunk.Position(pos);
- if (save.Exists(pos)) {
- save.Read(chunk);
- } else {
- gen(chunk);
- generated = true;
+void ChunkStore::Clean() {
+ for (auto i = loaded.begin(), end = loaded.end(); i != end;) {
+ if (i->Referenced()) {
+ ++i;
+ } else {
+ auto chunk = i;
+ ++i;
+ free.splice(free.end(), loaded, chunk);
+ chunk->Unlink();
+ chunk->InvalidateModel();
+ }
}
- Insert(chunk);
- return generated;
}
}
+++ /dev/null
-#include "ChunkRenderer.hpp"
-
-#include "World.hpp"
-#include "../app/Assets.hpp"
-#include "../graphics/BlockLighting.hpp"
-#include "../graphics/Viewport.hpp"
-
-
-namespace blank {
-
-ChunkRenderer::ChunkRenderer(World &world, int rd)
-: world(world)
-, block_tex()
-, render_dist(rd)
-, side_length(2 * rd + 1)
-, total_length(side_length * side_length * side_length)
-, total_indexed(0)
-, stride(1, side_length, side_length * side_length)
-, models(total_length)
-, chunks(total_length)
-, base(0, 0, 0)
-, fog_density(0.0f) {
-
-}
-
-
-void ChunkRenderer::LoadTextures(const AssetLoader &loader, const TextureIndex &tex_index) {
- block_tex.Bind();
- loader.LoadTextures(tex_index, block_tex);
- block_tex.FilterNearest();
-}
-
-
-bool ChunkRenderer::InRange(const Chunk::Pos &pos) const noexcept {
- return manhattan_radius(pos - base) <= render_dist;
-}
-
-int ChunkRenderer::IndexOf(const Chunk::Pos &pos) const noexcept {
- Chunk::Pos mod_pos(
- GetCol(pos.x),
- GetCol(pos.y),
- GetCol(pos.z)
- );
- return mod_pos.x * stride.x
- + mod_pos.y * stride.y
- + mod_pos.z * stride.z;
-}
-
-
-void ChunkRenderer::Rebase(const Chunk::Pos &new_base) {
- if (new_base == base) return;
-
- Chunk::Pos diff(new_base - base);
-
- if (manhattan_radius(diff) > render_dist) {
- // that's more than half, so probably not worth shifting
- base = new_base;
- Rescan();
- return;
- }
-
- while (diff.x > 0) {
- Shift(Block::FACE_RIGHT);
- --diff.x;
- }
- while (diff.x < 0) {
- Shift(Block::FACE_LEFT);
- ++diff.x;
- }
- while (diff.y > 0) {
- Shift(Block::FACE_UP);
- --diff.y;
- }
- while (diff.y < 0) {
- Shift(Block::FACE_DOWN);
- ++diff.y;
- }
- while (diff.z > 0) {
- Shift(Block::FACE_FRONT);
- --diff.z;
- }
- while (diff.z < 0) {
- Shift(Block::FACE_BACK);
- ++diff.z;
- }
-}
-
-int ChunkRenderer::GetCol(int c) const noexcept {
- c %= side_length;
- if (c < 0) c += side_length;
- return c;
-}
-
-void ChunkRenderer::Shift(Block::Face f) {
- int a_axis = Block::Axis(f);
- int b_axis = (a_axis + 1) % 3;
- int c_axis = (a_axis + 2) % 3;
- int dir = Block::Direction(f);
- base[a_axis] += dir;
- int a = GetCol(base[a_axis] + (render_dist * dir));
- int a_stride = a * stride[a_axis];
- for (int b = 0; b < side_length; ++b) {
- int b_stride = b * stride[b_axis];
- for (int c = 0; c < side_length; ++c) {
- int bc_stride = b_stride + c * stride[c_axis];
- int index = a_stride + bc_stride;
- if (chunks[index]) {
- chunks[index] = nullptr;
- --total_indexed;
- }
- int neighbor = ((a - dir + side_length) % side_length) * stride[a_axis] + bc_stride;
- if (chunks[neighbor] && chunks[neighbor]->HasNeighbor(f)) {
- chunks[index] = &chunks[neighbor]->GetNeighbor(f);
- chunks[index]->InvalidateModel();
- ++total_indexed;
- }
- }
- }
-}
-
-
-void ChunkRenderer::Rescan() {
- chunks.assign(total_length, nullptr);
- total_indexed = 0;
- Scan();
-}
-
-void ChunkRenderer::Scan() {
- for (Chunk &chunk : world.Loader().Loaded()) {
- if (!InRange(chunk.Position())) continue;
- int index = IndexOf(chunk.Position());
- if (!chunks[index]) {
- chunks[index] = &chunk;
- chunk.InvalidateModel();
- ++total_indexed;
- }
- }
-}
-
-void ChunkRenderer::Update(int dt) {
- if (MissingChunks()) {
- Scan();
- }
-
- // maximum of 1000 per second too high?
- for (int i = 0, updates = 0; i < total_length && updates < dt; ++i) {
- if (chunks[i] && chunks[i]->ShouldUpdateModel()) {
- chunks[i]->Update(models[i]);
- ++updates;
- }
- }
-}
-
-
-void ChunkRenderer::Render(Viewport &viewport) {
- BlockLighting &chunk_prog = viewport.ChunkProgram();
- chunk_prog.SetTexture(block_tex);
- chunk_prog.SetFogDensity(fog_density);
-
- for (int i = 0; i < total_length; ++i) {
- if (!chunks[i]) continue;
- glm::mat4 m(chunks[i]->Transform(base));
- glm::mat4 mvp(chunk_prog.GetVP() * m);
- if (!CullTest(Chunk::Bounds(), mvp)) {
- if (chunks[i]->ShouldUpdateModel()) {
- chunks[i]->Update(models[i]);
- }
- chunk_prog.SetM(m);
- models[i].Draw();
- }
- }
-}
-
-}
for (int i = 0; i < Block::FACE_COUNT; ++i) {
Block::Face face = Block::Face(i);
neighbor->Position(Block::FaceNormal(face));
- chunk->SetNeighbor(*neighbor);
+ chunk->SetNeighbor(face, *neighbor);
CPPUNIT_ASSERT_MESSAGE(
"chunk did not link right neighbor",
chunk->HasNeighbor(face)
"chunk did not link correct neighbor",
&*chunk, &neighbor->GetNeighbor(Block::Opposite(face))
);
- chunk->ClearNeighbors();
- }
-
- neighbor->Position({1, 1, 1});
- chunk->SetNeighbor(*neighbor);
- for (int i = 0; i < Block::FACE_COUNT; ++i) {
- CPPUNIT_ASSERT_MESSAGE(
- "chunk linked with non-neighbor",
- !chunk->HasNeighbor(Block::Face(i))
- );
+ chunk->Unlink();
}
}