3 * Copyright (C) 2006-2007 Mathias Froehlich, Tim Moore
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.
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.
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,
23 # include <simgear_config.h>
26 #include "SGOceanTile.hxx"
29 #include <simgear/compiler.h>
32 #include <osg/Geometry>
33 #include <osg/MatrixTransform>
34 #include <osg/StateSet>
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>
45 #include <simgear/scene/util/VectorArrayAdapter.hxx>
47 using namespace simgear;
48 // Ocean tile with curvature and apron to hide cracks. The cracks are
49 // mostly with adjoining coastal tiles that assume a flat ocean
50 // between corners of a tile; they also hide the micro cracks between
51 // adjoining ocean tiles. This is probably over-engineered, but it
52 // serves as a testbed for some things that will come later.
54 // Helper class for building and accessing the mesh. The layout of the
55 // points in the mesh is a little wacky. First is the bottom row of
56 // the points for the apron. Next is the left apron point, the points
57 // in the mesh, and the right apron point, for each of the rows of the
58 // mesh; the points for the top apron come last. This order should
59 // help with things like vertex caching in the OpenGL driver, though
60 // it may be superfluous for such a small mesh.
63 const int lonPoints = 5;
64 const int latPoints = 5;
69 geoPoints(latPoints * lonPoints + 2 * (lonPoints + latPoints)),
70 geod_nodes(latPoints * lonPoints),
71 vl(new osg::Vec3Array(geoPoints)),
72 nl(new osg::Vec3Array(geoPoints)),
73 tl(new osg::Vec2Array(geoPoints)),
74 vlArray(*vl, lonPoints + 2, lonPoints, 1),
75 nlArray(*nl, lonPoints + 2, lonPoints, 1),
76 tlArray(*tl, lonPoints + 2, lonPoints, 1)
80 SGGeod geod[latPoints][lonPoints];
81 SGVec3f normals[latPoints][lonPoints];
82 SGVec3d rel[latPoints][lonPoints];
84 point_list geod_nodes;
89 VectorArrayAdapter<osg::Vec3Array> vlArray;
90 VectorArrayAdapter<osg::Vec3Array> nlArray;
91 VectorArrayAdapter<osg::Vec2Array> tlArray;
93 void calcMesh(const SGVec3d& cartCenter, const SGQuatd& orient,
94 double clon, double clat,
95 double height, double width, double tex_width);
96 void calcApronPt(int latIdx, int lonIdx, int latInner, int lonInner,
97 int destIdx, double tex_width);
98 void calcApronPts(double tex_width);
103 void OceanMesh::calcMesh(const SGVec3d& cartCenter, const SGQuatd& orient,
104 double clon, double clat,
105 double height, double width, double tex_width)
107 // Calculate vertices. By splitting the tile up into 4 quads on a
108 // side we avoid curvature-of-the-earth problems; the error should
109 // be less than .5 meters.
110 double longInc = width * .25;
111 double latInc = height * .25;
112 double startLat = clat - height * .5;
113 double startLon = clon - width * .5;
114 for (int j = 0; j < latPoints; j++) {
115 double lat = startLat + j * latInc;
116 for (int i = 0; i < lonPoints; i++) {
117 geod[j][i] = SGGeod::fromDeg(startLon + i * longInc, lat);
118 SGVec3d cart = SGVec3d::fromGeod(geod[j][i]);
119 rel[j][i] = orient.transform(cart - cartCenter);
120 normals[j][i] = toVec3f(orient.transform(normalize(cart)));
124 // Calculate texture coordinates
125 point_list geod_nodes(latPoints * lonPoints);
126 VectorArrayAdapter<point_list> geodNodesArray(geod_nodes, lonPoints);
127 int_list rectangle(latPoints * lonPoints);
128 VectorArrayAdapter<int_list> rectArray(rectangle, lonPoints);
129 for (int j = 0; j < latPoints; j++) {
130 for (int i = 0; i < lonPoints; i++) {
131 geodNodesArray(j, i) = Point3D(geod[j][i].getLongitudeDeg(),
132 geod[j][i].getLatitudeDeg(),
133 geod[j][i].getElevationM());
134 rectArray(j, i) = j * 5 + i;
137 point_list texs = sgCalcTexCoords( clat, geod_nodes, rectangle,
138 1000.0 / tex_width );
139 VectorArrayAdapter<point_list> texsArray(texs, lonPoints);
141 for (int j = 0; j < latPoints; j++) {
142 for (int i = 0; i < lonPoints; ++i) {
143 vlArray(j, i) = toOsg(rel[j][i]);
144 nlArray(j, i) = toOsg(normals[j][i]);
145 tlArray(j, i) = toOsg(texsArray(j, i).toSGVec2f());
151 // Apron points. For each point on the edge we'll go 150
152 // metres "down" and 40 metres "out" to create a nice overlap. The
153 // texture should be applied according to this dimension. The
154 // normals of the apron polygons will be the same as the those of
155 // the points on the edge to better disguise the apron.
156 void OceanMesh::calcApronPt(int latIdx, int lonIdx, int latInner, int lonInner,
157 int destIdx, double tex_width)
159 static const float downDist = 150.0f;
160 static const float outDist = 40.0f;
161 // Get vector along edge, in the right direction to make a cross
162 // product with the normal vector that will point out from the
164 osg::Vec3f edgePt = vlArray(latIdx, lonIdx);
166 if (lonIdx == lonInner) { // bottom or top edge
168 edgeVec = vlArray(latIdx, lonIdx - 1) - edgePt;
170 edgeVec = edgePt - vlArray(latIdx, lonIdx + 1);
171 if (latIdx > latInner)
172 edgeVec = -edgeVec; // Top edge
173 } else { // right or left edge
175 edgeVec = edgePt - vlArray(latIdx - 1, lonIdx);
177 edgeVec = vlArray(latIdx + 1, lonIdx) - edgePt;
178 if (lonIdx > lonInner) // right edge
182 osg::Vec3f outVec = nlArray(latIdx, lonIdx) ^ edgeVec;
184 = edgePt - nlArray(latIdx, lonIdx) * downDist + outVec * outDist;
185 (*nl)[destIdx] = nlArray(latIdx, lonIdx);
186 static const float apronDist
187 = sqrtf(downDist * downDist + outDist * outDist);
188 float texDelta = apronDist / tex_width;
189 if (lonIdx == lonInner) {
190 if (latIdx > latInner)
192 = tlArray(latIdx, lonIdx) + osg::Vec2f(0.0f, texDelta);
195 = tlArray(latIdx, lonIdx) - osg::Vec2f(0.0f, texDelta);
197 if (lonIdx > lonInner)
199 = tlArray(latIdx, lonIdx) + osg::Vec2f(texDelta, 0.0f);
202 = tlArray(latIdx, lonIdx) - osg::Vec2f(texDelta, 0.0f);
206 void OceanMesh::calcApronPts(double tex_width)
208 for (int i = 0; i < lonPoints; i++)
209 calcApronPt(0, i, 1, i, i, tex_width);
210 int topApronOffset = latPoints + (2 + lonPoints) * latPoints;
211 for (int i = 0; i < lonPoints; i++)
212 calcApronPt(latPoints - 1, i, latPoints - 2, i,
213 i + topApronOffset, tex_width);
214 for (int i = 0; i < latPoints; i++) {
215 calcApronPt(i, 0, i, 1, lonPoints + i * (lonPoints + 2), tex_width);
216 calcApronPt(i, lonPoints - 1, i, lonPoints - 2,
217 lonPoints + i * (lonPoints + 2) + 1 + lonPoints, tex_width);
223 // Enter the vertices of triangles that fill one row of the
224 // mesh. The vertices are entered in counter-clockwise order.
225 void fillDrawElementsRow(int width, short row0Start, short row1Start,
226 osg::DrawElementsUShort::vector_type::iterator&
229 short row0Idx = row0Start;
230 short row1Idx = row1Start;
231 for (int i = 0; i < width - 1; i++, row0Idx++, row1Idx++) {
232 *elements++ = row0Idx;
233 *elements++ = row0Idx + 1;
234 *elements++ = row1Idx;
235 *elements++ = row1Idx;
236 *elements++ = row0Idx + 1;
237 *elements++ = row1Idx + 1;
241 void fillDrawElementsWithApron(short height, short width,
242 osg::DrawElementsUShort::vector_type::iterator
246 fillDrawElementsRow(width, 0, width + 1, elements);
247 for (short i = 0; i < height - 1; i++)
248 fillDrawElementsRow(width + 2, width + i * (width + 2),
249 width + (i + 1) * (width + 2),
252 short topApronBottom = width + (height - 1) * (width + 2) + 1;
253 fillDrawElementsRow(width, topApronBottom, topApronBottom + width + 1,
258 osg::Node* SGOceanTile(const SGBucket& b, SGMaterialLib *matlib)
262 double tex_width = 1000.0;
264 // find Ocean material in the properties list
265 SGMaterial *mat = matlib->find( "Ocean" );
267 // set the texture width and height values for this
269 tex_width = mat->get_xsize();
272 effect = mat->get_effect();
274 SG_LOG( SG_TERRAIN, SG_ALERT, "Ack! unknown use material name = Ocean");
277 // Calculate center point
278 SGVec3d cartCenter = SGVec3d::fromGeod(b.get_center());
279 SGGeod geodPos = SGGeod::fromCart(cartCenter);
280 SGQuatd hlOr = SGQuatd::fromLonLat(geodPos);
282 double clon = b.get_center_lon();
283 double clat = b.get_center_lat();
284 double height = b.get_height();
285 double width = b.get_width();
287 grid.calcMesh(cartCenter, hlOr, clon, clat, height, width, tex_width);
288 grid.calcApronPts(tex_width);
290 osg::Vec4Array* cl = new osg::Vec4Array;
291 cl->push_back(osg::Vec4(1, 1, 1, 1));
293 osg::Geometry* geometry = new osg::Geometry;
294 geometry->setVertexArray(grid.vl);
295 geometry->setNormalArray(grid.nl);
296 geometry->setNormalBinding(osg::Geometry::BIND_PER_VERTEX);
297 geometry->setColorArray(cl);
298 geometry->setColorBinding(osg::Geometry::BIND_OVERALL);
299 geometry->setTexCoordArray(0, grid.tl);
301 // Allocate the indices for triangles in the mesh and the apron
302 osg::DrawElementsUShort* drawElements
303 = new osg::DrawElementsUShort(GL_TRIANGLES,
304 6 * ((latPoints - 1) * (lonPoints + 1)
305 + 2 * (latPoints - 1)));
306 fillDrawElementsWithApron(latPoints, lonPoints, drawElements->begin());
307 geometry->addPrimitiveSet(drawElements);
309 EffectGeode* geode = new EffectGeode;
310 geode->setName("Ocean tile");
311 geode->setEffect(effect);
312 geode->addDrawable(geometry);
314 osg::MatrixTransform* transform = new osg::MatrixTransform;
315 transform->setName("Ocean");
316 transform->setMatrix(osg::Matrix::rotate(toOsg(hlOr))*
317 osg::Matrix::translate(toOsg(cartCenter)));
318 transform->addChild(geode);