/* -*- C++ -*- * Copyright ©2008 Hugo Mills * * This software is distributed under the terms of the GNU GPL v3 * For more information on the GPL, see the file COPYING or * visit http://www.gnu.org/ * * This software is distributed without warranty */ #include "content_terminator.h" #include "contentgroup.h" #include "output_canvas.h" #include "xfview_translate.h" #include "magellan/parsertools.h" #include "magellan/options.h" #include "plugins.h" #include std::string blend_keywords[] = { "linear", "quadratic", "normal" }; enum _blend_types { BLEND_LINEAR = 0, BLEND_QUADRATIC, BLEND_NORMAL, BLEND_END } blend_types; Content_Terminator::DarkFunction Content_Terminator::darkfns[] = { &Content_Terminator::dfn_linear, &Content_Terminator::dfn_quadratic, &Content_Terminator::dfn_normal }; extern "C" PluginInfo* getmodules(int i) { if(i==0) { PluginInfo_Content* pi = new PluginInfo_Content(); pi->name = "Terminator line"; pi->author = "Hugo Mills"; pi->copyright = "©2008 Hugo Mills"; pi->version = 0x10000; pi->parser = Content_Terminator::parse; return pi; } else return NULL; } Content_Terminator::Content_Terminator() : day(NULL), night(NULL), sunpos(NULL), blend(0), bwidth(0.0) { } Content_Terminator::~Content_Terminator() { if(day != NULL) day->destroy(); if(night != NULL) night->destroy(); if(sunpos != NULL) sunpos->destroy(); } Content* Content_Terminator::parse( ConfigLexer* lex, const std::string& param_type, const std::string& param_subtype ) { if(param_type != "terminator") return NULL; Content_Terminator* ct = new Content_Terminator(); while(!lex->eos()) { // We have some top-level config options if(readkeyedvalue(lex, "boundary-width", &ct->bwidth) || readoptionvalue(lex, "boundary-blend", &ct->blend, blend_keywords, blend_keywords+BLEND_END) ) { continue; } // The remaining options are complex if(lex->type() != TOKEN) { // Bitch about it std::cerr << "Type is not a token (line " << lex->lineno() << ")" << std::endl; return NULL; } std::string option = lex->value(); lex->next(); if(lex->type() != CHAR || lex->value() != "{") { // Bitch about it std::cerr << "Expected { but not found (line " << lex->lineno() << ")" << std::endl; return NULL; } lex->next(); // Sun definition. This is an orbit. if(option == "sun") { std::string type = ""; std::string subtype = ""; readkeyedvalue(lex, "type", &type); // Read it or not, we // don't care too much // here... try { XfOrbit* res = readplugin( lex, type, subtype, orbit_plugins.begin(), orbit_plugins.end() ); if(res) ct->sunpos = res; } catch(...) { std::cerr << "Caught error in sun definition" << std::endl << " at line " << lex->lineno() << " of config file" << std::endl; } continue; } // Day and night are both content groups if(option == "day") { // Read a content group ct->day = ContentGroup::parse(lex, "", ""); if(opts->debug_parse() >= 4 || opts->debug_render() >= 2) std::cerr << " terminator-day created at " << ct->day << std::endl; lex->next(); continue; } if(option == "night") { ct->night = ContentGroup::parse(lex, "", ""); if(opts->debug_parse() >= 4 || opts->debug_render() >= 2) std::cerr << " terminator-night created at " << ct->day << std::endl; lex->next(); continue; } // Unhandled situation. Throw an error throw(UnknownConfigToken(lex)); } if(ct->day == NULL) throw(PluginInitError("Required section \"day\" missing.")); if(ct->night == NULL) throw(PluginInitError("Required section \"night\" missing.")); if(ct->sunpos == NULL) throw(PluginInitError("Required section \"sun\" missing.")); // Convert angle to radians ct->bwidth = degrad(ct->bwidth); return ct; } void Content_Terminator::plot( Output* bitmap, XfView* viewport, XfMap* projection, XfOrbit* orbit, XfSphere* sphere ) { // Create temporary canvases to hold day and night int canx = viewport->right() - viewport->left(); int cany = viewport->top() - viewport->bottom(); Output_Canvas* day_canvas = new Output_Canvas(canx, cany); Output_Canvas* night_canvas = new Output_Canvas(canx, cany); // Translate the supplied viewport into something that has an // origin at (0,0), to match our canvases XfView* vp = new XfView_Translate(viewport, -viewport->left(), -viewport->bottom()); // Plot day and night separately day->plot(day_canvas, vp, projection, orbit, sphere); night->plot(night_canvas, vp, projection, orbit, sphere); ivec2 bm_pos; dvec2 map_pos; dvec3 shifted_pos; dvec3 globe_pos; dvec2 latlong; ivec3 colour; // Work out the direction of the sun from the centre of the globe dvec3 sun; shifted_pos[0] = 0.0; shifted_pos[1] = 0.0; shifted_pos[2] = 1.0; sunpos->reset(); sunpos->g(sun, shifted_pos); // For each pixel on those canvases, compute the relative sun // position and blend for(bm_pos[1] = vp->bottom(); bm_pos[1] < vp->top(); bm_pos[1]++) { for(bm_pos[0] = vp->left(); bm_pos[0] < vp->right(); bm_pos[0]++) { if(!vp->g(map_pos, bm_pos)) continue; // Compute the position of this point on the globe's surface if(projection->g(shifted_pos, map_pos) && orbit->g(globe_pos, shifted_pos)) { // globe_pos is now the (x,y,z) position on the // earth's surface of the point in question. // Compare to the sun position: dot product gives // cosine of the angle between the two unit vectors double cossigma = 0.0; cossigma += globe_pos[0]*sun[0]; cossigma += globe_pos[1]*sun[1]; cossigma += globe_pos[2]*sun[2]; ivec3 dcol; ivec3 ncol; day_canvas->getpixel(dcol, bm_pos); night_canvas->getpixel(ncol, bm_pos); // Work out the blend value between the two sides double darkness = (this->*darkfns[blend])(cossigma); // Compute the blend blendcolours(colour, dcol, ncol, darkness); // Set the colour ivec2 out_pos; out_pos[0] = bm_pos[0] + viewport->left(); out_pos[1] = bm_pos[1] + viewport->bottom(); bitmap->setpixel(out_pos, colour[0], colour[1], colour[2]); } } } // Tidy up vp->destroy(); night_canvas->destroy(); day_canvas->destroy(); } void Content_Terminator::blendcolours(ivec3& res, const ivec3& dc, const ivec3& nc, double darkness) const { // Use an RGB colour model for(int i = 0; i < 3; i++) res[i] = dc[i] + (nc[i]-dc[i]) * darkness; } double Content_Terminator::dfn_linear(double d) const { double sigma = M_PI/2 - acos(d); // Angle from notional terminator line if(sigma > bwidth) return 0.0; if(sigma < 0.0) return 1.0; return 1.0 - sigma/bwidth; } double Content_Terminator::dfn_quadratic(double d) const { double sigma = M_PI/2 - acos(d); if(d > bwidth) return 0.0; if(d < 0.0) return 1.0; if(d < bwidth/2) return -2/(bwidth*bwidth)*d*d + 1; else return 2/(bwidth*bwidth)*d*d - 4/bwidth*d + 2; } double Content_Terminator::dfn_normal(double d) const { if(d < 0.0) return 1.0; return 1.0-d; } bool Content_Terminator::isbound(void) const { return day->isbound() && night->isbound() && sunpos->isbound(); } Plugin* Content_Terminator::bind(Configuration* conf) { if(!day->isbound()) { Plugin* p = day->bind(conf); if(p == NULL) return NULL; day = dynamic_cast(p); } if(!night->isbound()) { Plugin* p = night->bind(conf); if(p == NULL) return NULL; night = dynamic_cast(p); } if(!sunpos->isbound()) { Plugin* p = sunpos->bind(conf); if(p == NULL) return NULL; sunpos = dynamic_cast(p); } return this; }