title: Tide with terrain in OpenSim
body: <p>I want to recreate Atlantis' disappearance in OpenSim for school. So far I only managed to get the water to rise. I now want the terrain to go down every time Atlantis gets put under water. This is what I have so far: what have I done wrong with my code?</p>
  
  <pre><code>// Place your methods here
  private void InterfaceLoadFile(Object[] args)
  {
      LoadFromFile((string)args[0]);
  }
  
  private void InterfaceLoadTileFile(Object[] args)
  {
      LoadFromFile((string)args[0],
                   (int)args[1],
                   (int)args[2],
                   (int)args[3],
                   (int)args[4]);
  }
  
  private void InterfaceSaveFile(Object[] args)
  {
      SaveToFile((string)args[0]);
  }
  
  private void InterfaceSaveTileFile(Object[] args)
  {
      SaveToFile((string)args[0],
                   (int)args[1],
                   (int)args[2],
                   (int)args[3],
                   (int)args[4]);
  }
  
  private void InterfaceBakeTerrain(Object[] args)
  {
      UpdateRevertMap();
  }
  
  private void InterfaceRevertTerrain(Object[] args)
  {
      int x, y;
      for(x = 0; x &lt; m_channel.Width; x++)
          for(y = 0; y &lt; m_channel.Height; y++)
              m_channel[x, y] = m_revert[x, y];
  
  }
  
  private void InterfaceFlipTerrain(Object[] args)
  {
      String direction = (String)args[0];
  
      if (direction.ToLower().StartsWith("y"))
      {
          for(int x = 0; x &lt; m_channel.Width; x++)
          {
              for(int y = 0; y &lt; m_channel.Height / 2; y++)
              {
                  double height = m_channel[x, y];
                  double flippedHeight = m_channel[x, (int)m_channel.Height - 1 - y];
                  m_channel[x, y] = flippedHeight;
                  m_channel[x, (int)m_channel.Height - 1 - y] = height;
  
              }
          }
      }
      else if (direction.ToLower().StartsWith("x"))
      {
          for(int y = 0; y &lt; m_channel.Height; y++)
          {
              for(int x = 0; x &lt; m_channel.Width / 2; x++)
              {
                  double height = m_channel[x, y];
                  double flippedHeight = m_channel[(int)m_channel.Width - 1 - x, y];
                  m_channel[x, y] = flippedHeight;
                  m_channel[(int)m_channel.Width - 1 - x, y] = height;
  
              }
          }
      }
      else
      {
          MainConsole.Instance.OutputFormat("ERROR: Unrecognised direction {0} - need x or y", direction);
      }
  }
  
  private void InterfaceRescaleTerrain(Object[] args)
  {
      double desiredMin = (double)args[0];
      double desiredMax = (double)args[1];
  
      // determine desired scaling factor
      double desiredRange = desiredMax - desiredMin;
      //m_log.InfoFormat("Desired {0}, {1} = {2}", new Object[] { desiredMin, desiredMax, desiredRange });
  
      if (desiredRange == 0d)
      {
          // delta is zero so flatten at requested height
          InterfaceFillTerrain(new Object[] { args[1] });
      }
      else
      {
          //work out current heightmap range
          double currMin = double.MaxValue;
          double currMax = double.MinValue;
  
          int width = m_channel.Width;
          int height = m_channel.Height;
  
          for(int x = 0; x &lt; width; x++)
          {
              for(int y = 0; y &lt; height; y++)
              {
                  double currHeight = m_channel[x, y];
                  if (currHeight &lt; currMin)
                  {
                      currMin = currHeight;
                  }
                  else if (currHeight &gt; currMax)
                  {
                      currMax = currHeight;
                  }
              }
          }
  
          double currRange = currMax - currMin;
          double scale = desiredRange / currRange;
  
          //m_log.InfoFormat("Current {0}, {1} = {2}", new Object[] { currMin, currMax, currRange });
          //m_log.InfoFormat("Scale = {0}", scale);
  
          // scale the heightmap accordingly
          for(int x = 0; x &lt; width; x++)
          {
              for(int y = 0; y &lt; height; y++)
              {
                  double currHeight = m_channel[x, y] - currMin;
                  m_channel[x, y] = desiredMin + (currHeight * scale);
              }
          }
  
      }
  
  }
  
  private void InterfaceElevateTerrain(Object[] args)
  {
      int x, y;
      for(x = 0; x &lt; m_channel.Width; x++)
          for(y = 0; y &lt; m_channel.Height; y++)
              m_channel[x, y] += (double)args[0];
  }
  
  private void InterfaceMultiplyTerrain(Object[] args)
  {
      int x, y;
      for(x = 0; x &lt; m_channel.Width; x++)
          for(y = 0; y &lt; m_channel.Height; y++)
              m_channel[x, y] *= (double)args[0];
  }
  
  private void InterfaceLowerTerrain(Object[] args)
  {
      int x, y;
      for(x = 0; x &lt; m_channel.Width; x++)
          for(y = 0; y &lt; m_channel.Height; y++)
              m_channel[x, y] -= (double)args[0];
  }
  
  public void InterfaceFillTerrain(Object[] args)
  {
      int x, y;
  
      for(x = 0; x &lt; m_channel.Width; x++)
          for(y = 0; y &lt; m_channel.Height; y++)
              m_channel[x, y] = (double)args[0];
  }
  
  private void InterfaceMinTerrain(Object[] args)
  {
      int x, y;
      for(x = 0; x &lt; m_channel.Width; x++)
      {
          for(y = 0; y &lt; m_channel.Height; y++)
          {
              m_channel[x, y] = Math.Max((double)args[0], m_channel[x, y]);
          }
      }
  }
  
  private void InterfaceMaxTerrain(Object[] args)
  {
      int x, y;
      for(x = 0; x &lt; m_channel.Width; x++)
      {
          for(y = 0; y &lt; m_channel.Height; y++)
          {
              m_channel[x, y] = Math.Min((double)args[0], m_channel[x, y]);
          }
      }
  }
  
  private void InterfaceShow(Object[] args)
  {
      Vector2 point;
  
      if (!ConsoleUtil.TryParseConsole2DVector((string)args[0], null, out point))
      {
          Console.WriteLine("ERROR: {0} is not a valid vector", args[0]);
          return;
      }
  
      double height = m_channel[(int)point.X, (int)point.Y];
  
      Console.WriteLine("Terrain height at {0} is {1}", point, height);
  }
  
  private void InterfaceShowDebugStats(Object[] args)
  {
      double max = Double.MinValue;
      double min = double.MaxValue;
      double sum = 0;
  
      int x;
      for(x = 0; x &lt; m_channel.Width; x++)
      {
          int y;
          for(y = 0; y &lt; m_channel.Height; y++)
          {
              sum += m_channel[x, y];
              if (max &lt; m_channel[x, y])
                  max = m_channel[x, y];
              if (min &gt; m_channel[x, y])
                  min = m_channel[x, y];
          }
      }
  
      double avg = sum / (m_channel.Height * m_channel.Width);
  
      MainConsole.Instance.OutputFormat("Channel {0}x{1}", m_channel.Width, m_channel.Height);
      MainConsole.Instance.OutputFormat("max/min/avg/sum: {0}/{1}/{2}/{3}", max, min, avg, sum);
  }
  
  private void InterfaceEnableExperimentalBrushes(Object[] args)
  {
      if ((bool)args[0])
      {
          m_painteffects[StandardTerrainEffects.Revert] = new WeatherSphere();
          m_painteffects[StandardTerrainEffects.Flatten] = new OlsenSphere();
          m_painteffects[StandardTerrainEffects.Smooth] = new ErodeSphere();
      }
      else
      {
          InstallDefaultEffects();
      }
  }
  
  private void InterfaceRunPluginEffect(Object[] args)
  {
      string firstArg = (string)args[0];
  
      if (firstArg == "list")
      {
          MainConsole.Instance.Output("List of loaded plugins");
          foreach(KeyValuePair&lt;string, ITerrainEffect&gt; kvp in m_plugineffects)
          {
              MainConsole.Instance.Output(kvp.Key);
          }
          return;
      }
  
      if (firstArg == "reload")
      {
          LoadPlugins();
          return;
      }
  
      if (m_plugineffects.ContainsKey(firstArg))
      {
          m_plugineffects[firstArg].RunEffect(m_channel);
      }
      else
      {
          MainConsole.Instance.Output("WARNING: No such plugin effect {0} loaded.", firstArg);
      }
  }
  
  private void InstallInterfaces()
  {
      Command loadFromFileCommand =
          new Command("load", CommandIntentions.COMMAND_HAZARDOUS, InterfaceLoadFile, "Loads a terrain from a specified file.");
      loadFromFileCommand.AddArgument("filename",
                                      "The file you wish to load from, the file extension determines the loader to be used. Supported extensions include: " +
                                      m_supportedFileExtensions, "String");
  
      Command saveToFileCommand =
          new Command("save", CommandIntentions.COMMAND_NON_HAZARDOUS, InterfaceSaveFile, "Saves the current heightmap to a specified file.");
      saveToFileCommand.AddArgument("filename",
                                    "The destination filename for your heightmap, the file extension determines the format to save in. Supported extensions include: " +
                                    m_supportedFileExtensions, "String");
  
      Command loadFromTileCommand =
          new Command("load-tile", CommandIntentions.COMMAND_HAZARDOUS, InterfaceLoadTileFile, "Loads a terrain from a section of a larger file.");
      loadFromTileCommand.AddArgument("filename",
                                      "The file you wish to load from, the file extension determines the loader to be used. Supported extensions include: " +
                                      m_supportedFileExtensions, "String");
      loadFromTileCommand.AddArgument("file width", "The width of the file in tiles", "Integer");
      loadFromTileCommand.AddArgument("file height", "The height of the file in tiles", "Integer");
      loadFromTileCommand.AddArgument("minimum X tile", "The X region coordinate of the first section on the file",
                                      "Integer");
      loadFromTileCommand.AddArgument("minimum Y tile", "The Y region coordinate of the first section on the file",
                                      "Integer");
  
      Command saveToTileCommand =
          new Command("save-tile", CommandIntentions.COMMAND_HAZARDOUS, InterfaceSaveTileFile, "Saves the current heightmap to the larger file.");
      saveToTileCommand.AddArgument("filename",
                                      "The file you wish to save to, the file extension determines the loader to be used. Supported extensions include: " +
                                      m_supportFileExtensionsForTileSave, "String");
      saveToTileCommand.AddArgument("file width", "The width of the file in tiles", "Integer");
      saveToTileCommand.AddArgument("file height", "The height of the file in tiles", "Integer");
      saveToTileCommand.AddArgument("minimum X tile", "The X region coordinate of the first section on the file",
                                      "Integer");
      saveToTileCommand.AddArgument("minimum Y tile", "The Y region coordinate of the first tile on the file\n"
                                    + "= Example =\n"
                                    + "To save a PNG file for a set of map tiles 2 regions wide and 3 regions high from map co-ordinate (9910,10234)\n"
                                    + "        # terrain save-tile ST06.png 2 3 9910 10234\n",
                                    "Integer");
  
      // Terrain adjustments
      Command fillRegionCommand =
          new Command("fill", CommandIntentions.COMMAND_HAZARDOUS, InterfaceFillTerrain, "Fills the current heightmap with a specified value.");
      fillRegionCommand.AddArgument("value", "The numeric value of the height you wish to set your region to.",
                                    "Double");
  
      Command elevateCommand =
          new Command("elevate", CommandIntentions.COMMAND_HAZARDOUS, InterfaceElevateTerrain, "Raises the current heightmap by the specified amount.");
      elevateCommand.AddArgument("amount", "The amount of height to add to the terrain in meters.", "Double");
  
      Command lowerCommand =
          new Command("lower", CommandIntentions.COMMAND_HAZARDOUS, InterfaceLowerTerrain, "Lowers the current heightmap by the specified amount.");
      lowerCommand.AddArgument("amount", "The amount of height to remove from the terrain in meters.", "Double");
  
      Command multiplyCommand =
          new Command("multiply", CommandIntentions.COMMAND_HAZARDOUS, InterfaceMultiplyTerrain, "Multiplies the heightmap by the value specified.");
      multiplyCommand.AddArgument("value", "The value to multiply the heightmap by.", "Double");
  
      Command bakeRegionCommand =
          new Command("bake", CommandIntentions.COMMAND_HAZARDOUS, InterfaceBakeTerrain, "Saves the current terrain into the regions revert map.");
      Command revertRegionCommand =
          new Command("revert", CommandIntentions.COMMAND_HAZARDOUS, InterfaceRevertTerrain, "Loads the revert map terrain into the regions heightmap.");
  
      Command flipCommand =
          new Command("flip", CommandIntentions.COMMAND_HAZARDOUS, InterfaceFlipTerrain, "Flips the current terrain about the X or Y axis");
      flipCommand.AddArgument("direction", "[x|y] the direction to flip the terrain in", "String");
  
      Command rescaleCommand =
          new Command("rescale", CommandIntentions.COMMAND_HAZARDOUS, InterfaceRescaleTerrain, "Rescales the current terrain to fit between the given min and max heights");
      rescaleCommand.AddArgument("min", "min terrain height after rescaling", "Double");
      rescaleCommand.AddArgument("max", "max terrain height after rescaling", "Double");
  
      Command minCommand = new Command("min", CommandIntentions.COMMAND_HAZARDOUS, InterfaceMinTerrain, "Sets the minimum terrain height to the specified value.");
      minCommand.AddArgument("min", "terrain height to use as minimum", "Double");
  
      Command maxCommand = new Command("max", CommandIntentions.COMMAND_HAZARDOUS, InterfaceMaxTerrain, "Sets the maximum terrain height to the specified value.");
      maxCommand.AddArgument("min", "terrain height to use as maximum", "Double");
  
  
      // Debug
      Command showDebugStatsCommand =
          new Command("stats", CommandIntentions.COMMAND_STATISTICAL, InterfaceShowDebugStats,
                      "Shows some information about the regions heightmap for debugging purposes.");
  
      Command showCommand =
          new Command("show", CommandIntentions.COMMAND_NON_HAZARDOUS, InterfaceShow,
                      "Shows terrain height at a given co-ordinate.");
      showCommand.AddArgument("point", "point in &lt;x&gt;,&lt;y&gt; format with no spaces (e.g. 45,45)", "String");
  
      Command experimentalBrushesCommand =
          new Command("newbrushes", CommandIntentions.COMMAND_HAZARDOUS, InterfaceEnableExperimentalBrushes,
                      "Enables experimental brushes which replace the standard terrain brushes. WARNING: This is a debug setting and may be removed at any time.");
      experimentalBrushesCommand.AddArgument("Enabled?", "true / false - Enable new brushes", "Boolean");
  
      // Plugins
      Command pluginRunCommand =
          new Command("effect", CommandIntentions.COMMAND_HAZARDOUS, InterfaceRunPluginEffect, "Runs a specified plugin effect");
      pluginRunCommand.AddArgument("name", "The plugin effect you wish to run, or 'list' to see all plugins", "String");
  
  
  
      m_commander.RegisterCommand("multiply", multiplyCommand);
  
      m
      // Add this to our scene so scripts can call these functions
      m_scene.RegisterModuleCommander(m_commander);
  
      // Add Modify command to Scene, since Command object requires fixed-length arglists
      m_scene.AddCommand("Terrain", this, "terrain modify",
                         "terrain modify &lt;operation&gt; &lt;value&gt; [&lt;area&gt;] [&lt;taper&gt;]",
                         "Modifies the terrain as instructed." +
                         "\nEach operation can be limited to an area of effect:" +
                         "\n * -ell=x,y,rx[,ry] constrains the operation to an ellipse centred at x,y" +
                         "\n * -rec=x,y,dx[,dy] constrains the operation to a rectangle based at x,y" +
                         "\nEach operation can have its effect tapered based on distance from centre:" +
                         "\n * elliptical operations taper as cones" +
                         "\n * rectangular operations taper as pyramids"
                         ,
                         ModifyCommand);
  
  }
  
  public void InterfaceMultiplyTerrain(Object[] args)
  {
      int x, y;
      for (x = 0; x &lt; m_channel.Width; x++)
          for (y = 0; y &lt; m_channel.Height; y++)
              m_channel[x, y] *= (double)args[0];
  }
  
  private void InstallInterfaces()
  {
  
  
      Command multiplyCommand =
          new Command("multiply", CommandIntentions.COMMAND_HAZARDOUS, InterfaceMultiplyTerrain, "Multiplies the heightmap by the value specified.");
      multiplyCommand.AddArgument("value", "The value to multiply the heightmap by.", "Double");
  
  
  
      // Debug
      Command showDebugStatsCommand =
          new Command("stats", CommandIntentions.COMMAND_STATISTICAL, InterfaceShowDebugStats,
                      "Shows some information about the regions heightmap for debugging purposes.");
  
  
      // Plugins
      Command pluginRunCommand =
          new Command("effect", CommandIntentions.COMMAND_HAZARDOUS, InterfaceRunPluginEffect, "Runs a specified plugin effect");
      pluginRunCommand.AddArgument("name", "The plugin effect you wish to run, or 'list' to see all plugins", "String");
  
  
  
      m_commander.RegisterCommand("multiply", multiplyCommand);
  
  
      // Add this to our scene so scripts can call these functions
      m_scene.RegisterModuleCommander(m_commander);
  
      // Add Modify command to Scene, since Command object requires fixed-length arglists
      m_scene.AddCommand("Terrain", this, "terrain modify",
                         "terrain modify &lt;operation&gt; &lt;value&gt; [&lt;area&gt;] [&lt;taper&gt;]",
                         "Modifies the terrain as instructed." +
                         "\nEach operation can be limited to an area of effect:" +
                         "\n * -ell=x,y,rx[,ry] constrains the operation to an ellipse centred at x,y" +
                         "\n * -rec=x,y,dx[,dy] constrains the operation to a rectangle based at x,y" +
                         "\nEach operation can have its effect tapered based on distance from centre:" +
                         "\n * elliptical operations taper as cones" +
                         "\n * rectangular operations taper as pyramids"
                         ,
                         ModifyCommand);
  
  }
  
  public void UpdateTerrainWithTide()
  {
              InterfaceMultiplyTerrain(0.95);
              multiplyCommand = 0.95;
              multiplyCommand(0.95);
     }
  
  public void ModifyCommand(string module, string[] cmd)
  {
      string result;
      Scene scene = SceneManager.Instance.CurrentScene;
      if ((scene != null) &amp;&amp; (scene != m_scene))
      {
          result = String.Empty;
      }
      else if (cmd.Length &gt; 2)
      {
          string operationType = cmd[2];
  
  
          ITerrainModifier operation;
          if (!m_modifyOperations.TryGetValue(operationType, out operation))
          {
              result = String.Format("Terrain Modify \"{0}\" not found.", operationType);
          }
          else if ((cmd.Length &gt; 3) &amp;&amp; (cmd[3] == "usage"))
          {
              result = "Usage: " + operation.GetUsage();
          }
          else
          {
              result = operation.ModifyTerrain(m_channel, cmd);
          }
  
          if (result == String.Empty)
          {
              result = "Modified terrain";
              m_log.DebugFormat("Performed terrain operation {0}", operationType);
          }
      }
      else
      {
          result = "Usage: &lt;operation-name&gt; &lt;arg1&gt; &lt;arg2&gt;...";
      }
      if (result != String.Empty)
      {
          MainConsole.Instance.Output(result);
      }
  }
  
  
  
  
  public void TideUpdate ()
  {
      ulong timeStamp;
      double cyclePos; //cycles from 0.0000000001 to 0.999999999999
      double cycleRadians;
      double tideRange;
      double tideMiddle;
      string tideLevelMsg;
  
      if (((m_frame++ % m_frameUpdateRate) != 0) || !m_ready) {
          return;
      }
      timeStamp = (ulong) (DateTime.Now.Ticks);
  
      cyclePos = (double)(timeStamp % (m_cycleTime * TICKS_PER_SECOND)) / (m_cycleTime * TICKS_PER_SECOND);
      cycleRadians = cyclePos * Math.PI * 2;
  
      if (cyclePos &lt; 0.5) m_tideDirection = false; else m_tideDirection = true;
  
      if (m_tideDirection != m_lastTideDirection)
      { //if the tide changes re-calculate the tide times
          if (cyclePos &lt; 0.5)
          { // tide just changed to be high-&gt;low
              m_lowTideTime = DateTime.Now.AddSeconds((double)(m_cycleTime * (0.5 - cyclePos)));
              m_highTideTime = m_lowTideTime.AddSeconds((double)(m_cycleTime / 2));
              m_tideAnnounceMsg = "High Tide";
          }
          else
          {   //tide just changed to be low-&gt;high
              m_highTideTime = DateTime.Now.AddSeconds((double)(m_cycleTime * (1.0 - cyclePos)));
              m_lowTideTime = m_highTideTime.AddSeconds((double)(m_cycleTime / 2));
              m_tideAnnounceMsg = "Low Tide";
          }
          m_lastTideDirection = m_tideDirection;
      }
      tideRange = (double) (m_highTide - m_lowTide) / 2;
      tideMiddle = (double) m_lowTide + tideRange;
      m_tideLevel = (float) (Math.Cos(cycleRadians) * tideRange + tideMiddle);
  
      tideLevelMsg = "Current Server Time: " + DateTime.Now.ToString("T") + "\n";
      tideLevelMsg += "Current Tide Level: " + m_tideLevel.ToString() + "\n";
      tideLevelMsg += "Low Tide Time: " + m_lowTideTime.ToString("T") + "\n";
      tideLevelMsg += "Low Tide Level: " + m_lowTide.ToString() + "\n";
      tideLevelMsg += "High Tide Time: " + m_highTideTime.ToString("T") + "\n";
      tideLevelMsg += "High Tide Level: " + m_highTide.ToString() + "\n";
      tideLevelMsg += "Tide Direction: " + ((m_tideDirection) ? "Coming In" : "Going Out") + "\n";
      tideLevelMsg += "Cycle Position: " + cyclePos.ToString() + "\n";
      if (m_tideAnnounceMsg != "")
      {
          if (m_tideAnnounceCounter++ &gt; m_tideAnnounceCount)
          {
              m_tideAnnounceCounter = 0;
              m_tideAnnounceMsg = "";
          }
          else
          {
              tideLevelMsg += "Tide Warning: " + m_tideAnnounceMsg + "\n";
          }
      }
  
      if (m_tideInfoDebug) m_log.InfoFormat("[{0}]: Sea Level currently at {1}m in Region: {2}", m_name, m_tideLevel, m_scene.RegionInfo.RegionName);
  
      if (m_tideInfoBroadcast &amp;&amp; m_tideDirection)
      {
          m_scene.SimChatBroadcast(Utils.StringToBytes(tideLevelMsg), ChatTypeEnum.Region, m_tideInfoChannel, m_shoutPos, "TIDE", UUID.Zero, false);
          m_scene.SimChatBroadcast(Utils.StringToBytes(m_tideLevel.ToString()), ChatTypeEnum.Region, m_tideLevelChannel, m_shoutPos, "TIDE", UUID.Zero, false);
      }
      if (m_tideInfoDebug) m_log.InfoFormat("[{0}]: Updating Region: {1}", m_name, m_scene.RegionInfo.RegionName);
  
      m_scene.RegionInfo.RegionSettings.WaterHeight = m_tideLevel;
      m_scene.EventManager.TriggerRequestChangeWaterHeight(m_tideLevel);
      m_scene.EventManager.TriggerTerrainTick();
  
      if (m_tideInfoBroadcast &amp;&amp; !m_tideDirection)
      {
          m_scene.SimChatBroadcast(Utils.StringToBytes(tideLevelMsg), ChatTypeEnum.Region, m_tideInfoChannel, m_shoutPos, "TIDE", UUID.Zero, false);
          m_scene.SimChatBroadcast(Utils.StringToBytes(m_tideLevel.ToString()), ChatTypeEnum.Region, m_tideLevelChannel, m_shoutPos, "TIDE", UUID.Zero, false);
      }
  }
  </code></pre>