float TerrainInteraction::setValue(TerrainTile & tile, unsigned row, unsigned column, float value, bool setToInteractionElement) { float stddev = tile.interactStdDeviation; assert(stddev > 0); /** clamp value */ if (value < tile.minValidValue) value = tile.minValidValue; if (value > tile.maxValidValue) value = tile.maxValidValue; // define the size of the affected interaction area, in grid coords const float effectRadiusWorld = stddev * 3; const uint32_t effectRadius = static_cast<uint32_t>(std::ceil(effectRadiusWorld * tile.samplesPerWorldCoord)); // = 0 means to change only the value at (row,column) bool moveUp = (value - tile.valueAt(row, column)) > 0; int invert = moveUp ? 1 : -1; // invert the curve if moving downwards float norm0Inv = 1.0f / normalDist(0, 0, stddev); float valueRange = std::abs(tile.maxValidValue - tile.minValidValue); std::function<float(float)> interactHeight = [stddev, norm0Inv, value, valueRange, invert](float x) { return normalDist(x, 0, stddev) // - normalize normDist to * norm0Inv // normDist value at interaction center * (valueRange + 10) // - scale to value range + offset to omit norm values near 0 * invert // - mirror the curve along the y axis if moving downward + value // - move along y so that value==0 => y==0 - (valueRange + 10) * invert; }; unsigned int minRow, maxRow, minColumn, maxColumn; { // unchecked signed min/max values, possibly < 0 or > numRows/Column int iMinRow = row - effectRadius, iMaxRow = row + effectRadius, iMinColumn = column - effectRadius, iMaxColumn = column + effectRadius; // work on rows and column that are in range of the terrain tile settings and larger than 0 minRow = iMinRow < 0 ? 0 : (iMinRow >= static_cast<signed>(tile.samplesPerAxis) ? tile.samplesPerAxis - 1 : static_cast<unsigned int>(iMinRow)); maxRow = iMaxRow < 0 ? 0 : (iMaxRow >= static_cast<signed>(tile.samplesPerAxis) ? tile.samplesPerAxis - 1 : static_cast<unsigned int>(iMaxRow)); minColumn = iMinColumn < 0 ? 0 : (iMinColumn >= static_cast<signed>(tile.samplesPerAxis) ? tile.samplesPerAxis - 1 : static_cast<unsigned int>(iMinColumn)); maxColumn = iMaxColumn < 0 ? 0 : (iMaxColumn >= static_cast<signed>(tile.samplesPerAxis) ? tile.samplesPerAxis - 1 : static_cast<unsigned int>(iMaxColumn)); } // also change element id's if requested and the tile supports it PhysicalTile * physicalTile = dynamic_cast<PhysicalTile*>(&tile); if (setToInteractionElement) { assert(physicalTile); } uint8_t elementIndex = 0; if (physicalTile) elementIndex = physicalTile->elementIndex(m_interactElement); for (unsigned int r = minRow; r <= maxRow; ++r) { float relWorldX = (signed(r) - signed(row)) * tile.sampleInterval; for (unsigned int c = minColumn; c <= maxColumn; ++c) { float relWorldZ = (signed(c) - signed(column)) * tile.sampleInterval; float localRadius = std::sqrt(relWorldX*relWorldX + relWorldZ*relWorldZ); if (localRadius > effectRadiusWorld) // interaction in a circle, not square continue; float newLocalHeight = interactHeight(localRadius); bool localMoveUp = newLocalHeight > tile.valueAt(r, c); // don't do anything if we pull up the terrain but the local height point is already higher than its calculated height. (vice versa) if (localMoveUp != moveUp) continue; tile.setValue(r, c, newLocalHeight); if (setToInteractionElement) physicalTile->setElement(r, c, elementIndex); } tile.addBufferUpdateRange(minColumn + r * tile.samplesPerAxis, 1u + effectRadius * 2u); } if (physicalTile) physicalTile->addToPxUpdateBox(minRow, maxRow, minColumn, maxColumn); return value; }
void TerrainGenerator::diamondSquare(TerrainTile & tile) const { // assuming the edge length of the field is a power of 2, + 1 // assuming the field is square const unsigned fieldEdgeLength = tile.samplesPerAxis; const float maxHeight = m_settings.maxHeight; float randomMax = 50.0f; std::function<float(float)> clampHeight = [maxHeight](float value) { if (value > maxHeight) value = maxHeight; if (value < -maxHeight) value = -maxHeight; return value; }; std::function<void(unsigned int, unsigned int, unsigned int, std::function<float(unsigned int, unsigned int)>&)> squareStep = [&tile, fieldEdgeLength, &clampHeight](unsigned int diamondRadius, unsigned int diamondCenterRow, unsigned int diamondCenterColumn, std::function<float(unsigned int, unsigned int)>& heightRnd) { // get the existing data values first: if we get out of the valid range, wrap around, to the next existing value on the other field side int upperRow = signed(diamondCenterRow) - signed(diamondRadius); if (upperRow < 0) upperRow = fieldEdgeLength - 1 - diamondRadius; // example: nbRows=5, centerRow=0, upperRow gets -1, we want the second last row (with existing value), so it's 3 int lowerRow = signed(diamondCenterRow) + signed(diamondRadius); if (lowerRow >= signed(fieldEdgeLength)) lowerRow = diamondRadius; // this is easier: use the first row in our column, that is already set int leftColumn = signed(diamondCenterColumn) - signed(diamondRadius); if (leftColumn < 0) leftColumn = fieldEdgeLength - 1 - diamondRadius; int rightColumn = signed(diamondCenterColumn) + signed(diamondRadius); if (rightColumn >= signed(fieldEdgeLength)) rightColumn = diamondRadius; float value = (tile.valueAt(upperRow, diamondCenterColumn) + tile.valueAt(lowerRow, diamondCenterColumn) + tile.valueAt(diamondCenterRow, leftColumn) + tile.valueAt(diamondCenterRow, rightColumn)) * 0.25f + heightRnd(diamondCenterRow, diamondCenterColumn); float clampedHeight = clampHeight(value); tile.setValue(diamondCenterRow, diamondCenterColumn, clampedHeight); // in case we are at the borders of the tile: also set the value at the opposite border, to allow seamless tile wrapping if (upperRow > signed(diamondCenterRow)) tile.setValue(fieldEdgeLength - 1, diamondCenterColumn, clampedHeight); if (leftColumn > signed(diamondCenterColumn)) tile.setValue(diamondCenterRow, fieldEdgeLength - 1, clampedHeight); }; unsigned nbSquareRows = 1; // number of squares in a row, doubles each time the current edge length increases [same for the columns] for (unsigned int len = fieldEdgeLength; len > 2; len = (len / 2) + 1) // length: 9, 5, 3, finished { const unsigned int currentEdgeLength = len; std::uniform_real_distribution<float> dist(-randomMax, randomMax); std::function<float(unsigned int, unsigned int)> heightRndPos = [fieldEdgeLength, &dist](unsigned int row, unsigned int column) { glm::vec2 pos(row, column); pos = pos / (fieldEdgeLength - 1.0f) * 2.0f - 1.0f; return float(glm::length(pos)) * dist(rng); //return std::abs(float(row + column) / float(2 * fieldEdgeLength - 2) * 2.0f - 1.0f) * dist(rng); }; // create diamonds for (unsigned int rowN = 0; rowN < nbSquareRows; ++rowN) { const unsigned int row = rowN * (currentEdgeLength - 1); const unsigned int midpointRow = row + (currentEdgeLength - 1) / 2; // this is always divisible, because of the edge length 2^n + 1 for (unsigned int columnN = 0; columnN < nbSquareRows; ++columnN) { const unsigned int column = columnN * (currentEdgeLength - 1); const unsigned int midpointColumn = column + (currentEdgeLength - 1) / 2; float heightValue = (tile.valueAt(row, column) + tile.valueAt(row + currentEdgeLength - 1, column) + tile.valueAt(row, column + currentEdgeLength - 1) + tile.valueAt(row + currentEdgeLength - 1, column + currentEdgeLength - 1)) * 0.25f + heightRndPos(midpointRow, midpointColumn); tile.setValue(midpointRow, midpointColumn, clampHeight(heightValue)); } } // create squares unsigned int diamondRadius = (currentEdgeLength - 1) / 2; // don't iterate over the last row/column here. These values are set with the first row/column, to allow seamless tile wrapping for (unsigned int rowN = 0; rowN < nbSquareRows; ++rowN) { const unsigned int seedpointRow = rowN * (currentEdgeLength - 1); for (unsigned int columnN = 0; columnN < nbSquareRows; ++columnN) { const unsigned int seedpointColumn = columnN * (currentEdgeLength - 1); unsigned int rightDiamondColumn = seedpointColumn + currentEdgeLength / 2; if (rightDiamondColumn < tile.samplesPerAxis) squareStep(diamondRadius, seedpointRow, rightDiamondColumn, heightRndPos); unsigned int bottomDiamondRow = seedpointRow + currentEdgeLength / 2; if (bottomDiamondRow < tile.samplesPerAxis) squareStep(diamondRadius, bottomDiamondRow, seedpointColumn, heightRndPos); } } nbSquareRows *= 2; randomMax *= 0.5; } }