From a8cb72d6536ac90bbd71f0d802734e79f7ea8cd2 Mon Sep 17 00:00:00 2001 From: Stuart Buchanan Date: Sat, 28 May 2022 21:03:48 +0100 Subject: [PATCH] WS30: Improved coastlines Build a high resolution texture containing coastline data that will be mixed with the landclass texture in the ws30 shader. Replaces the previous approach of creating a separate coastline mesh. --- simgear/scene/material/matlib.cxx | 3 +- simgear/scene/tgdb/VPBTechnique.cxx | 265 ++++++++++++++-------------- simgear/scene/tgdb/VPBTechnique.hxx | 14 +- 3 files changed, 139 insertions(+), 143 deletions(-) diff --git a/simgear/scene/material/matlib.cxx b/simgear/scene/material/matlib.cxx index 507b6e18..360d3fba 100644 --- a/simgear/scene/material/matlib.cxx +++ b/simgear/scene/material/matlib.cxx @@ -440,7 +440,7 @@ SGMaterialCache::Atlas SGMaterialLib::getMaterialTextureAtlas(SGVec2f center, co // TEXTURE NAME texture-unit Material texture index Default value // Primary texure 0 0 n/a // gradient_texture 2 13 Textures/Terrain/rock_alt.png - // dot_texture 3 15 Textures/Terrain/void.png + // dot_texture 3 15 Textures/Terrain/sand6.png // grain_texture 4 14 Textures/Terrain/grain_texture.png // mix_texture 5 12 Textures/Terrain/void.png // detail_texture 7 11 Textures/Terrain/void.png @@ -457,6 +457,7 @@ SGMaterialCache::Atlas SGMaterialLib::getMaterialTextureAtlas(SGVec2f center, co if (i < 13) texture = std::string("Textures/Terrain/void.png"); if (i == 13) texture = std::string("Textures/Terrain/rock_alt.png"); if (i == 14) texture = std::string("Textures/Terrain/grain_texture.png"); + if (i == 15) texture = std::string("Textures/Terrain/sand6.png"); if (i > 14) texture = std::string("Textures/Terrain/void.png"); } diff --git a/simgear/scene/tgdb/VPBTechnique.cxx b/simgear/scene/tgdb/VPBTechnique.cxx index dceb4b1d..b99ab9f6 100644 --- a/simgear/scene/tgdb/VPBTechnique.cxx +++ b/simgear/scene/tgdb/VPBTechnique.cxx @@ -174,7 +174,6 @@ void VPBTechnique::init(int dirtyMask, bool assumeMultiThreaded) applyColorLayers(*buffer, masterLocator); applyLineFeatures(*buffer, masterLocator); applyAreaFeatures(*buffer, masterLocator); - applyCoastline(*buffer, masterLocator); applyMaterials(*buffer, masterLocator); } } @@ -185,7 +184,6 @@ void VPBTechnique::init(int dirtyMask, bool assumeMultiThreaded) applyColorLayers(*buffer, masterLocator); applyLineFeatures(*buffer, masterLocator); applyAreaFeatures(*buffer, masterLocator); - applyCoastline(*buffer, masterLocator); applyMaterials(*buffer, masterLocator); } @@ -1374,9 +1372,19 @@ void VPBTechnique::applyColorLayers(BufferData& buffer, Locator* masterLocator) texture2D->setWrap(osg::Texture::WRAP_S,osg::Texture::CLAMP_TO_EDGE); texture2D->setWrap(osg::Texture::WRAP_T,osg::Texture::CLAMP_TO_EDGE); + osg::ref_ptr waterTexture = new osg::Texture2D; + waterTexture->setImage(generateWaterTexture(buffer, masterLocator)); + waterTexture->setMaxAnisotropy(16.0f); + waterTexture->setResizeNonPowerOfTwoHint(false); + waterTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::NEAREST_MIPMAP_NEAREST); + waterTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::NEAREST_MIPMAP_NEAREST); + waterTexture->setWrap(osg::Texture::WRAP_S,osg::Texture::CLAMP_TO_EDGE); + waterTexture->setWrap(osg::Texture::WRAP_T,osg::Texture::CLAMP_TO_EDGE); + osg::StateSet* stateset = buffer._landGeode->getOrCreateStateSet(); stateset->setTextureAttributeAndModes(0, texture2D, osg::StateAttribute::ON); stateset->setTextureAttributeAndModes(1, atlas.image, osg::StateAttribute::ON); + stateset->setTextureAttributeAndModes(7, waterTexture, osg::StateAttribute::ON); stateset->addUniform(new osg::Uniform(VPBTechnique::PHOTO_SCENERY, false)); stateset->addUniform(new osg::Uniform(VPBTechnique::Z_UP_TRANSFORM, osg::Matrixf(osg::Matrix::inverse(makeZUpFrameRelative(loc))))); stateset->addUniform(new osg::Uniform(VPBTechnique::MODEL_OFFSET, (osg::Vec3f) buffer._transform->getMatrix().getTrans())); @@ -2087,38 +2095,45 @@ void VPBTechnique::generateAreaFeature(BufferData& buffer, Locator* masterLocato } } -void VPBTechnique::applyCoastline(BufferData& buffer, Locator* masterLocator) -{ +osg::Image* VPBTechnique::generateWaterTexture(BufferData& buffer, Locator* masterLocator) { + osg::Image* waterTexture = new osg::Image(); + unsigned int coast_features_lod_range = 4; - float coastWidth = 40.0; + unsigned int waterTextureSize = 2048; + float coastWidth = 150.0; const unsigned int tileLevel = _terrainTile->getTileID().level; const SGPropertyNode* propertyNode = _options->getPropertyNode().get(); if (propertyNode) { const SGPropertyNode* static_lod = propertyNode->getNode("/sim/rendering/static-lod"); + waterTextureSize = static_lod->getIntValue("water-texture-size", coast_features_lod_range); coast_features_lod_range = static_lod->getIntValue("coastline-lod-level", coast_features_lod_range); - coastWidth = static_lod->getFloatValue("coastline-width", coastWidth); + coastWidth = static_lod->getFloatValue("coastline-width", coastWidth); } if (tileLevel < coast_features_lod_range) { // Do not generate coasts for tiles too far away - return; + waterTexture->allocateImage(1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE); + waterTexture->setColor(osg::Vec4f(0.0f,0.0f,0.0f,0.0f), 0,0); + return waterTexture; } - const SGMaterialLibPtr matlib = _options->getMaterialLib(); - if (! matlib) { - SG_LOG(SG_TERRAIN, SG_ALERT, "Unable to get materials library to generate areas"); - return; - } + waterTexture->allocateImage(waterTextureSize, waterTextureSize, 1, GL_RGBA, GL_FLOAT); + for (unsigned int y = 0; y < waterTextureSize; ++y) { + for (unsigned int x = 0; x < waterTextureSize; ++x) { + waterTexture->setColor(osg::Vec4f(0.0f,0.0f,0.0f,0.0f), x, y); + } + } // Get all appropriate coasts. We assume that the VPB terrain tile is smaller than a Bucket size. const osg::Vec3d world = buffer._transform->getMatrix().getTrans(); + float tileSize = sqrt(buffer._width * buffer._width + buffer._height * buffer._height); const SGGeod loc = SGGeod::fromCart(toSG(world)); const SGBucket bucket = SGBucket(loc); auto coasts = std::find_if(_coastFeatureLists.begin(), _coastFeatureLists.end(), [bucket](BucketCoastlineBinList b){return (b.first == bucket);}); - if (coasts == _coastFeatureLists.end()) return; + if (coasts == _coastFeatureLists.end()) return waterTexture; // We're in Earth-centered coordinates, so "up" is simply directly away from (0,0,0) osg::Vec3d up = world; @@ -2126,23 +2141,6 @@ void VPBTechnique::applyCoastline(BufferData& buffer, Locator* masterLocator) TileBounds tileBounds(masterLocator, up); - SGMaterialCache* matcache = matlib->generateMatCache(loc, _options); - SGMaterial* mat = matcache->find("ws30coastline"); - - if (!mat) { - SG_LOG(SG_TERRAIN, SG_ALERT, "Unable to find material ws30coastline at " << loc << " " << bucket); - return; - } - - unsigned int xsize = mat->get_xsize(); - unsigned int ysize = mat->get_ysize(); - - // Generate a geometry for this set of coasts. - osg::Vec3Array* v = new osg::Vec3Array; - osg::Vec2Array* t = new osg::Vec2Array; - osg::Vec3Array* n = new osg::Vec3Array; - osg::Vec4Array* c = new osg::Vec4Array; - for (; coasts != _coastFeatureLists.end(); ++coasts) { const CoastlineBinList coastBins = coasts->second; @@ -2155,128 +2153,131 @@ void VPBTechnique::applyCoastline(BufferData& buffer, Locator* masterLocator) if (clipped.size() > 1) { // We need at least two points to render a line. LineFeatureBin::LineFeature line = LineFeatureBin::LineFeature(clipped, coastWidth); - generateCoastlineFeature(buffer, masterLocator, line, world, v, t, n, xsize, ysize); + addCoastline(masterLocator, waterTexture, line, waterTextureSize, tileSize, coastWidth); } } } } - if (v->size() == 0) return; - - c->push_back(osg::Vec4(1.0,1.0,1.0,1.0)); - - osg::ref_ptr geometry = new osg::Geometry; - geometry->setVertexArray(v); - geometry->setTexCoordArray(0, t, osg::Array::BIND_PER_VERTEX); - geometry->setTexCoordArray(1, t, osg::Array::BIND_PER_VERTEX); - geometry->setNormalArray(n, osg::Array::BIND_PER_VERTEX); - geometry->setColorArray(c, osg::Array::BIND_OVERALL); - geometry->setUseDisplayList( false ); - geometry->setUseVertexBufferObjects( true ); - geometry->addPrimitiveSet( new osg::DrawArrays( GL_TRIANGLES, 0, v->size()) ); - - EffectGeode* geode = new EffectGeode; - geode->addDrawable(geometry); - - geode->setMaterial(mat); - geode->setEffect(mat->get_one_effect(0)); - geode->setNodeMask(SG_NODEMASK_TERRAIN_BIT); - buffer._transform->addChild(geode); + return waterTexture; } -void VPBTechnique::generateCoastlineFeature(BufferData& buffer, Locator* masterLocator, LineFeatureBin::LineFeature coastline, osg::Vec3d modelCenter, osg::Vec3Array* v, osg::Vec2Array* t, osg::Vec3Array* n, unsigned int xsize, unsigned int ysize) -{ - if (coastline._nodes.size() < 2) { +// Update the color of a particular texture pixel, clipping to the texture and not over-writing a larger value. +void VPBTechnique::updateWaterTexture(osg::Image* waterTexture, unsigned int waterTextureSize, osg::Vec4 color, float x, float y) { + if (floor(x) < 0.0) return; + if (floor(x) > (waterTextureSize -1)) return; + if (floor(y) < 0.0) return; + if (floor(y) > (waterTextureSize -1)) return; + auto clamp = [waterTextureSize](float l) { return (unsigned int) fmaxf(0, fminf(floor(l), waterTextureSize - 1)); }; + unsigned int clamped_x = clamp(x); + unsigned int clamped_y = clamp(y); + osg::Vec4 new_color = max(waterTexture->getColor(clamped_x, clamped_y), color); + waterTexture->setColor(new_color, clamped_x, clamped_y); +} + + +void VPBTechnique::writeShoreStripe(osg::Image* waterTexture, unsigned int waterTextureSize, float tileSize, float coastWidth, float x, float y, int dx, int dy) { + + // We need to create a shoreline of width coastWidth to define it better and to hide any discrepancy in the underlying terrain mesh + // which may be either mis-aligned or simply of insufficient resolution. + float waterTextureResolution = tileSize / waterTextureSize; + int width = (int) coastWidth / waterTextureResolution; + int aboveHWLtxUnits = 0.3*width; + int belowHWLtxUnits = width - aboveHWLtxUnits; + + // Above the high water mark we just want sand. However there can be interpolation artifacts right at the edge causing a border or water. To avoid + // this we create an extra texel where the G channel is used to ensure we get a hard fall-off to the underlying texture + // on the adjacent sand texture unit. + updateWaterTexture(waterTexture, waterTextureSize, osg::Vec4(0.0f, 1.0f, 0.0f, 0.0), x - dx*(aboveHWLtxUnits + 1), y -dy*(aboveHWLtxUnits + 1)); + + // Create the sand above the high water mark + for (int d = 0; d < aboveHWLtxUnits; d++) { + updateWaterTexture(waterTexture, waterTextureSize, osg::Vec4(0.0f, 1.0f, 0.0f, 0.0), x - dx*d, y -dy*d); + } + + // Create the shoreline below the high water level which will gradually merge into the underlying terrain mesh + for (int d = 0; d < belowHWLtxUnits; d++) { + updateWaterTexture(waterTexture, waterTextureSize, osg::Vec4(0.0f, 0.0f, 1.0f, 0.0)*(belowHWLtxUnits -d)/belowHWLtxUnits, x + dx*d, y + dy*d); + } +} + +void VPBTechnique::addCoastline(Locator* masterLocator, osg::Image* waterTexture, LineFeatureBin::LineFeature line, unsigned int waterTextureSize, float tileSize, float coastWidth) { + + + if (line._nodes.size() < 2) { SG_LOG(SG_TERRAIN, SG_ALERT, "Coding error - LineFeatureBin::LineFeature with fewer than two nodes"); return; } - osg::Vec3d ma, mb; - std::list coastlinePoints; - auto coastline_iter = coastline._nodes.begin(); + auto iter = line._nodes.begin(); - // We're in Earth-centered coordinates, so "up" is simply directly away from (0,0,0) - osg::Vec3d up = modelCenter; - up.normalize(); - - ma = getMeshIntersection(buffer, masterLocator, *coastline_iter - modelCenter, up); - coastline_iter++; - - for (; coastline_iter != coastline._nodes.end(); coastline_iter++) { - mb = getMeshIntersection(buffer, masterLocator, *coastline_iter - modelCenter, up); - auto esl = VPBElevationSlice::computeVPBElevationSlice(buffer._landGeometry, ma, mb, up); - - for(auto eslitr = esl.begin(); eslitr != esl.end(); ++eslitr) { - coastlinePoints.push_back(*eslitr); - } - - // Now traverse the next segment - ma = mb; + // LineFeature is in Model coordinates, but for the rasterization we will use local coordinates. + osg::Vec3d start; + bool success = masterLocator->convertModelToLocal(*iter, start); + if (!success) { + SG_LOG(SG_TERRAIN, SG_ALERT, "Unable to convert from model coordinates to local: " << *iter); + return; } - if (coastlinePoints.size() == 0) return; - - // We now have a series of points following the topography of the elevation mesh. - - auto iter = coastlinePoints.begin(); - osg::Vec3d start = *iter; iter++; + for (; iter != line._nodes.end(); iter++) { + osg::Vec3d end; + success = masterLocator->convertModelToLocal(*iter, end); + if (!success) { + SG_LOG(SG_TERRAIN, SG_ALERT, "Unable to convert from model coordinates to local: " << *iter); + continue; + } - osg::Vec3d last_spanwise = (*iter - start)^ up; - last_spanwise.normalize(); + // We now have two points in local (2d) space, so rasterize the line between them onto the water texture. + // We use a simple version of a DDA to render the lines. We should replace this with a scanline algorithm to handle coastlines + // and waterbodies efficiently. + // Do everything in the texture coordinates. + float dx = (end.x() - start.x()) * waterTextureSize; + float dy = (end.y() - start.y()) * waterTextureSize; + float step = abs(dy); + bool steep = true; + if (abs(dx) >= abs(dy)) { + step = abs(dx); + steep = false; + } + dx = dx / step; + dy = dy / step; + float x = start.x() * waterTextureSize; + float y = start.y() * waterTextureSize; + int i = 0; + while (i <= step) { + if ((x >= 0.0f) && (y >= 0.0f) && (x < waterTextureSize) && (y < waterTextureSize)) { + // The line defines the Mean High Water Level. By definition, the sea is always to + // the right of the line. We want to fill in a section of sea and shore to cover up any issues + // between the OSM data and the landclass texture. - float yTexBaseA = 0.0f; - float yTexBaseB = 0.0f; + if (steep) { + // A steep line, so we are should add width in the x-axis + if (dy < 0) { + // We are travelling downwards, so the seaward side is -x + writeShoreStripe(waterTexture, waterTextureSize, tileSize, coastWidth, x , y, -1, 0); + } else { + // We are travelling upwards, so the seaward side is +x + writeShoreStripe(waterTexture, waterTextureSize, tileSize, coastWidth, x , y, 1, 0); + } + } else { + // Not a steep line, so we should add width in the y axis + if (dx < 0) { + // We are travelling right to left, so the seaward side is +y + writeShoreStripe(waterTexture, waterTextureSize, tileSize, coastWidth, x , y, 0, 1); + } else { + // We are travelling left to right, so the seaward side is -y + writeShoreStripe(waterTexture, waterTextureSize, tileSize, coastWidth, x , y, 0, -1); + } + } + } - for (; iter != coastlinePoints.end(); iter++) { - - osg::Vec3d end = *iter; - - // Ignore small segments - we really don't need resolution less than 10m - if ((end - start).length2() < 100.0) continue; - - // Find a spanwise vector - osg::Vec3d spanwise = ((end-start) ^ up); - spanwise.normalize(); - - // Define the coastline extents. Angle it down slightly on the seaward side (b->d). - // OSM coastlines are always with the - const osg::Vec3d a = start + up; - const osg::Vec3d b = start + last_spanwise * coastline._width; - const osg::Vec3d c = end + up; - const osg::Vec3d d = end + spanwise * coastline._width; - - // Determine the x and y texture coordinates for the edges - const float xTex = coastline._width / xsize; - const float yTexA = yTexBaseA + (c-a).length() / ysize; - const float yTexB = yTexBaseB + (d-b).length() / ysize; - - // Now generate two triangles, . - v->push_back(a); - v->push_back(b); - v->push_back(c); - - t->push_back(osg::Vec2d(0, yTexBaseA)); - t->push_back(osg::Vec2d(xTex, yTexBaseB)); - t->push_back(osg::Vec2d(0, yTexA)); - - v->push_back(b); - v->push_back(d); - v->push_back(c); - - t->push_back(osg::Vec2d(xTex, yTexBaseB)); - t->push_back(osg::Vec2d(xTex, yTexB)); - t->push_back(osg::Vec2d(0, yTexA)); - - // Normal is straight from the quad - osg::Vec3d normal = -(end-start)^spanwise; - normal.normalize(); - for (unsigned int i = 0; i < 6; i++) n->push_back(normal); + x = x + dx; + y = y + dy; + i = i + 1; + } start = end; - yTexBaseA = yTexA; - yTexBaseB = yTexB; - last_spanwise = spanwise; } } diff --git a/simgear/scene/tgdb/VPBTechnique.hxx b/simgear/scene/tgdb/VPBTechnique.hxx index f83748f4..eb3084e0 100644 --- a/simgear/scene/tgdb/VPBTechnique.hxx +++ b/simgear/scene/tgdb/VPBTechnique.hxx @@ -169,16 +169,10 @@ class VPBTechnique : public TerrainTechnique unsigned int xsize, unsigned int ysize); - virtual void applyCoastline(BufferData& buffer, Locator* masterLocator); - virtual void generateCoastlineFeature(BufferData& buffer, - Locator* masterLocator, - LineFeatureBin::LineFeature coastLine, - osg::Vec3d modelCenter, - osg::Vec3Array* v, - osg::Vec2Array* t, - osg::Vec3Array* n, - unsigned int xsize, - unsigned int ysize); + virtual osg::Image* generateWaterTexture(BufferData& buffer, Locator* masterLocator); + virtual void addCoastline(Locator* masterLocator, osg::Image* waterTexture, LineFeatureBin::LineFeature line, unsigned int waterTextureSize, float tileSize, float coastWidth); + virtual void updateWaterTexture(osg::Image* waterTexture, unsigned int waterTextureSize, osg::Vec4 color, float x, float y); + virtual void writeShoreStripe(osg::Image* waterTexture, unsigned int waterTextureSize, float tileSize, float coastWidth, float x, float y, int dx, int dy); virtual osg::Vec3d getMeshIntersection(BufferData& buffer, Locator* masterLocator, osg::Vec3d pt, osg::Vec3d up);