/* bzflag
 * Copyright (c) 1993-2017 Tim Riker
 *
 * This package is free software;  you can redistribute it and/or
 * modify it under the terms of the license found in the file
 * named COPYING that should have accompanied this file.
 *
 * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

// interface header
#include "WeatherRenderer.h"

// common impl headers
#include "TextureManager.h"
#include "StateDatabase.h"
#include "BZDBCache.h"
#include "TimeKeeper.h"
#include "TextUtils.h"
#include "ParseColor.h"
#include "Intersect.h"
#include "Extents.h"

// local impl headers
#include "RoofTops.h"

// for debug
#define _CULLING_RAIN false


static void bzdbCallBack(const std::string& UNUSED(name), void *userData)
{
  static_cast<WeatherRenderer*>(userData)->set();
}


WeatherRenderer::WeatherRenderer()
{
  rainColor[0][0] = 0.75f;
  rainColor[0][1] = 0.75f;
  rainColor[0][2] = 0.75f;
  rainColor[0][3] = 0.75f;
  rainColor[1][0] = 0.0f;
  rainColor[1][1] = 0.0f;
  rainColor[1][2] = 0.0f;
  rainColor[1][3] = 0.0f;

  rainSize[0] = rainSize[1] = 1.0f;

  rainDensity = 1000;
  rainSpeed = -100.0f;
  rainSpeedMod = 10.0f;
  rainSpread = 500.0f;

  doPuddles = true;
  doLineRain = true;
  doBillBoards = false;
  spinRain = false;
  cullRoofTops = true;
  roofPuddles = false;

  rainStartZ = -1.0f;
  rainEndZ = 0.0f;

  lastRainTime = 0.0f;

  maxPuddleTime = 5.0f;
  puddleSpeed = 1.0f;

  puddleColor[0] = puddleColor[1] = puddleColor[2] = puddleColor[3] = 1.0f;

  dropList = puddleList = INVALID_GL_LIST_ID;

  gridSize = 200.0f;

  keyFactor = 1.0f / gridSize;

  rainCount = 0;
  cellCount = 0;

  // install callbacks
  BZDB.addCallback("_rainType", bzdbCallBack, this);
  BZDB.addCallback("_rainDensity", bzdbCallBack, this);
  BZDB.addCallback("_rainSpread", bzdbCallBack, this);
  BZDB.addCallback("_rainSpeedMod", bzdbCallBack, this);
  BZDB.addCallback("_rainStartZ", bzdbCallBack, this);
  BZDB.addCallback("_rainEndZ", bzdbCallBack, this);
  BZDB.addCallback("_rainBaseColor", bzdbCallBack, this);
  BZDB.addCallback("_useRainPuddles", bzdbCallBack, this);
  BZDB.addCallback("_useLineRain", bzdbCallBack, this);
  BZDB.addCallback("_useRainBillboards", bzdbCallBack, this);
  BZDB.addCallback("_rainPuddleSpeed", bzdbCallBack, this);
  BZDB.addCallback("_rainMaxPuddleTime", bzdbCallBack, this);
  BZDB.addCallback("_rainPuddleColor", bzdbCallBack, this);
  BZDB.addCallback("_rainPuddleTexture", bzdbCallBack, this);
  BZDB.addCallback("_rainTexture", bzdbCallBack, this);
  BZDB.addCallback("_rainSpins", bzdbCallBack, this);
  BZDB.addCallback("_rainRoofs", bzdbCallBack, this);
  BZDB.addCallback("userRainScale", bzdbCallBack, this);
}


WeatherRenderer::~WeatherRenderer()
{
  freeContext(); // free the display lists
  BZDB.removeCallback("_rainType", bzdbCallBack, this);
  BZDB.removeCallback("_rainDensity", bzdbCallBack, this);
  BZDB.removeCallback("_rainSpread", bzdbCallBack, this);
  BZDB.removeCallback("_rainSpeedMod", bzdbCallBack, this);
  BZDB.removeCallback("_rainStartZ", bzdbCallBack, this);
  BZDB.removeCallback("_rainEndZ", bzdbCallBack, this);
  BZDB.removeCallback("_rainBaseColor", bzdbCallBack, this);
  BZDB.removeCallback("_useRainPuddles", bzdbCallBack, this);
  BZDB.removeCallback("_useLineRain", bzdbCallBack, this);
  BZDB.removeCallback("_useRainBillboards", bzdbCallBack, this);
  BZDB.removeCallback("_rainPuddleSpeed", bzdbCallBack, this);
  BZDB.removeCallback("_rainMaxPuddleTime", bzdbCallBack, this);
  BZDB.removeCallback("_rainPuddleColor", bzdbCallBack, this);
  BZDB.removeCallback("_rainPuddleTexture", bzdbCallBack, this);
  BZDB.removeCallback("_rainTexture", bzdbCallBack, this);
  BZDB.removeCallback("_rainSpins", bzdbCallBack, this);
  BZDB.removeCallback("_rainRoofs", bzdbCallBack, this);
  BZDB.removeCallback("userRainScale", bzdbCallBack, this);
}


void WeatherRenderer::init(void)
{
  OpenGLGStateBuilder gstate;
  TextureManager& tm = TextureManager::instance();

  gstate.reset();
  gstate.setShading();
  gstate.setBlending((GLenum) GL_SRC_ALPHA,
		     (GLenum) GL_ONE_MINUS_SRC_ALPHA);
  gstate.setAlphaFunc();
  rainGState = gstate.getState();

  // just set up some default textures
  gstate.setTexture(tm.getTextureID("snowflake"));
  texturedRainState = gstate.getState();

  gstate.setTexture(tm.getTextureID("puddle"));
  puddleState = gstate.getState();

  buildPuddleList();
}


void WeatherRenderer::set(void)
{
  // check the bzdb and see if we need to change any rain stuff
  rainDensity = 0;

  if (dbItemSet("_rainType") || dbItemSet("_rainDensity")) {
    // default rain desnity
    rainDensity = (int)(1000.0f * BZDB.eval("userRainScale"));

    // some defaults
    doLineRain = false;
    rainSize[0] = 1.0f;
    rainSize[1] = 1.0f;
    rainSpeed = -100.0f;
    rainSpeedMod = 50.0f;
    doPuddles = true;
    rainColor[0][0] = 0.75f;
    rainColor[0][1] = 0.75f;
    rainColor[0][2] = 0.85f;
    rainColor[0][3] = 0.75f;
    rainColor[1][0] = 0.0f;
    rainColor[1][1] = 0.0f;
    rainColor[1][2] = 0.0f;
    rainColor[1][3] = 0.0f;
    rainSize[0] = rainSize[1] = 1.0f;
    maxPuddleTime = 1.5f;
    puddleSpeed = 1.0f;
    puddleColor[0] = puddleColor[1] = puddleColor[2] = puddleColor[3] =	1.0f;
    spinRain = true;
    cullRoofTops = true;
    roofPuddles = false;

    rainSpread = 500.0f;

    TextureManager& tm = TextureManager::instance();

    OpenGLGStateBuilder gstate;
    gstate.reset();
    gstate.setShading();
    gstate.setBlending((GLenum) GL_SRC_ALPHA,
		       (GLenum) GL_ONE_MINUS_SRC_ALPHA);
    gstate.setAlphaFunc();

    OpenGLGStateBuilder puddleGStateBuilder(puddleState);

    puddleGStateBuilder.setTexture(tm.getTextureID("puddle"));

    if (dbItemSet("_rainType")) {
      std::string rainType = TextUtils::tolower(BZDB.get("_rainType"));

      if (rainType == "snow") {
	doBillBoards = false;
	gstate.setTexture (tm.getTextureID("snowflake"));
	rainSpeed = -20.0f;
	rainSpeedMod = 5.0f;
	doPuddles = false;
      } else if (rainType == "fatrain") {
	doBillBoards = false;
	rainSize[0] = 0.5f;
	rainSize[1] = 0.75f;
	gstate.setTexture(tm.getTextureID("raindrop"));
	rainSpeed = -50.0f;
	rainSpeedMod = 25.0f;
	spinRain = false;
      } else if (rainType == "frog") {
	gstate.setTexture(tm.getTextureID ("frog"));
	rainSpeed = -100.0f;
	rainSpeedMod = 5.0f;
	doPuddles = true;
	doBillBoards = true;
	// ewww
	puddleSpeed = 3.0f;
	puddleColor[0] = 0.75f;
	puddleColor[1] = puddleColor[2] = 0.0f;
	rainSize[0] = 2.0f;
	rainSize[1] = 2.0f;
      }
      else if (rainType == "particle") {
	rainDensity = (int)(500.0f * BZDB.eval("userRainScale"));
	gstate.setTexture(tm.getTextureID ("red_super_bolt"));
	rainSpeed = -20.0f;
	rainSpeedMod = 5.0f;
	doPuddles = true;
	doBillBoards = true;
	puddleSpeed = 10.0f;
	// ewww
	puddleColor[0] = 1.0f;
	puddleColor[1] = puddleColor[2] = 0.0f;
	rainSize[0] = 1.0f;
	rainSize[1] = 1.0f;
      }
      else if (rainType == "bubble") {
	gstate.setTexture(tm.getTextureID("bubble"));
	rainSpeed = 20.0f;
	rainSpeedMod = 1.0f;
	doBillBoards = true;
	doPuddles = false;
      }
      else if (rainType == "rain") {
	puddleColor[0] = 1.0f;
	puddleColor[1] = puddleColor[2] = 1.0f;
	doLineRain = true;
	rainSize[0] = 0.0f;
	rainSize[1] = 0.75f;
	puddleGStateBuilder.setTexture(tm.getTextureID("puddle"));
      }
    }

    if (dbItemSet("_rainPuddleTexture")) {
      puddleGStateBuilder.setTexture(
	tm.getTextureID(BZDB.get("_rainPuddleTexture").c_str()));
    }

    // see if the texture is specificly overiden
    if (dbItemSet("_rainTexture")) {
      gstate.setTexture (
	tm.getTextureID(BZDB.get("_rainTexture").c_str()));
    }

    texturedRainState = gstate.getState();

    // if there is a specific overides for stuff
    if (dbItemSet("_rainSpread"))
      rainSpread = BZDB.eval("_rainSpread");

    if (dbItemSet("_rainDensity"))
      rainDensity = (int)(BZDB.eval("_rainDensity") * BZDB.eval("userRainScale"));

    if (dbItemSet("_rainSpeed"))
      rainSpeed = BZDB.eval("_rainSpeed");

    if (dbItemSet("_rainSpeedMod"))
      rainSpeedMod = BZDB.eval("_rainSpeedMod");

    if (dbItemSet("_rainSize"))
      BZDB.evalPair("_rainSize", rainSize);

    if (dbItemSet("_rainStartZ"))
      rainStartZ = BZDB.eval("_rainStartZ");

    if (dbItemSet("_rainEndZ"))
      rainEndZ = BZDB.eval("_rainEndZ");

    if (dbItemSet("_rainBaseColor"))
      parseColorString(BZDB.get("_rainBaseColor"), rainColor[0]);

    if (dbItemSet("_rainTopColor"))
      parseColorString(BZDB.get("_rainTopColor"), rainColor[1]);

    if (dbItemSet("_useRainPuddles"))
      doPuddles = BZDB.isTrue("_useRainPuddles");

    if (dbItemSet("_useLineRain"))
      doLineRain = BZDB.isTrue("_useLineRain");

    if (dbItemSet("_useRainBillboards"))
      doBillBoards = BZDB.isTrue("_useRainBillboards");

    if (dbItemSet("_rainPuddleColor"))
      parseColorString(BZDB.get("_rainPuddleColor"), puddleColor);

    if (dbItemSet("_rainPuddleSpeed"))
      puddleSpeed = BZDB.eval("rainPuddleSpeed");

    if (dbItemSet("_rainMaxPuddleTime"))
      maxPuddleTime = BZDB.eval("_rainMaxPuddleTime");

    if (dbItemSet("_rainSpins"))
      spinRain = BZDB.isTrue("_rainSpins");

    if (dbItemSet("_rainRoofs")) {
      cullRoofTops = (BZDB.evalInt("_rainRoofs") >= 1);
      roofPuddles = (BZDB.evalInt("_rainRoofs") >= 2);
    }

    // update the actual puddle material
    puddleState = puddleGStateBuilder.getState();


    // make sure we know where to start and stop the rain
    // we want to compute the heights for us
    if (rainStartZ == -1 && rainEndZ == 0) {
      // check the dir
      if (rainSpeed < 0) { // rain going down
	rainStartZ = 120.0f * BZDBCache::tankHeight; // same as the clouds
	rainEndZ = 0;
      } else { // rain going up (tiny bubbles)
	if (rainSpeed == 0)
	  rainSpeed = 0.1f;

	rainEndZ = 120.0f * BZDBCache::tankHeight;  // same as the clouds
	rainStartZ = 0;
      }
    } else {
      // the specified rain start and end values,
      // make sure they make sense with the directon
      if (rainSpeed < 0) { // rain going down
	if (rainEndZ > rainStartZ) {
	  float temp = rainStartZ;
	  rainStartZ = rainEndZ;
	  rainEndZ = temp;
	}
      } else { // rain going up
	if (rainEndZ < rainStartZ) {
	  float temp = rainStartZ;
	  rainStartZ = rainEndZ;
	  rainEndZ = temp;
	}
      }
    }

    float rainHeightDelta = rainEndZ - rainStartZ;

    int totalRain = rainCount;
    if (!_CULLING_RAIN)
      totalRain = raindrops.size();

    if (totalRain < rainDensity) {
      for (int drops = totalRain; drops < rainDensity; drops++) {
	rain drop;
	drop.speed = rainSpeed +
		      (((float) bzfrand() * 2.0f - 1.0f) * rainSpeedMod);
	drop.pos[0] = (((float) bzfrand() * 2.0f - 1.0f) * rainSpread);
	drop.pos[1] = (((float) bzfrand() * 2.0f - 1.0f) * rainSpread);
	drop.pos[2] = rainStartZ + (((float) bzfrand()) * rainHeightDelta);
	if (cullRoofTops) {
	  drop.roofTop =
	    RoofTops::getTopHeight(drop.pos[0], drop.pos[1], drop.pos[2]);
	} else {
	  drop.roofTop = 0.0f;
	}

	if (_CULLING_RAIN) {
	  addDrop (drop);
	} else {
	  raindrops.push_back (drop);
	}
      }
      lastRainTime = float(TimeKeeper::getCurrent().getSeconds());
    }
    // recompute the drops based on the posible new size
    buildDropList();

    if (_CULLING_RAIN) { // need to update the bbox depths on all the chunks
      std::map<int, visibleChunk>::iterator itr = chunkMap.begin();

      while (itr != chunkMap.end()) {
	itr->second.bbox.mins[2] =
	    rainStartZ > rainEndZ ? rainEndZ : rainStartZ;
	itr->second.bbox.maxs[2] =
	    rainStartZ > rainEndZ ? rainStartZ : rainEndZ;
	itr++;
      }
    }
  } else {
    raindrops.clear();
    chunkMap.clear();
    rainCount = 0;
  }
}


void WeatherRenderer::update(void)
{
  // update the time
  float frameTime = float(TimeKeeper::getCurrent().getSeconds() - lastRainTime);
  lastRainTime = float(TimeKeeper::getCurrent().getSeconds());

  std::vector<rain> dropsToAdd;

  // clamp the update time
  // its not an important sim so just keep it smooth
  if (frameTime > 0.06f)
    frameTime = 0.06f;

  if (!_CULLING_RAIN) {
    // update all the drops in the world
    std::vector<rain>::iterator itr = raindrops.begin();
    while (itr != raindrops.end()) {
      if (updateDrop(itr, frameTime, dropsToAdd))
	itr++;
    }
  } else {
    std::map<int, visibleChunk>::iterator itr = chunkMap.begin();

    while (itr != chunkMap.end()) {
      if (!itr->second.drops.size()) {   // kill any empty chunks
	// cellCount--;
	// itr == chunkMap.erase(itr);
      } else {
	std::vector<rain>::iterator dropItr = itr->second.drops.begin();
	while (dropItr != itr->second.drops.end()) {
	  if (updateDrop(dropItr, frameTime, dropsToAdd)) {
	    dropItr++;
	  } else {
	    dropItr = itr->second.drops.erase(dropItr);
	    rainCount--;
	  }
	}
	itr++;
      }
    }

    // add in any new drops
    std::vector<rain>::iterator dropItr = dropsToAdd.begin();
    while (dropItr != dropsToAdd.end()) {
      addDrop(*dropItr);
      dropItr++;
    }
  }

  // update all the puddles
  std::vector<puddle>::iterator puddleItr = puddles.begin();

  while (puddleItr != puddles.end()) {
    if (updatePuddle(puddleItr, frameTime))
      puddleItr++;
  }
}


void WeatherRenderer::draw(const SceneRenderer& sr)
{
  if (!_CULLING_RAIN) {
    if (raindrops.empty())
      return;
  } else {
    if (!rainCount)
      return;
  }

  glDisable(GL_CULL_FACE);
  glMatrixMode(GL_MODELVIEW);
  glColor4f(1, 1, 1, 1.0f);
  glDepthMask(GL_FALSE);

  if (doLineRain) { // we are doing line rain
    rainGState.setState();
    glPushMatrix();
    glBegin(GL_LINES);
  } else {
    texturedRainState.setState();
  }

  if (!_CULLING_RAIN) { // draw ALL the rain
    std::vector<rain>::iterator itr = raindrops.begin();
    while (itr != raindrops.end()) {
      drawDrop(*itr, sr);
      itr++;
    }
  } else {
    // do the smart thing and just draw the rain that is VISIBLE
    std::map<int, visibleChunk>::iterator itr = chunkMap.begin();

    const Frustum *frustum = &sr.getViewFrustum();
    while (itr != chunkMap.end()) {
      if (itr->second.drops.size()) { // skip any empty chunks
	// see if the chunk is visible
	Extents exts; // FIXME - possible nasty slowdown
	// Not using an Extents bbox directly because it is nice to
	// block the Extents copy constructor to avoid passing by value.
	exts.set(itr->second.bbox.mins, itr->second.bbox.maxs);
	if (testAxisBoxInFrustum(exts, frustum) != Outside) {
	  std::vector<rain>::iterator dropItr = itr->second.drops.begin();
	  while (dropItr != itr->second.drops.end()) {
	    drawDrop(*dropItr, sr);
	    dropItr++;
	  }
	}
      }
      itr++;
    }
  }

  if (doLineRain) {
    glEnd();
    glPopMatrix();
  }

  if (doPuddles) {
    std::vector<puddle>::iterator puddleItr = puddles.begin();
    puddleState.setState();
    while (puddleItr != puddles.end()) {
      drawPuddle(*puddleItr);
      puddleItr++;
    }
  }

  glEnable(GL_CULL_FACE);
  glColor4f(1, 1, 1, 1);
  glDepthMask(GL_TRUE);
}


void WeatherRenderer::freeContext(void)
{
  if (dropList != INVALID_GL_LIST_ID) {
    glDeleteLists(dropList, 1);
    dropList = INVALID_GL_LIST_ID;
  }
  if (puddleList != INVALID_GL_LIST_ID) {
    glDeleteLists(puddleList, 1);
    puddleList = INVALID_GL_LIST_ID;
  }
  return;
}


void WeatherRenderer::rebuildContext(void)
{
  buildDropList();
  buildPuddleList();
  return;
}


void WeatherRenderer::buildDropList(bool _draw)
{
  if (!_draw) {
    if (dropList != INVALID_GL_LIST_ID) {
      glDeleteLists(dropList, 1);
      dropList = INVALID_GL_LIST_ID;
    }
    dropList = glGenLists(1);
    glNewList(dropList, GL_COMPILE);
  }

  if (doBillBoards) {
    glBegin(GL_QUADS);
    glTexCoord2f(0, 0);
    glVertex3f(-rainSize[0], -rainSize[1], 0);

    glTexCoord2f(1, 0);
    glVertex3f(rainSize[0], -rainSize[1], 0);

    glTexCoord2f(1, 1);
    glVertex3f(rainSize[0], rainSize[1], 0);

    glTexCoord2f(0, 1);
    glVertex3f(-rainSize[0], rainSize[1], 0);
    glEnd();
  } else {
    glPushMatrix();
    glBegin(GL_QUADS);
    glTexCoord2f(0, 0);
    glVertex3f(-rainSize[0], 0, -rainSize[1]);

    glTexCoord2f(1, 0);
    glVertex3f(rainSize[0], 0, -rainSize[1]);

    glTexCoord2f(1, 1);
    glVertex3f(rainSize[0], 0, rainSize[1]);

    glTexCoord2f(0, 1);
    glVertex3f(-rainSize[0], 0, rainSize[1]);
    glEnd();

    glRotatef(120, 0, 0, 1);

    glBegin(GL_QUADS);
    glTexCoord2f(0, 0);
    glVertex3f(-rainSize[0], 0, -rainSize[1]);

    glTexCoord2f (1, 0);
    glVertex3f(rainSize[0], 0, -rainSize[1]);

    glTexCoord2f (1, 1);
    glVertex3f(rainSize[0], 0, rainSize[1]);

    glTexCoord2f (0, 1);
    glVertex3f(-rainSize[0], 0, rainSize[1]);
    glEnd();

    glRotatef(120, 0, 0, 1);

    glBegin(GL_QUADS);
    glTexCoord2f(0, 0);
    glVertex3f(-rainSize[0], 0, -rainSize[1]);

    glTexCoord2f(1, 0);
    glVertex3f(rainSize[0], 0, -rainSize[1]);

    glTexCoord2f(1, 1);
    glVertex3f(rainSize[0], 0, rainSize[1]);

    glTexCoord2f(0, 1);
    glVertex3f(-rainSize[0], 0, rainSize[1]);
    glEnd();
    glPopMatrix();
  }

  if (!_draw) {
    glEndList();
  }
}


void WeatherRenderer::buildPuddleList(bool _draw)
{
  float scale = 1;
  if (!_draw) {
    if (puddleList != INVALID_GL_LIST_ID) {
      glDeleteLists(puddleList, 1);
      puddleList = INVALID_GL_LIST_ID;
    }
    puddleList = glGenLists(1);
    glNewList(puddleList, GL_COMPILE);
  }

  glBegin(GL_QUADS);
  glTexCoord2f(0, 0);
  glVertex3f(-scale, -scale, 0);

  glTexCoord2f(1, 0);
  glVertex3f(scale, -scale, 0);

  glTexCoord2f(1, 1);
  glVertex3f(scale, scale, 0);

  glTexCoord2f(0, 1);
  glVertex3f(-scale, scale, 0);
  glEnd();

  if (!_draw) {
    glEndList();
  }
}


bool WeatherRenderer::updateDrop(std::vector<rain>::iterator& drop,
				 float frameTime, std::vector<rain> &toAdd)
{
  drop->pos[2] += drop->speed * frameTime;

  bool killDrop = false;

  if (drop->speed < 0) {
    if (cullRoofTops) {
      killDrop = (drop->pos[2] < drop->roofTop);
    } else {
      killDrop = (drop->pos[2] < rainEndZ);
    }
  } else {
    killDrop = (drop->pos[2] > rainEndZ);
  }

  if (killDrop) {
    if (doPuddles && (roofPuddles ||
	!(cullRoofTops && (drop->speed < 0.0f) && (drop->roofTop != 0.0f)))) {
      puddle thePuddle;
      thePuddle.pos[0] = drop->pos[0];
      thePuddle.pos[1] = drop->pos[1];
      if (!cullRoofTops) {
	thePuddle.pos[2] = rainEndZ;
      } else {
	thePuddle.pos[2] = drop->roofTop + 0.05f;
      }
      thePuddle.time = 0.001f;
      puddles.push_back (thePuddle);
    }

    if (!_CULLING_RAIN) {
      if ((int)(raindrops.size()) <= rainDensity) {
	// reset this drop
	drop->pos[2] = rainStartZ;
	drop->speed =
	    rainSpeed + ((float) (bzfrand() * 2.0f - 1.0f) * rainSpeedMod);
	drop->pos[0] = (((float) bzfrand() * 2.0f - 1.0f) * rainSpread);
	drop->pos[1] = (((float) bzfrand() * 2.0f - 1.0f) * rainSpread);
	if (cullRoofTops) {
	  drop->roofTop =
	    RoofTops::getTopHeight (drop->pos[0], drop->pos[1], drop->pos[2]);
	  // clamp the rain to the valid rain range.
	  if ( rainSpeed > 0)
	  {
		  if ( drop->roofTop < rainEndZ )
			  drop->roofTop = rainEndZ;
		  else if ( drop->roofTop > rainEndZ )
			  drop->roofTop = rainEndZ;
	  }
	} else {
	  drop->roofTop = rainEndZ;
	}

	return true;
      } else { // we need less rain, so don't do this one
	drop = raindrops.erase(drop);
	return false;
      }
    }
    else {
      if (rainCount <= rainDensity) {
	rain newDrop;
	newDrop.pos[2] = rainStartZ;
	newDrop.speed =
	    rainSpeed +
	    ((float) (bzfrand() * 2.0f - 1.0f) * rainSpeedMod);
	newDrop.pos[0] = (((float) bzfrand() * 2.0f - 1.0f) * rainSpread);
	newDrop.pos[1] = (((float) bzfrand() * 2.0f - 1.0f) * rainSpread);
	if (cullRoofTops) {
	  newDrop.roofTop =
	    RoofTops::getTopHeight (newDrop.pos[0], newDrop.pos[1], newDrop.pos[2]);
	  // clamp the rain to the valid rain range.
	  if ( rainSpeed > 0)
	  {
		  if ( newDrop.roofTop < rainEndZ )
			newDrop.roofTop = rainEndZ;
		  else if ( newDrop.roofTop > rainEndZ )
			  newDrop.roofTop = rainEndZ;
	  }
	} else {
	  newDrop.roofTop = rainEndZ;
	}

	toAdd.push_back(newDrop);
      }
      // kill the drop
      return false;
    }
  }
  return true;
}


bool WeatherRenderer::updatePuddle(std::vector<puddle>::iterator& splash,
				   float frameTime)
{
  if (splash->time > maxPuddleTime) {
    splash = puddles.erase(splash);
    return false;
  }
  splash->time += frameTime;
  return true;
}


void WeatherRenderer::drawDrop(rain& drop, const SceneRenderer& sr)
{
  if (doLineRain) {
    float alphaMod = 0;

    if (drop.pos[2] < 5.0f)
      alphaMod = 1.0f - (5.0f / drop.pos[2]);

    float alphaVal = rainColor[0][3] - alphaMod;
    if (alphaVal < 0)
      alphaVal = 0;

    glColor4f(rainColor[0][0], rainColor[0][1], rainColor[0][2], alphaVal);
    glVertex3fv(drop.pos);

    alphaVal = rainColor[1][3] - alphaMod;
    if (alphaVal < 0)
      alphaVal = 0;

    glColor4f(rainColor[1][0], rainColor[1][1], rainColor[1][2], alphaVal);
    glVertex3f(drop.pos[0], drop.pos[1],
		drop.pos[2] + (rainSize[1] - (drop.speed * 0.15f)));
  } else {
    float alphaMod = 0;

    if (drop.pos[2] < 2.0f)
      alphaMod = (2.0f - drop.pos[2]) * 0.5f;

    glColor4f(1, 1, 1, 1.0f - alphaMod);
    glPushMatrix();
    glTranslatef(drop.pos[0], drop.pos[1], drop.pos[2]);
    if (doBillBoards)
      sr.getViewFrustum().executeBillboard();
    else if (spinRain)
      glRotatef(lastRainTime * 10.0f * rainSpeed * 0.85f, 0, 1, 0);

    if (spinRain)
      glRotatef(lastRainTime * 10.0f * rainSpeed, 0, 0, 1);

    if (1) {
      glCallList(dropList);
    } else {
      buildDropList(true);
    }
    glPopMatrix();
  }
}


void WeatherRenderer::drawPuddle(puddle& splash)
{
  glPushMatrix();
  glTranslatef(splash.pos[0], splash.pos[1], splash.pos[2]);

  float scale = fabsf(splash.time * rainSpeed * 0.035f * puddleSpeed);
  float lifeTime = splash.time / maxPuddleTime;

  glColor4f(puddleColor[0], puddleColor[1], puddleColor[2], 1.0f - lifeTime);

  glScalef(scale, scale, scale);
  if (1) {
    glCallList(puddleList);
  } else {
    buildPuddleList(true);
  }

  glPopMatrix();
}


void WeatherRenderer::addDrop(rain& drop)
{
  int key = keyFromPos(drop.pos[0], drop.pos[1]);

  std::map<int, visibleChunk>::iterator itr = chunkMap.find(key);
  rainCount++;

  if (itr != chunkMap.end()) {
    itr->second.drops.push_back(drop);
    return;
  }

  visibleChunk chunk;

  setChunkFromDrop(chunk, drop);

  chunk.drops.push_back(drop);
  chunkMap[key] = chunk;
  cellCount++;
}


int WeatherRenderer::keyFromPos(float x, float y) const
{
  union {
    short pos[2];
    int   key;
  } temp;
  temp.pos[0] = (short)(x * keyFactor);
  temp.pos[1] = (short)(y * keyFactor);
  return temp.key;
}


void WeatherRenderer::setChunkFromDrop(visibleChunk& chunk, rain& drop)
{
  int keyX = (int)(drop.pos[0] * keyFactor);
  int keyY = (int)(drop.pos[1] * keyFactor);

  chunk.bbox.mins[0] = keyX * gridSize;
  chunk.bbox.mins[1] = keyY * gridSize;
  chunk.bbox.mins[2] = rainStartZ > rainEndZ ? rainEndZ : rainStartZ;
  chunk.bbox.maxs[0] = keyX * gridSize + gridSize;
  chunk.bbox.maxs[1] = keyY * gridSize + gridSize;
  chunk.bbox.maxs[2] = rainStartZ > rainEndZ ? rainStartZ : rainEndZ;
}


bool WeatherRenderer::dbItemSet(const char *name)
{
  if (!BZDB.isSet(name))
    return false;

  if (TextUtils::tolower(BZDB.get(name)) == "none")
    return false;

  return true;
}


// Local Variables: ***
// mode: C++ ***
// tab-width: 8 ***
// c-basic-offset: 2 ***
// indent-tabs-mode: t ***
// End: ***
// ex: shiftwidth=2 tabstop=8
