From 6c30f6269388c8eb4b850e4817567c06f79c8d80 Mon Sep 17 00:00:00 2001 From: timoore Date: Sun, 12 Aug 2007 21:03:43 +0000 Subject: [PATCH] Add curvature to ocean tiles. An apron around the tile hides cracks with coastal tiles. The VectorArrayAdapter, which lives in the simgear namespace, is a useful utility class for treating vectors as 2D arrays. --- simgear/misc/texcoord.cxx | 8 +- simgear/misc/texcoord.hxx | 3 + simgear/scene/tgdb/SGOceanTile.cxx | 362 +++++++++++++++------- simgear/scene/util/Makefile.am | 4 +- simgear/scene/util/VectorArrayAdapter.hxx | 63 ++++ 5 files changed, 333 insertions(+), 107 deletions(-) create mode 100644 simgear/scene/util/VectorArrayAdapter.hxx diff --git a/simgear/misc/texcoord.cxx b/simgear/misc/texcoord.cxx index 885728e9..ea56ea93 100644 --- a/simgear/misc/texcoord.cxx +++ b/simgear/misc/texcoord.cxx @@ -176,13 +176,19 @@ static inline Point3D basic_tex_coord( const Point3D& p, // calculate "none stretching" texture coordinates point_list sgCalcTexCoords( const SGBucket& b, const point_list& geod_nodes, const int_list& fan, double scale ) +{ + return sgCalcTexCoords(b.get_center_lat(), geod_nodes, fan, scale); +} + +point_list sgCalcTexCoords( double centerLat, const point_list& geod_nodes, + const int_list& fan, double scale ) { // cout << "calculating texture coordinates for a specific fan of size = " // << fan.size() << endl; // calculate perimeter based on center of this degree (not center // of bucket) - double clat = (int)b.get_center_lat(); + double clat = (int)centerLat; if ( clat > 0 ) { clat = (int)clat + 0.5; } else { diff --git a/simgear/misc/texcoord.hxx b/simgear/misc/texcoord.hxx index 127c0c1e..a5d49bd4 100644 --- a/simgear/misc/texcoord.hxx +++ b/simgear/misc/texcoord.hxx @@ -49,5 +49,8 @@ point_list sgCalcTexCoords( const SGBucket& b, const point_list& geod_nodes, const int_list& fan, double scale = 1.0 ); +point_list sgCalcTexCoords( double centerLat, const point_list& geod_nodes, + const int_list& fan, double scale = 1.0 ); + #endif // _TEXCOORD_HXX diff --git a/simgear/scene/tgdb/SGOceanTile.cxx b/simgear/scene/tgdb/SGOceanTile.cxx index 797b89fc..af1dddf4 100644 --- a/simgear/scene/tgdb/SGOceanTile.cxx +++ b/simgear/scene/tgdb/SGOceanTile.cxx @@ -1,6 +1,6 @@ /* -*-c++-*- * - * Copyright (C) 2006-2007 Mathias Froehlich + * Copyright (C) 2006-2007 Mathias Froehlich, Tim Moore * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -25,6 +25,7 @@ #include "SGOceanTile.hxx" +#include #include #include @@ -38,124 +39,275 @@ #include #include #include +#include -void fillDrawElements(int width, int height, - osg::DrawElementsUShort::vector_type::iterator elements) +using namespace simgear; +// Ocean tile with curvature and apron to hide cracks. The cracks are +// mostly with adjoining coastal tiles that assume a flat ocean +// between corners of a tile; they also hide the micro cracks between +// adjoining ocean tiles. This is probably over-engineered, but it +// serves as a testbed for some things that will come later. + +// Helper class for building and accessing the mesh. The layout of the +// points in the mesh is a little wacky. First is the bottom row of +// the points for the apron. Next is the left apron point, the points +// in the mesh, and the right apron point, for each of the rows of the +// mesh; the points for the top apron come last. This order should +// help with things like vertex caching in the OpenGL driver, though +// it may be superfluous for such a small mesh. +namespace { - for (int j = 0; j < height - 1; j++) { - for (int i = 0; i < width - 1; i++) { - *elements++ = j * width + i; - *elements++ = j * width + i + 1; - *elements++ = (j + 1) * width + i; - *elements++ = (j + 1) * width + i; - *elements++ = j * width + i + 1; - *elements++ = (j + 1) * width + i + 1; +const int lonPoints = 5; +const int latPoints = 5; + +class OceanMesh { +public: + OceanMesh(): + geoPoints(latPoints * lonPoints + 2 * (lonPoints + latPoints)), + geod_nodes(latPoints * lonPoints), + vl(new osg::Vec3Array(geoPoints)), + nl(new osg::Vec3Array(geoPoints)), + tl(new osg::Vec2Array(geoPoints)), + vlArray(*vl, lonPoints + 2, lonPoints, 1), + nlArray(*nl, lonPoints + 2, lonPoints, 1), + tlArray(*tl, lonPoints + 2, lonPoints, 1) + { + } + const int geoPoints; + SGGeod geod[latPoints][lonPoints]; + SGVec3f normals[latPoints][lonPoints]; + SGVec3d rel[latPoints][lonPoints]; + + point_list geod_nodes; + + osg::Vec3Array* vl; + osg::Vec3Array* nl; + osg::Vec2Array* tl; + VectorArrayAdapter vlArray; + VectorArrayAdapter nlArray; + VectorArrayAdapter tlArray; + + void calcMesh(const SGVec3d& cartCenter, double clon, double clat, + double height, double width, double tex_width); + void calcApronPt(int latIdx, int lonIdx, int latInner, int lonInner, + int destIdx, double tex_width); + void calcApronPts(double tex_width); + +}; +} + +void OceanMesh::calcMesh(const SGVec3d& cartCenter, double clon, double clat, + double height, double width, double tex_width) +{ + // Calculate vertices. By splitting the tile up into 4 quads on a + // side we avoid curvature-of-the-earth problems; the error should + // be less than .5 meters. + double longInc = width * .25; + double latInc = height * .25; + double startLat = clat - height * .5; + double startLon = clon - width * .5; + for (int j = 0; j < latPoints; j++) { + double lat = startLat + j * latInc; + for (int i = 0; i < lonPoints; i++) { + geod[j][i] = SGGeod::fromDeg(startLon + i * longInc, lat); + SGVec3d cart = SGVec3d::fromGeod(geod[j][i]); + rel[j][i] = cart - cartCenter; + normals[j][i] = toVec3f(normalize(cart)); + } + } + + // Calculate texture coordinates + point_list geod_nodes(latPoints * lonPoints); + VectorArrayAdapter geodNodesArray(geod_nodes, lonPoints); + int_list rectangle(latPoints * lonPoints); + VectorArrayAdapter rectArray(rectangle, lonPoints); + for (int j = 0; j < latPoints; j++) { + for (int i = 0; i < lonPoints; i++) { + geodNodesArray(j, i) = Point3D(geod[j][i].getLongitudeDeg(), + geod[j][i].getLatitudeDeg(), + geod[j][i].getElevationM()); + rectArray(j, i) = j * 5 + i; + } + } + point_list texs = sgCalcTexCoords( clat, geod_nodes, rectangle, + 1000.0 / tex_width ); + VectorArrayAdapter texsArray(texs, lonPoints); + + for (int j = 0; j < latPoints; j++) { + for (int i = 0; i < lonPoints; ++i) { + vlArray(j, i) = rel[j][i].osg(); + nlArray(j, i) = normals[j][i].osg(); + tlArray(j, i) = texsArray(j, i).toSGVec2f().osg(); } } + +} + +// Apron points. For each point on the edge we'll go 150 +// metres "down" and 40 metres "out" to create a nice overlap. The +// texture should be applied according to this dimension. The +// normals of the apron polygons will be the same as the those of +// the points on the edge to better disguise the apron. +void OceanMesh::calcApronPt(int latIdx, int lonIdx, int latInner, int lonInner, + int destIdx, double tex_width) +{ + static const float downDist = 150.0f; + static const float outDist = 40.0f; + // Get vector along edge, in the right direction to make a cross + // product with the normal vector that will point out from the + // mesh. + osg::Vec3f edgePt = vlArray(latIdx, lonIdx); + osg::Vec3f edgeVec; + if (lonIdx == lonInner) { // bottom or top edge + if (lonIdx > 0) + edgeVec = vlArray(latIdx, lonIdx - 1) - edgePt; + else + edgeVec = edgePt - vlArray(latIdx, lonIdx + 1); + if (latIdx > latInner) + edgeVec = -edgeVec; // Top edge + } else { // right or left edge + if (latIdx > 0) + edgeVec = edgePt - vlArray(latIdx - 1, lonIdx); + else + edgeVec = vlArray(latIdx + 1, lonIdx) - edgePt; + if (lonIdx > lonInner) // right edge + edgeVec = -edgeVec; + } + edgeVec.normalize(); + osg::Vec3f outVec = nlArray(latIdx, lonIdx) ^ edgeVec; + (*vl)[destIdx] + = edgePt - nlArray(latIdx, lonIdx) * downDist + outVec * outDist; + (*nl)[destIdx] = nlArray(latIdx, lonIdx); + static const float apronDist + = sqrtf(downDist * downDist + outDist * outDist); + float texDelta = apronDist / tex_width; + if (lonIdx == lonInner) { + if (latIdx > latInner) + (*tl)[destIdx] + = tlArray(latIdx, lonIdx) + osg::Vec2f(0.0f, texDelta); + else + (*tl)[destIdx] + = tlArray(latIdx, lonIdx) - osg::Vec2f(0.0f, texDelta); + } else { + if (lonIdx > lonInner) + (*tl)[destIdx] + = tlArray(latIdx, lonIdx) + osg::Vec2f(texDelta, 0.0f); + else + (*tl)[destIdx] + = tlArray(latIdx, lonIdx) - osg::Vec2f(texDelta, 0.0f); + } +} + +void OceanMesh::calcApronPts(double tex_width) +{ + for (int i = 0; i < lonPoints; i++) + calcApronPt(0, i, 1, i, i, tex_width); + int topApronOffset = latPoints + (2 + lonPoints) * latPoints; + for (int i = 0; i < lonPoints; i++) + calcApronPt(latPoints - 1, i, latPoints - 2, i, + i + topApronOffset, tex_width); + for (int i = 0; i < latPoints; i++) { + calcApronPt(i, 0, i, 1, lonPoints + i * (lonPoints + 2), tex_width); + calcApronPt(i, lonPoints - 1, i, lonPoints - 2, + lonPoints + i * (lonPoints + 2) + 1 + lonPoints, tex_width); + } +} + +namespace +{ +// Enter the vertices of triangles that fill one row of the +// mesh. The vertices are entered in counter-clockwise order. +void fillDrawElementsRow(int width, short row0Start, short row1Start, + osg::DrawElementsUShort::vector_type::iterator& + elements) +{ + short row0Idx = row0Start; + short row1Idx = row1Start; + for (int i = 0; i < width - 1; i++, row0Idx++, row1Idx++) { + *elements++ = row0Idx; + *elements++ = row0Idx + 1; + *elements++ = row1Idx; + *elements++ = row1Idx; + *elements++ = row0Idx + 1; + *elements++ = row1Idx + 1; + } +} + +void fillDrawElementsWithApron(short height, short width, + osg::DrawElementsUShort::vector_type::iterator + elements) +{ + // First apron row + fillDrawElementsRow(width, 0, width + 1, elements); + for (short i = 0; i < height - 1; i++) + fillDrawElementsRow(width + 2, width + i * (width + 2), + width + (i + 1) * (width + 2), + elements); + // Last apron row + short topApronBottom = width + (height - 1) * (width + 2) + 1; + fillDrawElementsRow(width, topApronBottom, topApronBottom + width + 1, + elements); +} } -// Generate an ocean tile osg::Node* SGOceanTile(const SGBucket& b, SGMaterialLib *matlib) { - osg::StateSet *stateSet = 0; + osg::StateSet *stateSet = 0; - double tex_width = 1000.0; - - // find Ocean material in the properties list - SGMaterial *mat = matlib->find( "Ocean" ); - if ( mat != NULL ) { - // set the texture width and height values for this - // material - tex_width = mat->get_xsize(); - - // set ssgState - stateSet = mat->get_state(); - } else { - SG_LOG( SG_TERRAIN, SG_ALERT, "Ack! unknown use material name = Ocean"); - } - - // Calculate center point - SGVec3d cartCenter = SGVec3d::fromGeod(b.get_center()); + double tex_width = 1000.0; - double clon = b.get_center_lon(); - double clat = b.get_center_lat(); - double height = b.get_height(); - double width = b.get_width(); - - // Calculate vertices. By splitting the tile up into 4 quads on a - // side we avoid curvature-of-the-earth problems; the error should - // be less than .5 meters. - SGGeod geod[5][5]; - SGVec3f normals[5][5]; - SGVec3d rel[5][5]; - double longInc = width * .25; - double latInc = height * .25; - double startLat = clat - height * .5; - double startLon = clon - width * .5; - for (int j = 0; j < 5; j++) { - double lat = startLat + j * latInc; - for (int i = 0; i < 5; i++) { - geod[i][j] = SGGeod::fromDeg(startLon + i * longInc, lat); - SGVec3d cart = SGVec3d::fromGeod(geod[i][j]); - rel[i][j] = cart - cartCenter; - normals[i][j] = toVec3f(normalize(cart)); - } - } + // find Ocean material in the properties list + SGMaterial *mat = matlib->find( "Ocean" ); + if ( mat != NULL ) { + // set the texture width and height values for this + // material + tex_width = mat->get_xsize(); - // Calculate texture coordinates - point_list geod_nodes; - geod_nodes.reserve(5 * 5); - int_list rectangle; - rectangle.reserve(5 * 5); - for (int j = 0; j < 5; j++) { - for (int i = 0; i < 5; ++i) { - geod_nodes.push_back(Point3D(geod[i][j].getLongitudeDeg(), - geod[i][j].getLatitudeDeg(), - geod[i][j].getElevationM())); - rectangle.push_back(j * 5 + i); + // set OSG State + stateSet = mat->get_state(); + } else { + SG_LOG( SG_TERRAIN, SG_ALERT, "Ack! unknown use material name = Ocean"); } - } - point_list texs = sgCalcTexCoords( b, geod_nodes, rectangle, - 1000.0 / tex_width ); + OceanMesh grid; + // Calculate center point + SGVec3d cartCenter = SGVec3d::fromGeod(b.get_center()); - // Allocate osg structures - osg::Vec3Array *vl = new osg::Vec3Array; - osg::Vec3Array *nl = new osg::Vec3Array; - osg::Vec2Array *tl = new osg::Vec2Array; - - for (int j = 0; j < 5; j++) { - for (int i = 0; i < 5; ++i) { - vl->push_back(rel[i][j].osg()); - nl->push_back(normals[i][j].osg()); - tl->push_back(texs[j * 5 + i].toSGVec2f().osg()); - } - } + double clon = b.get_center_lon(); + double clat = b.get_center_lat(); + double height = b.get_height(); + double width = b.get_width(); + + grid.calcMesh(cartCenter, clon, clat, height, width, tex_width); + grid.calcApronPts(tex_width); - osg::Vec4Array* cl = new osg::Vec4Array; - cl->push_back(osg::Vec4(1, 1, 1, 1)); + osg::Vec4Array* cl = new osg::Vec4Array; + cl->push_back(osg::Vec4(1, 1, 1, 1)); - osg::Geometry* geometry = new osg::Geometry; - geometry->setVertexArray(vl); - geometry->setNormalArray(nl); - geometry->setNormalBinding(osg::Geometry::BIND_PER_VERTEX); - geometry->setColorArray(cl); - geometry->setColorBinding(osg::Geometry::BIND_OVERALL); - geometry->setTexCoordArray(0, tl); - - osg::DrawElementsUShort* drawElements - = new osg::DrawElementsUShort(GL_TRIANGLES, 32 * 3); - fillDrawElements(5, 5, drawElements->begin()); - geometry->addPrimitiveSet(drawElements); - - osg::Geode* geode = new osg::Geode; - geode->setName("Ocean tile"); - geode->addDrawable(geometry); - geode->setStateSet(stateSet); - - osg::MatrixTransform* transform = new osg::MatrixTransform; - transform->setName("Ocean"); - transform->setMatrix(osg::Matrix::translate(cartCenter.osg())); - transform->addChild(geode); + osg::Geometry* geometry = new osg::Geometry; + geometry->setVertexArray(grid.vl); + geometry->setNormalArray(grid.nl); + geometry->setNormalBinding(osg::Geometry::BIND_PER_VERTEX); + geometry->setColorArray(cl); + geometry->setColorBinding(osg::Geometry::BIND_OVERALL); + geometry->setTexCoordArray(0, grid.tl); + + // Allocate the indices for triangles in the mesh and the apron + osg::DrawElementsUShort* drawElements + = new osg::DrawElementsUShort(GL_TRIANGLES, + 6 * ((latPoints - 1) * (lonPoints + 1) + + 2 * (latPoints - 1))); + fillDrawElementsWithApron(latPoints, lonPoints, drawElements->begin()); + geometry->addPrimitiveSet(drawElements); + + osg::Geode* geode = new osg::Geode; + geode->setName("Ocean tile"); + geode->addDrawable(geometry); + geode->setStateSet(stateSet); + + osg::MatrixTransform* transform = new osg::MatrixTransform; + transform->setName("Ocean"); + transform->setMatrix(osg::Matrix::translate(cartCenter.osg())); + transform->addChild(geode); - return transform; + return transform; } diff --git a/simgear/scene/util/Makefile.am b/simgear/scene/util/Makefile.am index f7615c8e..869fc8eb 100644 --- a/simgear/scene/util/Makefile.am +++ b/simgear/scene/util/Makefile.am @@ -13,7 +13,9 @@ include_HEADERS = \ SGSceneFeatures.hxx \ SGSceneUserData.hxx \ SGStateAttributeVisitor.hxx \ - SGTextureStateAttributeVisitor.hxx + SGTextureStateAttributeVisitor.hxx \ + VectorArrayAdapter.hxx + libsgutil_a_SOURCES = \ SGEnlargeBoundingBox.cxx \ diff --git a/simgear/scene/util/VectorArrayAdapter.hxx b/simgear/scene/util/VectorArrayAdapter.hxx new file mode 100644 index 00000000..44c515b9 --- /dev/null +++ b/simgear/scene/util/VectorArrayAdapter.hxx @@ -0,0 +1,63 @@ +/* -*-c++-*- + * + * Copyright (C) 2007 Tim Moore + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + * + */ + +#ifndef VECTORARRAYADAPTERHXX +#define VECTORARRAYADAPTERHXX 1 + +namespace simgear +{ +template +class VectorArrayAdapter { +public: + /* + * Adapter that provides 2D array access to the elements of a + * std::vector in row major order. + * @param v the vector + * @param rowStride distance from an element to the corresponding + * element in the next row. + * @param baseOffset offset of the first element of the array from the + * beginning of the vector. + * @param rowOffset offset of the first element in a row from the + * actual beginning of the row. + */ + VectorArrayAdapter(Vector& v, int rowStride, int baseOffset = 0, + int rowOffset = 0): + _v(v), _rowStride(rowStride), _baseOffset(baseOffset), + _rowOffset(rowOffset) + { + } + + typename Vector::value_type& operator() (int i, int j) + { + return _v[_baseOffset + i * _rowStride + _rowOffset + j]; + } + const typename Vector::value_type& operator() (int i, int j) const + { + return _v[_baseOffset + i * _rowStride + _rowOffset + j]; + } +private: + Vector& _v; + const int _rowStride; + const int _baseOffset; + const int _rowOffset; +}; +} +#endif -- 2.39.5