1 /* -*-c++-*-
2  *
3  * Copyright (C) 2006-2007 Mathias Froehlich, Tim Moore
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License as
7  * published by the Free Software Foundation; either version 2 of the
8  * License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18  * MA 02110-1301, USA.
19  *
20  */
21 
22 #ifdef HAVE_CONFIG_H
23 #  include <simgear_config.h>
24 #endif
25 
26 #include "SGOceanTile.hxx"
27 
28 #include <math.h>
29 #include <simgear/compiler.h>
30 
31 #include <osg/Geode>
32 #include <osg/Geometry>
33 #include <osg/MatrixTransform>
34 #include <osg/StateSet>
35 
36 #include <simgear/bucket/newbucket.hxx>
37 #include <simgear/math/sg_geodesy.hxx>
38 #include <simgear/math/sg_types.hxx>
39 #include <simgear/misc/texcoord.hxx>
40 #include <simgear/scene/material/Effect.hxx>
41 #include <simgear/scene/material/EffectGeode.hxx>
42 #include <simgear/scene/material/mat.hxx>
43 #include <simgear/scene/material/matlib.hxx>
44 #include <simgear/scene/model/BoundingVolumeBuildVisitor.hxx>
45 #include <simgear/scene/util/OsgMath.hxx>
46 #include <simgear/scene/util/VectorArrayAdapter.hxx>
47 #include <simgear/scene/util/SGNodeMasks.hxx>
48 
49 using namespace simgear;
50 // Ocean tile with curvature and apron to hide cracks. The cracks are
51 // mostly with adjoining coastal tiles that assume a flat ocean
52 // between corners of a tile; they also hide the micro cracks between
53 // adjoining ocean tiles. This is probably over-engineered, but it
54 // serves as a testbed for some things that will come later.
55 
56 // Helper class for building and accessing the mesh. The layout of the
57 // points in the mesh is a little wacky. First is the bottom row of
58 // the points for the apron. Next is the left apron point, the points
59 // in the mesh, and the right apron point, for each of the rows of the
60 // mesh; the points for the top apron come last. This order should
61 // help with things like vertex caching in the OpenGL driver, though
62 // it may be superfluous for such a small mesh.
63 namespace
64 {
65 class OceanMesh {
66 public:
OceanMesh(int latP,int lonP)67     OceanMesh(int latP, int lonP):
68         latPoints(latP),
69         lonPoints(lonP),
70         geoPoints(latPoints * lonPoints + 2 * (lonPoints + latPoints)),
71         geod_nodes(latPoints * lonPoints),
72         vl(new osg::Vec3Array(geoPoints)),
73         nl(new osg::Vec3Array(geoPoints)),
74         tl(new osg::Vec2Array(geoPoints)),
75         vlArray(*vl, lonPoints + 2, lonPoints, 1),
76         nlArray(*nl, lonPoints + 2, lonPoints, 1),
77         tlArray(*tl, lonPoints + 2, lonPoints, 1)
78     {
79         int numPoints = latPoints * lonPoints;
80         geod = new SGGeod[numPoints];
81         normals = new SGVec3f[numPoints];
82         rel = new SGVec3d[numPoints];
83     }
84 
~OceanMesh()85     ~OceanMesh()
86     {
87         delete[] geod;
88         delete[] normals;
89         delete[] rel;
90     }
91 
92     const int latPoints, lonPoints;
93     const int geoPoints;
94     SGGeod* geod;
95     SGVec3f* normals;
96     SGVec3d* rel;
97 
98     std::vector<SGGeod> geod_nodes;
99 
100     osg::Vec3Array* vl;
101     osg::Vec3Array* nl;
102     osg::Vec2Array* tl;
103     VectorArrayAdapter<osg::Vec3Array> vlArray;
104     VectorArrayAdapter<osg::Vec3Array> nlArray;
105     VectorArrayAdapter<osg::Vec2Array> tlArray;
106 
107     void calcMesh(const SGVec3d& cartCenter, const SGQuatd& orient,
108                   double clon, double clat,
109                   double height, double width, double tex_width);
110     void calcApronPt(int latIdx, int lonIdx, int latInner, int lonInner,
111                      int destIdx, double tex_width);
112     void calcApronPts(double tex_width);
113 
114 };
115 
calcMesh(const SGVec3d & cartCenter,const SGQuatd & orient,double clon,double clat,double height,double width,double tex_width)116 void OceanMesh::calcMesh(const SGVec3d& cartCenter, const SGQuatd& orient,
117                          double clon, double clat,
118                          double height, double width, double tex_width)
119 {
120     // Calculate vertices. By splitting the tile up into 4 quads on a
121     // side we avoid curvature-of-the-earth problems; the error should
122     // be less than .5 meters.
123     double longInc = width * .25;
124     double latInc = height * .25;
125     double startLat = clat - height * .5;
126     double startLon = clon - width * .5;
127     for (int j = 0; j < latPoints; j++) {
128         double lat = startLat + j * latInc;
129         for (int i = 0; i < lonPoints; i++) {
130             int index = (j * lonPoints) + i;
131             geod[index] = SGGeod::fromDeg(startLon + i * longInc, lat);
132             SGVec3d cart = SGVec3d::fromGeod(geod[index]);
133             rel[index] = orient.transform(cart - cartCenter);
134             normals[index] = toVec3f(orient.transform(normalize(cart)));
135         }
136     }
137 
138     // Calculate texture coordinates
139     typedef std::vector<SGGeod> GeodVector;
140 
141     GeodVector geod_nodes(latPoints * lonPoints);
142     VectorArrayAdapter<GeodVector> geodNodesArray(geod_nodes, lonPoints);
143     int_list rectangle(latPoints * lonPoints);
144     VectorArrayAdapter<int_list> rectArray(rectangle, lonPoints);
145     for (int j = 0; j < latPoints; j++) {
146         for (int i = 0; i < lonPoints; i++) {
147             int index = (j * lonPoints) + i;
148             geodNodesArray(j, i) = geod[index];
149             rectArray(j, i) = index;
150         }
151     }
152 
153     typedef std::vector<SGVec2f> Vec2Array;
154     Vec2Array texs = sgCalcTexCoords( clat, geod_nodes, rectangle,
155                                        1000.0 / tex_width );
156 
157 
158     VectorArrayAdapter<Vec2Array> texsArray(texs, lonPoints);
159 
160     for (int j = 0; j < latPoints; j++) {
161         for (int i = 0; i < lonPoints; ++i) {
162             int index = (j * lonPoints) + i;
163             vlArray(j, i) = toOsg(rel[index]);
164             nlArray(j, i) = toOsg(normals[index]);
165             tlArray(j, i) = toOsg(texsArray(j, i));
166         }
167     }
168 
169 }
170 
171 // Apron points. For each point on the edge we'll go 150
172 // metres "down" and 40 metres "out" to create a nice overlap. The
173 // texture should be applied according to this dimension. The
174 // normals of the apron polygons will be the same as the those of
175 // the points on the edge to better disguise the apron.
calcApronPt(int latIdx,int lonIdx,int latInner,int lonInner,int destIdx,double tex_width)176 void OceanMesh::calcApronPt(int latIdx, int lonIdx, int latInner, int lonInner,
177                             int destIdx, double tex_width)
178 {
179     static const float downDist = 150.0f;
180     static const float outDist = 40.0f;
181     // Get vector along edge, in the right direction to make a cross
182     // product with the normal vector that will point out from the
183     // mesh.
184     osg::Vec3f edgePt = vlArray(latIdx, lonIdx);
185     osg::Vec3f edgeVec;
186     if (lonIdx == lonInner) {   // bottom or top edge
187         if (lonIdx > 0)
188             edgeVec = vlArray(latIdx, lonIdx - 1) - edgePt;
189         else
190             edgeVec = edgePt - vlArray(latIdx, lonIdx + 1);
191         if (latIdx > latInner)
192             edgeVec = -edgeVec;  // Top edge
193     } else {                     // right or left edge
194         if (latIdx > 0)
195             edgeVec = edgePt - vlArray(latIdx - 1, lonIdx);
196         else
197             edgeVec = vlArray(latIdx + 1, lonIdx) - edgePt;
198         if (lonIdx > lonInner)  // right edge
199             edgeVec = -edgeVec;
200     }
201     edgeVec.normalize();
202     osg::Vec3f outVec = nlArray(latIdx, lonIdx) ^ edgeVec;
203     (*vl)[destIdx]
204         = edgePt - nlArray(latIdx, lonIdx) * downDist + outVec * outDist;
205     (*nl)[destIdx] = nlArray(latIdx, lonIdx);
206     static const float apronDist
207         = sqrtf(downDist * downDist  + outDist * outDist);
208     float texDelta = apronDist / tex_width;
209     if (lonIdx == lonInner) {
210         if (latIdx > latInner)
211             (*tl)[destIdx]
212                 = tlArray(latIdx, lonIdx) + osg::Vec2f(0.0f, texDelta);
213         else
214             (*tl)[destIdx]
215                 = tlArray(latIdx, lonIdx) - osg::Vec2f(0.0f, texDelta);
216     } else {
217         if (lonIdx > lonInner)
218             (*tl)[destIdx]
219                 = tlArray(latIdx, lonIdx) + osg::Vec2f(texDelta, 0.0f);
220         else
221             (*tl)[destIdx]
222                 = tlArray(latIdx, lonIdx) - osg::Vec2f(texDelta, 0.0f);
223     }
224 }
225 
calcApronPts(double tex_width)226 void OceanMesh::calcApronPts(double tex_width)
227 {
228     for (int i = 0; i < lonPoints; i++)
229         calcApronPt(0, i, 1, i, i, tex_width);
230     int topApronOffset = latPoints + (2 + lonPoints) * latPoints;
231     for (int i = 0; i < lonPoints; i++)
232         calcApronPt(latPoints - 1, i, latPoints - 2, i,
233                     i + topApronOffset, tex_width);
234     for (int i = 0; i < latPoints; i++) {
235         calcApronPt(i, 0, i, 1, lonPoints + i * (lonPoints + 2), tex_width);
236         calcApronPt(i, lonPoints - 1, i, lonPoints - 2,
237                     lonPoints + i * (lonPoints + 2) + 1 + lonPoints, tex_width);
238     }
239 }
240 
241 // Enter the vertices of triangles that fill one row of the
242 // mesh. The vertices are entered in counter-clockwise order.
fillDrawElementsRow(int width,short row0Start,short row1Start,osg::DrawElementsUShort::vector_type::iterator & elements)243 void fillDrawElementsRow(int width, short row0Start, short row1Start,
244                          osg::DrawElementsUShort::vector_type::iterator&
245                          elements)
246 {
247     short row0Idx = row0Start;
248     short row1Idx = row1Start;
249     for (int i = 0; i < width - 1; i++, row0Idx++, row1Idx++) {
250         *elements++ = row0Idx;
251         *elements++ = row0Idx + 1;
252         *elements++ = row1Idx;
253         *elements++ = row1Idx;
254         *elements++ = row0Idx + 1;
255         *elements++ = row1Idx + 1;
256     }
257 }
258 
fillDrawElementsWithApron(short height,short width,osg::DrawElementsUShort::vector_type::iterator elements)259 void fillDrawElementsWithApron(short height, short width,
260                                osg::DrawElementsUShort::vector_type::iterator
261                                elements)
262 {
263     // First apron row
264     fillDrawElementsRow(width, 0, width + 1, elements);
265     for (short i = 0; i < height - 1; i++)
266         fillDrawElementsRow(width + 2, width + i * (width + 2),
267                             width + (i + 1) * (width + 2),
268                             elements);
269     // Last apron row
270     short topApronBottom = width + (height - 1) * (width + 2) + 1;
271     fillDrawElementsRow(width, topApronBottom, topApronBottom + width + 1,
272                         elements);
273 }
274 }
275 
SGOceanTile(const SGBucket & b,SGMaterialLib * matlib,int latPoints,int lonPoints)276 osg::Node* SGOceanTile(const SGBucket& b, SGMaterialLib *matlib, int latPoints, int lonPoints)
277 {
278     Effect *effect = 0;
279 
280     double tex_width = 1000.0;
281 
282     // find Ocean material in the properties list
283     SGMaterialCache* matcache = matlib->generateMatCache(b.get_center());
284     SGMaterial* mat = matcache->find( "Ocean" );
285     delete matcache;
286 
287     if ( mat != NULL ) {
288         // set the texture width and height values for this
289         // material
290         tex_width = mat->get_xsize();
291 
292         // set OSG State
293         effect = mat->get_effect();
294     } else {
295         SG_LOG( SG_TERRAIN, SG_ALERT, "Ack! unknown use material name = Ocean");
296     }
297     OceanMesh grid(latPoints, lonPoints);
298     // Calculate center point
299     SGVec3d cartCenter = SGVec3d::fromGeod(b.get_center());
300     SGGeod geodPos = SGGeod::fromCart(cartCenter);
301     SGQuatd hlOr = SGQuatd::fromLonLat(geodPos)*SGQuatd::fromEulerDeg(0, 0, 180);
302 
303     double clon = b.get_center_lon();
304     double clat = b.get_center_lat();
305     double height = b.get_height();
306     double width = b.get_width();
307 
308     grid.calcMesh(cartCenter, hlOr, clon, clat, height, width, tex_width);
309     grid.calcApronPts(tex_width);
310 
311     osg::Vec4Array* cl = new osg::Vec4Array;
312     cl->push_back(osg::Vec4(1, 1, 1, 1));
313 
314     osg::Geometry* geometry = new osg::Geometry;
315     geometry->setDataVariance(osg::Object::STATIC);
316     geometry->setVertexArray(grid.vl);
317     geometry->setNormalArray(grid.nl);
318     geometry->setNormalBinding(osg::Geometry::BIND_PER_VERTEX);
319     geometry->setColorArray(cl);
320     geometry->setColorBinding(osg::Geometry::BIND_OVERALL);
321     geometry->setTexCoordArray(0, grid.tl);
322 
323     // Allocate the indices for triangles in the mesh and the apron
324     osg::DrawElementsUShort* drawElements
325         = new osg::DrawElementsUShort(GL_TRIANGLES,
326                                       6 * ((latPoints - 1) * (lonPoints + 1)
327                                            + 2 * (latPoints - 1)));
328     fillDrawElementsWithApron(latPoints, lonPoints, drawElements->begin());
329     geometry->addPrimitiveSet(drawElements);
330 
331     EffectGeode* geode = new EffectGeode;
332     geode->setName("Ocean tile");
333     geode->setEffect(effect);
334     geode->addDrawable(geometry);
335     geode->runGenerators(geometry);
336 
337     osg::MatrixTransform* transform = new osg::MatrixTransform;
338     transform->setName("Ocean");
339     transform->setMatrix(osg::Matrix::rotate(toOsg(hlOr))*
340                          osg::Matrix::translate(toOsg(cartCenter)));
341     transform->addChild(geode);
342     transform->setNodeMask( ~(simgear::CASTSHADOW_BIT | simgear::MODELLIGHT_BIT) );
343 
344     // Create a BVH at this point.  This is normally provided by the file loader, but as we create the
345     // geometry programmatically, no file loader is involved.
346     BoundingVolumeBuildVisitor bvhBuilder(false);
347     transform->accept(bvhBuilder);
348 
349     return transform;
350 }
351