#include "Chaser.hpp"
#include "RandomWalk.hpp"
+#include "../model/shapes.hpp"
#include "../world/BlockLookup.hpp"
#include "../world/BlockType.hpp"
-#include "../world/BlockTypeRegistry.hpp"
#include "../world/Entity.hpp"
#include "../world/World.hpp"
, max_entities(16)
, chunk_range(4) {
EntityModel::Buffer buf;
- for (size_t i = 0; i < 14; ++i) {
- world.BlockTypes()[i + 1].FillEntityModel(buf);
- models[i].Update(buf);
+ {
+ CuboidShape shape({{ -0.25f, -0.5f, -0.25f }, { 0.25f, 0.5f, 0.25f }});
+ shape.Vertices(buf, 1.0f);
+ buf.colors.resize(shape.VertexCount(), { 1.0f, 1.0f, 0.0f });
+ models[0].Update(buf);
+ }
+ {
+ CuboidShape shape({{ -0.5f, -0.25f, -0.5f }, { 0.5f, 0.25f, 0.5f }});
+ buf.Clear();
+ shape.Vertices(buf, 2.0f);
+ buf.colors.resize(shape.VertexCount(), { 0.0f, 1.0f, 1.0f });
+ models[1].Update(buf);
+ }
+ {
+ StairShape shape({{ -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f }}, { 0.4f, 0.4f });
buf.Clear();
+ shape.Vertices(buf, 3.0f);
+ buf.colors.resize(shape.VertexCount(), { 1.0f, 0.0f, 1.0f });
+ models[2].Update(buf);
}
timer.Start();
e.Position(chunk, pos);
e.Bounds({ { -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f } });
e.WorldCollidable(true);
- e.GetModel().SetNodeModel(&models[rand() % 14]);
+ e.GetModel().SetNodeModel(&models[rand() % 3]);
e.AngularVelocity(rot);
Controller *ctrl;
if (rand() % 2) {
World &world;
std::vector<Controller *> controllers;
- EntityModel models[14];
+ EntityModel models[3];
IntervalTimer timer;
float despawn_range;
#include "Environment.hpp"
#include "../world/ChunkLoader.hpp"
+#include "../world/ChunkRenderer.hpp"
namespace blank {
-PreloadState::PreloadState(Environment &env, ChunkLoader &loader)
+PreloadState::PreloadState(Environment &env, ChunkLoader &loader, ChunkRenderer &render)
: env(env)
, loader(loader)
+, render(render)
, progress(env.assets.large_ui_font)
, total(loader.ToLoad())
, per_update(64) {
void PreloadState::Update(int dt) {
loader.LoadN(per_update);
if (loader.ToLoad() == 0) {
- for (auto &chunk : loader.Loaded()) {
- chunk.CheckUpdate();
- }
env.state.Pop();
+ render.Update(render.MissingChunks());
} else {
progress.Update(total - loader.ToLoad(), total);
}
namespace blank {
class ChunkLoader;
+class ChunkRenderer;
class Environment;
class PreloadState
: public State {
public:
- PreloadState(Environment &, ChunkLoader &);
+ PreloadState(Environment &, ChunkLoader &, ChunkRenderer &);
void Handle(const SDL_Event &) override;
void Update(int dt) override;
private:
Environment &env;
ChunkLoader &loader;
+ ChunkRenderer &render;
Progress progress;
std::size_t total;
std::size_t per_update;
#include "WorldState.hpp"
#include "Environment.hpp"
+#include "TextureIndex.hpp"
#include <SDL.h>
const WorldSave &save
)
: env(env)
-, world(env.assets, wc, save)
+, block_types()
+, world(block_types, wc, save)
+, chunk_renderer(world, wc.load.load_dist)
, spawner(world)
, interface(ic, env, world)
-, preload(env, world.Loader())
+, preload(env, world.Loader(), chunk_renderer)
, unload(env, world.Loader()) {
-
+ TextureIndex tex_index;
+ env.assets.LoadBlockTypes("default", block_types, tex_index);
+ chunk_renderer.LoadTextures(env.assets, tex_index);
+ chunk_renderer.FogDensity(wc.fog_density);
+ // TODO: better solution for initializing HUD
+ interface.SelectNext();
}
interface.Update(dt);
spawner.Update(dt);
world.Update(dt);
+ chunk_renderer.Rebase(world.Player().ChunkCoords());
+ chunk_renderer.Update(dt);
- glm::mat4 trans = world.Player().Transform(Chunk::Pos(0, 0, 0));
+ glm::mat4 trans = world.Player().Transform(world.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(world.Player().Position());
}
void WorldState::Render(Viewport &viewport) {
+ viewport.WorldPosition(world.Player().Transform(world.Player().ChunkCoords()));
+ chunk_renderer.Render(viewport);
world.Render(viewport);
interface.Render(viewport);
}
#include "UnloadState.hpp"
#include "../ai/Spawner.hpp"
#include "../ui/Interface.hpp"
+#include "../world/BlockTypeRegistry.hpp"
+#include "../world/ChunkRenderer.hpp"
#include "../world/World.hpp"
private:
Environment &env;
+ BlockTypeRegistry block_types;
World world;
+ ChunkRenderer chunk_renderer;
Spawner spawner;
Interface interface;
#include "filesystem.hpp"
#include <cctype>
+#include <cstring>
#include <fstream>
#include <iostream>
#include <limits>
HUD(const HUD &) = delete;
HUD &operator =(const HUD &) = delete;
+ void DisplayNone();
void Display(const Block &);
void Render(Viewport &) noexcept;
}
+void HUD::DisplayNone() {
+ block_visible = false;
+}
+
void HUD::Display(const Block &b) {
const BlockType &type = types.Get(b.type);
, place_timer(256)
, remove_timer(256)
, remove(0)
-, selection(1)
+, selection(0)
, place_sound(env.assets.LoadSound("thump"))
, remove_sound(env.assets.LoadSound("plop"))
, fwd(0)
messages.Position(glm::vec3(25.0f, -25.0f, 0.0f), Gravity::SOUTH_WEST);
messages.Foreground(glm::vec4(1.0f));
messages.Background(glm::vec4(0.5f));
- hud.Display(selection);
+ hud.DisplayNone();
}
}
}
+ /// returns 1 for pro-axis, -1 for retro-axis, 0 for invalid faces
+ static int Direction(Face f) noexcept {
+ switch (f) {
+ case FACE_RIGHT:
+ case FACE_UP:
+ case FACE_FRONT:
+ return 1;
+ case FACE_LEFT:
+ case FACE_DOWN:
+ case FACE_BACK:
+ return -1;
+ default:
+ return 0;
+ }
+ }
+
static glm::ivec3 FaceNormal(Face face) noexcept {
return face2normal[face];
}
size_t Size() const noexcept { return types.size(); }
BlockType &operator [](Block::Type id) { return types[id]; }
+ const BlockType &operator [](Block::Type id) const { return types[id]; }
+
+ BlockType &Get(Block::Type id) { return types[id]; }
const BlockType &Get(Block::Type id) const { return types[id]; }
private:
#include "Block.hpp"
#include "BlockTypeRegistry.hpp"
-#include "../model/BlockModel.hpp"
#include "../model/geometry.hpp"
#include <vector>
bool ShouldUpdateModel() const noexcept { return dirty_model; }
bool ShouldUpdateSave() const noexcept { return dirty_save; }
- void CheckUpdate() noexcept;
- void Draw() noexcept;
-
-private:
- void Update() noexcept;
+ void Update(BlockModel &) noexcept;
private:
const BlockTypeRegistry *types;
Chunk *neighbor[Block::FACE_COUNT];
Block blocks[size];
unsigned char light[size];
- BlockModel model;
Pos position;
bool dirty_model;
bool dirty_save;
--- /dev/null
+#ifndef BLANK_WORLD_CHUNKRENDERER_HPP_
+#define BLANK_WORLD_CHUNKRENDERER_HPP_
+
+#include "Block.hpp"
+#include "Chunk.hpp"
+#include "../graphics/ArrayTexture.hpp"
+#include "../model/BlockModel.hpp"
+
+#include <vector>
+
+
+namespace blank {
+
+class Assets;
+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);
+
+ void LoadTextures(const Assets &, 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 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;
+ std::vector<BlockModel> models;
+ std::vector<Chunk *> chunks;
+
+ Chunk::Pos base;
+
+ float fog_density;
+
+};
+
+}
+
+#endif
#include "EntityCollision.hpp"
#include "WorldCollision.hpp"
#include "../app/Assets.hpp"
-#include "../app/TextureIndex.hpp"
#include "../graphics/Format.hpp"
#include "../graphics/Viewport.hpp"
namespace blank {
-World::World(const Assets &assets, const Config &config, const WorldSave &save)
-: block_type()
-, block_tex()
+World::World(const BlockTypeRegistry &types, const Config &config, const WorldSave &save)
+: block_type(types)
, generate(config.gen)
-, chunks(config.load, block_type, generate, save)
+, chunks(config.load, types, generate, save)
, player()
, entities()
, light_direction(config.light_direction)
, fog_density(config.fog_density) {
- TextureIndex tex_index;
- assets.LoadBlockTypes("default", block_type, tex_index);
-
- block_tex.Bind();
- assets.LoadTextures(tex_index, block_tex);
- block_tex.FilterNearest();
-
generate.Space(0);
generate.Light(13);
generate.Solids({ 1, 4, 7, 10 });
void World::Render(Viewport &viewport) {
- viewport.WorldPosition(player->Transform(player->ChunkCoords()));
-
- BlockLighting &chunk_prog = viewport.ChunkProgram();
- chunk_prog.SetTexture(block_tex);
- chunk_prog.SetFogDensity(fog_density);
-
- for (Chunk &chunk : chunks.Loaded()) {
- glm::mat4 m(chunk.Transform(player->ChunkCoords()));
- chunk_prog.SetM(m);
- glm::mat4 mvp(chunk_prog.GetVP() * m);
- if (!CullTest(Chunk::Bounds(), mvp)) {
- chunk.Draw();
- }
- }
-
DirectionalLighting &entity_prog = viewport.EntityProgram();
entity_prog.SetLightDirection(light_direction);
entity_prog.SetFogDensity(fog_density);
#ifndef BLANK_WORLD_WORLD_HPP_
#define BLANK_WORLD_WORLD_HPP_
-#include "BlockTypeRegistry.hpp"
#include "ChunkLoader.hpp"
#include "Entity.hpp"
#include "Generator.hpp"
-#include "../graphics/ArrayTexture.hpp"
#include <list>
#include <vector>
namespace blank {
-class Assets;
+class BlockTypeRegistry;
class EntityCollision;
class Viewport;
class WorldCollision;
ChunkLoader::Config load = ChunkLoader::Config();
};
- World(const Assets &, const Config &, const WorldSave &);
+ World(const BlockTypeRegistry &, const Config &, const WorldSave &);
/// check if this ray hits a block
/// depth in the collision is the distance between the ray's
bool Intersection(const Entity &e, std::vector<WorldCollision> &);
void Resolve(Entity &e, std::vector<WorldCollision> &);
- BlockTypeRegistry &BlockTypes() noexcept { return block_type; }
+ const BlockTypeRegistry &BlockTypes() noexcept { return block_type; }
ChunkLoader &Loader() noexcept { return chunks; }
Entity &Player() { return *player; }
void Render(Viewport &);
private:
- BlockTypeRegistry block_type;
-
- ArrayTexture block_tex;
+ const BlockTypeRegistry &block_type;
Generator generate;
ChunkLoader chunks;
, neighbor{0}
, blocks{}
, light{0}
-, model()
, position(0, 0, 0)
, dirty_model(false)
, dirty_save(false) {
Chunk::Chunk(Chunk &&other) noexcept
: types(other.types)
-, model(std::move(other.model))
, position(other.position)
, dirty_model(other.dirty_model)
, dirty_save(other.dirty_save) {
std::copy(other.neighbor, other.neighbor + sizeof(neighbor), neighbor);
std::copy(other.blocks, other.blocks + sizeof(blocks), blocks);
std::copy(other.light, other.light + sizeof(light), light);
- model = std::move(other.model);
position = other.position;
dirty_model = other.dirty_save;
dirty_save = other.dirty_save;
}
-void Chunk::Draw() noexcept {
- if (ShouldUpdateModel()) {
- Update();
- }
- model.Draw();
-}
-
-
bool Chunk::Intersection(
const Ray &ray,
const glm::mat4 &M,
}
-void Chunk::CheckUpdate() noexcept {
- if (ShouldUpdateModel()) {
- Update();
- }
-}
-
-void Chunk::Update() noexcept {
+void Chunk::Update(BlockModel &model) noexcept {
int vtx_count = 0, idx_count = 0;
for (const auto &block : blocks) {
const Shape *shape = Type(block).shape;
--- /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 Assets &assets, const TextureIndex &tex_index) {
+ block_tex.Bind();
+ assets.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();
+ }
+ }
+}
+
+}
-#include "app/init.hpp"
-
#include <cppunit/extensions/TestFactoryRegistry.h>
#include <cppunit/ui/text/TestRunner.h>
int main(int, char **) {
- blank::Init init;
-
TestRunner runner;
TestFactoryRegistry ®istry = TestFactoryRegistry::getRegistry();
runner.addTest(registry.makeTest());