]> git.mxchange.org Git - simgear.git/commitdiff
Add curvature to ocean tiles.
authortimoore <timoore>
Sun, 12 Aug 2007 21:03:43 +0000 (21:03 +0000)
committertimoore <timoore>
Sun, 12 Aug 2007 21:03:43 +0000 (21:03 +0000)
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
simgear/misc/texcoord.hxx
simgear/scene/tgdb/SGOceanTile.cxx
simgear/scene/util/Makefile.am
simgear/scene/util/VectorArrayAdapter.hxx [new file with mode: 0644]

index 885728e9df8bb2ec539c0c9bfd62c32c0eeecadc..ea56ea9396879e85692b8ff04b1d96b8146348ee 100644 (file)
@@ -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 {
index 127c0c1e2b93abb84da1e769dd6cea8cb7e2ee08..a5d49bd482fcdc91c9d4ec23d609c31d83c07ea3 100644 (file)
@@ -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
index 797b89fc21a3d9aa12ea9be9917860565933037f..af1dddf4914e46719b92bac6656395e4d2f7ba4e 100644 (file)
@@ -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 <math.h>
 #include <simgear/compiler.h>
 
 #include <osg/Geode>
 #include <simgear/misc/texcoord.hxx>
 #include <simgear/scene/material/mat.hxx>
 #include <simgear/scene/material/matlib.hxx>
+#include <simgear/scene/util/VectorArrayAdapter.hxx>
 
-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<osg::Vec3Array> vlArray;
+    VectorArrayAdapter<osg::Vec3Array> nlArray;
+    VectorArrayAdapter<osg::Vec2Array> 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<point_list> geodNodesArray(geod_nodes, lonPoints);
+    int_list rectangle(latPoints * lonPoints);
+    VectorArrayAdapter<int_list> 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<point_list> 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;
 }
index f7615c8e494cca4e0ce8eb43fe1bfbe3c6efdf14..869fc8ebea484e13ea0bf563a9c3ee5384188eaa 100644 (file)
@@ -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 (file)
index 0000000..44c515b
--- /dev/null
@@ -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 <typename Vector>
+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