terrain-gen/src/block.cpp

356 lines
11 KiB
C++

#include "block.h"
Perlin getPerlinNoise(int seed, double frequency, int octave) {
Perlin noise;
noise.SetSeed(seed);
noise.SetFrequency(frequency);
noise.SetOctaveCount(octave);
return noise;
}
Perlin Block::m_temperature = getPerlinNoise(5243, 0.02, 3);
Perlin Block::m_height = getPerlinNoise(8241, 0.02, 3);
Perlin Block::m_montainHeight = getPerlinNoise(2763, 0.16, 3);
Block::Block(int x, int z, unsigned int width, double step): m_x(x), m_z(z), m_step(step), m_width(width), m_debug(false)
{
m_land = new Mesh(float(signed(m_width) * m_step));
m_water = new Mesh(float(signed(m_width) * m_step));
for (int j = signed(m_width) * z; j < signed(m_width) * (z + 1); j++) {
for (int i = signed(m_width) * x; i < signed(m_width) * (x + 1); i++) {
createUnitBlock(double(i) * m_step, double(j) * m_step);
}
}
m_land->removeDoubles();
m_land->removeDoubles();
m_land->normalizeNormals();
createWaterSurface();
}
Block::~Block()
{
delete m_water;
delete m_land;
}
Biome Block::getBiome(double temperature, double height)
{
if (height < 0.1) {
return Biome::Sea;
} else if (height < 0.5 && temperature <= 0){
return Biome::GrassLand;
} else if (height >= 0.5 && temperature <= 0) {
return Biome::Montain;
} else if (height < 0.5 && temperature > 0) {
return Biome::Desert;
} else {
return Biome::Canyon;
}
}
double Block::getYGrassLand(double height)
{
return 0.5 + ((height - 0.1) / 0.9) * 3;
}
double Block::getYDesert(double temperature, double height)
{
return temperature * 3 + getYGrassLand(height);
}
double Block::getYSea(double height)
{
if (height < -1) {
height = -1; // HOT FIX
}
if (height >= 0) {
return getYGrassLand(height);
} else {
return (height / (1 + (height * 0.9))) * 5;
}
}
double Block::getYCanyon(double temperature, double height)
{
return getYDesert(temperature, height) + 5;
}
double Block::getYMontain(double height, double x, double z)
{
double montainFactor = (height - 0.5) * 2;
double montainHeight = m_montainHeight.GetValue(x, 0, z);
return (1 - montainFactor) * getYGrassLand(height) + montainFactor * (12 + montainHeight * montainHeight * 3);
}
double Block::getYFromBiomeParam(Biome biome, double temperature, double height, double x, double z)
{
switch (biome) {
case Biome::GrassLand:
return getYGrassLand(height);
case Biome::Sea:
return getYSea(height);
case Biome::Desert:
return getYDesert(temperature, height);
case Biome::Canyon:
return getYCanyon(temperature, height);
case Biome::Montain:
return getYMontain(height, x, z);
}
return 0;
}
double Block::getYFromPosition(double x, double z)
{
double temperature = m_temperature.GetValue(x, 0, z);
double height = m_height.GetValue(x, 0, z);
Biome biome = getBiome(temperature, height);
return getYFromBiomeParam(biome, temperature, height, x, z);
}
TypedVertex Block::getVertex(double x, double z)
{
TypedVertex typedvertex;
double temperature = m_temperature.GetValue(x, 0, z);
double height = m_height.GetValue(x, 0, z);
Biome biome = getBiome(temperature, height);
typedvertex.x = float(x);
typedvertex.z = float(z);
typedvertex.y = float(getYFromBiomeParam(biome, temperature, height, x, z));
typedvertex.normalX = 0;
typedvertex.normalY = 0;
typedvertex.normalZ = 0;
typedvertex.height = float(height);
typedvertex.temperature = float(temperature);
typedvertex.biome = biome;
return typedvertex;
}
Mesh *Block::land() const
{
return m_land;
}
Mesh *Block::water() const
{
return m_water;
}
void Block::createUnitBlock(double x, double z)
{
TypedVertex v1 = getVertex(x, z);
TypedVertex v2 = getVertex(x + m_step, z);
TypedVertex v3 = getVertex(x, z + m_step);
TypedVertex v4 = getVertex(x + m_step, z + m_step);
linkGenericQuad(v1, v2, v3, v4);
}
TypedVertex Block::getBiomeIntersection(TypedVertex &start, TypedVertex &end)
{
TypedVertex segmentStart(start);
TypedVertex segmentEnd(end);
TypedVertex segmentMiddle(segmentStart);
float distance = float(m_step);
float threshold = 0.0005f * distance;
Biome wantedBiome = start.biome;
while (distance > threshold) {
float xMiddle = (segmentStart.x + segmentEnd.x) / 2;
float zMiddle = (segmentStart.z + segmentEnd.z) / 2;
if (segmentMiddle.biome == wantedBiome) {
segmentStart = segmentMiddle;
} else {
segmentEnd = segmentMiddle;
}
segmentMiddle = getVertex(double(xMiddle), double(zMiddle));
float dx = segmentStart.x - segmentEnd.x;
float dz = segmentStart.z - segmentEnd.z;
distance = dx * dx + dz * dz;
}
return segmentMiddle;
}
void Block::getSplitPoints(TypedVertex &v1, TypedVertex &v2, TypedVertex &before, TypedVertex &after)
{
float dx = (v2.x - v1.x) / 10;
float dz = (v2.z - v1.z) / 10;
after = v1;
uint i = 0;
do {
before = after;
after = getVertex(double(v1.x + (dx * i)), double(v1.z + (dz * i)));
i++;
} while (i < 11 && before.biome == after.biome);
}
void Block::splitTriangle(TypedVertex &v1, TypedVertex &v2, TypedVertex &v3, int splitCount)
{
Biome wantedBiome = v1.biome;
TypedVertex triangle[3] = {v1, v2, v3};
int wantedVertex = 0;
int sameVerticesCount = 0;
bool changeable = true;
for (int i = 0; i < 3; i++) {
if (triangle[i].biome == wantedBiome) {
if (changeable) {
changeable = false;
wantedVertex = i;
}
sameVerticesCount ++;
} else {
changeable = true;
}
}
if (sameVerticesCount == 2) {
wantedVertex = (wantedVertex + 2) % 3;
}
int firstSegmentEnd = (wantedVertex + 2) % 3;
int secondSegmentEnd = (wantedVertex + 1) % 3;
TypedVertex firstIntervalStart;
TypedVertex firstIntervalEnd;
TypedVertex secondIntervalStart;
TypedVertex secondIntervalEnd;
getSplitPoints(triangle[wantedVertex], triangle[firstSegmentEnd], firstIntervalStart, firstIntervalEnd);
getSplitPoints(triangle[wantedVertex], triangle[secondSegmentEnd], secondIntervalStart, secondIntervalEnd);
TypedVertex firstIntersectionWanted = getBiomeIntersection(firstIntervalStart, firstIntervalEnd);
TypedVertex secondIntersectionWanted = getBiomeIntersection(secondIntervalStart, secondIntervalEnd);
TypedVertex firstIntersectionOther(firstIntersectionWanted);
TypedVertex secondIntersectionOther(secondIntersectionWanted);
changeBiome(firstIntersectionWanted, firstIntervalStart.biome);
changeBiome(secondIntersectionWanted, secondIntervalStart.biome);
changeBiome(firstIntersectionOther, firstIntervalEnd.biome);
changeBiome(secondIntersectionOther, secondIntervalEnd.biome);
addTriangleOrSplit(firstIntersectionWanted, triangle[wantedVertex], secondIntersectionWanted, splitCount);
linkGenericQuad(firstIntersectionOther, secondIntersectionOther, triangle[firstSegmentEnd], triangle[secondSegmentEnd], splitCount);
if (abs(firstIntersectionWanted.y - firstIntersectionOther.y) < 0.05f) {
joinBiomeMiddle(firstIntersectionWanted, firstIntersectionOther, secondIntersectionWanted, secondIntersectionOther);
} else {
joinBiomeQuad(firstIntersectionOther, firstIntersectionWanted, secondIntersectionOther, secondIntersectionWanted);
}
}
void Block::changeBiome(TypedVertex &v, Biome newBiome)
{
v.temperature = float(m_temperature.GetValue(double(v.x), 0, double(v.z)));
v.height = float(m_height.GetValue(double(v.x), 0, double(v.z)));
v.y = float(getYFromBiomeParam(newBiome, double(v.temperature), double(v.height), double(v.x), double(v.z)));
v.biome = newBiome;
}
void Block::joinBiomeMiddle(TypedVertex &firstSegmentStart, TypedVertex &firstSegmentEnd, TypedVertex &secondSegmentStart, TypedVertex &secondSegmentEnd)
{
firstSegmentEnd.y = firstSegmentStart.y;
secondSegmentEnd.y = secondSegmentStart.y;
}
void Block::joinBiomeQuad(TypedVertex firstSegmentStart, TypedVertex firstSegmentEnd, TypedVertex secondSegmentStart, TypedVertex secondSegmentEnd)
{
if (firstSegmentStart.y > firstSegmentEnd.y) {
firstSegmentEnd.biome = firstSegmentStart.biome;
secondSegmentEnd.biome = secondSegmentStart.biome;
} else {
firstSegmentStart.biome = firstSegmentEnd.biome;
secondSegmentStart.biome = secondSegmentEnd.biome;
}
linkGenericQuad(firstSegmentStart, firstSegmentEnd, secondSegmentStart, secondSegmentEnd, 0);
}
void Block::linkGenericQuad(TypedVertex &v1, TypedVertex &v2, TypedVertex &v3, TypedVertex &v4)
{
linkGenericQuad(v1, v2, v3, v4, 0);
}
void Block::linkGenericQuad(TypedVertex &v1, TypedVertex &v2, TypedVertex &v3, TypedVertex &v4, int splitCount)
{
addTriangleOrSplit(v1, v2, v3, splitCount);
addTriangleOrSplit(v3, v2, v4, splitCount);
}
void Block::addTriangleOrSplit(TypedVertex &v1, TypedVertex &v2, TypedVertex &v3, int splitCount)
{
if (v1.biome == v2.biome && v1.biome == v3.biome) {
QVector3D normal = faceNormal(&v1, &v2, &v3);
v1.normalX = normal.x();
v3.normalX = normal.x();
v2.normalX = normal.x();
v1.normalY = normal.y();
v3.normalY = normal.y();
v2.normalY = normal.y();
v1.normalZ = normal.z();
v3.normalZ = normal.z();
v2.normalZ = normal.z();
m_land->addTriangle(m_land->addVertex(v1), m_land->addVertex(v2), m_land->addVertex(v3));
// TODO: Voir pour changer la condition d'arret pour la taille du triangle (puis fuisionner les points si triangle petit)
} else if (splitCount <= 10) {
splitTriangle(v1, v2, v3, splitCount + 1);
}
}
QVector3D Block::faceNormal(TypedVertex* v1, TypedVertex* v2, TypedVertex* v3)
{
QVector3D v1v2 = QVector3D(v2->x - v1->x, v2->y - v1->y, v2->z - v1->z);
QVector3D v1v3 = QVector3D(v3->x - v1->x, v3->y - v1->y, v3->z - v1->z);
return QVector3D::normal(v1v3, v1v2);
}
void Block::createWaterSurface()
{
std::vector<int> indexMap;
indexMap.resize(m_land->vertices().size(), -1);
for (uint i = 0; i < m_land->indexes().size(); i++)
{
uint vertexIndex = m_land->indexes()[i];
if (m_land->vertices()[vertexIndex].biome == Biome::Sea) {
if (indexMap[vertexIndex] < 0) {
TypedVertex newVertex = m_land->vertices()[vertexIndex];
newVertex.normalX = 0;
newVertex.normalY = 1;
newVertex.normalZ = 0;
indexMap[vertexIndex] = int(m_water->vertices().size());
m_water->vertices().push_back(newVertex);
}
m_water->indexes().push_back(GLuint(indexMap[vertexIndex]));
}
}
}