--- /dev/null
+/* -*-c++-*-
+ *
+ * Copyright (C) 2012 Stuart Buchanan
+ *
+ * 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.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <simgear_config.h>
+#endif
+
+#include <algorithm>
+#include <vector>
+#include <string>
+#include <map>
+
+#include <boost/foreach.hpp>
+#include <boost/tuple/tuple_comparison.hpp>
+
+#include <osg/Geode>
+#include <osg/Geometry>
+#include <osg/Math>
+#include <osg/MatrixTransform>
+#include <osg/Matrix>
+#include <osg/ShadeModel>
+#include <osg/Material>
+#include <osg/CullFace>
+
+#include <osgDB/ReadFile>
+#include <osgDB/FileUtils>
+
+#include <simgear/debug/logstream.hxx>
+#include <simgear/math/sg_random.h>
+#include <simgear/misc/sg_path.hxx>
+#include <simgear/scene/material/Effect.hxx>
+#include <simgear/scene/material/EffectGeode.hxx>
+#include <simgear/scene/model/model.hxx>
+#include <simgear/props/props.hxx>
+#include <simgear/scene/util/QuadTreeBuilder.hxx>
+#include <simgear/scene/util/RenderConstants.hxx>
+#include <simgear/scene/util/StateAttributeFactory.hxx>
+#include <simgear/structure/OSGUtils.hxx>
+
+
+#include "ShaderGeometry.hxx"
+#include "SGBuildingBin.hxx"
+
+#define SG_BUILDING_QUAD_TREE_DEPTH 3
+#define SG_BUILDING_FADE_OUT_LEVELS 10
+
+using namespace osg;
+
+namespace simgear
+{
+
+typedef std::map<std::string, osg::observer_ptr<osg::StateSet> > BuildingStateSetMap;
+static BuildingStateSetMap statesetmap;
+
+void addBuildingToLeafGeode(Geode* geode, const SGBuildingBin::Building& building)
+{
+ // Generate a repeatable random seed
+ mt seed;
+ mt_init(&seed, unsigned(building.position.x()));
+
+ // Get or create geometry.
+ osg::ref_ptr<osg::Geometry> geom;
+ osg::Vec3Array* v = new osg::Vec3Array;
+ osg::Vec2Array* t = new osg::Vec2Array;
+
+ // Color and Normal will be per QUAD
+ osg::Vec4Array* c = new osg::Vec4Array;
+ osg::Vec3Array* n = new osg::Vec3Array;
+
+ if (geode->getNumDrawables() == 0) {
+ geom = new osg::Geometry;
+ v = new osg::Vec3Array;
+ t = new osg::Vec2Array;
+ c = new osg::Vec4Array;
+ n = new osg::Vec3Array;
+
+ // Set the color, which is bound overall, and simply white
+ c->push_back( osg::Vec4( 1, 1, 1, 1) );
+ geom->setColorArray(c);
+ geom->setColorBinding(osg::Geometry::BIND_OVERALL);
+
+ geom->setNormalBinding(osg::Geometry::BIND_PER_PRIMITIVE);
+ // Temporary primitive set. Will be over-written later.
+ geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS,0,1));
+ geode->addDrawable(geom);
+ } else {
+ geom = (osg::Geometry*) geode->getDrawable(0);
+ v = (osg::Vec3Array*) geom->getVertexArray();
+ t = (osg::Vec2Array*) geom->getTexCoordArray(0);
+ c = (osg::Vec4Array*) geom->getColorArray();
+ n = (osg::Vec3Array*) geom->getNormalArray();
+ }
+
+ // For the moment we'll create a simple box with 5 sides (no need
+ // for a base).
+ int num_quads = 5;
+
+ if (building.pitched) {
+ // If it's a pitched roof, we add another 3 quads (we'll be
+ // removing the flat top).
+ num_quads+=3;
+ }
+
+ // Set up the rotation and translation matrix, which we apply to
+ // vertices as they are created as we'll be adding buildings later.
+ osg::Matrix transformMat;
+ transformMat = osg::Matrix::translate(toOsg(building.position));
+ double hdg = - building.rotation * M_PI * 2;
+ osg::Matrix rotationMat = osg::Matrix::rotate(hdg,
+ osg::Vec3d(0.0, 0.0, 1.0));
+ transformMat.preMult(rotationMat);
+
+ // Create the vertices
+ float cw = 0.5f * building.width;
+ float cd = building.depth;
+ float ch = building.height;
+
+ // 0,0,0 is the bottom center of the front
+ // face, e.g. where the front door would be
+
+
+ // BASEMENT
+ // This exteds 10m below the main section
+ // Front face
+ v->push_back( osg::Vec3( 0, cw, -10) * transformMat ); // bottom right
+ v->push_back( osg::Vec3( 0, -cw, -10) * transformMat ); // bottom left
+ v->push_back( osg::Vec3( 0, -cw, 0) * transformMat ); // top left
+ v->push_back( osg::Vec3( 0, cw, 0) * transformMat ); // top right
+
+ n->push_back( osg::Vec3(-1, 0, 0) * rotationMat ); // normal
+
+ // Left face
+ v->push_back( osg::Vec3( 0, -cw, -10) * transformMat ); // bottom right
+ v->push_back( osg::Vec3( cd, -cw, -10) * transformMat ); // bottom left
+ v->push_back( osg::Vec3( cd, -cw, 0) * transformMat ); // top left
+ v->push_back( osg::Vec3( 0, -cw, 0) * transformMat ); // top right
+
+ n->push_back( osg::Vec3(0, -1, 0) * rotationMat ); // normal
+
+ // Back face
+ v->push_back( osg::Vec3( cd, -cw, -10) * transformMat ); // bottom right
+ v->push_back( osg::Vec3( cd, cw, -10) * transformMat ); // bottom left
+ v->push_back( osg::Vec3( cd, cw, 0) * transformMat ); // top left
+ v->push_back( osg::Vec3( cd, -cw, 0) * transformMat ); // top right
+
+ n->push_back( osg::Vec3(1, 0, 0) * rotationMat ); // normal
+
+ // Right face
+ v->push_back( osg::Vec3( cd, cw, -10) * transformMat ); // bottom right
+ v->push_back( osg::Vec3( 0, cw, -10) * transformMat ); // bottom left
+ v->push_back( osg::Vec3( 0, cw, 0) * transformMat ); // top left
+ v->push_back( osg::Vec3( cd, cw, 0) * transformMat ); // top right
+
+ n->push_back( osg::Vec3(0, 1, 0) * rotationMat ); // normal
+
+ // MAIN BODY
+ // Front face
+ v->push_back( osg::Vec3( 0, cw, 0) * transformMat ); // bottom right
+ v->push_back( osg::Vec3( 0, -cw, 0) * transformMat ); // bottom left
+ v->push_back( osg::Vec3( 0, -cw, ch) * transformMat ); // top left
+ v->push_back( osg::Vec3( 0, cw, ch) * transformMat ); // top right
+
+ n->push_back( osg::Vec3(-1, 0, 0) * rotationMat ); // normal
+
+ // Left face
+ v->push_back( osg::Vec3( 0, -cw, 0) * transformMat ); // bottom right
+ v->push_back( osg::Vec3( cd, -cw, 0) * transformMat ); // bottom left
+ v->push_back( osg::Vec3( cd, -cw, ch) * transformMat ); // top left
+ v->push_back( osg::Vec3( 0, -cw, ch) * transformMat ); // top right
+
+ n->push_back( osg::Vec3(0, -1, 0) * rotationMat ); // normal
+
+ // Back face
+ v->push_back( osg::Vec3( cd, -cw, 0) * transformMat ); // bottom right
+ v->push_back( osg::Vec3( cd, cw, 0) * transformMat ); // bottom left
+ v->push_back( osg::Vec3( cd, cw, ch) * transformMat ); // top left
+ v->push_back( osg::Vec3( cd, -cw, ch) * transformMat ); // top right
+
+ n->push_back( osg::Vec3(1, 0, 0) * rotationMat ); // normal
+
+ // Right face
+ v->push_back( osg::Vec3( cd, cw, 0) * transformMat ); // bottom right
+ v->push_back( osg::Vec3( 0, cw, 0) * transformMat ); // bottom left
+ v->push_back( osg::Vec3( 0, cw, ch) * transformMat ); // top left
+ v->push_back( osg::Vec3( cd, cw, ch) * transformMat ); // top right
+
+ n->push_back( osg::Vec3(0, 1, 0) * rotationMat ); // normal
+
+ // ROOF
+ if (building.pitched) {
+
+ // Front pitched roof
+ v->push_back( osg::Vec3( 0, cw, ch) * transformMat ); // bottom right
+ v->push_back( osg::Vec3( 0, -cw, ch) * transformMat ); // bottom left
+ v->push_back( osg::Vec3(0.5*cd, -cw, ch+3) * transformMat ); // top left
+ v->push_back( osg::Vec3(0.5*cd, cw, ch+3) * transformMat ); // top right
+ n->push_back( osg::Vec3(-0.707, 0, 0.707) * rotationMat ); // normal
+
+ // Left pitched roof
+ v->push_back( osg::Vec3( 0, -cw, ch) * transformMat ); // bottom right
+ v->push_back( osg::Vec3( cd, -cw, ch) * transformMat ); // bottom left
+ v->push_back( osg::Vec3(0.5*cd, -cw, ch+3) * transformMat ); // top left
+ v->push_back( osg::Vec3(0.5*cd, -cw, ch+3) * transformMat ); // top right
+ n->push_back( osg::Vec3(0, -1, 0) * rotationMat ); // normal
+
+ // Back pitched roof
+ v->push_back( osg::Vec3( cd, -cw, ch) * transformMat ); // bottom right
+ v->push_back( osg::Vec3( cd, cw, ch) * transformMat ); // bottom left
+ v->push_back( osg::Vec3(0.5*cd, cw, ch+3) * transformMat ); // top left
+ v->push_back( osg::Vec3(0.5*cd, -cw, ch+3) * transformMat ); // top right
+ n->push_back( osg::Vec3(0.707, 0, 0.707) * rotationMat ); // normal
+
+ // Right pitched roof
+ v->push_back( osg::Vec3( cd, cw, ch) * transformMat ); // bottom right
+ v->push_back( osg::Vec3( 0, cw, ch) * transformMat ); // bottom left
+ v->push_back( osg::Vec3(0.5*cd, cw, ch+3) * transformMat ); // top left
+ v->push_back( osg::Vec3(0.5*cd, cw, ch+3) * transformMat ); // top right
+ n->push_back( osg::Vec3(0, 1, 0) * rotationMat ); // normal
+ } else {
+ // Top face
+ v->push_back( osg::Vec3( 0, cw, ch) * transformMat ); // bottom right
+ v->push_back( osg::Vec3( 0, -cw, ch) * transformMat ); // bottom left
+ v->push_back( osg::Vec3( cd, -cw, ch) * transformMat ); // top left
+ v->push_back( osg::Vec3( cd, cw, ch) * transformMat ); // top right
+ n->push_back( osg::Vec3( 0, 0, 1) * rotationMat ); // normal
+ }
+
+ // The 1024x1024 texture is split into 32x16 blocks.
+ // For a small building, each block is 6m wide and 3m high.
+ // For a medium building, each block is 10m wide and 3m high.
+ // For a large building, each block is 20m wide and 3m high
+
+ if (building.type == SGBuildingBin::SMALL) {
+ // Small buildings are represented on the bottom 5 rows of 3 floors
+ int row = ((int) (mt_rand(&seed) * 1000)) % 5;
+ float base_y = (float) row * 16.0 * 3.0 / 1024.0;
+ float top_y = base_y + 16.0 * (float) building.floors / 1024.0;
+ float left_x = 0.0f;
+ float right_x = 32.0 / 1024.0 * round((float) building.width / 6.0f);
+ float front_x = 384.0/1024.0;
+ float back_x = 384.0/1024.0 + 32.0 / 1024.0 * round((float) building.depth/ 6.0f);
+
+ // BASEMENT - uses the baseline texture
+ for (unsigned int i = 0; i < 16; i++) {
+ t->push_back( osg::Vec2( left_x, base_y) );
+ }
+ // MAIN BODY
+ // Front
+ t->push_back( osg::Vec2( right_x, base_y) ); // bottom right
+ t->push_back( osg::Vec2( left_x, base_y) ); // bottom left
+ t->push_back( osg::Vec2( left_x, top_y ) ); // top left
+ t->push_back( osg::Vec2( right_x, top_y ) ); // top right
+
+ // Left
+ t->push_back( osg::Vec2( front_x, base_y) ); // bottom right
+ t->push_back( osg::Vec2( back_x, base_y) ); // bottom left
+ t->push_back( osg::Vec2( back_x, top_y ) ); // top left
+ t->push_back( osg::Vec2( front_x, top_y ) ); // top right
+
+ // Back (same as front for the moment)
+ t->push_back( osg::Vec2( right_x, base_y) ); // bottom right
+ t->push_back( osg::Vec2( left_x, base_y) ); // bottom left
+ t->push_back( osg::Vec2( left_x, top_y ) ); // top left
+ t->push_back( osg::Vec2( right_x, top_y ) ); // top right
+
+ // Right (same as left for the moment)
+ t->push_back( osg::Vec2( front_x, base_y) ); // bottom right
+ t->push_back( osg::Vec2( back_x, base_y) ); // bottom left
+ t->push_back( osg::Vec2( back_x, top_y ) ); // top left
+ t->push_back( osg::Vec2( front_x, top_y ) ); // top right
+
+ // ROOF
+ if (building.pitched) {
+ // Use the entire height of the roof texture
+ top_y = base_y + 16.0 * 3.0 / 1024.0;
+ left_x = 512/1024.0;
+ right_x = 512/1024.0 + 32.0 / 1024.0 * round(building.width / 6.0f);
+ front_x = 480.0/1024.0;
+ back_x = 512.0/1024.0;
+
+ // Front
+ t->push_back( osg::Vec2( right_x, base_y) ); // bottom right
+ t->push_back( osg::Vec2( left_x, base_y) ); // bottom left
+ t->push_back( osg::Vec2( left_x, top_y ) ); // top left
+ t->push_back( osg::Vec2( right_x, top_y ) ); // top right
+
+ // Left
+ t->push_back( osg::Vec2( front_x, base_y) ); // bottom right
+ t->push_back( osg::Vec2( back_x, base_y) ); // bottom left
+ t->push_back( osg::Vec2( back_x, top_y ) ); // top left
+ t->push_back( osg::Vec2( front_x, top_y ) ); // top right
+
+ // Back (same as front for the moment)
+ t->push_back( osg::Vec2( right_x, base_y) ); // bottom right
+ t->push_back( osg::Vec2( left_x, base_y) ); // bottom left
+ t->push_back( osg::Vec2( left_x, top_y ) ); // top left
+ t->push_back( osg::Vec2( right_x, top_y ) ); // top right
+
+ // Right (same as left for the moment)
+ t->push_back( osg::Vec2( front_x, base_y) ); // bottom right
+ t->push_back( osg::Vec2( back_x, base_y) ); // bottom left
+ t->push_back( osg::Vec2( back_x, top_y ) ); // top left
+ t->push_back( osg::Vec2( front_x, top_y ) ); // top right
+ } else {
+ // Flat roof
+ left_x = 512.0/1024.0;
+ right_x = 640.0/1024.0;
+ // Use the entire height of the roof texture
+ top_y = base_y + 16.0 * 3.0 / 1024.0;
+
+ t->push_back( osg::Vec2( right_x, base_y) ); // bottom right
+ t->push_back( osg::Vec2( left_x, base_y) ); // bottom left
+ t->push_back( osg::Vec2( left_x, top_y ) ); // top left
+ t->push_back( osg::Vec2( right_x, top_y ) ); // top right
+ }
+
+ }
+
+ if (building.type == SGBuildingBin::MEDIUM)
+ {
+ int column = ((int) (mt_rand(&seed) * 1000)) % 5;
+ float base_y = 288 / 1024.0;
+ float top_y = base_y + 16.0 * (float) building.floors / 1024.0;
+ float left_x = column * 192.0 /1024.0;
+ float right_x = left_x + 32.0 / 1024.0 * round((float) building.width / 10.0f);
+
+ // BASEMENT - uses the baseline texture
+ for (unsigned int i = 0; i < 16; i++) {
+ t->push_back( osg::Vec2( left_x, base_y) );
+ }
+
+ // MAIN BODY
+ // Front
+ t->push_back( osg::Vec2( right_x, base_y) ); // bottom right
+ t->push_back( osg::Vec2( left_x, base_y) ); // bottom left
+ t->push_back( osg::Vec2( left_x, top_y ) ); // top left
+ t->push_back( osg::Vec2( right_x, top_y ) ); // top right
+
+ // Left
+ t->push_back( osg::Vec2( right_x, base_y) ); // bottom right
+ t->push_back( osg::Vec2( left_x, base_y) ); // bottom left
+ t->push_back( osg::Vec2( left_x, top_y ) ); // top left
+ t->push_back( osg::Vec2( right_x, top_y ) ); // top right
+
+ // Back (same as front for the moment)
+ t->push_back( osg::Vec2( right_x, base_y) ); // bottom right
+ t->push_back( osg::Vec2( left_x, base_y) ); // bottom left
+ t->push_back( osg::Vec2( left_x, top_y ) ); // top left
+ t->push_back( osg::Vec2( right_x, top_y ) ); // top right
+
+ // Right (same as left for the moment)
+ t->push_back( osg::Vec2( right_x, base_y) ); // bottom right
+ t->push_back( osg::Vec2( left_x, base_y) ); // bottom left
+ t->push_back( osg::Vec2( left_x, top_y ) ); // top left
+ t->push_back( osg::Vec2( right_x, top_y ) ); // top right
+
+ // ROOF
+ if (building.pitched) {
+ base_y = 288.0/1024.0;
+ top_y = 576.0/1024.0;
+ left_x = 960.0/1024.0;
+ right_x = 1.0;
+ // Front
+ t->push_back( osg::Vec2( right_x, base_y) ); // bottom right
+ t->push_back( osg::Vec2( left_x, base_y) ); // bottom left
+ t->push_back( osg::Vec2( left_x, top_y ) ); // top left
+ t->push_back( osg::Vec2( right_x, top_y ) ); // top right
+
+ // Left
+ t->push_back( osg::Vec2( right_x, base_y) ); // bottom right
+ t->push_back( osg::Vec2( left_x, base_y) ); // bottom left
+ t->push_back( osg::Vec2( left_x, top_y ) ); // top left
+ t->push_back( osg::Vec2( right_x, top_y ) ); // top right
+
+ // Back (same as front for the moment)
+ t->push_back( osg::Vec2( right_x, base_y) ); // bottom right
+ t->push_back( osg::Vec2( left_x, base_y) ); // bottom left
+ t->push_back( osg::Vec2( left_x, top_y ) ); // top left
+ t->push_back( osg::Vec2( right_x, top_y ) ); // top right
+
+ // Right (same as left for the moment)
+ t->push_back( osg::Vec2( right_x, base_y) ); // bottom right
+ t->push_back( osg::Vec2( left_x, base_y) ); // bottom left
+ t->push_back( osg::Vec2( left_x, top_y ) ); // top left
+ t->push_back( osg::Vec2( right_x, top_y ) ); // top right
+ } else {
+ // Flat roof
+ base_y = 416/1024.0;
+ top_y = 576.0/1024.0;
+ right_x = left_x + 32.0 / 1024.0 * 6.0;
+
+ t->push_back( osg::Vec2( right_x, base_y) ); // bottom right
+ t->push_back( osg::Vec2( left_x, base_y) ); // bottom left
+ t->push_back( osg::Vec2( left_x, top_y ) ); // top left
+ t->push_back( osg::Vec2( right_x, top_y ) ); // top right
+ }
+ }
+
+ if (building.type == SGBuildingBin::LARGE)
+ {
+ int column = ((int) (mt_rand(&seed) * 1000)) % 8;
+ float base_y = 576 / 1024.0;
+ float top_y = base_y + 16.0 * (float) building.floors / 1024.0;
+ float left_x = column * 128.0 /1024.0;
+ float right_x = left_x + 32.0 / 1024.0 * round((float) building.width / 20.0f);
+
+ // BASEMENT - uses the baseline texture
+ for (unsigned int i = 0; i < 16; i++) {
+ t->push_back( osg::Vec2( left_x, base_y) );
+ }
+
+ // MAIN BODY
+ // Front
+ t->push_back( osg::Vec2( right_x, base_y) ); // bottom right
+ t->push_back( osg::Vec2( left_x, base_y) ); // bottom left
+ t->push_back( osg::Vec2( left_x, top_y ) ); // top left
+ t->push_back( osg::Vec2( right_x, top_y ) ); // top right
+
+ // Left
+ t->push_back( osg::Vec2( right_x, base_y) ); // bottom right
+ t->push_back( osg::Vec2( left_x, base_y) ); // bottom left
+ t->push_back( osg::Vec2( left_x, top_y ) ); // top left
+ t->push_back( osg::Vec2( right_x, top_y ) ); // top right
+
+ // Back (same as front for the moment)
+ t->push_back( osg::Vec2( right_x, base_y) ); // bottom right
+ t->push_back( osg::Vec2( left_x, base_y) ); // bottom left
+ t->push_back( osg::Vec2( left_x, top_y ) ); // top left
+ t->push_back( osg::Vec2( right_x, top_y ) ); // top right
+
+ // Right (same as left for the moment)
+ t->push_back( osg::Vec2( right_x, base_y) ); // bottom right
+ t->push_back( osg::Vec2( left_x, base_y) ); // bottom left
+ t->push_back( osg::Vec2( left_x, top_y ) ); // top left
+ t->push_back( osg::Vec2( right_x, top_y ) ); // top right
+
+ // ROOF
+ if (building.pitched) {
+ base_y = 896/1024.0;
+ top_y = 1.0;
+ // Front
+ t->push_back( osg::Vec2( right_x, base_y) ); // bottom right
+ t->push_back( osg::Vec2( left_x, base_y) ); // bottom left
+ t->push_back( osg::Vec2( left_x, top_y ) ); // top left
+ t->push_back( osg::Vec2( right_x, top_y ) ); // top right
+
+ // Left
+ t->push_back( osg::Vec2( right_x, base_y) ); // bottom right
+ t->push_back( osg::Vec2( left_x, base_y) ); // bottom left
+ t->push_back( osg::Vec2( left_x, top_y ) ); // top left
+ t->push_back( osg::Vec2( right_x, top_y ) ); // top right
+
+ // Back (same as front for the moment)
+ t->push_back( osg::Vec2( right_x, base_y) ); // bottom right
+ t->push_back( osg::Vec2( left_x, base_y) ); // bottom left
+ t->push_back( osg::Vec2( left_x, top_y ) ); // top left
+ t->push_back( osg::Vec2( right_x, top_y ) ); // top right
+
+ // Right (same as left for the moment)
+ t->push_back( osg::Vec2( right_x, base_y) ); // bottom right
+ t->push_back( osg::Vec2( left_x, base_y) ); // bottom left
+ t->push_back( osg::Vec2( left_x, top_y ) ); // top left
+ t->push_back( osg::Vec2( right_x, top_y ) ); // top right
+ } else {
+ // Flat roof
+ base_y = 896/1024.0;
+ top_y = 1.0;
+
+ t->push_back( osg::Vec2( right_x, base_y) ); // bottom right
+ t->push_back( osg::Vec2( left_x, base_y) ); // bottom left
+ t->push_back( osg::Vec2( left_x, top_y ) ); // top left
+ t->push_back( osg::Vec2( right_x, top_y ) ); // top right
+ }
+
+ }
+
+ // Set the vertex, texture and normals back.
+ geom->setVertexArray(v);
+ geom->setTexCoordArray(0, t);
+ geom->setNormalArray(n);
+
+ geom->setPrimitiveSet(0, new osg::DrawArrays(osg::PrimitiveSet::QUADS,0,v->size()));
+ geode->setDrawable(0, geom);
+}
+
+// Helper classes for creating the quad tree
+namespace
+{
+struct MakeBuildingLeaf
+{
+ MakeBuildingLeaf(float range, Effect* effect) :
+ _range(range), _effect(effect) {}
+
+ MakeBuildingLeaf(const MakeBuildingLeaf& rhs) :
+ _range(rhs._range), _effect(rhs._effect)
+ {}
+
+ LOD* operator() () const
+ {
+ LOD* result = new LOD;
+
+ // Create a series of LOD nodes so trees cover decreases slightly
+ // gradually with distance from _range to 2*_range
+ for (float i = 0.0; i < SG_BUILDING_FADE_OUT_LEVELS; i++)
+ {
+ //osg::ref_ptr<EffectGeode> geode = new EffectGeode();
+ //geode->setEffect(_effect);
+ osg::ref_ptr<osg::Geode> geode = new osg::Geode();
+ result->addChild(geode, 0, _range * (1.0 + i / (SG_BUILDING_FADE_OUT_LEVELS - 1.0)));
+ }
+ return result;
+ }
+
+ float _range;
+ Effect* _effect;
+};
+
+struct AddBuildingLeafObject
+{
+ void operator() (LOD* lod, const SGBuildingBin::Building& building) const
+ {
+ Geode* geode = static_cast<Geode*>(lod->getChild(int(building.position.x() * 10.0f) % lod->getNumChildren()));
+ addBuildingToLeafGeode(geode, building);
+ }
+};
+
+struct GetBuildingCoord
+{
+ Vec3 operator() (const SGBuildingBin::Building& building) const
+ {
+ return toOsg(building.position);
+ }
+};
+
+typedef QuadTreeBuilder<LOD*, SGBuildingBin::Building, MakeBuildingLeaf, AddBuildingLeafObject,
+ GetBuildingCoord> BuildingGeometryQuadtree;
+}
+
+struct BuildingTransformer
+{
+ BuildingTransformer(Matrix& mat_) : mat(mat_) {}
+ SGBuildingBin::Building operator()(const SGBuildingBin::Building& building) const
+ {
+ Vec3 pos = toOsg(building.position);
+ return SGBuildingBin::Building(toSG(pos * mat), building);
+ }
+ Matrix mat;
+};
+
+
+
+// This actually returns a MatrixTransform node. If we rotate the whole
+// forest into the local Z-up coordinate system we can reuse the
+// primitive building geometry for all the forests of the same type.
+osg::Group* createRandomBuildings(SGBuildingBinList buildings, const osg::Matrix& transform,
+ const SGReaderWriterOptions* options)
+{
+ Matrix transInv = Matrix::inverse(transform);
+ static Matrix ident;
+ // Set up some shared structures.
+ MatrixTransform* mt = new MatrixTransform(transform);
+ Effect* effect = makeEffect("Effects/model-default", true);
+
+ SGBuildingBin* bin = NULL;
+
+ BOOST_FOREACH(bin, buildings)
+ {
+ // Now, create a quadbuilding for the buildings.
+ BuildingGeometryQuadtree
+ quadbuilding(GetBuildingCoord(), AddBuildingLeafObject(),
+ SG_BUILDING_QUAD_TREE_DEPTH,
+ MakeBuildingLeaf(20000.0f, effect)); // FIXME - tie to property
+
+ // Transform building positions from the "geocentric" positions we
+ // get from the scenery polys into the local Z-up coordinate
+ // system.
+ std::vector<SGBuildingBin::Building> rotatedBuildings;
+ rotatedBuildings.reserve(bin->buildings.size());
+ std::transform(bin->buildings.begin(), bin->buildings.end(),
+ std::back_inserter(rotatedBuildings),
+ BuildingTransformer(transInv));
+ quadbuilding.buildQuadTree(rotatedBuildings.begin(), rotatedBuildings.end());
+
+ ref_ptr<Group> group = quadbuilding.getRoot();
+
+ // Set up the stateset for this building bin and the texture to use.
+ osg::StateSet* stateSet = group->getOrCreateStateSet();
+ const std::string texturename = bin->texture;
+ osg::Texture2D* texture = SGLoadTexture2D(texturename);
+ texture->setWrap(osg::Texture2D::WRAP_S, osg::Texture2D::CLAMP_TO_EDGE);
+ texture->setWrap(osg::Texture2D::WRAP_T, osg::Texture2D::CLAMP_TO_EDGE);
+ stateSet->setTextureAttributeAndModes(0, texture);
+
+ osg::ShadeModel* shadeModel = new osg::ShadeModel;
+ shadeModel->setMode(osg::ShadeModel::FLAT);
+ stateSet->setAttributeAndModes(shadeModel);
+ stateSet->setMode(GL_LIGHTING, osg::StateAttribute::ON);
+ stateSet->setMode(GL_FOG, osg::StateAttribute::ON);
+ stateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::ON);
+ stateSet->setMode(GL_CULL_FACE, osg::StateAttribute::ON);
+ stateSet->setMode(GL_BLEND, osg::StateAttribute::OFF);
+ stateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::OFF);
+ stateSet->setAttribute(new osg::CullFace(osg::CullFace::BACK));
+
+ osg::Material* material = new osg::Material;
+ material->setAmbient(osg::Material::FRONT, osg::Vec4(0.3,0.3,0.3,1.0));
+ material->setDiffuse(osg::Material::FRONT, osg::Vec4(1.0,1.0,1.0,1.0));
+ material->setSpecular(osg::Material::FRONT, osg::Vec4(0,0,0,1.0));
+ material->setShininess(osg::Material::FRONT, 0.0);
+ material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE);
+ stateSet->setAttribute(material);
+ mt->addChild(group);
+ }
+
+ return mt;
+}
+
+}
#include "SGTexturedTriangleBin.hxx"
#include "SGLightBin.hxx"
#include "SGModelBin.hxx"
+#include "SGBuildingBin.hxx"
#include "TreeBin.hxx"
#include "SGDirectionalLightBin.hxx"
#include "GroundLightManager.hxx"
SGLightListBin odalLights;
SGDirectionalLightListBin reilLights;
SGMatModelBin randomModels;
+ SGBuildingBinList randomBuildings;
static SGVec4f
getMaterialLightColor(const SGMaterial* material)
}
}
}
+
+ void computeRandomBuildings(SGMaterialLib* matlib, float building_density)
+ {
+ SGMaterialTriangleMap::iterator i;
+
+ // generate a repeatable random seed
+ mt seed;
+ mt_init(&seed, unsigned(123));
+
+ for (i = materialTriangleMap.begin(); i != materialTriangleMap.end(); ++i) {
+ SGMaterial *mat = matlib->find(i->first);
+ SGTexturedTriangleBin triangleBin = i->second;
+
+ if (!mat)
+ continue;
+
+ osg::Texture2D* object_mask = mat->get_object_mask(triangleBin);
+
+ float coverage = mat->get_building_coverage();
+
+ // Minimum spacing needs to include the maximum footprint of a building.
+ // As the 0,0,0 point is the center of the front of the building, we need
+ // to consider the full depth, but only half the possible width.
+ float min_spacing = mat->get_building_spacing();
+
+ if (coverage <= 0)
+ continue;
+
+ bool found = false;
+ SGBuildingBin* bin = NULL;
+
+ BOOST_FOREACH(bin, randomBuildings)
+ {
+ if (bin->texture == mat->get_building_texture()) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ bin = new SGBuildingBin();
+ bin->texture = mat->get_building_texture();
+ SG_LOG(SG_INPUT, SG_DEBUG, "Building texture " << bin->texture);
+ randomBuildings.push_back(bin);
+ }
+
+ std::vector<std::pair<SGVec3f, float> > randomPoints;
+
+ unsigned num = i->second.getNumTriangles();
+ int triangle_dropped = 0;
+ int building_dropped = 0;
+ int random_dropped = 0;
+
+ for (unsigned i = 0; i < num; ++i) {
+ SGBuildingBin::BuildingList triangle_buildings;
+ SGTexturedTriangleBin::triangle_ref triangleRef = triangleBin.getTriangleRef(i);
+
+ SGVec3f v0 = triangleBin.getVertex(triangleRef[0]).vertex;
+ SGVec3f v1 = triangleBin.getVertex(triangleRef[1]).vertex;
+ SGVec3f v2 = triangleBin.getVertex(triangleRef[2]).vertex;
+ SGVec2f t0 = triangleBin.getVertex(triangleRef[0]).texCoord;
+ SGVec2f t1 = triangleBin.getVertex(triangleRef[1]).texCoord;
+ SGVec2f t2 = triangleBin.getVertex(triangleRef[2]).texCoord;
+ SGVec3f normal = cross(v1 - v0, v2 - v0);
+
+ // Compute the area
+ float area = 0.5f*length(normal);
+ if (area <= SGLimitsf::min())
+ continue;
+
+ // for partial units of area, use a zombie door method to
+ // create the proper random chance of an object being created
+ // for this triangle.
+ double num = area / coverage + mt_rand(&seed);
+
+ // Apply density.
+ num = num * building_density;
+
+ // place an object each unit of area
+ while ( num > 1.0 ) {
+ float a = mt_rand(&seed);
+ float b = mt_rand(&seed);
+ if ( a + b > 1 ) {
+ a = 1 - a;
+ b = 1 - b;
+ }
+
+ float c = 1 - a - b;
+ SGVec3f randomPoint = a*v0 + b*v1 + c*v2;
+ float rotation = mt_rand(&seed);
+
+ if (object_mask != NULL) {
+ SGVec2f texCoord = a*t0 + b*t1 + c*t2;
+ osg::Image* img = object_mask->getImage();
+ unsigned int x = (int) (img->s() * texCoord.x()) % img->s();
+ unsigned int y = (int) (img->t() * texCoord.y()) % img->t();
+
+ if (mt_rand(&seed) < img->getColor(x, y).b()) {
+ // Object passes mask. Rotation is taken from the red channel
+ rotation = img->getColor(x,y).r();
+ } else {
+ // Fails mask test - try again.
+ num -= 1.0;
+ continue;
+ }
+ }
+
+ // Now create the building, so we have an idea of its footprint
+ // and therefore appropriate spacing.
+ SGBuildingBin::BuildingType buildingtype;
+ float width;
+ float depth;
+ int floors;
+ float height;
+ bool pitched;
+
+ // Determine the building type, and hence dimensions.
+ float type = mt_rand(&seed);
+
+ if (type < mat->get_building_small_fraction()) {
+ // Small building
+ buildingtype = SGBuildingBin::SMALL;
+ width = mat->get_building_small_min_width() + mt_rand(&seed) * mt_rand(&seed) * (mat->get_building_small_max_width() - mat->get_building_small_min_width());
+ depth = mat->get_building_small_min_depth() + mt_rand(&seed) * mt_rand(&seed) * (mat->get_building_small_max_depth() - mat->get_building_small_min_depth());
+ floors = round(mat->get_building_small_min_floors() + mt_rand(&seed) * (mat->get_building_small_max_floors() - mat->get_building_small_min_floors()));
+ height = floors * (2.8 + mt_rand(&seed));
+
+ if (depth > width) { depth = width; }
+
+ pitched = (mt_rand(&seed) < mat->get_building_small_pitch());
+ } else if (type < (mat->get_building_small_fraction() + mat->get_building_medium_fraction())) {
+ buildingtype = SGBuildingBin::MEDIUM;
+ width = mat->get_building_medium_min_width() + mt_rand(&seed) * mt_rand(&seed) * (mat->get_building_medium_max_width() - mat->get_building_medium_min_width());
+ depth = mat->get_building_medium_min_depth() + mt_rand(&seed) * mt_rand(&seed) * (mat->get_building_medium_max_depth() - mat->get_building_medium_min_depth());
+ floors = round(mat->get_building_medium_min_floors() + mt_rand(&seed) * (mat->get_building_medium_max_floors() - mat->get_building_medium_min_floors()));
+ height = floors * (2.8 + mt_rand(&seed));
+ pitched = (mt_rand(&seed) < mat->get_building_medium_pitch());
+ } else {
+ buildingtype = SGBuildingBin::LARGE;
+ width = mat->get_building_large_min_width() + mt_rand(&seed) * (mat->get_building_large_max_width() - mat->get_building_large_min_width());
+ depth = mat->get_building_large_min_depth() + mt_rand(&seed) * (mat->get_building_large_max_depth() - mat->get_building_large_min_depth());
+ floors = round(mat->get_building_large_min_floors() + mt_rand(&seed) * (mat->get_building_large_max_floors() - mat->get_building_large_min_floors()));
+ height = floors * (2.8 + mt_rand(&seed));
+ pitched = (mt_rand(&seed) < mat->get_building_large_pitch());
+ }
+
+ // Determine an appropriate minimum spacing for the object. Note that the
+ // origin of the building model is the center of the front face, hence we
+ // consider the full depth. We choose _not_ to use the diagonal distance
+ // to one of the rear corners, as we assume that terrain masking will
+ // make the buildings place in some sort of grid.
+ float radius = std::max(depth, 0.5f*width);
+
+ // Check that the point is sufficiently far from
+ // the edge of the triangle by measuring the distance
+ // from the three lines that make up the triangle.
+ if (((length(cross(randomPoint - v0, randomPoint - v1)) / length(v1 - v0)) < radius) ||
+ ((length(cross(randomPoint - v1, randomPoint - v2)) / length(v2 - v1)) < radius) ||
+ ((length(cross(randomPoint - v2, randomPoint - v0)) / length(v0 - v2)) < radius) )
+ {
+ triangle_dropped++;
+ num -= 1.0;
+ continue;
+ }
+ // Check against the generic random objects. TODO - make this more efficient by
+ // masking ahead of time objects outside of the triangle.
+ bool too_close = false;
+ for (unsigned int i = 0; i < randomModels.getNumModels(); ++i) {
+ float min_dist = randomModels.getMatModel(i).model->get_spacing_m() + radius + min_spacing;
+ min_dist = min_dist * min_dist;
+
+ if (distSqr(randomModels.getMatModel(i).position, randomPoint) < min_dist) {
+ too_close = true;
+ random_dropped++;
+ continue;
+ }
+ }
+
+ if (too_close) {
+ // Too close to a random model - drop and try again
+ num -= 1.0;
+ continue;
+ }
+
+ SGBuildingBin::BuildingList::iterator l;
+
+ // Check that the building is sufficiently far from any other building within the triangle.
+ for (l = triangle_buildings.begin(); l != triangle_buildings.end(); ++l) {
+
+ float min_dist = l->radius + radius + min_spacing;
+ min_dist = min_dist * min_dist;
+
+ if (distSqr(randomPoint, l->position) < min_dist) {
+ building_dropped++;
+ too_close = true;
+ continue;
+ }
+ }
+
+ if (too_close) {
+ // Too close to another building - drop and try again
+ num -= 1.0;
+ continue;
+ }
+
+ // If we've passed all of the above tests we have a valid
+ // building, so create it!
+ SGBuildingBin::Building building =
+ SGBuildingBin::Building(buildingtype,
+ randomPoint,
+ width,
+ depth,
+ height,
+ floors,
+ rotation,
+ pitched);
+ triangle_buildings.push_back(building);
+ num -= 1.0;
+ }
+
+ // Add the buildings from this triangle to the overall list.
+ SGBuildingBin::BuildingList::iterator l;
+
+ for (l = triangle_buildings.begin(); l != triangle_buildings.end(); ++l) {
+ bin->insert(*l);
+ }
+ }
+
+ SG_LOG(SG_INPUT, SG_DEBUG, "Random Buildings: " << bin->getNumBuildings());
+ SG_LOG(SG_INPUT, SG_DEBUG, " Dropped due to triangle edge: " << triangle_dropped);
+ SG_LOG(SG_INPUT, SG_DEBUG, " Dropped due to random object: " << random_dropped);
+ SG_LOG(SG_INPUT, SG_DEBUG, " Dropped due to other building: " << building_dropped);
+ }
+ }
+
void computeRandomForest(SGMaterialLib* matlib, float vegetation_density)
{
SGMaterialTriangleMap::iterator i;
object->get_spacing_m(),
mat->get_object_mask(i->second),
randomPoints);
+
std::vector<std::pair<SGVec3f, float> >::iterator l;
for (l = randomPoints.begin(); l != randomPoints.end(); ++l) {
-
// Only add the model if it is sufficiently far from the
// other models
bool close = false;
if (distSqr(randomModels.getMatModel(i).position, l->first) < spacing) {
close = true;
+ continue;
}
}
+
if (!close) {
randomModels.insert(l->first, object, (int)object->get_randomized_range_m(&seed), l->second);
}
}
}
}
-
bool insertBinObj(const SGBinObject& obj, SGMaterialLib* matlib)
{
SGMaterialLib* matlib = 0;
bool use_random_objects = false;
bool use_random_vegetation = false;
- float vegetation_density = 1.0f;
+ bool use_random_buildings = false;
+ float vegetation_density = 1.0f;
+ float building_density = 1.0f;
if (options) {
matlib = options->getMaterialLib();
SGPropertyNode* propertyNode = options->getPropertyNode().get();
use_random_objects);
use_random_vegetation
= propertyNode->getBoolValue("/sim/rendering/random-vegetation",
- use_random_vegetation);
+ use_random_vegetation);
vegetation_density
= propertyNode->getFloatValue("/sim/rendering/vegetation-density",
vegetation_density);
+ use_random_buildings
+ = propertyNode->getBoolValue("/sim/rendering/random-buildings",
+ use_random_buildings);
+ building_density
+ = propertyNode->getFloatValue("/sim/rendering/building-density",
+ building_density);
}
}
osg::ref_ptr<osg::Group> lightGroup = new SGOffsetTransform(0.94);
osg::ref_ptr<osg::Group> randomObjects;
osg::ref_ptr<osg::Group> forestNode;
+ osg::ref_ptr<osg::Group> buildingNode;
osg::Group* terrainGroup = new osg::Group;
osg::Node* node = tileGeometryBin.getSurfaceGeometry(matlib);
if (node)
terrainGroup->addChild(node);
- if (use_random_objects || use_random_vegetation) {
- if (use_random_objects) {
- if (matlib)
- tileGeometryBin.computeRandomObjects(matlib);
-
- if (tileGeometryBin.randomModels.getNumModels() > 0) {
- // Generate a repeatable random seed
- mt seed;
- mt_init(&seed, unsigned(123));
-
- std::vector<ModelLOD> models;
- for (unsigned int i = 0;
- i < tileGeometryBin.randomModels.getNumModels(); i++) {
- SGMatModelBin::MatModel obj
- = tileGeometryBin.randomModels.getMatModel(i);
-
- SGPropertyNode* root = options->getPropertyNode()->getRootNode();
- osg::Node* node = obj.model->get_random_model(root, &seed);
-
- // Create a matrix to place the object in the correct
- // location, and then apply the rotation matrix created
- // above, with an additional random (or taken from
- // the object mask) heading rotation if appropriate.
- osg::Matrix transformMat;
- transformMat = osg::Matrix::translate(toOsg(obj.position));
- if (obj.model->get_heading_type() == SGMatModel::HEADING_RANDOM) {
- // Rotate the object around the z axis.
- double hdg = mt_rand(&seed) * M_PI * 2;
- transformMat.preMult(osg::Matrix::rotate(hdg,
- osg::Vec3d(0.0, 0.0, 1.0)));
- }
+ if (use_random_objects && matlib) {
+ tileGeometryBin.computeRandomObjects(matlib);
- if (obj.model->get_heading_type() == SGMatModel::HEADING_MASK) {
- // Rotate the object around the z axis.
- double hdg = - obj.rotation * M_PI * 2;
- transformMat.preMult(osg::Matrix::rotate(hdg,
- osg::Vec3d(0.0, 0.0, 1.0)));
- }
-
- osg::MatrixTransform* position =
- new osg::MatrixTransform(transformMat);
- position->addChild(node);
- models.push_back(ModelLOD(position, obj.lod));
- }
- RandomObjectsQuadtree quadtree((GetModelLODCoord()), (AddModelLOD()));
- quadtree.buildQuadTree(models.begin(), models.end());
- randomObjects = quadtree.getRoot();
- randomObjects->setName("random objects");
- }
- }
+ if (tileGeometryBin.randomModels.getNumModels() > 0) {
+ // Generate a repeatable random seed
+ mt seed;
+ mt_init(&seed, unsigned(123));
- if (use_random_vegetation && matlib) {
- // Now add some random forest.
- tileGeometryBin.computeRandomForest(matlib, vegetation_density);
+ std::vector<ModelLOD> models;
+ for (unsigned int i = 0;
+ i < tileGeometryBin.randomModels.getNumModels(); i++) {
+ SGMatModelBin::MatModel obj
+ = tileGeometryBin.randomModels.getMatModel(i);
+
+ SGPropertyNode* root = options->getPropertyNode()->getRootNode();
+ osg::Node* node = obj.model->get_random_model(root, &seed);
- if (tileGeometryBin.randomForest.size() > 0) {
- forestNode = createForest(tileGeometryBin.randomForest, osg::Matrix::identity(),
- options);
- forestNode->setName("Random trees");
+ // Create a matrix to place the object in the correct
+ // location, and then apply the rotation matrix created
+ // above, with an additional random (or taken from
+ // the object mask) heading rotation if appropriate.
+ osg::Matrix transformMat;
+ transformMat = osg::Matrix::translate(toOsg(obj.position));
+ if (obj.model->get_heading_type() == SGMatModel::HEADING_RANDOM) {
+ // Rotate the object around the z axis.
+ double hdg = mt_rand(&seed) * M_PI * 2;
+ transformMat.preMult(osg::Matrix::rotate(hdg,
+ osg::Vec3d(0.0, 0.0, 1.0)));
+ }
+
+ if (obj.model->get_heading_type() == SGMatModel::HEADING_MASK) {
+ // Rotate the object around the z axis.
+ double hdg = - obj.rotation * M_PI * 2;
+ transformMat.preMult(osg::Matrix::rotate(hdg,
+ osg::Vec3d(0.0, 0.0, 1.0)));
+ }
+
+ osg::MatrixTransform* position =
+ new osg::MatrixTransform(transformMat);
+ position->addChild(node);
+ models.push_back(ModelLOD(position, obj.lod));
}
- }
+ RandomObjectsQuadtree quadtree((GetModelLODCoord()), (AddModelLOD()));
+ quadtree.buildQuadTree(models.begin(), models.end());
+ randomObjects = quadtree.getRoot();
+ randomObjects->setName("random objects");
+ }
}
+ if (use_random_vegetation && matlib) {
+ // Now add some random forest.
+ tileGeometryBin.computeRandomForest(matlib, vegetation_density);
+
+ if (tileGeometryBin.randomForest.size() > 0) {
+ forestNode = createForest(tileGeometryBin.randomForest, osg::Matrix::identity(),
+ options);
+ forestNode->setName("Random trees");
+ }
+ }
+
+ if (use_random_buildings && matlib) {
+ tileGeometryBin.computeRandomBuildings(matlib, building_density);
+ if (tileGeometryBin.randomBuildings.size() > 0) {
+ buildingNode = createRandomBuildings(tileGeometryBin.randomBuildings, osg::Matrix::identity(),
+ options);
+ buildingNode->setName("Random buildings");
+ }
+ }
+
// FIXME: ugly, has a side effect
if (matlib)
tileGeometryBin.computeRandomSurfaceLights(matlib);
groundLights0->addChild(geode);
lightGroup->addChild(groundLights0);
}
+
if (tileGeometryBin.randomTileLights.getNumLights() > 0) {
osg::Group* groundLights1 = new osg::Group;
groundLights1->setStateSet(lightManager->getGroundLightStateSet());
vasiGeode->setStateSet(lightManager->getRunwayLightStateSet());
lightGroup->addChild(vasiGeode);
}
+
Effect* runwayEffect = 0;
if (tileGeometryBin.runwayLights.getNumLights() > 0
|| !tileGeometryBin.rabitLights.empty()
transform->addChild(lightLOD);
}
- if (randomObjects.valid() || forestNode.valid()) {
+ if (randomObjects.valid() || forestNode.valid() || buildingNode.valid()) {
// Add a LoD node, so we don't try to display anything when the tile center
// is more than 20km away.
if (randomObjects.valid()) objectLOD->addChild(randomObjects.get(), 0, 20000);
if (forestNode.valid()) objectLOD->addChild(forestNode.get(), 0, 20000);
+ if (buildingNode.valid()) objectLOD->addChild(buildingNode.get(), 0, 20000);
unsigned nodeMask = SG_NODEMASK_CASTSHADOW_BIT | SG_NODEMASK_RECIEVESHADOW_BIT | SG_NODEMASK_TERRAIN_BIT;
objectLOD->setNodeMask(nodeMask);