-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathchunk.cpp
422 lines (366 loc) · 17.3 KB
/
chunk.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
#include "chunk.h"
#include "block.h"
#include "perlin.hpp"
#include <memory>
Chunk::Chunk(int xOff, int zOff, const siv::PerlinNoise &p) : xOffset(xOff), zOffset(zOff){
//set blocks
generateHeightMap(p);
setBlockTexture();
for(int x = 0; x < Chunks::size; x++){
for(int z = 0; z < Chunks::size; z++){
double noiseValue = getNoiseValue(p, x, z);
int iNoiseVal = noiseValue < 0.0 ? glm::ceil(noiseValue) : glm::floor(noiseValue);
int height = iNoiseVal + Chunks::size;
for(int y = 0; y < Chunks::height; y++){
if (voxelGrid[x+1][y][z+1] == 1) {
//our block position
glm::ivec3 bPos((xOffset * Chunks::size) + x, y, (zOffset * Chunks::size) + z);
blockType empty = Empty;
//stores a vector containing integers corresponding to face to draw
std::vector<int>faces = checkNeighbors(x + 1, y, z + 1, empty);
if (faces.size() > 0) {
blocks.emplace(bPos, std::make_unique<Block>(bPos.x, bPos.y, bPos.z));
blocks[bPos]->setType(y == height - 1 ? Grass : y > height - 4 ? Dirt : Stone);
setFaces(bPos, faces);
}
}
}
}
}
initChunk();
updateVertices();
}
//This creates a heightmap we use to check adjacent blocks
//created padding around map to allow for adjacent blocks in other chunks to be visible for determinng renderd faces
double Chunk::getNoiseValue(const siv::PerlinNoise &p, int x, int z) {
return p.normalizedOctave2D_01(((xOffset * Chunks::size) + x) * frequency, ((zOffset * Chunks::size) + z) * frequency, octaves, persistence) * (double)25;
}
void Chunk::generateHeightMap(const siv::PerlinNoise &p){
for(int x = -1; x < Chunks::size + 1; x++){
for(int z = -1; z < Chunks::size + 1; z++){
double noiseValue = getNoiseValue(p, x, z);
int height = static_cast<int>(noiseValue) + Chunks::size;
for(int y = 0; y < Chunks::height; y++){
voxelGrid[x+1][y][z+1] = (y < height) ? 1 : 0;
}
}
}
}
std::shared_ptr<Block> Chunk::fetchBlock(glm::ivec3 blockCoords){
auto block = blocks[blockCoords];
return block;
}
int Chunk::getNumBlocks(){
return blocks.size();
}
void Chunk::deleteBlock(glm::ivec3 voxel, const siv::PerlinNoise &p, std::vector<Chunk*> adjChunks) {
// Normalize voxel coordinates into chunk-local coordinates (0-15 range)
glm::ivec3 normalizedBlockCoords(
(voxel.x % Chunks::size + Chunks::size) % Chunks::size, // Handles negative modulo correctly
voxel.y,
(voxel.z % Chunks::size + Chunks::size) % Chunks::size // Handles negative modulo correctly
);
voxelGrid[normalizedBlockCoords.x + 1][normalizedBlockCoords.y][normalizedBlockCoords.z + 1] = 0;
//if we are on boundary of chunk, we need adjacent
//think this needs to be in the chunkManager so I can easly access adjacent chunk from map
blocks.erase(voxel);
std::vector<glm::ivec3> directions = {
{0, 1, 0}, // Above
{0, -1, 0}, // Below
{-1, 0, 0}, // Left
{1, 0, 0}, // Right
{0, 0, -1}, // Behind
{0, 0, 1} // In front
};
// Iterate through directions to check adjacent blocks
for (const auto& direction : directions) {
glm::ivec3 neighborPos = voxel + direction;
glm::ivec3 normalizedNeighborPos = normalizedBlockCoords + direction;
// Check if the neighbor block is within the current chunk
bool isWithinChunk = (normalizedNeighborPos.x >= 0 && normalizedNeighborPos.x < Chunks::size &&
normalizedNeighborPos.z >= 0 && normalizedNeighborPos.z < Chunks::size);
// Check if the neighbor block is part of the current chunk
if (isWithinChunk) {
if (voxelGrid[normalizedNeighborPos.x + 1][normalizedNeighborPos.y][normalizedNeighborPos.z + 1] == 1) {
double noiseValue = getNoiseValue(p, normalizedNeighborPos.x, normalizedNeighborPos.z);
int iNoiseVal = noiseValue < 0.0 ? glm::ceil(noiseValue) : glm::floor(noiseValue);
int height = iNoiseVal + Chunks::size;
blockType empty = Empty;
std::vector<int> faces = checkNeighbors(normalizedNeighborPos.x + 1, normalizedNeighborPos.y, normalizedNeighborPos.z + 1, empty);
// If the neighbor block does not exist and new faces are needed, create a new block
if (blocks.find(neighborPos) == blocks.end() && !faces.empty()) {
blocks.emplace(neighborPos, std::make_unique<Block>(neighborPos.x, neighborPos.y, neighborPos.z));
blocks[neighborPos]->setType(neighborPos.y == height - 1 ? Grass : neighborPos.y > height - 4 ? Dirt : Stone);
setFaces(neighborPos, faces);
} else if (!faces.empty()) { // Update existing block faces if necessary
setFaces(neighborPos, faces);
}
}
} else {
// Handle cases where the neighboring block is in an adjacent chunk
glm::ivec2 neighborChunkCoords(
neighborPos.x >= 0 ? neighborPos.x / Chunks::size : (neighborPos.x - Chunks::size + 1) / Chunks::size,
neighborPos.z >= 0 ? neighborPos.z / Chunks::size : (neighborPos.z - Chunks::size + 1) / Chunks::size
);
for( auto c : adjChunks){
if(c->xOffset == neighborChunkCoords.x && c->zOffset == neighborChunkCoords.y){
const glm::ivec2 chunkPos(xOffset, zOffset);
c->updateChunkOnBlockBreak(neighborPos, voxel, chunkPos, p);
}
}
}
}
// Modify buffer with new data
updateVertices();
}
//the idea here is to look across chunk boundaries and find if the block adjacent to what was broken is solid
//if it is we need to regen its buffers based on what is exposed then update the vbo
void Chunk::updateChunkOnBlockBreak(const glm::ivec3 blockPos, const glm::ivec3 originalBlockPos, const glm::ivec2 originalChunkPos, const siv::PerlinNoise &p){
const glm::ivec3 normalizedBlockPos(
(blockPos.x % Chunks::size + Chunks::size) % Chunks::size, // Handles negative modulo correctly
blockPos.y,
(blockPos.z % Chunks::size + Chunks::size) % Chunks::size // Handles negative modulo correctly
);
const glm::ivec3 normalizedOriginalBlockPos(
(originalBlockPos.x % Chunks::size + Chunks::size) % Chunks::size, // Handles negative modulo correctly
originalBlockPos.y,
(originalBlockPos.z % Chunks::size + Chunks::size) % Chunks::size // Handles negative modulo correctly
);
//update chunks voxelGrid with new deleted Block
//what I was trying to do was properly update the DELETED block not the block we are looking at based on direction from delete block function
if(normalizedOriginalBlockPos.x == 0) voxelGrid[Chunks::size + 1][normalizedOriginalBlockPos.y][normalizedOriginalBlockPos.z + 1] = 0;
if(normalizedOriginalBlockPos.x == Chunks::size - 1) voxelGrid[0][normalizedOriginalBlockPos.y][normalizedOriginalBlockPos.z + 1] = 0;
if(normalizedOriginalBlockPos.z == 0) voxelGrid[normalizedOriginalBlockPos.x + 1][normalizedOriginalBlockPos.y][Chunks::size + 1] = 0;
if(normalizedOriginalBlockPos.z == Chunks::size - 1) voxelGrid[normalizedOriginalBlockPos.x + 1][normalizedOriginalBlockPos.y][0] = 0;
/*if(normalizedBlockPos.x == 0) voxelGrid[0][normalizedBlockPos.y][normalizedBlockPos.z + 1] = 0;
if(normalizedBlockPos.x == Chunks::size - 1) voxelGrid[Chunks::size + 1][normalizedBlockPos.y][normalizedBlockPos.z + 1] = 0;
if(normalizedBlockPos.z == 0) voxelGrid[normalizedBlockPos.x + 1][normalizedBlockPos.y][0] = 0;
if(normalizedBlockPos.x == Chunks::size - 1) voxelGrid[normalizedBlockPos.x + 1][normalizedBlockPos.y][Chunks::size + 1] = 0;*/
//idea is to find what direction we moved in in chunk space to determine what block in padding needs to be set to zero
/*if(this->xOffset > originalChunkPos.x) voxelGrid[0][normalizedOriginalBlockPos.y][normalizedOriginalBlockPos.z + 1] = 0;
if(this->xOffset < originalChunkPos.x) voxelGrid[Chunks::size + 1][normalizedOriginalBlockPos.y][normalizedOriginalBlockPos.z + 1] = 0;
if(this->zOffset > originalChunkPos.y) voxelGrid[normalizedOriginalBlockPos.x + 1][normalizedOriginalBlockPos.y][0] = 0;
if(this->zOffset < originalChunkPos.y) voxelGrid[normalizedOriginalBlockPos.x + 1][normalizedOriginalBlockPos.y][Chunks::size + 1] = 0;*/
blockType empty = Empty;
std::vector<int>faces = checkNeighbors(normalizedBlockPos.x + 1, normalizedBlockPos.y, normalizedBlockPos.z + 1, empty);
if (voxelGrid[normalizedBlockPos.x + 1][normalizedBlockPos.y][normalizedBlockPos.z + 1] == 1 && faces.size() > 0) {
// we need to create more blocks in order to properly present new faces (this is generally the case when digging down)
if(blocks.find(blockPos) == blocks.end()){
double noiseValue = getNoiseValue(p, normalizedBlockPos.x, normalizedBlockPos.z); //future optimization could be storing heights in vector of some sort
int iNoiseVal = noiseValue < 0.0 ? glm::ceil(noiseValue) : glm::floor(noiseValue);
int height = iNoiseVal + Chunks::size;
blocks.emplace(blockPos, std::make_unique<Block>(blockPos.x, blockPos.y, blockPos.z));
blocks[blockPos]->setType(blockPos.y == height - 1 ? Grass : blockPos.y > height - 4 ? Dirt : Stone);
setFaces(blockPos, faces);
}else{
blockType bT = blocks[blockPos]->getBlockType();
blocks.erase(blockPos);
blocks.emplace(blockPos, std::make_unique<Block>(blockPos.x, blockPos.y, blockPos.z));
blocks[blockPos]->setType(bT);
setFaces(blockPos, faces);
}
}
updateVertices();
}
//will need to modify to remove face between blocks next to and current block being places
void Chunk::placeBlock(const glm::ivec3 voxel, blockType block, std::vector<Chunk *> adjChunks){
glm::ivec3 newBlock(voxel.x, voxel.y, voxel.z);
glm::ivec3 normalizedBlockCoords(
(newBlock.x % Chunks::size + Chunks::size) % Chunks::size, // Handles negative modulo correctly
newBlock.y,
(newBlock.z % Chunks::size + Chunks::size) % Chunks::size // Handles negative modulo correctly
);
//check adjacent blocks and update faces if a leaf is placed, turned out to be more complicated than needed. will probably just stay broken, not too committed on getting this working
/*if(block == Leaf){
}*/
//make 1 again, was zero if we had a leaf
voxelGrid[normalizedBlockCoords.x+1][normalizedBlockCoords.y][normalizedBlockCoords.z + 1] = 1;
std::vector<int> faces = checkNeighbors(normalizedBlockCoords.x + 1, normalizedBlockCoords.y, normalizedBlockCoords.z + 1, block);
blocks.emplace(newBlock, std::make_unique<Block>(newBlock.x, newBlock.y, newBlock.z));
blocks[newBlock]->setType(block);
setFaces(newBlock, faces);
updateVertices();
}
//used if we need to add a face to a block adjacent to a transparent one
void Chunk::updateNeighborFaces(const glm::ivec3 blockPos, const glm::ivec3 normalizedBlockPos){
if(voxelGrid[normalizedBlockPos.x + 1][normalizedBlockPos.y][normalizedBlockPos.z + 1] == 1){
blockType neighborType = blocks[blockPos]->getBlockType();
std::vector<int> faces = checkNeighbors(normalizedBlockPos.x + 1, normalizedBlockPos.y, normalizedBlockPos.z + 1, neighborType);
setFaces(blockPos, faces);
}
updateVertices();
}
std::vector<glm::ivec3> Chunk::getBlocks(){
std::vector<glm::ivec3> ret;
for(auto &b : blocks){
ret.push_back(glm::ivec3(b.second->x, b.second->y, b.second->z));
}
return ret;
}
void Chunk::initChunk(){
int totalSize = 0;
for(auto &b : blocks){
totalSize += b.second->vertices.size();
}
verticeCount = totalSize;
glGenTextures(1, &texture);
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, totalSize * sizeof(float), nullptr, GL_DYNAMIC_DRAW);
//position attrib
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// texture attribute
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3* sizeof(float))); //we have to offset by 3*sizeof(float);
glEnableVertexAttribArray(1);
}
//we need to update our vbo with new vertex data
void Chunk::textureBlocks() {
glBindTexture(GL_TEXTURE_2D, texture);
// set the texture wrapping/filtering options (on the currently bound texture object)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 7); // pick mipmap level 7 or lower
// load and generate the texture
int width, height, nrChannels;
unsigned char *data = stbi_load("../img/atlas.png", &width, &height, &nrChannels, 0);
if (data)
{
//stbi_set_flip_vertically_on_load(1);
GLenum format;
if (nrChannels == 1)
format = GL_RED;
else if (nrChannels == 3)
format = GL_RGB;
else if (nrChannels == 4)
format = GL_RGBA;
glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);
}
else
{
std::cout << "Failed to load texture" << std::endl;
}
stbi_image_free(data);
}
//this generates our textures into an array - should probably be done in the chunkManager
void Chunk::setBlockTexture(){
constexpr float sheetWidth = 64.0f;
constexpr float sheetHeight = 64.0f;
constexpr float spriteWidth = 16.0f , spriteHeight = 16.0f;
for(float y = 0; y < 2; y++){
for(float x = 0; x < 4; x++){
blockTextures[static_cast<int>(x) + static_cast<int>(y * 4)] = {
{ (x * spriteWidth) / sheetWidth, (y * spriteHeight) / sheetHeight}, //topleft
{ ((x + 1) * spriteWidth) / sheetWidth, (y * spriteHeight) / sheetHeight}, //top right
{ ((x + 1) * spriteWidth) / sheetWidth, ((y + 1) * spriteHeight) / sheetHeight}, //bottomright
{ ((x + 1) * spriteWidth) / sheetWidth, ((y + 1) * spriteHeight) / sheetHeight}, //bottom right
{ (x * (spriteWidth)) / sheetWidth, ((y + 1) * spriteHeight) / sheetHeight}, //bottom left
{ (x * spriteWidth) / sheetWidth, (y * spriteHeight) / sheetHeight} //topleft
};
}
}
}
//we need to update our vbo with new vertex data
void Chunk::updateVertices() {
int totalSize = 0;
std::vector<float> data;
for (auto &b : blocks) {
totalSize += b.second->vertices.size();
data.insert(data.end(), b.second->vertices.begin(), b.second->vertices.end());
}
verticeCount = totalSize;
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, data.size() * sizeof(float), data.data(), GL_DYNAMIC_DRAW);
// Optionally, bind the VAO again if required by your rendering process.
//glBindVertexArray(VAO);
// Ensure the texture setup is called correctly (if needed).
textureBlocks();
// Unbind the VBO (optional, for safety).
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
//This checks to see what faces are visible
//Will eventually need to use the voxelGrid for this
std::vector<int> Chunk::checkNeighbors(int x, int y, int z, blockType block){
std::vector<int> faces;
// Helper lambda to check if the block is air (or not solid)
auto isAir = [&](int x, int y, int z) -> bool {
// Check if coordinates are within the chunk
if (x >= 0 && x < Chunks::size + 2 && z >= 0 && z < Chunks::size + 2 && y > 0 && y < Chunks::height) {
return voxelGrid[x][y][z] == 0;
}
return false; // Assume out of bounds means air
};
//Above = 0
//Below = 1
//Right = 2
//Left = 3
//Front = 4
//Behind = 5
// Above
if (isAir(x, y + 1, z) || block == Leaf) {
faces.push_back(0);
}
// Below
if (isAir(x, y - 1, z) || block == Leaf) {
faces.push_back(1);
}
// Right
if (isAir(x + 1, y, z)|| block == Leaf) {
faces.push_back(2);
}
// Left
if (isAir(x - 1, y, z)|| block == Leaf) {
faces.push_back(3);
}
// Front
if (isAir(x, y, z - 1)|| block == Leaf) {
faces.push_back(4);
}
// Behind
if (isAir(x, y, z + 1)|| block == Leaf) {
faces.push_back(5);
}
return faces;
}
void Chunk::setFaces(glm::ivec3 bPos, std::vector<int> faces){
blockTexCoords b_texSides = blockTextures[blocks[bPos]->getBlockId()];
blockTexCoords b_texTop = blockTextures[blocks[bPos]->getBlockId()];
blockTexCoords b_texBottom = blockTextures[blocks[bPos]->getBlockId()];
if(blocks[bPos]->getBlockId() == Grass) {
b_texTop = blockTextures[GrassTop];
b_texBottom = blockTextures[Dirt];
}
for(int f : faces){
if(f == 0){
blocks[bPos]->insertVertices(topFace, b_texTop);
}
if(f == 1){
blocks[bPos]->insertVertices(bottomFace, b_texBottom);
}
if(f == 2){
blocks[bPos]->insertVertices(rightFace, b_texSides);
}
if(f == 3){
blocks[bPos]->insertVertices(leftFace, b_texSides);
}
if(f == 4){
blocks[bPos]->insertVertices(frontFace, b_texSides);
}
if(f == 5){
blocks[bPos]->insertVertices(backFace, b_texSides);
}
}
}
void Chunk::drawChunk(){
glDisable(GL_CULL_FACE);
glBindTexture(GL_TEXTURE_2D, texture);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, verticeCount);
glBindVertexArray(0);
glEnable(GL_CULL_FACE);
}