Chunks render moved to ChunksLoader
This commit is contained in:
parent
15bed4169e
commit
f569378b88
@ -1,12 +1,12 @@
|
|||||||
#include "VoxelRenderer.h"
|
#include "VoxelRenderer.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
#include "Mesh.h"
|
#include "Mesh.h"
|
||||||
#include "../voxels/Chunk.h"
|
#include "../voxels/Chunk.h"
|
||||||
#include "../voxels/voxel.h"
|
#include "../voxels/voxel.h"
|
||||||
#include "../voxels/Block.h"
|
#include "../voxels/Block.h"
|
||||||
#include "../lighting/Lightmap.h"
|
#include "../lighting/Lightmap.h"
|
||||||
|
|
||||||
#define VERTEX_SIZE (3 + 2 + 4)
|
|
||||||
|
|
||||||
#define CDIV(X,A) (((X) < 0) ? ((X) / (A) - 1) : ((X) / (A)))
|
#define CDIV(X,A) (((X) < 0) ? ((X) / (A) - 1) : ((X) / (A)))
|
||||||
#define LOCAL_NEG(X, SIZE) (((X) < 0) ? ((SIZE)+(X)) : (X))
|
#define LOCAL_NEG(X, SIZE) (((X) < 0) ? ((SIZE)+(X)) : (X))
|
||||||
#define LOCAL(X, SIZE) ((X) >= (SIZE) ? ((X) - (SIZE)) : LOCAL_NEG(X, SIZE))
|
#define LOCAL(X, SIZE) ((X) >= (SIZE) ? ((X) - (SIZE)) : LOCAL_NEG(X, SIZE))
|
||||||
@ -17,16 +17,16 @@
|
|||||||
#define VOXEL(X,Y,Z) (GET_CHUNK(X,Y,Z)->voxels[(LOCAL(Y, CHUNK_H) * CHUNK_D + LOCAL(Z, CHUNK_D)) * CHUNK_W + LOCAL(X, CHUNK_W)])
|
#define VOXEL(X,Y,Z) (GET_CHUNK(X,Y,Z)->voxels[(LOCAL(Y, CHUNK_H) * CHUNK_D + LOCAL(Z, CHUNK_D)) * CHUNK_W + LOCAL(X, CHUNK_W)])
|
||||||
#define IS_BLOCKED(X,Y,Z,GROUP) ((!IS_CHUNK(X, Y, Z)) || Block::blocks[VOXEL(X, Y, Z).id]->drawGroup == (GROUP))
|
#define IS_BLOCKED(X,Y,Z,GROUP) ((!IS_CHUNK(X, Y, Z)) || Block::blocks[VOXEL(X, Y, Z).id]->drawGroup == (GROUP))
|
||||||
|
|
||||||
#define VERTEX(INDEX, X,Y,Z, U,V, R,G,B,S) buffer[INDEX+0] = (X);\
|
#define VERTEX(INDEX, X,Y,Z, U,V, R,G,B,S) buffer.push_back(X);\
|
||||||
buffer[INDEX+1] = (Y);\
|
buffer.push_back(Y);\
|
||||||
buffer[INDEX+2] = (Z);\
|
buffer.push_back(Z);\
|
||||||
buffer[INDEX+3] = (U);\
|
buffer.push_back(U);\
|
||||||
buffer[INDEX+4] = (V);\
|
buffer.push_back(V);\
|
||||||
buffer[INDEX+5] = (R);\
|
buffer.push_back(R);\
|
||||||
buffer[INDEX+6] = (G);\
|
buffer.push_back(G);\
|
||||||
buffer[INDEX+7] = (B);\
|
buffer.push_back(B);\
|
||||||
buffer[INDEX+8] = (S);\
|
buffer.push_back(S);\
|
||||||
INDEX += VERTEX_SIZE;
|
INDEX += CHUNK_VERTEX_SIZE;
|
||||||
|
|
||||||
|
|
||||||
#define SETUP_UV(INDEX) float u1 = ((INDEX) % 16) * uvsize;\
|
#define SETUP_UV(INDEX) float u1 = ((INDEX) % 16) * uvsize;\
|
||||||
@ -36,15 +36,13 @@
|
|||||||
|
|
||||||
int chunk_attrs[] = {3,2,4, 0};
|
int chunk_attrs[] = {3,2,4, 0};
|
||||||
|
|
||||||
VoxelRenderer::VoxelRenderer(size_t capacity) : capacity(capacity) {
|
VoxelRenderer::VoxelRenderer() {
|
||||||
buffer = new float[capacity * VERTEX_SIZE * 6];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
VoxelRenderer::~VoxelRenderer(){
|
VoxelRenderer::~VoxelRenderer(){
|
||||||
delete[] buffer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void _renderBlock(float* buffer, int x, int y, int z, const Chunk** chunks, voxel vox, size_t& index){
|
inline void _renderBlock(std::vector<float>& buffer, int x, int y, int z, const Chunk** chunks, voxel vox, size_t& index){
|
||||||
unsigned int id = vox.id;
|
unsigned int id = vox.id;
|
||||||
|
|
||||||
if (!id){
|
if (!id){
|
||||||
@ -289,7 +287,8 @@ inline void _renderBlock(float* buffer, int x, int y, int z, const Chunk** chunk
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Mesh* VoxelRenderer::render(Chunk* chunk, const Chunk** chunks){
|
const float* VoxelRenderer::render(Chunk* chunk, const Chunk** chunks, size_t& size){
|
||||||
|
buffer.clear();
|
||||||
size_t index = 0;
|
size_t index = 0;
|
||||||
for (int y = 0; y < CHUNK_H; y++){
|
for (int y = 0; y < CHUNK_H; y++){
|
||||||
for (int z = 0; z < CHUNK_D; z++){
|
for (int z = 0; z < CHUNK_D; z++){
|
||||||
@ -323,5 +322,6 @@ Mesh* VoxelRenderer::render(Chunk* chunk, const Chunk** chunks){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new Mesh(buffer, index / VERTEX_SIZE, chunk_attrs);
|
size = buffer.size();
|
||||||
|
return &buffer[0];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,18 +2,20 @@
|
|||||||
#define GRAPHICS_VOXELRENDERER_H_
|
#define GRAPHICS_VOXELRENDERER_H_
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
class Mesh;
|
class Mesh;
|
||||||
class Chunk;
|
class Chunk;
|
||||||
|
|
||||||
|
#define CHUNK_VERTEX_SIZE (3 + 2 + 4)
|
||||||
|
|
||||||
class VoxelRenderer {
|
class VoxelRenderer {
|
||||||
float* buffer;
|
|
||||||
size_t capacity;
|
|
||||||
public:
|
public:
|
||||||
VoxelRenderer(size_t capacity);
|
std::vector<float> buffer;
|
||||||
|
VoxelRenderer();
|
||||||
~VoxelRenderer();
|
~VoxelRenderer();
|
||||||
|
|
||||||
Mesh* render(Chunk* chunk, const Chunk** chunks);
|
const float* render(Chunk* chunk, const Chunk** chunks, size_t& size);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* GRAPHICS_VOXELRENDERER_H_ */
|
#endif /* GRAPHICS_VOXELRENDERER_H_ */
|
||||||
|
|||||||
@ -25,7 +25,7 @@
|
|||||||
#define CAMERA_SHAKING_OFFSET_Y 0.031f
|
#define CAMERA_SHAKING_OFFSET_Y 0.031f
|
||||||
#define CAMERA_SHAKING_SPEED 1.6f
|
#define CAMERA_SHAKING_SPEED 1.6f
|
||||||
#define CAMERA_SHAKING_DELTA_K 10.0f
|
#define CAMERA_SHAKING_DELTA_K 10.0f
|
||||||
#define FLIGHT_SPEED_MUL 5.0f
|
#define FLIGHT_SPEED_MUL 8.0f
|
||||||
#define JUMP_FORCE 7.0f
|
#define JUMP_FORCE 7.0f
|
||||||
|
|
||||||
void update_controls(PhysicsSolver* physics,
|
void update_controls(PhysicsSolver* physics,
|
||||||
|
|||||||
@ -81,10 +81,12 @@ void update_level(World* world, Level* level, vec3 position, float delta, long f
|
|||||||
update_controls(level->physics, level->chunks, level->player, delta);
|
update_controls(level->physics, level->chunks, level->player, delta);
|
||||||
update_interaction(level, lineBatch);
|
update_interaction(level, lineBatch);
|
||||||
|
|
||||||
level->chunks->setCenter(world->wfile, position.x,0,position.z);
|
level->chunks->setCenter(world->wfile, position.x, 0, position.z);
|
||||||
level->chunksController->_buildMeshes(renderer, frame);
|
|
||||||
|
|
||||||
int freeLoaders = level->chunksController->countFreeLoaders();
|
int freeLoaders = level->chunksController->countFreeLoaders();
|
||||||
|
for (int i = 0; i < freeLoaders; i++)
|
||||||
|
level->chunksController->_buildMeshes(renderer, frame);
|
||||||
|
|
||||||
|
freeLoaders = level->chunksController->countFreeLoaders();
|
||||||
for (int i = 0; i < freeLoaders; i++)
|
for (int i = 0; i < freeLoaders; i++)
|
||||||
level->chunksController->loadVisible(world->wfile);
|
level->chunksController->loadVisible(world->wfile);
|
||||||
}
|
}
|
||||||
@ -140,7 +142,7 @@ int main() {
|
|||||||
bool occlusion = false;
|
bool occlusion = false;
|
||||||
bool devdata = false;
|
bool devdata = false;
|
||||||
|
|
||||||
glfwSwapInterval(0);
|
Window::swapInterval(0);
|
||||||
|
|
||||||
std::cout << "-- initializing finished" << std::endl;
|
std::cout << "-- initializing finished" << std::endl;
|
||||||
while (!Window::isShouldClose()){
|
while (!Window::isShouldClose()){
|
||||||
|
|||||||
@ -8,6 +8,7 @@ Chunk::Chunk(int xpos, int ypos, int zpos) : x(xpos), y(ypos), z(zpos){
|
|||||||
for (unsigned int i = 0; i < CHUNK_VOL; i++)
|
for (unsigned int i = 0; i < CHUNK_VOL; i++)
|
||||||
voxels[i].id = 1;
|
voxels[i].id = 1;
|
||||||
lightmap = new Lightmap();
|
lightmap = new Lightmap();
|
||||||
|
renderData.vertices = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
Chunk::~Chunk(){
|
Chunk::~Chunk(){
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
#ifndef VOXELS_CHUNK_H_
|
#ifndef VOXELS_CHUNK_H_
|
||||||
#define VOXELS_CHUNK_H_
|
#define VOXELS_CHUNK_H_
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
#define CHUNK_W 16
|
#define CHUNK_W 16
|
||||||
#define CHUNK_H 256
|
#define CHUNK_H 256
|
||||||
#define CHUNK_D 16
|
#define CHUNK_D 16
|
||||||
@ -9,6 +11,11 @@
|
|||||||
class voxel;
|
class voxel;
|
||||||
class Lightmap;
|
class Lightmap;
|
||||||
|
|
||||||
|
struct RenderData {
|
||||||
|
float* vertices;
|
||||||
|
size_t size;
|
||||||
|
};
|
||||||
|
|
||||||
class Chunk {
|
class Chunk {
|
||||||
public:
|
public:
|
||||||
int x,y,z;
|
int x,y,z;
|
||||||
@ -19,6 +26,8 @@ public:
|
|||||||
bool loaded = false;
|
bool loaded = false;
|
||||||
int surrounding = 0;
|
int surrounding = 0;
|
||||||
int references = 1;
|
int references = 1;
|
||||||
|
RenderData renderData;
|
||||||
|
|
||||||
Chunk(int x, int y, int z);
|
Chunk(int x, int y, int z);
|
||||||
~Chunk();
|
~Chunk();
|
||||||
|
|
||||||
|
|||||||
@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
|
|
||||||
ChunksController::ChunksController(Chunks* chunks, Lighting* lighting) : chunks(chunks), lighting(lighting){
|
ChunksController::ChunksController(Chunks* chunks, Lighting* lighting) : chunks(chunks), lighting(lighting){
|
||||||
loadersCount = std::thread::hardware_concurrency() - 1;
|
loadersCount = std::thread::hardware_concurrency() * 2 - 1;
|
||||||
if (loadersCount <= 0)
|
if (loadersCount <= 0)
|
||||||
loadersCount = 1;
|
loadersCount = 1;
|
||||||
loaders = new ChunksLoader*[loadersCount];
|
loaders = new ChunksLoader*[loadersCount];
|
||||||
@ -130,7 +130,7 @@ bool ChunksController::loadVisible(WorldFiles* worldFiles){
|
|||||||
oz += 1;
|
oz += 1;
|
||||||
closes[(oy * 3 + oz) * 3 + ox] = other;
|
closes[(oy * 3 + oz) * 3 + ox] = other;
|
||||||
}
|
}
|
||||||
freeLoader->perform(chunk, (Chunk**)closes);
|
freeLoader->load(chunk, (Chunk**)closes);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,6 +139,35 @@ bool ChunksController::_buildMeshes(VoxelRenderer* renderer, int tick) {
|
|||||||
const int h = chunks->h;
|
const int h = chunks->h;
|
||||||
const int d = chunks->d;
|
const int d = chunks->d;
|
||||||
|
|
||||||
|
for (int y = 0; y < h; y++){
|
||||||
|
for (int z = 1; z < d-1; z++){
|
||||||
|
for (int x = 1; x < w-1; x++){
|
||||||
|
int index = (y * d + z) * w + x;
|
||||||
|
Chunk* chunk = chunks->chunks[index];
|
||||||
|
if (chunk == nullptr)
|
||||||
|
continue;
|
||||||
|
if (chunk->renderData.vertices > (void*)1){
|
||||||
|
const int chunk_attrs[] = {3,2,4, 0};
|
||||||
|
Mesh* mesh = new Mesh(chunk->renderData.vertices, chunk->renderData.size / CHUNK_VERTEX_SIZE, chunk_attrs);
|
||||||
|
chunks->meshes[index] = mesh;
|
||||||
|
delete[] chunk->renderData.vertices;
|
||||||
|
chunk->renderData.vertices = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ChunksLoader* freeLoader = nullptr;
|
||||||
|
for (int i = 0; i < loadersCount; i++){
|
||||||
|
ChunksLoader* loader = loaders[i];
|
||||||
|
if (loader->isBusy()){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
freeLoader = loader;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (freeLoader == nullptr)
|
||||||
|
return false;
|
||||||
|
|
||||||
int nearX = 0;
|
int nearX = 0;
|
||||||
int nearY = 0;
|
int nearY = 0;
|
||||||
int nearZ = 0;
|
int nearZ = 0;
|
||||||
@ -208,9 +237,15 @@ bool ChunksController::_buildMeshes(VoxelRenderer* renderer, int tick) {
|
|||||||
oz += 1;
|
oz += 1;
|
||||||
closes[(oy * 3 + oz) * 3 + ox] = other;
|
closes[(oy * 3 + oz) * 3 + ox] = other;
|
||||||
}
|
}
|
||||||
mesh = renderer->render(chunk, (const Chunk**)closes);
|
if (chunk->renderData.vertices == nullptr){
|
||||||
chunks->meshes[index] = mesh;
|
chunk->renderData.vertices = (float*)1;
|
||||||
return true;
|
freeLoader->render(chunk, (Chunk**)closes);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
//mesh = renderer->render(chunk, (const Chunk**)closes);
|
||||||
|
//chunks->meshes[index] = mesh;
|
||||||
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
#include "Chunks.h"
|
#include "Chunks.h"
|
||||||
#include "WorldGenerator.h"
|
#include "WorldGenerator.h"
|
||||||
#include "../lighting/Lighting.h"
|
#include "../lighting/Lighting.h"
|
||||||
|
#include "../graphics/VoxelRenderer.h"
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
@ -13,7 +14,8 @@
|
|||||||
void ChunksLoader::_thread(){
|
void ChunksLoader::_thread(){
|
||||||
Chunks chunks(3,3,3, -1,-1,-1);
|
Chunks chunks(3,3,3, -1,-1,-1);
|
||||||
Lighting lighting(&chunks);
|
Lighting lighting(&chunks);
|
||||||
while (working){
|
VoxelRenderer renderer;
|
||||||
|
while (state != OFF){
|
||||||
if (current == nullptr){
|
if (current == nullptr){
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
continue;
|
continue;
|
||||||
@ -27,12 +29,24 @@ void ChunksLoader::_thread(){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!chunk->loaded){
|
if (state == LOAD){
|
||||||
WorldGenerator::generate(chunk->voxels, chunk->x, chunk->y, chunk->z);
|
chunks.putChunk(chunk);
|
||||||
|
if (!chunk->loaded){
|
||||||
|
WorldGenerator::generate(chunk->voxels, chunk->x, chunk->y, chunk->z);
|
||||||
|
}
|
||||||
|
|
||||||
|
lighting.onChunkLoaded(chunk->x, chunk->y, chunk->z, true);
|
||||||
|
}
|
||||||
|
else if (state == RENDER){
|
||||||
|
size_t size;
|
||||||
|
renderer.render(chunk, (const Chunk**)(closes.load()), size);
|
||||||
|
float* vertices = new float[size];
|
||||||
|
for (size_t i = 0; i < size; i++)
|
||||||
|
vertices[i] = renderer.buffer[i];
|
||||||
|
chunk->renderData.vertices = vertices;
|
||||||
|
chunk->renderData.size = size;
|
||||||
}
|
}
|
||||||
|
|
||||||
chunks.putChunk(chunk);
|
|
||||||
lighting.onChunkLoaded(chunk->x, chunk->y, chunk->z, true);
|
|
||||||
chunks.clear(false);
|
chunks.clear(false);
|
||||||
for (int i = 0; i < CLOSES_C; i++){
|
for (int i = 0; i < CLOSES_C; i++){
|
||||||
Chunk* other = closes[i];
|
Chunk* other = closes[i];
|
||||||
@ -45,7 +59,7 @@ void ChunksLoader::_thread(){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChunksLoader::perform(Chunk* chunk, Chunk** closes_passed){
|
void ChunksLoader::perform(Chunk* chunk, Chunk** closes_passed, LoaderMode mode){
|
||||||
if (isBusy()){
|
if (isBusy()){
|
||||||
std::cerr << "performing while busy" << std::endl;
|
std::cerr << "performing while busy" << std::endl;
|
||||||
return;
|
return;
|
||||||
@ -64,4 +78,13 @@ void ChunksLoader::perform(Chunk* chunk, Chunk** closes_passed){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
current = chunk;
|
current = chunk;
|
||||||
|
state = mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChunksLoader::load(Chunk* chunk, Chunk** closes_passed){
|
||||||
|
perform(chunk, closes_passed, LOAD);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChunksLoader::render(Chunk* chunk, Chunk** closes_passed){
|
||||||
|
perform(chunk, closes_passed, RENDER);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,19 +12,25 @@
|
|||||||
|
|
||||||
class Chunk;
|
class Chunk;
|
||||||
|
|
||||||
|
enum LoaderMode {
|
||||||
|
OFF, IDLE, LOAD, RENDER,
|
||||||
|
};
|
||||||
|
|
||||||
class ChunksLoader final {
|
class ChunksLoader final {
|
||||||
private:
|
private:
|
||||||
std::thread loaderThread;
|
std::thread loaderThread;
|
||||||
void _thread();
|
void _thread();
|
||||||
std::atomic<Chunk*> current {nullptr};
|
std::atomic<Chunk*> current {nullptr};
|
||||||
std::atomic<Chunk**> closes {nullptr};
|
std::atomic<Chunk**> closes {nullptr};
|
||||||
std::atomic<bool> working {true};
|
std::atomic<LoaderMode> state {IDLE};
|
||||||
|
|
||||||
|
void perform(Chunk* chunk, Chunk** closes_passed, LoaderMode mode);
|
||||||
public:
|
public:
|
||||||
ChunksLoader() : loaderThread{} {
|
ChunksLoader() : loaderThread{} {
|
||||||
loaderThread = std::thread{&ChunksLoader::_thread, this};
|
loaderThread = std::thread{&ChunksLoader::_thread, this};
|
||||||
}
|
}
|
||||||
~ChunksLoader(){
|
~ChunksLoader(){
|
||||||
working = false;
|
state = OFF;
|
||||||
loaderThread.join();
|
loaderThread.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,10 +38,11 @@ public:
|
|||||||
return current != nullptr;
|
return current != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void perform(Chunk* chunk, Chunk** closes_passed);
|
void load(Chunk* chunk, Chunk** closes_passed);
|
||||||
|
void render(Chunk* chunk, Chunk** closes_passed);
|
||||||
|
|
||||||
void stop(){
|
void stop(){
|
||||||
working = false;
|
state = OFF;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -57,6 +57,10 @@ void Window::setShouldClose(bool flag){
|
|||||||
glfwSetWindowShouldClose(window, flag);
|
glfwSetWindowShouldClose(window, flag);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Window::swapInterval(int interval){
|
||||||
|
glfwSwapInterval(interval);
|
||||||
|
}
|
||||||
|
|
||||||
void Window::swapBuffers(){
|
void Window::swapBuffers(){
|
||||||
glfwSwapBuffers(window);
|
glfwSwapBuffers(window);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,6 +15,7 @@ public:
|
|||||||
static bool isShouldClose();
|
static bool isShouldClose();
|
||||||
static void setShouldClose(bool flag);
|
static void setShouldClose(bool flag);
|
||||||
static void swapBuffers();
|
static void swapBuffers();
|
||||||
|
static void swapInterval(int interval);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* WINDOW_WINDOW_H_ */
|
#endif /* WINDOW_WINDOW_H_ */
|
||||||
|
|||||||
@ -57,7 +57,7 @@ void init_renderer(){
|
|||||||
uicamera->perspective = false;
|
uicamera->perspective = false;
|
||||||
uicamera->flipped = true;
|
uicamera->flipped = true;
|
||||||
|
|
||||||
renderer = new VoxelRenderer(1024*1024);
|
renderer = new VoxelRenderer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user