Beispiel #1
0
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;
}
Beispiel #2
0
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;
    }
}